xref: /haiku/src/add-ons/accelerants/radeon_hd/displayport.cpp (revision 85fb3e7df81f8d5b6e47a9a64a53873ea906ea6e)
1 /*
2  * Copyright 2011, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Alexander von Gluck IV, kallisti5@unixzen.com
7  */
8 
9 
10 #include "displayport.h"
11 
12 #include <Debug.h>
13 
14 #include "accelerant_protos.h"
15 #include "connector.h"
16 #include "mode.h"
17 
18 
19 #undef TRACE
20 
21 #define TRACE_DP
22 #ifdef TRACE_DP
23 #   define TRACE(x...) _sPrintf("radeon_hd: " x)
24 #else
25 #   define TRACE(x...) ;
26 #endif
27 
28 #define ERROR(x...) _sPrintf("radeon_hd: " x)
29 
30 
31 static int
32 dp_aux_speak(uint32 hwPin, uint8* send, int sendBytes,
33 	uint8* recv, int recvBytes, uint8 delay, uint8* ack)
34 {
35 	if (hwPin == 0) {
36 		ERROR("%s: cannot speak on invalid GPIO pin!\n", __func__);
37 		return B_IO_ERROR;
38 	}
39 
40 	int index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction);
41 
42 	// Build AtomBIOS Transaction
43 	union auxChannelTransaction {
44 		PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION v1;
45 		PROCESS_AUX_CHANNEL_TRANSACTION_PARAMETERS_V2 v2;
46 	};
47 	union auxChannelTransaction args;
48 	memset(&args, 0, sizeof(args));
49 
50 	args.v1.lpAuxRequest = 0;
51 	args.v1.lpDataOut = 16;
52 	args.v1.ucDataOutLen = 0;
53 	args.v1.ucChannelID = hwPin;
54 	args.v1.ucDelay = delay / 10;
55 
56 	//if (ASIC_IS_DCE4(rdev))
57 	//	args.v2.ucHPD_ID = chan->rec.hpd;
58 
59 	unsigned char* base = (unsigned char*)gAtomContext->scratch;
60 	memcpy(base, send, sendBytes);
61 
62 	atom_execute_table(gAtomContext, index, (uint32*)&args);
63 
64 	*ack = args.v1.ucReplyStatus;
65 
66 	switch (args.v1.ucReplyStatus) {
67 		case 1:
68 			ERROR("%s: dp_aux_ch timeout!\n", __func__);
69 			return B_TIMED_OUT;
70 		case 2:
71 			ERROR("%s: dp_aux_ch flags not zero!\n", __func__);
72 			return B_BUSY;
73 		case 3:
74 			ERROR("%s: dp_aux_ch error!\n", __func__);
75 			return B_IO_ERROR;
76 	}
77 
78 	int recvLength = args.v1.ucDataOutLen;
79 	if (recvLength > recvBytes)
80 		recvLength = recvBytes;
81 
82 	if (recv && recvBytes)
83 		memcpy(recv, base + 16, recvLength);
84 
85 	return recvLength;
86 }
87 
88 
89 int
90 dp_aux_write(uint32 hwPin, uint16 address,
91 	uint8* send, uint8 sendBytes, uint8 delay)
92 {
93 	uint8 auxMessage[20];
94 	int auxMessageBytes = sendBytes + 4;
95 
96 	if (sendBytes > 16)
97 		return -1;
98 
99 	auxMessage[0] = address;
100 	auxMessage[1] = address >> 8;
101 	auxMessage[2] = AUX_NATIVE_WRITE << 4;
102 	auxMessage[3] = (auxMessageBytes << 4) | (sendBytes - 1);
103 	memcpy(&auxMessage[4], send, sendBytes);
104 
105 	uint8 retry;
106 	for (retry = 0; retry < 4; retry++) {
107 		uint8 ack;
108 		int result = dp_aux_speak(hwPin, auxMessage, auxMessageBytes,
109 			NULL, 0, delay, &ack);
110 
111 		if (result == B_BUSY)
112 			continue;
113 		else if (result < B_OK)
114 			return result;
115 
116 		if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_ACK)
117 			return sendBytes;
118 		else if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_DEFER)
119 			snooze(400);
120 		else
121 			return B_IO_ERROR;
122 	}
123 
124 	return B_IO_ERROR;
125 }
126 
127 
128 int
129 dp_aux_read(uint32 hwPin, uint16 address,
130 	uint8* recv, int recvBytes, uint8 delay)
131 {
132 	uint8 auxMessage[4];
133 	int auxMessageBytes = 4;
134 
135 	auxMessage[0] = address;
136 	auxMessage[1] = address >> 8;
137 	auxMessage[2] = AUX_NATIVE_READ << 4;
138 	auxMessage[3] = (auxMessageBytes << 4) | (recvBytes - 1);
139 
140 	uint8 retry;
141 	for (retry = 0; retry < 4; retry++) {
142 		uint8 ack;
143 		int result = dp_aux_speak(hwPin, auxMessage, auxMessageBytes,
144 			recv, recvBytes, delay, &ack);
145 
146 		if (result == B_BUSY)
147 			continue;
148 		else if (result < B_OK)
149 			return result;
150 
151 		if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_ACK)
152 			return result;
153 		else if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_DEFER)
154 			snooze(400);
155 		else
156 			return B_IO_ERROR;
157 	}
158 
159 	return B_IO_ERROR;
160 }
161 
162 
163 static void
164 dpcd_reg_write(uint32 hwPin, uint16 address, uint8 value)
165 {
166 	dp_aux_write(hwPin, address, &value, 1, 0);
167 }
168 
169 
170 static uint8
171 dpcd_reg_read(uint32 hwPin, uint16 address)
172 {
173 	uint8 value = 0;
174 	dp_aux_read(hwPin, address, &value, 1, 0);
175 
176 	return value;
177 }
178 
179 
180 status_t
181 dp_aux_get_i2c_byte(uint32 hwPin, uint16 address, uint8* data, bool end)
182 {
183 	uint8 auxMessage[5];
184 	int auxMessageBytes = 4; // 4 for read
185 
186 	/* Set up the command byte */
187 	auxMessage[2] = AUX_I2C_READ << 4;
188 	if (end == false)
189 		auxMessage[2] |= AUX_I2C_MOT << 4;
190 
191 	auxMessage[0] = address;
192 	auxMessage[1] = address >> 8;
193 
194 	auxMessage[3] = auxMessageBytes << 4;
195 
196 	int retry;
197 	for (retry = 0; retry < 4; retry++) {
198 		uint8 ack;
199 		uint8 reply[2];
200 		int replyBytes = 1;
201 
202 		int result = dp_aux_speak(hwPin, auxMessage, auxMessageBytes,
203 			reply, replyBytes, 0, &ack);
204 		if (result == B_BUSY)
205 			continue;
206 		else if (result < 0) {
207 			ERROR("%s: aux_ch failed: %d\n", __func__, result);
208 			return B_ERROR;
209 		}
210 
211 		switch (ack & AUX_NATIVE_REPLY_MASK) {
212 			case AUX_NATIVE_REPLY_ACK:
213 				// I2C-over-AUX Reply field is only valid for AUX_ACK
214 				break;
215 			case AUX_NATIVE_REPLY_NACK:
216 				TRACE("%s: aux_ch native nack\n", __func__);
217 				return B_IO_ERROR;
218 			case AUX_NATIVE_REPLY_DEFER:
219 				TRACE("%s: aux_ch native defer\n", __func__);
220 				snooze(400);
221 				continue;
222 			default:
223 				TRACE("%s: aux_ch invalid native reply: 0x%02x\n",
224 					__func__, ack);
225 				return B_ERROR;
226 		}
227 
228 		switch (ack & AUX_I2C_REPLY_MASK) {
229 			case AUX_I2C_REPLY_ACK:
230 				*data = reply[0];
231 				return B_OK;
232 			case AUX_I2C_REPLY_NACK:
233 				TRACE("%s: aux_i2c nack\n", __func__);
234 				return B_IO_ERROR;
235 			case AUX_I2C_REPLY_DEFER:
236 				TRACE("%s: aux_i2c defer\n", __func__);
237 				snooze(400);
238 				break;
239 			default:
240 				TRACE("%s: aux_i2c invalid native reply: 0x%02x\n",
241 					__func__, ack);
242 				return B_ERROR;
243 		}
244 	}
245 
246 	TRACE("%s: aux i2c too many retries, giving up.\n", __func__);
247 	return B_ERROR;
248 }
249 
250 
251 status_t
252 dp_aux_set_i2c_byte(uint32 hwPin, uint16 address, uint8* data, bool end)
253 {
254 	uint8 auxMessage[5];
255 	int auxMessageBytes = 5; // 5 for write
256 
257 	/* Set up the command byte */
258 	auxMessage[2] = AUX_I2C_WRITE << 4;
259 	if (end == false)
260 		auxMessage[2] |= AUX_I2C_MOT << 4;
261 
262 	auxMessage[0] = address;
263 	auxMessage[1] = address >> 8;
264 
265 	auxMessage[3] = auxMessageBytes << 4;
266 	auxMessage[4] = *data;
267 
268 	int retry;
269 	for (retry = 0; retry < 4; retry++) {
270 		uint8 ack;
271 		uint8 reply[2];
272 		int replyBytes = 1;
273 
274 		int result = dp_aux_speak(hwPin, auxMessage, auxMessageBytes,
275 			reply, replyBytes, 0, &ack);
276 		if (result == B_BUSY)
277 			continue;
278 		else if (result < 0) {
279 			ERROR("%s: aux_ch failed: %d\n", __func__, result);
280 			return B_ERROR;
281 		}
282 
283 		switch (ack & AUX_NATIVE_REPLY_MASK) {
284 			case AUX_NATIVE_REPLY_ACK:
285 				// I2C-over-AUX Reply field is only valid for AUX_ACK
286 				break;
287 			case AUX_NATIVE_REPLY_NACK:
288 				TRACE("%s: aux_ch native nack\n", __func__);
289 				return B_IO_ERROR;
290 			case AUX_NATIVE_REPLY_DEFER:
291 				TRACE("%s: aux_ch native defer\n", __func__);
292 				snooze(400);
293 				continue;
294 			default:
295 				TRACE("%s: aux_ch invalid native reply: 0x%02x\n",
296 					__func__, ack);
297 				return B_ERROR;
298 		}
299 
300 		switch (ack & AUX_I2C_REPLY_MASK) {
301 			case AUX_I2C_REPLY_ACK:
302 				// Success!
303 				return B_OK;
304 			case AUX_I2C_REPLY_NACK:
305 				TRACE("%s: aux_i2c nack\n", __func__);
306 				return B_IO_ERROR;
307 			case AUX_I2C_REPLY_DEFER:
308 				TRACE("%s: aux_i2c defer\n", __func__);
309 				snooze(400);
310 				break;
311 			default:
312 				TRACE("%s: aux_i2c invalid native reply: 0x%02x\n",
313 					__func__, ack);
314 				return B_ERROR;
315 		}
316 	}
317 
318 	TRACE("%s: aux i2c too many retries, giving up.\n", __func__);
319 	return B_OK;
320 }
321 
322 
323 uint32
324 dp_get_link_clock(uint32 connectorIndex)
325 {
326 	uint16 encoderID = gConnector[connectorIndex]->encoderExternal.objectID;
327 
328 	if (encoderID == ENCODER_OBJECT_ID_NUTMEG)
329 		return 270000;
330 
331 	// TODO: calculate DisplayPort max pixel clock based on bpp and DP channels
332 	return 162000;
333 }
334 
335 
336 void
337 dp_setup_connectors()
338 {
339 	TRACE("%s\n", __func__);
340 
341 	for (uint32 index = 0; index < ATOM_MAX_SUPPORTED_DEVICE; index++) {
342 		dp_info* dpInfo = &gConnector[index]->dpInfo;
343 		dpInfo->valid = false;
344 		if (gConnector[index]->valid == false) {
345 			dpInfo->config[0] = 0;
346 			continue;
347 		}
348 
349 		if (connector_is_dp(index) == false) {
350 			dpInfo->config[0] = 0;
351 			continue;
352 		}
353 
354 		uint32 gpioID = gConnector[index]->gpioID;
355 
356 		uint32 auxPin = gGPIOInfo[gpioID]->hwPin;
357 		dpInfo->auxPin = auxPin;
358 
359 		uint8 auxMessage[25];
360 		int result;
361 
362 		result = dp_aux_read(auxPin, DP_DPCD_REV, auxMessage, 8, 0);
363 		if (result > 0) {
364 			dpInfo->valid = true;
365 			memcpy(dpInfo->config, auxMessage, 8);
366 		}
367 
368 		dpInfo->linkRate = dp_get_link_clock(index);
369 	}
370 }
371 
372 
373 static bool
374 dp_get_link_status(dp_info* dp)
375 {
376 	int result = dp_aux_read(dp->auxPin, DP_LANE_STATUS_0_1,
377 		dp->linkStatus, DP_LINK_STATUS_SIZE, 100);
378 
379 	if (result <= 0) {
380 		ERROR("%s: DisplayPort link status failed\n", __func__);
381 		return false;
382 	}
383 
384 	return true;
385 }
386 
387 
388 static uint8
389 dp_get_lane_status(dp_info* dp, int lane)
390 {
391 	int i = DP_LANE_STATUS_0_1 + (lane >> 1);
392 	int s = (lane & 1) * 4;
393 	uint8 l = dp->linkStatus[i - DP_LANE_STATUS_0_1];
394 	return (l >> s) & 0xf;
395 }
396 
397 
398 static bool
399 dp_clock_recovery_ok(dp_info* dp)
400 {
401 	int lane;
402 	uint8 laneStatus;
403 
404 	for (lane = 0; lane < dp->laneCount; lane++) {
405 		laneStatus = dp_get_lane_status(dp, lane);
406 		if ((laneStatus & DP_LANE_STATUS_CR_DONE_A) == 0)
407 			return false;
408 	}
409 	return true;
410 }
411 
412 
413 static void
414 dp_update_vs_emph(uint32 connectorIndex)
415 {
416 	dp_info* dp = &gConnector[connectorIndex]->dpInfo;
417 
418 	// Set initial vs and emph on source
419 	transmitter_dig_setup(connectorIndex, dp->linkRate, 0,
420 		dp->trainingSet[0], ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH);
421 
422 	// Set vs and emph on the sink
423 	dp_aux_write(dp->auxPin, DP_TRAIN_LANE0,
424 		dp->trainingSet, dp->laneCount, 0);
425 }
426 
427 
428 static uint8
429 dp_get_adjust_request_voltage(dp_info* dp, int lane)
430 {
431 	int i = DP_ADJ_REQUEST_0_1 + (lane >> 1);
432 	int s = (((lane & 1) != 0) ? DP_ADJ_VCC_SWING_LANEB_SHIFT
433 		: DP_ADJ_VCC_SWING_LANEA_SHIFT);
434 	uint8 l = dp->linkStatus[i - DP_LANE_STATUS_0_1];
435 
436 	return ((l >> s) & 0x3) << DP_TRAIN_VCC_SWING_SHIFT;
437 }
438 
439 
440 static uint8
441 dp_get_adjust_request_pre_emphasis(dp_info* dp, int lane)
442 {
443 	int i = DP_ADJ_REQUEST_0_1 + (lane >> 1);
444 	int s = (((lane & 1) != 0) ? DP_ADJ_PRE_EMPHASIS_LANEB_SHIFT
445 		: DP_ADJ_PRE_EMPHASIS_LANEB_SHIFT);
446 	uint8 l = dp->linkStatus[i - DP_LANE_STATUS_0_1];
447 
448 	return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
449 }
450 
451 
452 static void
453 dp_get_adjust_train(dp_info* dp)
454 {
455 	TRACE("%s\n", __func__);
456 
457 	const char* voltageNames[] = {
458 		"0.4V", "0.6V", "0.8V", "1.2V"
459 	};
460 	const char* preEmphasisNames[] = {
461 		"0dB", "3.5dB", "6dB", "9.5dB"
462 	};
463 
464 	uint8 voltage = 0;
465 	uint8 preEmphasis = 0;
466 	int lane;
467 
468 	for (lane = 0; lane < dp->laneCount; lane++) {
469 		uint8 laneVoltage = dp_get_adjust_request_voltage(dp, lane);
470 		uint8 lanePreEmphasis = dp_get_adjust_request_pre_emphasis(dp, lane);
471 
472 		TRACE("%s: Requested %s at %s for lane %d\n", __func__,
473 			preEmphasisNames[lanePreEmphasis >> DP_TRAIN_PRE_EMPHASIS_SHIFT],
474 			voltageNames[laneVoltage >> DP_TRAIN_VCC_SWING_SHIFT],
475 			lane);
476 
477 		if (laneVoltage > voltage)
478 			voltage = laneVoltage;
479 		if (lanePreEmphasis > preEmphasis)
480 			preEmphasis = lanePreEmphasis;
481 	}
482 
483 	// Check for maximum voltage and toggle max if reached
484 	if (voltage >= DP_TRAIN_VCC_SWING_1200)
485 		voltage |= DP_TRAIN_MAX_SWING_EN;
486 
487 	// Check for maximum pre-emphasis and toggle max if reached
488 	if (preEmphasis >= DP_TRAIN_PRE_EMPHASIS_9_5)
489 		preEmphasis |= DP_TRAIN_MAX_EMPHASIS_EN;
490 
491 	for (lane = 0; lane < 4; lane++)
492 		dp->trainingSet[lane] = voltage | preEmphasis;
493 }
494 
495 
496 static void
497 dp_set_tp(uint32 connectorIndex, int trainingPattern)
498 {
499 	TRACE("%s\n", __func__);
500 
501 	radeon_shared_info &info = *gInfo->shared_info;
502 	dp_info* dp = &gConnector[connectorIndex]->dpInfo;
503 
504 	int rawTrainingPattern = 0;
505 
506 	/* set training pattern on the source */
507 	if (info.dceMajor >= 4 || !dp->trainingUseEncoder) {
508 		switch (trainingPattern) {
509 			case DP_TRAIN_PATTERN_1:
510 				rawTrainingPattern = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN1;
511 				break;
512 			case DP_TRAIN_PATTERN_2:
513 				rawTrainingPattern = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN2;
514 				break;
515 			case DP_TRAIN_PATTERN_3:
516 				rawTrainingPattern = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN3;
517 				break;
518 		}
519 		// TODO: PixelClock 0 ok?
520 		encoder_dig_setup(connectorIndex, 0, rawTrainingPattern);
521 	} else {
522 		ERROR("%s: TODO: dp_encoder_service\n", __func__);
523 		return;
524 		#if 0
525 		switch (trainingPattern) {
526 			case DP_TRAINING_PATTERN_1:
527 				rawTrainingPattern = 0;
528 				break;
529 			case DP_TRAINING_PATTERN_2:
530 				rawTrainingPattern = 1;
531 				break;
532 		}
533 		radeon_dp_encoder_service(dp_info->rdev,
534 			ATOM_DP_ACTION_TRAINING_PATTERN_SEL, dp_info->dp_clock,
535 			dp_info->enc_id, rawTrainingPattern);
536 		#endif
537 	}
538 
539 	// Enable training pattern on the sink
540 	dpcd_reg_write(dp->auxPin, DP_TRAIN, trainingPattern);
541 }
542 
543 
544 status_t
545 dp_link_train_cr(uint32 connectorIndex)
546 {
547 	TRACE("%s\n", __func__);
548 
549 	dp_info* dp = &gConnector[connectorIndex]->dpInfo;
550 
551 	// Display Port Clock Recovery Training
552 
553 	bool clockRecovery = false;
554 	uint8 voltage = 0xff;
555 	int lane;
556 
557 	dp_set_tp(connectorIndex, DP_TRAIN_PATTERN_1);
558 	memset(dp->trainingSet, 0, 4);
559 	dp_update_vs_emph(connectorIndex);
560 
561 	while (1) {
562 		if (dp->trainingReadInterval == 0)
563 			snooze(100);
564 		else
565 			snooze(1000 * 4 * dp->trainingReadInterval);
566 
567 		if (!dp_get_link_status(dp))
568 			break;
569 
570 		if (dp_clock_recovery_ok(dp)) {
571 			clockRecovery = true;
572 			break;
573 		}
574 
575 		for (lane = 0; lane < dp->laneCount; lane++) {
576 			if ((dp->trainingSet[lane] & DP_TRAIN_MAX_SWING_EN) == 0)
577 				break;
578 		}
579 
580 		if (lane == dp->laneCount) {
581 			ERROR("%s: clock recovery reached max voltage\n", __func__);
582 			break;
583 		}
584 
585 		if ((dp->trainingSet[0] & DP_TRAIN_VCC_SWING_MASK) == voltage) {
586 			dp->trainingAttempts++;
587 			if (dp->trainingAttempts >= 5) {
588 				ERROR("%s: clock recovery tried 5 times\n", __func__);
589 				break;
590 			}
591 		} else
592 			dp->trainingAttempts = 0;
593 
594 		voltage = dp->trainingSet[0] & DP_TRAIN_VCC_SWING_MASK;
595 
596 		// Compute new trainingSet as requested by sink
597 		dp_get_adjust_train(dp);
598 
599 		dp_update_vs_emph(connectorIndex);
600 	}
601 
602 	if (!clockRecovery) {
603 		ERROR("%s: clock recovery failed\n", __func__);
604 		return B_ERROR;
605 	}
606 
607 	TRACE("%s: clock recovery at voltage %d pre-emphasis %d\n",
608 		__func__, dp->trainingSet[0] & DP_TRAIN_VCC_SWING_MASK,
609 		(dp->trainingSet[0] & DP_TRAIN_PRE_EMPHASIS_MASK)
610 		>> DP_TRAIN_PRE_EMPHASIS_SHIFT);
611 	return B_OK;
612 }
613 
614 
615 status_t
616 dp_link_train(uint8 crtcID, display_mode* mode)
617 {
618 	TRACE("%s\n", __func__);
619 
620 	uint32 connectorIndex = gDisplay[crtcID]->connectorIndex;
621 	dp_info* dp = &gConnector[connectorIndex]->dpInfo;
622 
623 	if (dp->valid != true) {
624 		ERROR("%s: started on invalid DisplayPort connector #%" B_PRIu32 "\n",
625 			__func__, connectorIndex);
626 		return B_ERROR;
627 	}
628 
629 	int index = GetIndexIntoMasterTable(COMMAND, DPEncoderService);
630 	// Table version
631 	uint8 tableMajor;
632 	uint8 tableMinor;
633 
634 	dp->trainingUseEncoder = true;
635 	if (atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor)
636 		== B_OK) {
637 		if (tableMinor > 1) {
638 			// The AtomBIOS DPEncoderService greater then 1.1 can't program the
639 			// training pattern properly.
640 			dp->trainingUseEncoder = false;
641 		}
642 	}
643 
644 	uint32 linkEnumeration
645 		= gConnector[connectorIndex]->encoder.linkEnumeration;
646 	uint32 gpioID = gConnector[connectorIndex]->gpioID;
647 	uint32 hwPin = gGPIOInfo[gpioID]->hwPin;
648 
649 	uint32 dpEncoderID = 0;
650 	if (encoder_pick_dig(connectorIndex) > 0)
651 		dpEncoderID |= ATOM_DP_CONFIG_DIG2_ENCODER;
652 	else
653 		dpEncoderID |= ATOM_DP_CONFIG_DIG1_ENCODER;
654 	if (linkEnumeration == GRAPH_OBJECT_ENUM_ID2)
655 		dpEncoderID |= ATOM_DP_CONFIG_LINK_B;
656 	else
657 		dpEncoderID |= ATOM_DP_CONFIG_LINK_A;
658 
659 	dp->trainingReadInterval
660 		= dpcd_reg_read(hwPin, DP_TRAINING_AUX_RD_INTERVAL);
661 
662 	uint8 sandbox = dpcd_reg_read(hwPin, DP_MAX_LANE_COUNT);
663 
664 	radeon_shared_info &info = *gInfo->shared_info;
665 	//bool dpTPS3Supported = false;
666 	//if (info.dceMajor >= 5 && (sandbox & DP_TPS3_SUPPORTED) != 0)
667 	//	dpTPS3Supported = true;
668 
669 	// *** DisplayPort link training initialization
670 
671 	// Power up the DP sink
672 	if (dp->config[0] >= DP_DPCD_REV_11)
673 		dpcd_reg_write(hwPin, DP_SET_POWER, DP_SET_POWER_D0);
674 
675 	// Possibly enable downspread on the sink
676 	if ((dp->config[3] & 0x1) != 0)
677 		dpcd_reg_write(hwPin, DP_DOWNSPREAD_CTRL, DP_DOWNSPREAD_CTRL_AMP_EN);
678 	else
679 		dpcd_reg_write(hwPin, DP_DOWNSPREAD_CTRL, 0);
680 
681 	encoder_dig_setup(connectorIndex, mode->timing.pixel_clock,
682 		ATOM_ENCODER_CMD_SETUP_PANEL_MODE);
683 
684 	if (dp->config[0] >= DP_DPCD_REV_11)
685 		sandbox |= DP_ENHANCED_FRAME_EN;
686 	dpcd_reg_write(hwPin, DP_LANE_COUNT, sandbox);
687 
688 	// Set the link rate on the DP sink
689 	sandbox = dp_encode_link_rate(dp->linkRate);
690 	dpcd_reg_write(hwPin, DP_LINK_RATE, sandbox);
691 
692 	// Start link training on source
693 	if (info.dceMajor >= 4 || !dp->trainingUseEncoder) {
694 		encoder_dig_setup(connectorIndex, mode->timing.pixel_clock,
695 			ATOM_ENCODER_CMD_DP_LINK_TRAINING_START);
696 	} else {
697 		ERROR("%s: TODO: cannot use AtomBIOS DPEncoderService on card!\n",
698 			__func__);
699 	}
700 
701 	// Disable the training pattern on the sink
702 	dpcd_reg_write(hwPin, DP_TRAIN, DP_TRAIN_PATTERN_DISABLED);
703 
704 	dp_link_train_cr(connectorIndex);
705 	// TODO: dp_link_train_ce
706 
707 
708 	// *** DisplayPort link training finish
709 	snooze(400);
710 
711 	// Disable the training pattern on the sink
712 	dpcd_reg_write(hwPin, DP_TRAIN, DP_TRAIN_PATTERN_DISABLED);
713 
714 	// Disable the training pattern on the source
715 	if (info.dceMajor >= 4 || !dp->trainingUseEncoder) {
716 		encoder_dig_setup(connectorIndex, mode->timing.pixel_clock,
717 			ATOM_ENCODER_CMD_DP_LINK_TRAINING_COMPLETE);
718 	} else {
719 		ERROR("%s: TODO: cannot use AtomBIOS DPEncoderService on card!\n",
720 			__func__);
721 	}
722 
723 	return B_OK;
724 }
725