⛏️ index : haiku.git

/*
 * Copyright 2011-2015, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Alexander von Gluck IV, kallisti5@unixzen.com
 *		Bill Randle, billr@neocat.org
 */


#include "displayport.h"

#include <Debug.h>

#include "accelerant_protos.h"
#include "atombios-obsolete.h"
#include "connector.h"
#include "mode.h"
#include "edid.h"
#include "encoder.h"


#undef TRACE

#define TRACE_DP
#ifdef TRACE_DP
#   define TRACE(x...) _sPrintf("radeon_hd: " x)
#else
#   define TRACE(x...) ;
#endif

#define ERROR(x...) _sPrintf("radeon_hd: " x)


static ssize_t
dp_aux_speak(uint32 connectorIndex, uint8* send, int sendBytes,
	uint8* recv, int recvBytes, uint8 delay, uint8* ack)
{
	dp_info* dpInfo = &gConnector[connectorIndex]->dpInfo;
	if (!dpInfo->valid) {
		ERROR("%s: cannot speak on invalid dpInfo!\n", __func__);
		return B_IO_ERROR;
	}

	int index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction);

	// Build AtomBIOS Transaction
	union auxChannelTransaction {
		PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION v1;
		PROCESS_AUX_CHANNEL_TRANSACTION_PARAMETERS_V2 v2;
	};
	union auxChannelTransaction args;
	memset(&args, 0, sizeof(args));

	args.v2.lpAuxRequest = B_HOST_TO_LENDIAN_INT16(0 + 4);
	args.v2.lpDataOut = B_HOST_TO_LENDIAN_INT16(16 + 4);
	args.v2.ucDataOutLen = 0;
	args.v2.ucChannelID = dpInfo->auxPin;
	args.v2.ucDelay = delay / 10;

	// Careful! This value differs in different atombios calls :-|
	args.v2.ucHPD_ID = connector_pick_atom_hpdid(connectorIndex);

	unsigned char* base = (unsigned char*)(gAtomContext->scratch + 1);

	// TODO: This isn't correct for big endian systems!
	// send needs to be swapped on big endian.
	memcpy(base, send, sendBytes);

	atom_execute_table(gAtomContext, index, (uint32*)&args);

	*ack = args.v2.ucReplyStatus;

	switch (args.v2.ucReplyStatus) {
		case 1:
			ERROR("%s: dp_aux channel timeout!\n", __func__);
			return B_TIMED_OUT;
		case 2:
			ERROR("%s: dp_aux channel flags not zero!\n", __func__);
			return B_BUSY;
		case 3:
			ERROR("%s: dp_aux channel error!\n", __func__);
			return B_IO_ERROR;
	}

	int recvLength = args.v1.ucDataOutLen;
	if (recvLength > recvBytes)
		recvLength = recvBytes;

	// TODO: This isn't correct for big endian systems!
	// recv needs to be swapped on big endian.
	if (recv && recvBytes)
		memcpy(recv, base + 16, recvLength);

	return recvLength;
}


status_t
dp_aux_transaction(uint32 connectorIndex, dp_aux_msg* message)
{
	uint8 delay = 0;
	if (message == NULL) {
		ERROR("%s: DP message is invalid!\n", __func__);
		return B_ERROR;
	}

	if (message->size > 16) {
		ERROR("%s: Too many bytes! (%" B_PRIuSIZE ")\n", __func__,
			message->size);
		return B_ERROR;
	}

	uint8 transactionSize = 4;

	switch(message->request & ~DP_AUX_I2C_MOT) {
		case DP_AUX_NATIVE_WRITE:
		case DP_AUX_I2C_WRITE:
		case DP_AUX_I2C_WRITE_STATUS_UPDATE:
			transactionSize += message->size;
			break;
	}

	// If not bare address, check for buffer
	if (message->size > 0 && message->buffer == NULL) {
		ERROR("%s: DP message uninitalized buffer!\n", __func__);
		return B_ERROR;
	}

	uint8 auxMessage[20];
	auxMessage[0] = message->address & 0xff;
	auxMessage[1] = (message->address >> 8) & 0xff;
	auxMessage[2] = (message->request << 4) | ((message->address >> 16) & 0xf);
	auxMessage[3] = message->size ? (message->size - 1) : 0;

	if (message->size == 0)
		auxMessage[3] |= 3 << 4;
	else
		auxMessage[3] |= transactionSize << 4;

	uint8 retry;
	for (retry = 0; retry < 7; retry++) {
		uint8 ack;
		ssize_t result = B_ERROR;
		switch(message->request & ~DP_AUX_I2C_MOT) {
			case DP_AUX_NATIVE_WRITE:
			case DP_AUX_I2C_WRITE:
			case DP_AUX_I2C_WRITE_STATUS_UPDATE:
				memcpy(auxMessage + 4, message->buffer, message->size);
				result = dp_aux_speak(connectorIndex, auxMessage,
					transactionSize, NULL, 0, delay, &ack);
				break;
			case DP_AUX_NATIVE_READ:
			case DP_AUX_I2C_READ:
				result = dp_aux_speak(connectorIndex, auxMessage,
					transactionSize, (uint8*)message->buffer, message->size,
					delay, &ack);
				break;
			default:
				ERROR("%s: Unknown dp_aux_msg request!\n", __func__);
				return B_ERROR;
		}

		if (result == B_BUSY)
			continue;
		else if (result < B_OK)
			return result;

		ack >>= 4;
		message->reply = ack;
		switch(message->reply & DP_AUX_NATIVE_REPLY_MASK) {
			case DP_AUX_NATIVE_REPLY_ACK:
				return B_OK;
			case DP_AUX_NATIVE_REPLY_DEFER:
				TRACE("%s: aux reply defer received. Snoozing.\n", __func__);
				snooze(400);
				break;
			default:
				TRACE("%s: aux invalid native reply: 0x%02x\n", __func__,
					message->reply);
				return B_IO_ERROR;
		}
	}

	ERROR("%s: IO Error. %" B_PRIu8 " attempts\n", __func__, retry);
	return B_IO_ERROR;
}


void
dpcd_reg_write(uint32 connectorIndex, uint32 address, uint8 value)
{
	//TRACE("%s: connector(%" B_PRId32 "): 0x%" B_PRIx32 " -> 0x%" B_PRIx8 "\n",
	//	__func__, connectorIndex, address, value);
	dp_aux_msg message;
	memset(&message, 0, sizeof(message));

	message.address = address;
	message.buffer = &value;
	message.request = DP_AUX_NATIVE_WRITE;
	message.size = 1;

	status_t result = dp_aux_transaction(connectorIndex, &message);
	if (result != B_OK) {
		ERROR("%s: error on DisplayPort aux write (0x%" B_PRIx32 ")\n",
			__func__, result);
	}
}


uint8
dpcd_reg_read(uint32 connectorIndex, uint32 address)
{
	//TRACE("%s: connector(%" B_PRId32 "): read 0x%" B_PRIx32 ".\n",
	//	__func__, connectorIndex, address);
	uint8 response[3];

	dp_aux_msg message;
	memset(&message, 0, sizeof(message));

	message.address = address;
	message.buffer = &response;
	message.request = DP_AUX_NATIVE_READ;
	message.size = 1;

	status_t result = dp_aux_transaction(connectorIndex, &message);
	if (result != B_OK) {
		ERROR("%s: error on DisplayPort aux read (0x%" B_PRIx32 ")\n",
			__func__, result);
	}

	return response[0];
}


status_t
dp_aux_get_i2c_byte(uint32 connectorIndex, uint32 address, uint8* data,
	bool start, bool stop)
{
	uint8 reply[3];
	dp_aux_msg message;
	memset(&message, 0, sizeof(message));

	message.address = address;
	message.buffer = reply;
	message.request = DP_AUX_I2C_READ;
	message.size = 1;
	if (stop == false) {
		// Remove Middle-Of-Transmission on final transaction
		message.request |= DP_AUX_I2C_MOT;
	}
	if  (stop || start) {
		// Bare address packet
		message.buffer = NULL;
		message.size = 0;
	}

	for (int attempt = 0; attempt < 7; attempt++) {
		status_t result = dp_aux_transaction(connectorIndex, &message);
		if (result != B_OK) {
			ERROR("%s: aux_ch transaction failed!\n", __func__); 
			return result;
		}

		switch (message.reply & DP_AUX_I2C_REPLY_MASK) {
			case DP_AUX_I2C_REPLY_ACK:
				*data = reply[0];
				return B_OK;
			case DP_AUX_I2C_REPLY_NACK:
				TRACE("%s: aux i2c nack\n", __func__);
				return B_IO_ERROR;
			case DP_AUX_I2C_REPLY_DEFER:
				TRACE("%s: aux i2c defer\n", __func__);
				snooze(400);
				break;
			default:
				TRACE("%s: aux invalid I2C reply: 0x%02x\n",
					__func__, message.reply);
				return B_ERROR;
		}
	}
	return B_ERROR;
}


status_t
dp_aux_set_i2c_byte(uint32 connectorIndex, uint32 address, uint8* data,
	bool start, bool stop)
{
	dp_aux_msg message;
	memset(&message, 0, sizeof(message));

	message.address = address;
	message.buffer = data;
	message.request = DP_AUX_I2C_WRITE;
	message.size = 1;
	if (stop == false)
		message.request |= DP_AUX_I2C_MOT;
	if (stop || start) {
		message.buffer = NULL;
		message.size = 0;
	}

	for (int attempt = 0; attempt < 7; attempt++) {
		status_t result = dp_aux_transaction(connectorIndex, &message);
		if (result != B_OK) {
			ERROR("%s: aux_ch transaction failed!\n", __func__); 
			return result;
		}
		switch (message.reply & DP_AUX_I2C_REPLY_MASK) {
			case DP_AUX_I2C_REPLY_ACK:
				return B_OK;
			case DP_AUX_I2C_REPLY_NACK:
				ERROR("%s: aux i2c nack\n", __func__);
				return B_IO_ERROR;
			case DP_AUX_I2C_REPLY_DEFER:
				TRACE("%s: aux i2c defer\n", __func__);
				snooze(400);
				break;
			default:
				ERROR("%s: aux invalid I2C reply: 0x%02x\n", __func__,
					message.reply);
				return B_IO_ERROR;
		}
	}

	return B_ERROR;
}


uint32
dp_get_encoder_config(uint32 connectorIndex)
{
	uint32 result = 0;

	uint32 digEncoderID = encoder_pick_dig(connectorIndex);
	if (digEncoderID)
		result |= ATOM_DP_CONFIG_DIG2_ENCODER;
	else
		result |= ATOM_DP_CONFIG_DIG1_ENCODER;

	bool linkB = gConnector[connectorIndex]->encoder.linkEnumeration
		== GRAPH_OBJECT_ENUM_ID2 ? true : false;
	if (linkB)
		result |= ATOM_DP_CONFIG_LINK_B;
	else
		result |= ATOM_DP_CONFIG_LINK_A;

	return result;
}


uint32
dp_get_lane_count(uint32 connectorIndex, display_mode* mode)
{
	size_t pixelChunk;
	size_t pixelsPerChunk;
	status_t result = dp_get_pixel_size_for((color_space)mode->space,
		&pixelChunk, NULL, &pixelsPerChunk);

	if (result != B_OK) {
		TRACE("%s: Invalid color space!\n", __func__);
		return 0;
	}

	uint32 bitsPerPixel = (pixelChunk / pixelsPerChunk) * 8;

	uint32 dpMaxLinkRate
		= dp_decode_link_rate(dpcd_reg_read(connectorIndex, DP_MAX_LINK_RATE));
	uint32 dpMaxLaneCount = dpcd_reg_read(connectorIndex,
			DP_MAX_LANE_COUNT) & DP_MAX_LANE_COUNT_MASK;

	uint32 lane;
	// don't go below 2 lanes or display is jittery
	for (lane = 2; lane < dpMaxLaneCount; lane <<= 1) {
		uint32 maxPixelClock = dp_get_pixel_clock_max(dpMaxLinkRate, lane,
			bitsPerPixel);
		if (mode->timing.pixel_clock <= maxPixelClock)
			break;
	}

	TRACE("%s: Lanes: %" B_PRIu32 "\n", __func__, lane);
	return lane;
}


uint32
dp_get_link_rate(uint32 connectorIndex, display_mode* mode)
{
	uint16 encoderID = gConnector[connectorIndex]->encoderExternal.objectID;

	if (encoderID == ENCODER_OBJECT_ID_NUTMEG)
		return 270000;

	size_t pixelChunk;
	size_t pixelsPerChunk;
	status_t result = dp_get_pixel_size_for((color_space)mode->space,
		&pixelChunk, NULL, &pixelsPerChunk);

	if (result != B_OK) {
		TRACE("%s: Invalid color space!\n", __func__);
		return 0;
	}

	uint32 bitsPerPixel = (pixelChunk / pixelsPerChunk) * 8;
	uint32 laneCount = dp_get_lane_count(connectorIndex, mode);

	uint32 maxPixelClock
		= dp_get_pixel_clock_max(162000, laneCount, bitsPerPixel);
	if (mode->timing.pixel_clock <= maxPixelClock)
		return 162000;

	maxPixelClock = dp_get_pixel_clock_max(270000, laneCount, bitsPerPixel);
	if (mode->timing.pixel_clock <= maxPixelClock)
		return 270000;

	// TODO: DisplayPort 1.2
	#if 0
	if (dp_is_dp12_capable(connectorIndex)) {
		maxPixelClock = dp_get_pixel_clock_max(540000, laneCount, bitsPerPixel);
		if (mode->timing.pixel_clock <= maxPixelClock)
			return 540000;
	}
	#endif

	return dp_decode_link_rate(dpcd_reg_read(connectorIndex, DP_MAX_LINK_RATE));
}


static uint8
dp_encoder_service(int action, int linkRate, uint8 lane, uint8 config)
{
	DP_ENCODER_SERVICE_PARAMETERS args;
	int index = GetIndexIntoMasterTable(COMMAND, DPEncoderService);

	memset(&args, 0, sizeof(args));
	args.ucLinkClock = linkRate / 10;
	args.ucConfig = config;
	args.ucAction = action;
	args.ucLaneNum = lane;
	args.ucStatus = 0;

	atom_execute_table(gAtomContext, index, (uint32*)&args);
	return args.ucStatus;
}


uint8
dp_get_sink_type(uint32 connectorIndex)
{
	dp_info* dpInfo = &gConnector[connectorIndex]->dpInfo;
	return dp_encoder_service(ATOM_DP_ACTION_GET_SINK_TYPE, 0, 0,
		dpInfo->auxPin);
}


void
dp_setup_connectors()
{
	TRACE("%s\n", __func__);

	for (uint32 index = 0; index < ATOM_MAX_SUPPORTED_DEVICE; index++) {
		dp_info* dpInfo = &gConnector[index]->dpInfo;
		// Validate connector is actually display port
		if (gConnector[index]->valid == false
			|| connector_is_dp(index) == false) {
			dpInfo->valid = false;
			continue;
		}

		// Configure communication pins.
		uint32 i2cPinIndex = gConnector[index]->i2cPinIndex;
		uint32 auxPin = gGPIOInfo[i2cPinIndex]->hwPin;
		dpInfo->auxPin = auxPin;
		dpInfo->valid = true;
	}
}


bool
dp_get_link_status(uint32 connectorIndex)
{
	dp_info* dp = &gConnector[connectorIndex]->dpInfo;

	dp_aux_msg message;
	memset(&message, 0, sizeof(message));

	message.request = DP_AUX_NATIVE_READ;
	message.address = DP_LANE_STATUS_0_1;
	message.size = DP_LINK_STATUS_SIZE;
	message.buffer = dp->linkStatus;

	// TODO: Delay 100? Newer AMD code doesn't care about link status
	status_t result = dp_aux_transaction(connectorIndex, &message);

	if (result != B_OK) {
		ERROR("%s: DisplayPort link status failed\n", __func__);
		return false;
	}

	return true;
}


static uint8
dp_get_lane_status(dp_info* dp, int lane)
{
	int i = DP_LANE_STATUS_0_1 + (lane >> 1);
	int s = (lane & 1) * 4;
	uint8 l = dp->linkStatus[i - DP_LANE_STATUS_0_1];
	return (l >> s) & 0xf;
}


static bool
dp_clock_recovery_ok(dp_info* dp)
{
	int lane;
	uint8 laneStatus;

	for (lane = 0; lane < dp->laneCount; lane++) {
		laneStatus = dp_get_lane_status(dp, lane);
		if ((laneStatus & DP_LANE_STATUS_CR_DONE_A) == 0)
			return false;
	}
	return true;
}


static bool
dp_clock_equalization_ok(dp_info* dp)
{
	uint8 laneAlignment
		= dp->linkStatus[DP_LANE_ALIGN_STATUS_UPDATED - DP_LANE_STATUS_0_1];

	if ((laneAlignment & DP_INTERLANE_ALIGN_DONE) == 0) {
		TRACE("%s: false. Lane alignment incomplete.\n", __func__);
		return false;
	}

	int lane;
	for (lane = 0; lane < dp->laneCount; lane++) {
		uint8 laneStatus = dp_get_lane_status(dp, lane);
		if ((laneStatus & DP_LANE_STATUS_EQUALIZED_A)
			!= DP_LANE_STATUS_EQUALIZED_A) {
			TRACE("%s: false. Lanes not yet equalized.\n", __func__);
			return false;
		}
	}
	TRACE("%s: true. Lanes equalized.\n", __func__);
	return true;
}


static void
dp_update_vs_emph(uint32 connectorIndex)
{
	dp_info* dp = &gConnector[connectorIndex]->dpInfo;

	// Set initial vs and emph on source
	transmitter_dig_setup(connectorIndex, dp->linkRate, 0,
		dp->trainingSet[0], ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH);

	dp_aux_msg message;
	memset(&message, 0, sizeof(message));

	message.request = DP_AUX_NATIVE_WRITE;
	message.address = DP_TRAINING_LANE0_SET;
	message.buffer = dp->trainingSet;
	message.size = dp->laneCount;
		// TODO: Review laneCount as it sounds strange.
	dp_aux_transaction(connectorIndex, &message);
}


static uint8
dp_get_adjust_request_voltage(dp_info* dp, int lane)
{
	int i = DP_ADJ_REQUEST_0_1 + (lane >> 1);
	int s = (((lane & 1) != 0) ? DP_ADJ_VCC_SWING_LANEB_SHIFT
		: DP_ADJ_VCC_SWING_LANEA_SHIFT);
	uint8 l = dp->linkStatus[i - DP_LANE_STATUS_0_1];

	return ((l >> s) & 0x3) << DP_TRAIN_VCC_SWING_SHIFT;
}


static uint8
dp_get_adjust_request_pre_emphasis(dp_info* dp, int lane)
{
	int i = DP_ADJ_REQUEST_0_1 + (lane >> 1);
	int s = (((lane & 1) != 0) ? DP_ADJ_PRE_EMPHASIS_LANEB_SHIFT
		: DP_ADJ_PRE_EMPHASIS_LANEA_SHIFT);
	uint8 l = dp->linkStatus[i - DP_LANE_STATUS_0_1];

	return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
}


static void
dp_get_adjust_train(dp_info* dp)
{
	TRACE("%s\n", __func__);

	const char* voltageNames[] = {
		"0.4V", "0.6V", "0.8V", "1.2V"
	};
	const char* preEmphasisNames[] = {
		"0dB", "3.5dB", "6dB", "9.5dB"
	};

	uint8 voltage = 0;
	uint8 preEmphasis = 0;
	int lane;

	for (lane = 0; lane < dp->laneCount; lane++) {
		uint8 laneVoltage = dp_get_adjust_request_voltage(dp, lane);
		uint8 lanePreEmphasis = dp_get_adjust_request_pre_emphasis(dp, lane);

		TRACE("%s: Requested %s at %s for lane %d\n", __func__,
			preEmphasisNames[lanePreEmphasis >> DP_TRAIN_PRE_EMPHASIS_SHIFT],
			voltageNames[laneVoltage >> DP_TRAIN_VCC_SWING_SHIFT],
			lane);

		if (laneVoltage > voltage)
			voltage = laneVoltage;
		if (lanePreEmphasis > preEmphasis)
			preEmphasis = lanePreEmphasis;
	}

	// Check for maximum voltage and toggle max if reached
	if (voltage >= DP_TRAIN_VCC_SWING_1200)
		voltage |= DP_TRAIN_MAX_SWING_EN;

	// Check for maximum pre-emphasis and toggle max if reached
	if (preEmphasis >= DP_TRAIN_PRE_EMPHASIS_9_5)
		preEmphasis |= DP_TRAIN_MAX_EMPHASIS_EN;

	for (lane = 0; lane < 4; lane++)
		dp->trainingSet[lane] = voltage | preEmphasis;
}


static void
dp_set_tp(uint32 connectorIndex, int trainingPattern)
{
	TRACE("%s\n", __func__);
	radeon_shared_info &info = *gInfo->shared_info;

	pll_info* pll = &gConnector[connectorIndex]->encoder.pll;

	int rawTrainingPattern = 0;

	if (info.dceMajor >= 4) {
		TRACE("%s: Training with encoder...\n", __func__);
		switch (trainingPattern) {
			case DP_TRAINING_PATTERN_1:
				rawTrainingPattern = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN1;
				break;
			case DP_TRAINING_PATTERN_2:
				rawTrainingPattern = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN2;
				break;
			case DP_TRAINING_PATTERN_3:
				rawTrainingPattern = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN3;
				break;
		}
		encoder_dig_setup(connectorIndex, pll->pixelClock, rawTrainingPattern);
	} else {
		switch (trainingPattern) {
			case DP_TRAINING_PATTERN_1:
				rawTrainingPattern = 0;
				break;
			case DP_TRAINING_PATTERN_2:
				rawTrainingPattern = 1;
				break;
		}
		uint32 encoderConfig = dp_get_encoder_config(connectorIndex);
		dp_encoder_service(ATOM_DP_ACTION_TRAINING_PATTERN_SEL, pll->pixelClock,
			rawTrainingPattern, encoderConfig);
	}

	// Enable training pattern on the sink
	dpcd_reg_write(connectorIndex, DP_TRAINING_PATTERN_SET, trainingPattern);
}


status_t
dp_link_train_cr(uint32 connectorIndex)
{
	TRACE("%s: connector %" B_PRIu32 "\n", __func__, connectorIndex);

	dp_info* dp = &gConnector[connectorIndex]->dpInfo;

	// Display Port Clock Recovery Training

	bool clockRecovery = false;
	uint8 voltage = 0xff;
	int lane;

	dp_set_tp(connectorIndex, DP_TRAINING_PATTERN_1);
	memset(dp->trainingSet, 0, 4);
	dp_update_vs_emph(connectorIndex);
	snooze(400);

	while (1) {
		if (dp->trainingReadInterval == 0)
			snooze(100);
		else
			snooze(1000 * 4 * dp->trainingReadInterval);

		if (!dp_get_link_status(connectorIndex))
			break;

		if (dp_clock_recovery_ok(dp)) {
			clockRecovery = true;
			break;
		}

		for (lane = 0; lane < dp->laneCount; lane++) {
			if ((dp->trainingSet[lane] & DP_TRAIN_MAX_SWING_EN) == 0)
				break;
		}

		if (lane == dp->laneCount) {
			ERROR("%s: clock recovery reached max voltage\n", __func__);
			break;
		}

		if ((dp->trainingSet[0] & DP_TRAIN_VCC_SWING_MASK) == voltage) {
			dp->trainingAttempts++;
			if (dp->trainingAttempts >= 5) {
				ERROR("%s: clock recovery tried 5 times\n", __func__);
				break;
			}
		} else
			dp->trainingAttempts = 0;

		voltage = dp->trainingSet[0] & DP_TRAIN_VCC_SWING_MASK;

		// Compute new trainingSet as requested by sink
		dp_get_adjust_train(dp);

		dp_update_vs_emph(connectorIndex);
	}

	if (!clockRecovery) {
		ERROR("%s: clock recovery failed\n", __func__);
		return B_ERROR;
	}

	TRACE("%s: clock recovery at voltage %d pre-emphasis %d\n",
		__func__, dp->trainingSet[0] & DP_TRAIN_VCC_SWING_MASK,
		(dp->trainingSet[0] & DP_TRAIN_PRE_EMPHASIS_MASK)
		>> DP_TRAIN_PRE_EMPHASIS_SHIFT);
	return B_OK;
}


status_t
dp_link_train_ce(uint32 connectorIndex, bool tp3Support)
{
	TRACE("%s: connector %" B_PRIu32 "\n", __func__, connectorIndex);

	dp_info* dp = &gConnector[connectorIndex]->dpInfo;

	if (tp3Support)
		dp_set_tp(connectorIndex, DP_TRAINING_PATTERN_3);
	else
		dp_set_tp(connectorIndex, DP_TRAINING_PATTERN_2);

	dp->trainingAttempts = 0;
	bool channelEqual = false;

	while (1) {
		if (dp->trainingReadInterval == 0)
			snooze(400);
		else
			snooze(1000 * 4 * dp->trainingReadInterval);

		if (!dp_get_link_status(connectorIndex)) {
			ERROR("%s: ERROR: Unable to get link status!\n", __func__);
			break;
		}

		if (dp_clock_equalization_ok(dp)) {
			channelEqual = true;
			break;
		}

		if (dp->trainingAttempts > 5) {
			ERROR("%s: ERROR: failed > 5 times!\n", __func__);
			break;
		}

		dp_get_adjust_train(dp);

		dp_update_vs_emph(connectorIndex);
		dp->trainingAttempts++;
	}

	if (!channelEqual) {
		ERROR("%s: ERROR: failed\n", __func__);
		return B_ERROR;
	}

	TRACE("%s: channels equalized at voltage %d pre-emphasis %d\n",
		__func__, dp->trainingSet[0] & DP_ADJ_VCC_SWING_LANEA_MASK,
		(dp->trainingSet[0] & DP_TRAIN_PRE_EMPHASIS_MASK)
		>> DP_TRAIN_PRE_EMPHASIS_SHIFT);

	return B_OK;
}


status_t
dp_link_train(uint8 crtcID)
{
	TRACE("%s\n", __func__);

	uint32 connectorIndex = gDisplay[crtcID]->connectorIndex;
	dp_info* dp = &gConnector[connectorIndex]->dpInfo;
	display_mode* mode = &gDisplay[crtcID]->currentMode;

	if (dp->valid != true) {
		ERROR("%s: started on invalid DisplayPort connector #%" B_PRIu32 "\n",
			__func__, connectorIndex);
		return B_ERROR;
	}

	dp->revision = dpcd_reg_read(connectorIndex, DP_DPCD_REV);
	dp->trainingReadInterval
		= dpcd_reg_read(connectorIndex, DP_TRAINING_AUX_RD_INTERVAL);

	uint8 sandbox = dpcd_reg_read(connectorIndex, DP_MAX_LANE_COUNT);

	radeon_shared_info &info = *gInfo->shared_info;
	bool dpTPS3Supported = false;
	if (info.dceMajor >= 5 && (sandbox & DP_TPS3_SUPPORTED) != 0)
		dpTPS3Supported = true;

	// *** DisplayPort link training initialization

	// Power up the DP sink
	if (dp->revision >= DP_DPCD_REV_11)
		dpcd_reg_write(connectorIndex, DP_SET_POWER, DP_SET_POWER_D0);

	// Possibly enable downspread on the sink
	if ((dpcd_reg_read(connectorIndex, DP_MAX_DOWNSPREAD) & 0x1) != 0) {
		dpcd_reg_write(connectorIndex, DP_DOWNSPREAD_CTRL,
			DP_DOWNSPREAD_CTRL_AMP_EN);
	} else
		dpcd_reg_write(connectorIndex, DP_DOWNSPREAD_CTRL, 0);

	encoder_dig_setup(connectorIndex, mode->timing.pixel_clock,
		ATOM_ENCODER_CMD_SETUP_PANEL_MODE);

	// Enable enhanced frame if supported
	sandbox = dpcd_reg_read(connectorIndex, DP_LANE_COUNT);
	if (dp->revision >= DP_DPCD_REV_11
		&& (dpcd_reg_read(connectorIndex, DP_MAX_LANE_COUNT)
			& DP_ENHANCED_FRAME_CAP)) {
		sandbox |= DP_ENHANCED_FRAME_EN;
	}
	dpcd_reg_write(connectorIndex, DP_LANE_COUNT, sandbox);

	// Set the link rate on the DP sink
	sandbox = dp_encode_link_rate(dp->linkRate);
	dpcd_reg_write(connectorIndex, DP_LINK_RATE, sandbox);

	uint32 encoderConfig = dp_get_encoder_config(connectorIndex);

	// Start link training on source
	if (info.dceMajor >= 4) {
		encoder_dig_setup(connectorIndex, mode->timing.pixel_clock,
			ATOM_ENCODER_CMD_DP_LINK_TRAINING_START);
	} else {
		dp_encoder_service(ATOM_DP_ACTION_TRAINING_START,
			mode->timing.pixel_clock, 0, encoderConfig);
	}

	// Disable the training pattern on the sink
	dpcd_reg_write(connectorIndex, DP_TRAINING_PATTERN_SET, DP_TRAINING_PATTERN_DISABLE);

	dp_link_train_cr(connectorIndex);
	dp_link_train_ce(connectorIndex, dpTPS3Supported);

	// *** DisplayPort link training finish
	snooze(400);

	// Disable the training pattern on the sink
	dpcd_reg_write(connectorIndex, DP_TRAINING_PATTERN_SET, DP_TRAINING_PATTERN_DISABLE);

	// Disable the training pattern on the source
	if (info.dceMajor >= 4) {
		encoder_dig_setup(connectorIndex, mode->timing.pixel_clock,
			ATOM_ENCODER_CMD_DP_LINK_TRAINING_COMPLETE);
	} else {
		dp_encoder_service(ATOM_DP_ACTION_TRAINING_COMPLETE,
			mode->timing.pixel_clock, 0, encoderConfig);
	}

	return B_OK;
}


bool
ddc2_dp_read_edid1(uint32 connectorIndex, edid1_info* edid)
{
	TRACE("%s\n", __func__);

	dp_info* dpInfo = &gConnector[connectorIndex]->dpInfo;

	if (!dpInfo->valid) {
		ERROR("%s: connector(%" B_PRIu32 ") missing valid DisplayPort data!\n",
			__func__, connectorIndex);
		return false;
	}

	edid1_raw raw;
	uint8* rdata = (uint8*)&raw;
	uint8 sdata = 0;

	// The following sequence is from a trace of the Linux kernel
	// radeon code; not sure if the initial writes to address 0 are
	// requried.
	// TODO: This surely cane be cleaned up
	dp_aux_set_i2c_byte(connectorIndex, 0x00, &sdata, true, false);
	dp_aux_set_i2c_byte(connectorIndex, 0x00, &sdata, false, true);

	dp_aux_set_i2c_byte(connectorIndex, 0x50, &sdata, true, false);
	dp_aux_set_i2c_byte(connectorIndex, 0x50, &sdata, false, false);
	dp_aux_get_i2c_byte(connectorIndex, 0x50, rdata, true, false);
	dp_aux_get_i2c_byte(connectorIndex, 0x50, rdata, false, false);
	dp_aux_get_i2c_byte(connectorIndex, 0x50, rdata, false, true);

	dp_aux_set_i2c_byte(connectorIndex, 0x50, &sdata, true, false);
	dp_aux_set_i2c_byte(connectorIndex, 0x50, &sdata, false, false);
	dp_aux_get_i2c_byte(connectorIndex, 0x50, rdata, true, false);

	for (uint32 i = 0; i < sizeof(raw); i++) {
		status_t result = dp_aux_get_i2c_byte(connectorIndex, 0x50,
			rdata++, false, false);
		if (result != B_OK) {
			TRACE("%s: error reading EDID data at index %" B_PRIu32 ", "
				"result = 0x%" B_PRIx32 "\n", __func__, i, result);
			dp_aux_get_i2c_byte(connectorIndex, 0x50, &sdata, false, true);
			return false;
		}
	}
	dp_aux_get_i2c_byte(connectorIndex, 0x50, &sdata, false, true);

	if (raw.version.version != 1 || raw.version.revision > 4) {
		ERROR("%s: EDID version or revision out of range\n", __func__);
		return false;
	}

	edid_decode(edid, &raw);

	return true;
}


status_t
dp_get_pixel_size_for(color_space space, size_t *pixelChunk,
	size_t *rowAlignment, size_t *pixelsPerChunk)
{
	status_t result = get_pixel_size_for(space, pixelChunk, NULL,
		pixelsPerChunk);

	if ((space == B_RGB32) || (space == B_RGBA32) || (space == B_RGB32_BIG)
		|| (space == B_RGBA32_BIG)) {
		*pixelChunk = 3;
	}

	return result;
}


bool
dp_is_dp12_capable(uint32 connectorIndex)
{
	TRACE("%s\n", __func__);
	radeon_shared_info &info = *gInfo->shared_info;

	uint32 capabilities = gConnector[connectorIndex]->encoder.capabilities;

	// DP_DPCD_REV_12 as well?

	if (info.dceMajor >= 5
		&& gInfo->dpExternalClock >= 539000
		&& (capabilities & ATOM_ENCODER_CAP_RECORD_HBR2) != 0) {
		return true;
	}

	return false;
}


void
debug_dp_info()
{
	ERROR("Current DisplayPort Info =================\n");
	for (uint32 id = 0; id < ATOM_MAX_SUPPORTED_DEVICE; id++) {
		if (gConnector[id]->valid == true) {
			dp_info* dp = &gConnector[id]->dpInfo;
			ERROR("Connector #%" B_PRIu32 ") DP: %s\n", id,
				dp->valid ? "true" : "false");

			if (!dp->valid)
				continue;
			ERROR(" + DP Config Data\n");
			ERROR("   - max lane count:          %d\n",
				dpcd_reg_read(id, DP_MAX_LANE_COUNT) & DP_MAX_LANE_COUNT_MASK);
			ERROR("   - max link rate:           %d\n",
				dpcd_reg_read(id, DP_MAX_LINK_RATE));
			ERROR("   - receiver port count:     %d\n",
				dpcd_reg_read(id, DP_NORP) & DP_NORP_MASK);
			ERROR("   - downstream port present: %s\n",
				dpcd_reg_read(id, DP_DOWNSTREAMPORT_PRESENT) & DP_DWN_STRM_PORT_PRESENT
					? "yes" : "no");
			ERROR("   - downstream port count:   %d\n",
				dpcd_reg_read(id, DP_DOWN_STREAM_PORT_COUNT)
					& DP_PORT_COUNT_MASK);
			ERROR(" + Training\n");
			ERROR("   - attempts:                %" B_PRIu8 "\n",
				dp->trainingAttempts);
			ERROR("   - delay:                   %d\n",
				dp->trainingReadInterval);
			ERROR(" + Data\n");
			ERROR("   - auxPin:                  0x%" B_PRIX32"\n", dp->auxPin);
			ERROR(" + Video\n");
			ERROR("   - laneCount:               %d\n", dp->laneCount);
			ERROR("   - linkRate:                %" B_PRIu32 "\n",
				dp->linkRate);
		}
	}
	ERROR("==========================================\n");
}