#include "Ports.h"
#include <ddc.h>
#include <dp_raw.h>
#include <stdlib.h>
#include <string.h>
#include <Debug.h>
#include <KernelExport.h>
#include "accelerant.h"
#include "accelerant_protos.h"
#include "intel_extreme.h"
#include "FlexibleDisplayInterface.h"
#include "PanelFitter.h"
#include "TigerLakePLL.h"
#include <new>
#undef TRACE
#define TRACE_PORTS
#ifdef TRACE_PORTS
# define TRACE(x...) _sPrintf("intel_extreme: " x)
#else
# define TRACE(x...)
#endif
#define ERROR(x...) _sPrintf("intel_extreme: " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
static bool
wait_for_set(addr_t address, uint32 mask, uint32 timeout)
{
int interval = 50;
uint32 i = 0;
for(i = 0; i <= timeout; i += interval) {
spin(interval);
if ((read32(address) & mask) != 0)
return true;
}
return false;
}
static bool
wait_for_clear(addr_t address, uint32 mask, uint32 timeout)
{
int interval = 50;
uint32 i = 0;
for(i = 0; i <= timeout; i += interval) {
spin(interval);
if ((read32(address) & mask) == 0)
return true;
}
return false;
}
static uint32
wait_for_clear_status(addr_t address, uint32 mask, uint32 timeout)
{
int interval = 50;
uint32 i = 0;
uint32 status = 0;
for(i = 0; i <= timeout; i += interval) {
spin(interval);
status = read32(address);
if ((status & mask) == 0)
return status;
}
return status;
}
Port::Port(port_index index, const char* baseName)
:
fPipe(NULL),
fEDIDState(B_NO_INIT),
fPortIndex(index),
fPortName(NULL)
{
char portID[2];
portID[0] = 'A' + index - INTEL_PORT_A;
portID[1] = 0;
char buffer[32];
buffer[0] = 0;
strlcat(buffer, baseName, sizeof(buffer));
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, portID, sizeof(buffer));
fPortName = strdup(buffer);
}
Port::~Port()
{
free(fPortName);
}
bool
Port::HasEDID()
{
if (fEDIDState == B_NO_INIT)
GetEDID(NULL);
return fEDIDState == B_OK;
}
status_t
Port::SetPipe(Pipe* pipe)
{
CALLED();
if (pipe == NULL) {
ERROR("%s: Invalid pipe provided!\n", __func__);
return B_ERROR;
}
uint32 portRegister = _PortRegister();
if (portRegister == 0) {
ERROR("%s: Invalid PortRegister ((0x%" B_PRIx32 ") for %s\n", __func__,
portRegister, PortName());
return B_ERROR;
}
if (fPipe != NULL) {
ERROR("%s: Can't reassign display pipe (yet)\n", __func__);
return B_ERROR;
}
switch (pipe->Index()) {
case INTEL_PIPE_B:
TRACE("%s: Assigning %s (0x%" B_PRIx32 ") to pipe B\n", __func__,
PortName(), portRegister);
break;
case INTEL_PIPE_C:
TRACE("%s: Assigning %s (0x%" B_PRIx32 ") to pipe C\n", __func__,
PortName(), portRegister);
break;
case INTEL_PIPE_D:
TRACE("%s: Assigning %s (0x%" B_PRIx32 ") to pipe D\n", __func__,
PortName(), portRegister);
break;
default:
TRACE("%s: Assigning %s (0x%" B_PRIx32 ") to pipe A\n", __func__,
PortName(), portRegister);
break;
}
uint32 portState = read32(portRegister);
if (gInfo->shared_info->pch_info == INTEL_PCH_CPT) {
portState &= ~PORT_TRANS_SEL_MASK;
switch (pipe->Index()) {
case INTEL_PIPE_B:
write32(portRegister, portState | PORT_TRANS_B_SEL_CPT);
break;
case INTEL_PIPE_C:
write32(portRegister, portState | PORT_TRANS_C_SEL_CPT);
break;
default:
write32(portRegister, portState | PORT_TRANS_A_SEL_CPT);
break;
}
} else {
if (pipe->Index() == INTEL_PIPE_A)
write32(portRegister, portState & ~DISPLAY_MONITOR_PIPE_B);
else
write32(portRegister, portState | DISPLAY_MONITOR_PIPE_B);
}
fPipe = pipe;
if (fPipe == NULL)
return B_NO_MEMORY;
if (fPipe->IsEnabled())
fPipe->Enable(false);
read32(portRegister);
return B_OK;
}
status_t
Port::Power(bool enabled)
{
if (fPipe == NULL) {
ERROR("%s: Setting power mode without assigned pipe!\n", __func__);
return B_ERROR;
}
fPipe->Enable(enabled);
return B_OK;
}
status_t
Port::GetEDID(edid1_info* edid, bool forceRead)
{
CALLED();
if (fEDIDState == B_NO_INIT || forceRead) {
TRACE("%s: trying to read EDID\n", PortName());
i2c_bus bus;
if (SetupI2c(&bus) != B_OK)
return fEDIDState;
fEDIDState = ddc2_read_edid1(&bus, &fEDIDInfo, NULL, NULL);
if (fEDIDState == B_OK) {
TRACE("%s: found EDID information!\n", PortName());
edid_dump(&fEDIDInfo);
} else if (SetupI2cFallback(&bus) == B_OK) {
fEDIDState = ddc2_read_edid1(&bus, &fEDIDInfo, NULL, NULL);
if (fEDIDState == B_OK) {
TRACE("%s: found EDID information!\n", PortName());
edid_dump(&fEDIDInfo);
}
}
}
if (fEDIDState != B_OK) {
TRACE("%s: no EDID information found.\n", PortName());
return fEDIDState;
}
if (edid != NULL)
memcpy(edid, &fEDIDInfo, sizeof(edid1_info));
return B_OK;
}
status_t
Port::SetupI2c(i2c_bus *bus)
{
addr_t ddcRegister = _DDCRegister();
if (ddcRegister == 0) {
TRACE("%s: no DDC register found\n", PortName());
fEDIDState = B_ERROR;
return fEDIDState;
}
TRACE("%s: using ddc @ 0x%" B_PRIxADDR "\n", PortName(), ddcRegister);
ddc2_init_timing(bus);
bus->cookie = (void*)ddcRegister;
bus->set_signals = &_SetI2CSignals;
bus->get_signals = &_GetI2CSignals;
return B_OK;
}
status_t
Port::SetupI2cFallback(i2c_bus *bus)
{
return B_ERROR;
}
status_t
Port::GetPLLLimits(pll_limits& limits)
{
return B_ERROR;
}
pipe_index
Port::PipePreference()
{
CALLED();
if (gInfo->shared_info->device_type.Generation() < 4)
return INTEL_PIPE_ANY;
if ((gInfo->shared_info->device_type.Generation() <= 7) &&
(!gInfo->shared_info->device_type.HasDDI())) {
uint32 portState = read32(_PortRegister());
if (gInfo->shared_info->pch_info == INTEL_PCH_CPT) {
portState &= PORT_TRANS_SEL_MASK;
if (portState == PORT_TRANS_B_SEL_CPT)
return INTEL_PIPE_B;
else
return INTEL_PIPE_A;
} else {
if (portState & DISPLAY_MONITOR_PIPE_B)
return INTEL_PIPE_B;
else
return INTEL_PIPE_A;
}
}
if (gInfo->shared_info->device_type.HasDDI()) {
uint32 pipeState = 0;
for (uint32 pipeCnt = 0; pipeCnt < 4; pipeCnt++) {
switch (pipeCnt) {
case 0:
pipeState = read32(PIPE_DDI_FUNC_CTL_A);
break;
case 1:
pipeState = read32(PIPE_DDI_FUNC_CTL_B);
break;
case 2:
pipeState = read32(PIPE_DDI_FUNC_CTL_C);
break;
default:
pipeState = read32(PIPE_DDI_FUNC_CTL_EDP);
break;
}
if ((((pipeState & PIPE_DDI_SELECT_MASK) >> PIPE_DDI_SELECT_SHIFT) + 1)
== (uint32)PortIndex()) {
switch (pipeCnt) {
case 0:
return INTEL_PIPE_A;
case 1:
return INTEL_PIPE_B;
case 2:
return INTEL_PIPE_C;
default:
return INTEL_PIPE_D;
}
}
}
}
return INTEL_PIPE_ANY;
}
status_t
Port::_GetI2CSignals(void* cookie, int* _clock, int* _data)
{
addr_t ioRegister = (addr_t)cookie;
uint32 value = read32(ioRegister);
*_clock = (value & I2C_CLOCK_VALUE_IN) != 0;
*_data = (value & I2C_DATA_VALUE_IN) != 0;
return B_OK;
}
status_t
Port::_SetI2CSignals(void* cookie, int clock, int data)
{
addr_t ioRegister = (addr_t)cookie;
uint32 value;
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_83x)) {
value = 0;
} else {
value = read32(ioRegister) & I2C_RESERVED;
}
value |= I2C_DATA_VALUE_MASK;
value |= I2C_CLOCK_VALUE_MASK;
if (data != 0)
value |= I2C_DATA_DIRECTION_MASK;
else {
value |= I2C_DATA_DIRECTION_MASK | I2C_DATA_DIRECTION_OUT;
}
if (clock != 0)
value |= I2C_CLOCK_DIRECTION_MASK;
else {
value |= I2C_CLOCK_DIRECTION_MASK | I2C_CLOCK_DIRECTION_OUT;
}
write32(ioRegister, value);
read32(ioRegister);
return B_OK;
}
bool
Port::_IsPortInVBT(uint32* foundIndex)
{
bool found = false;
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
for (uint32 i = 0; i < deviceConfigCount; i++) {
child_device_config& config = gInfo->shared_info->device_configs[i];
if (config.dvo_port > DVO_PORT_HDMII) {
ERROR("%s: DVO port unknown\n", __func__);
continue;
}
dvo_port port = (dvo_port)config.dvo_port;
switch (PortIndex()) {
case INTEL_PORT_A:
found = port == DVO_PORT_HDMIA || port == DVO_PORT_DPA;
break;
case INTEL_PORT_B:
found = port == DVO_PORT_HDMIB || port == DVO_PORT_DPB;
break;
case INTEL_PORT_C:
found = port == DVO_PORT_HDMIC || port == DVO_PORT_DPC;
break;
case INTEL_PORT_D:
found = port == DVO_PORT_HDMID || port == DVO_PORT_DPD;
break;
case INTEL_PORT_E:
found = port == DVO_PORT_HDMIE || port == DVO_PORT_DPE || port == DVO_PORT_CRT;
break;
case INTEL_PORT_F:
found = port == DVO_PORT_HDMIF || port == DVO_PORT_DPF;
break;
case INTEL_PORT_G:
found = port == DVO_PORT_HDMIG || port == DVO_PORT_DPG;
break;
default:
ERROR("%s: DDI port unknown\n", __func__);
break;
}
if (found) {
if (foundIndex != NULL)
*foundIndex = i;
break;
}
}
return found;
}
bool
Port::_IsDisplayPortInVBT()
{
uint32 foundIndex = 0;
if (!_IsPortInVBT(&foundIndex))
return false;
child_device_config& config = gInfo->shared_info->device_configs[foundIndex];
return config.aux_channel > 0 && (config.device_type & DEVICE_TYPE_DISPLAYPORT_OUTPUT) != 0;
}
bool
Port::_IsHdmiInVBT()
{
uint32 foundIndex = 0;
if (!_IsPortInVBT(&foundIndex))
return false;
child_device_config& config = gInfo->shared_info->device_configs[foundIndex];
return config.ddc_pin > 0 && ((config.device_type & DEVICE_TYPE_NOT_HDMI_OUTPUT) == 0
|| (config.device_type & DEVICE_TYPE_TMDS_DVI_SIGNALING) != 0);
}
bool
Port::_IsEDPPort()
{
uint32 foundIndex = 0;
if (!_IsPortInVBT(&foundIndex))
return false;
child_device_config& config = gInfo->shared_info->device_configs[foundIndex];
return (config.device_type & (DEVICE_TYPE_INTERNAL_CONNECTOR | DEVICE_TYPE_DISPLAYPORT_OUTPUT))
== (DEVICE_TYPE_INTERNAL_CONNECTOR | DEVICE_TYPE_DISPLAYPORT_OUTPUT);
}
addr_t
Port::_DDCPin()
{
uint32 foundIndex = 0;
if (!_IsPortInVBT(&foundIndex))
return 0;
child_device_config& config = gInfo->shared_info->device_configs[foundIndex];
if (gInfo->shared_info->pch_info >= INTEL_PCH_ICP) {
switch (config.ddc_pin) {
case 1:
return INTEL_I2C_IO_A;
case 2:
return INTEL_I2C_IO_B;
case 3:
return INTEL_I2C_IO_C;
case 4:
return INTEL_I2C_IO_I;
case 5:
return INTEL_I2C_IO_J;
case 6:
return INTEL_I2C_IO_K;
case 7:
return INTEL_I2C_IO_L;
case 8:
return INTEL_I2C_IO_M;
case 9:
return INTEL_I2C_IO_N;
default:
return 0;
}
} else if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
switch (config.ddc_pin) {
case 1:
return INTEL_I2C_IO_A;
case 2:
return INTEL_I2C_IO_B;
case 3:
return INTEL_I2C_IO_D;
case 4:
return INTEL_I2C_IO_C;
default:
return 0;
}
} else if (gInfo->shared_info->device_type.Generation() == 9) {
switch (config.ddc_pin) {
case 4:
return INTEL_I2C_IO_D;
case 5:
return INTEL_I2C_IO_E;
case 6:
return INTEL_I2C_IO_F;
default:
return 0;
}
} else if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_BDW)) {
switch (config.ddc_pin) {
case 2:
return INTEL_I2C_IO_A;
case 4:
return INTEL_I2C_IO_D;
case 5:
return INTEL_I2C_IO_E;
case 6:
return INTEL_I2C_IO_F;
default:
return 0;
}
} else {
switch (config.ddc_pin) {
case 1:
return INTEL_I2C_IO_B;
case 2:
return INTEL_I2C_IO_A;
case 3:
return INTEL_I2C_IO_C;
case 4:
return INTEL_I2C_IO_D;
case 5:
return INTEL_I2C_IO_E;
case 6:
return INTEL_I2C_IO_F;
default:
return 0;
}
}
}
status_t
Port::_SetupDpAuxI2c(i2c_bus *bus)
{
CALLED();
ddc2_init_timing(bus);
bus->cookie = this;
bus->send_receive = &_DpAuxSendReceiveHook;
if (gInfo->shared_info->device_type.Generation() >= 11) {
uint32 value = read32(ICL_PWR_WELL_CTL_AUX2);
if ((value & HSW_PWR_WELL_CTL_STATE(0)) != 0)
return B_OK;
write32(ICL_PWR_WELL_CTL_AUX2, value | HSW_PWR_WELL_CTL_REQ(0));
if (!wait_for_set(ICL_PWR_WELL_CTL_AUX2, HSW_PWR_WELL_CTL_STATE(0), 1000))
ERROR("%s: %s AUX didn't power on within 1000us!\n", __func__, PortName());
}
return B_OK;
}
status_t
Port::_DpAuxSendReceive(uint32 slaveAddress,
const uint8 *writeBuffer, size_t writeLength, uint8 *readBuffer, size_t readLength)
{
size_t transferLength = 16;
dp_aux_msg message;
memset(&message, 0, sizeof(message));
if (writeBuffer != NULL) {
message.address = slaveAddress;
message.buffer = NULL;
message.request = DP_AUX_I2C_WRITE;
message.size = 0;
ssize_t result = _DpAuxTransfer(&message);
if (result < 0)
return result;
for (size_t i = 0; i < writeLength;) {
message.buffer = (void*)(writeBuffer + i);
message.size = min_c(transferLength, writeLength - i);
if (writeLength - i > transferLength)
message.request |= DP_AUX_I2C_MOT;
else
message.request &= ~DP_AUX_I2C_MOT;
for (int attempt = 0; attempt < 7; attempt++) {
ssize_t result = _DpAuxTransfer(&message);
if (result < 0) {
ERROR("%s: aux_ch transaction failed!\n", __func__);
return result;
}
switch (message.reply & DP_AUX_I2C_REPLY_MASK) {
case DP_AUX_I2C_REPLY_ACK:
goto nextWrite;
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;
}
}
nextWrite:
if (result < 0)
return result;
i += message.size;
}
}
if (readBuffer != NULL) {
message.address = slaveAddress;
message.buffer = NULL;
message.request = DP_AUX_I2C_READ;
message.size = 0;
ssize_t result = _DpAuxTransfer(&message);
if (result < 0)
return result;
for (size_t i = 0; i < readLength;) {
message.buffer = readBuffer + i;
message.size = min_c(transferLength, readLength - i);
if (readLength - i > transferLength)
message.request |= DP_AUX_I2C_MOT;
else
message.request &= ~DP_AUX_I2C_MOT;
for (int attempt = 0; attempt < 7; attempt++) {
result = _DpAuxTransfer(&message);
if (result < 0) {
ERROR("%s: aux_ch transaction failed!\n", __func__);
return result;
}
switch (message.reply & DP_AUX_I2C_REPLY_MASK) {
case DP_AUX_I2C_REPLY_ACK:
goto nextRead;
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;
}
}
nextRead:
if (result < 0)
return result;
if (result == 0)
i += message.size;
}
}
return B_OK;
}
status_t
Port::_DpAuxSendReceiveHook(const struct i2c_bus *bus, uint32 slaveAddress,
const uint8 *writeBuffer, size_t writeLength, uint8 *readBuffer, size_t readLength)
{
CALLED();
Port* port = (Port*)bus->cookie;
return port->_DpAuxSendReceive(slaveAddress, writeBuffer, writeLength, readBuffer, readLength);
}
ssize_t
Port::_DpAuxTransfer(dp_aux_msg* message)
{
CALLED();
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 transmitSize = message->size > 0 ? 4 : 3;
uint8 receiveSize;
switch(message->request & ~DP_AUX_I2C_MOT) {
case DP_AUX_NATIVE_WRITE:
case DP_AUX_I2C_WRITE:
case DP_AUX_I2C_WRITE_STATUS_UPDATE:
transmitSize += message->size;
break;
}
if (message->size > 0 && message->buffer == NULL) {
ERROR("%s: DP message uninitalized buffer!\n", __func__);
return B_ERROR;
}
uint8 receiveBuffer[20];
uint8 transmitBuffer[20];
transmitBuffer[0] = (message->request << 4) | ((message->address >> 16) & 0xf);
transmitBuffer[1] = (message->address >> 8) & 0xff;
transmitBuffer[2] = message->address & 0xff;
transmitBuffer[3] = message->size != 0 ? (message->size - 1) : 0;
uint8 retry;
for (retry = 0; retry < 7; retry++) {
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:
receiveSize = 2;
if (message->buffer != NULL)
memcpy(transmitBuffer + 4, message->buffer, message->size);
result = _DpAuxTransfer(transmitBuffer,
transmitSize, receiveBuffer, receiveSize);
if (result > 0) {
message->reply = receiveBuffer[0] >> 4;
if (result > 1)
result = min_c(receiveBuffer[1], message->size);
else
result = message->size;
}
break;
case DP_AUX_NATIVE_READ:
case DP_AUX_I2C_READ:
receiveSize = message->size + 1;
result = _DpAuxTransfer(transmitBuffer,
transmitSize, receiveBuffer, receiveSize);
if (result > 0) {
message->reply = receiveBuffer[0] >> 4;
result--;
if (message->buffer != NULL)
memcpy(message->buffer, receiveBuffer + 1, result);
}
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;
switch (message->reply & DP_AUX_NATIVE_REPLY_MASK) {
case DP_AUX_NATIVE_REPLY_ACK:
return B_OK;
case DP_AUX_NATIVE_REPLY_NACK:
TRACE("%s: aux native reply nack\n", __func__);
return B_IO_ERROR;
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;
}
ssize_t
Port::_DpAuxTransfer(uint8* transmitBuffer, uint8 transmitSize,
uint8* receiveBuffer, uint8 receiveSize)
{
addr_t channelControl;
addr_t channelData[5];
aux_channel channel = _DpAuxChannel();
TRACE("%s: %s DpAuxChannel: 0x%x\n", __func__, PortName(), channel);
if (gInfo->shared_info->device_type.Generation() >= 9
|| (gInfo->shared_info->pch_info != INTEL_PCH_NONE && channel == AUX_CH_A)) {
channelControl = DP_AUX_CH_CTL(channel);
for (int i = 0; i < 5; i++)
channelData[i] = DP_AUX_CH_DATA(channel, i);
} else if (gInfo->shared_info->pch_info != INTEL_PCH_NONE) {
channelControl = PCH_DP_AUX_CH_CTL(channel);
for (int i = 0; i < 5; i++)
channelData[i] = PCH_DP_AUX_CH_DATA(channel, i);
} else {
ERROR("Port::_DpAuxTransfer() unknown register config\n");
return B_BUSY;
}
if (transmitSize > 20 || receiveSize > 20)
return E2BIG;
int tries = 0;
while ((read32(channelControl) & INTEL_DP_AUX_CTL_BUSY) != 0) {
if (tries++ == 3) {
ERROR("%s: %s AUX channel is busy!\n", __func__, PortName());
return B_BUSY;
}
snooze(1000);
}
uint32 sendControl = 0;
if (gInfo->shared_info->device_type.Generation() >= 9) {
sendControl = INTEL_DP_AUX_CTL_BUSY | INTEL_DP_AUX_CTL_DONE | INTEL_DP_AUX_CTL_INTERRUPT
| INTEL_DP_AUX_CTL_TIMEOUT_ERROR | INTEL_DP_AUX_CTL_TIMEOUT_1600us | INTEL_DP_AUX_CTL_RECEIVE_ERROR
| (transmitSize << INTEL_DP_AUX_CTL_MSG_SIZE_SHIFT) | INTEL_DP_AUX_CTL_FW_SYNC_PULSE_SKL(32)
| INTEL_DP_AUX_CTL_SYNC_PULSE_SKL(32);
} else {
uint32 frequency = gInfo->shared_info->hw_cdclk;
if (channel != AUX_CH_A)
frequency = gInfo->shared_info->hraw_clock;
uint32 aux_clock_divider = (frequency + 2000 / 2) / 2000;
if (gInfo->shared_info->pch_info == INTEL_PCH_LPT && channel != AUX_CH_A)
aux_clock_divider = 0x48;
uint32 timeout = INTEL_DP_AUX_CTL_TIMEOUT_400us;
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_BDW))
timeout = INTEL_DP_AUX_CTL_TIMEOUT_600us;
sendControl = INTEL_DP_AUX_CTL_BUSY | INTEL_DP_AUX_CTL_DONE | INTEL_DP_AUX_CTL_INTERRUPT
| INTEL_DP_AUX_CTL_TIMEOUT_ERROR | timeout | INTEL_DP_AUX_CTL_RECEIVE_ERROR
| (transmitSize << INTEL_DP_AUX_CTL_MSG_SIZE_SHIFT) | (3 << INTEL_DP_AUX_CTL_PRECHARGE_2US_SHIFT)
| (aux_clock_divider << INTEL_DP_AUX_CTL_BIT_CLOCK_2X_SHIFT);
}
uint8 retry;
uint32 status = 0;
for (retry = 0; retry < 5; retry++) {
for (uint8 i = 0; i < transmitSize;) {
uint8 index = i / 4;
uint32 data = ((uint32)transmitBuffer[i++]) << 24;
if (i < transmitSize)
data |= ((uint32)transmitBuffer[i++]) << 16;
if (i < transmitSize)
data |= ((uint32)transmitBuffer[i++]) << 8;
if (i < transmitSize)
data |= transmitBuffer[i++];
write32(channelData[index], data);
}
write32(channelControl, sendControl);
status = wait_for_clear_status(channelControl, INTEL_DP_AUX_CTL_BUSY, 10000);
if ((status & INTEL_DP_AUX_CTL_BUSY) != 0) {
ERROR("%s: %s AUX channel stayed busy for 10000us!\n", __func__, PortName());
}
write32(channelControl, status | INTEL_DP_AUX_CTL_DONE | INTEL_DP_AUX_CTL_TIMEOUT_ERROR
| INTEL_DP_AUX_CTL_RECEIVE_ERROR);
if ((status & INTEL_DP_AUX_CTL_TIMEOUT_ERROR) != 0)
continue;
if ((status & INTEL_DP_AUX_CTL_RECEIVE_ERROR) != 0) {
snooze(400);
continue;
}
if ((status & INTEL_DP_AUX_CTL_DONE) != 0)
goto done;
}
if ((status & INTEL_DP_AUX_CTL_DONE) == 0) {
ERROR("%s: Busy Error. %" B_PRIu8 " attempts\n", __func__, retry);
return B_BUSY;
}
done:
if ((status & INTEL_DP_AUX_CTL_RECEIVE_ERROR) != 0)
return B_IO_ERROR;
if ((status & INTEL_DP_AUX_CTL_TIMEOUT_ERROR) != 0)
return B_TIMEOUT;
uint8 bytes = (status & INTEL_DP_AUX_CTL_MSG_SIZE_MASK) >> INTEL_DP_AUX_CTL_MSG_SIZE_SHIFT;
if (bytes == 0 || bytes > 20) {
ERROR("%s: Status byte count incorrect %u\n", __func__, bytes);
return B_BUSY;
}
if (bytes > receiveSize)
bytes = receiveSize;
for (uint8 i = 0; i < bytes;) {
uint32 data = read32(channelData[i / 4]);
receiveBuffer[i++] = data >> 24;
if (i < bytes)
receiveBuffer[i++] = data >> 16;
if (i < bytes)
receiveBuffer[i++] = data >> 8;
if (i < bytes)
receiveBuffer[i++] = data;
}
return bytes;
}
aux_channel
Port::_DpAuxChannel()
{
uint32 foundIndex = 0;
if (!_IsPortInVBT(&foundIndex))
return AUX_CH_A;
child_device_config& config = gInfo->shared_info->device_configs[foundIndex];
switch (config.aux_channel) {
case DP_AUX_B:
return AUX_CH_B;
case DP_AUX_C:
return AUX_CH_C;
case DP_AUX_D:
return AUX_CH_D;
case DP_AUX_E:
return AUX_CH_E;
case DP_AUX_F:
return AUX_CH_F;
default:
return AUX_CH_A;
}
}
AnalogPort::AnalogPort()
:
Port(INTEL_PORT_A, "Analog")
{
}
bool
AnalogPort::IsConnected()
{
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
_PortRegister());
return HasEDID();
}
addr_t
AnalogPort::_DDCRegister()
{
return INTEL_I2C_IO_A;
}
addr_t
AnalogPort::_PortRegister()
{
return INTEL_ANALOG_PORT;
}
status_t
AnalogPort::SetDisplayMode(display_mode* target, uint32 colorMode)
{
CALLED();
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->timing.h_display,
target->timing.v_display);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
PanelFitter* fitter = fPipe->PFT();
if (fitter != NULL)
fitter->Enable(target->timing);
FDILink* link = fPipe->FDI();
if (link != NULL) {
uint32 lanes = 0;
uint32 linkBandwidth = 0;
uint32 bitsPerPixel = 0;
link->PreTrain(&target->timing, &linkBandwidth, &lanes, &bitsPerPixel);
fPipe->SetFDILink(target->timing, linkBandwidth, lanes, bitsPerPixel);
link->Train(&target->timing, lanes);
}
pll_divisors divisors;
compute_pll_divisors(&target->timing, &divisors, false);
uint32 extraPLLFlags = 0;
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_NORMAL;
fPipe->Configure(target);
fPipe->ConfigureClocks(divisors, target->timing.pixel_clock, extraPLLFlags);
write32(_PortRegister(), (read32(_PortRegister())
& ~(DISPLAY_MONITOR_POLARITY_MASK | DISPLAY_MONITOR_VGA_POLARITY))
| ((target->timing.flags & B_POSITIVE_HSYNC) != 0
? DISPLAY_MONITOR_POSITIVE_HSYNC : 0)
| ((target->timing.flags & B_POSITIVE_VSYNC) != 0
? DISPLAY_MONITOR_POSITIVE_VSYNC : 0));
fPipe->ConfigureTimings(target);
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
LVDSPort::LVDSPort()
:
Port(INTEL_PORT_C, "LVDS")
{
uint32 panelControl = INTEL_PANEL_CONTROL;
if (gInfo->shared_info->pch_info != INTEL_PCH_NONE) {
return;
}
write32(panelControl, read32(panelControl) | PANEL_REGISTER_UNLOCK);
}
pipe_index
LVDSPort::PipePreference()
{
CALLED();
if (gInfo->shared_info->device_type.Generation() < 4)
return INTEL_PIPE_B;
if (gInfo->shared_info->device_type.Generation() <= 7) {
uint32 portState = read32(_PortRegister());
if (gInfo->shared_info->pch_info == INTEL_PCH_CPT) {
portState &= PORT_TRANS_SEL_MASK;
if (portState == PORT_TRANS_B_SEL_CPT)
return INTEL_PIPE_B;
else
return INTEL_PIPE_A;
} else {
if (portState & DISPLAY_MONITOR_PIPE_B)
return INTEL_PIPE_B;
else
return INTEL_PIPE_A;
}
}
return INTEL_PIPE_B;
}
bool
LVDSPort::IsConnected()
{
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
_PortRegister());
if (gInfo->shared_info->pch_info != INTEL_PCH_NONE) {
uint32 registerValue = read32(_PortRegister());
if ((registerValue & PCH_LVDS_DETECTED) == 0) {
TRACE("LVDS: Not detected\n");
return false;
}
} else if (gInfo->shared_info->device_type.Generation() <= 4) {
if (!gInfo->shared_info->device_type.IsMobile()) {
TRACE("LVDS: Skipping LVDS detection due to gen and not mobile\n");
return false;
}
if (!HasEDID()) {
if (gInfo->shared_info->has_vesa_edid_info) {
TRACE("LVDS: Using VESA edid info\n");
memcpy(&fEDIDInfo, &gInfo->shared_info->vesa_edid_info,
sizeof(edid1_info));
if (fEDIDState != B_OK) {
fEDIDState = B_OK;
edid_dump(&fEDIDInfo);
}
} else if (gInfo->shared_info->got_vbt) {
TRACE("LVDS: No EDID, but force enabled as we have a VBT\n");
return true;
} else {
TRACE("LVDS: Couldn't find any valid EDID!\n");
return false;
}
}
}
return HasEDID();
}
addr_t
LVDSPort::_DDCRegister()
{
return INTEL_I2C_IO_C;
}
addr_t
LVDSPort::_PortRegister()
{
return INTEL_DIGITAL_LVDS_PORT;
}
status_t
LVDSPort::SetDisplayMode(display_mode* target, uint32 colorMode)
{
CALLED();
if (target == NULL) {
ERROR("%s: Invalid target mode passed!\n", __func__);
return B_ERROR;
}
TRACE("%s: %s-%d %dx%d\n", __func__, PortName(), PortIndex(),
target->timing.h_display, target->timing.v_display);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
addr_t panelControl = INTEL_PANEL_CONTROL;
addr_t panelStatus = INTEL_PANEL_STATUS;
if (gInfo->shared_info->pch_info != INTEL_PCH_NONE) {
panelControl = PCH_PANEL_CONTROL;
panelStatus = PCH_PANEL_STATUS;
}
if (gInfo->shared_info->device_type.Generation() != 4) {
write32(panelControl,
read32(panelControl) & ~PANEL_CONTROL_POWER_TARGET_ON);
read32(panelControl);
if (!wait_for_clear(panelStatus, PANEL_STATUS_POWER_ON, 1000)) {
ERROR("%s: %s didn't power off within 1000ms!\n", __func__,
PortName());
}
}
display_timing hardwareTarget;
bool needsScaling = false;
if (gInfo->shared_info->device_type.Generation() <= 6
&& gInfo->shared_info->device_type.Generation() >= 3
&& gInfo->shared_info->got_vbt) {
hardwareTarget = gInfo->shared_info->panel_timing;
if (hardwareTarget.h_display == target->timing.h_display
&& hardwareTarget.v_display == target->timing.v_display) {
hardwareTarget = target->timing;
TRACE("%s: Setting LVDS to native resolution at %" B_PRIu32 "Hz\n", __func__,
hardwareTarget.pixel_clock * 1000 / (hardwareTarget.h_total * hardwareTarget.v_total));
} else {
TRACE("%s: Hardware mode will actually be %dx%d at %" B_PRIu32 "Hz\n", __func__,
hardwareTarget.h_display, hardwareTarget.v_display,
hardwareTarget.pixel_clock * 1000 / (hardwareTarget.h_total * hardwareTarget.v_total));
needsScaling = true;
}
} else {
TRACE("Setting LVDS mode without VBT info or on unhandled hardware "
"generation, scaling may not work\n");
hardwareTarget = target->timing;
}
PanelFitter* fitter = fPipe->PFT();
if (fitter != NULL)
fitter->Enable(hardwareTarget);
FDILink* link = fPipe->FDI();
if (link != NULL) {
uint32 lanes = 0;
uint32 linkBandwidth = 0;
uint32 bitsPerPixel = 0;
link->PreTrain(&hardwareTarget, &linkBandwidth, &lanes, &bitsPerPixel);
fPipe->SetFDILink(hardwareTarget, linkBandwidth, lanes, bitsPerPixel);
link->Train(&hardwareTarget, lanes);
}
pll_divisors divisors;
compute_pll_divisors(&hardwareTarget, &divisors, true);
uint32 lvds = read32(_PortRegister())
| LVDS_PORT_EN | LVDS_A0A2_CLKA_POWER_UP;
if (gInfo->shared_info->device_type.Generation() == 4) {
if ((lvds & LVDS_A3_POWER_MASK) != LVDS_A3_POWER_UP)
lvds |= LVDS_18BIT_DITHER;
}
if (gInfo->shared_info->pch_info == INTEL_PCH_CPT) {
lvds &= ~PORT_TRANS_SEL_MASK;
if (fPipe->Index() == INTEL_PIPE_A)
lvds |= PORT_TRANS_A_SEL_CPT;
else
lvds |= PORT_TRANS_B_SEL_CPT;
}
if (divisors.p2 == 5 || divisors.p2 == 7) {
TRACE("LVDS: dual channel\n");
lvds |= LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP;
} else {
TRACE("LVDS: single channel\n");
lvds &= ~(LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP);
}
lvds &= ~(LVDS_HSYNC_POLARITY | LVDS_VSYNC_POLARITY);
if ((target->timing.flags & B_POSITIVE_HSYNC) == 0)
lvds |= LVDS_HSYNC_POLARITY;
if ((target->timing.flags & B_POSITIVE_VSYNC) == 0)
lvds |= LVDS_VSYNC_POLARITY;
TRACE("%s: LVDS Write: 0x%" B_PRIx32 "\n", __func__, lvds);
write32(_PortRegister(), lvds);
read32(_PortRegister());
uint32 extraPLLFlags = 0;
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_LVDS | DISPLAY_PLL_2X_CLOCK;
fPipe->Configure(target);
fPipe->ConfigureClocks(divisors, hardwareTarget.pixel_clock,
extraPLLFlags);
if (gInfo->shared_info->device_type.Generation() != 4) {
write32(panelControl,
read32(panelControl) | PANEL_CONTROL_POWER_TARGET_ON);
read32(panelControl);
if (!wait_for_set(panelStatus, PANEL_STATUS_POWER_ON, 1000)) {
ERROR("%s: %s didn't power on within 1000us!\n", __func__,
PortName());
}
}
fPipe->ConfigureTimings(target, !needsScaling);
if (needsScaling) {
if (gInfo->shared_info->device_type.Generation() <= 4) {
uint32 panelFitterControl = read32(INTEL_PANEL_FIT_CONTROL);
panelFitterControl |= PANEL_FITTER_ENABLED;
panelFitterControl &= ~(PANEL_FITTER_SCALING_MODE_MASK
| PANEL_FITTER_PIPE_MASK);
panelFitterControl |= PANEL_FITTER_PIPE_B;
write32(INTEL_PANEL_FIT_CONTROL, panelFitterControl);
}
} else {
if (gInfo->shared_info->device_type.Generation() == 4
|| gInfo->shared_info->device_type.Generation() == 3) {
uint32 panelFitterControl = read32(INTEL_PANEL_FIT_CONTROL);
panelFitterControl &= ~PANEL_FITTER_ENABLED;
write32(INTEL_PANEL_FIT_CONTROL, panelFitterControl);
} else {
#if 0
write32(INTEL_PANEL_FIT_CONTROL, 0x4);
#endif
}
}
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
DigitalPort::DigitalPort(port_index index, const char* baseName)
:
Port(index, baseName)
{
}
bool
DigitalPort::IsConnected()
{
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
_PortRegister());
return HasEDID();
}
addr_t
DigitalPort::_DDCRegister()
{
switch (PortIndex()) {
case INTEL_PORT_B:
return INTEL_I2C_IO_E;
case INTEL_PORT_C:
return INTEL_I2C_IO_D;
case INTEL_PORT_D:
return INTEL_I2C_IO_F;
default:
return 0;
}
return 0;
}
addr_t
DigitalPort::_PortRegister()
{
switch (PortIndex()) {
case INTEL_PORT_A:
return INTEL_DIGITAL_PORT_A;
case INTEL_PORT_B:
return INTEL_DIGITAL_PORT_B;
case INTEL_PORT_C:
return INTEL_DIGITAL_PORT_C;
default:
return 0;
}
return 0;
}
status_t
DigitalPort::SetDisplayMode(display_mode* target, uint32 colorMode)
{
CALLED();
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->timing.h_display,
target->timing.v_display);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
PanelFitter* fitter = fPipe->PFT();
if (fitter != NULL)
fitter->Enable(target->timing);
FDILink* link = fPipe->FDI();
if (link != NULL) {
uint32 lanes = 0;
uint32 linkBandwidth = 0;
uint32 bitsPerPixel = 0;
link->PreTrain(&target->timing, &linkBandwidth, &lanes, &bitsPerPixel);
fPipe->SetFDILink(target->timing, linkBandwidth, lanes, bitsPerPixel);
link->Train(&target->timing, lanes);
}
pll_divisors divisors;
compute_pll_divisors(&target->timing, &divisors, false);
uint32 extraPLLFlags = 0;
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_NORMAL | DISPLAY_PLL_2X_CLOCK;
fPipe->Configure(target);
fPipe->ConfigureClocks(divisors, target->timing.pixel_clock, extraPLLFlags);
fPipe->ConfigureTimings(target);
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
HDMIPort::HDMIPort(port_index index)
:
DigitalPort(index, "HDMI")
{
}
bool
HDMIPort::IsConnected()
{
if (!gInfo->shared_info->device_type.SupportsHDMI())
return false;
addr_t portRegister = _PortRegister();
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
portRegister);
if (portRegister == 0)
return false;
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
if (gInfo->shared_info->device_type.Generation() >= 6 && deviceConfigCount > 0) {
if (!_IsPortInVBT()) {
TRACE("%s: %s: port not found in VBT\n", __func__, PortName());
return false;
} else
TRACE("%s: %s: port found in VBT\n", __func__, PortName());
}
if ((read32(portRegister) & DISPLAY_MONITOR_PORT_ENABLED) == 0)
return false;
return HasEDID();
}
addr_t
HDMIPort::_PortRegister()
{
bool hasPCH = (gInfo->shared_info->pch_info != INTEL_PCH_NONE);
bool fourthGen = gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV);
switch (PortIndex()) {
case INTEL_PORT_B:
if (fourthGen)
return GEN4_HDMI_PORT_B;
return hasPCH ? PCH_HDMI_PORT_B : INTEL_HDMI_PORT_B;
case INTEL_PORT_C:
if (fourthGen)
return GEN4_HDMI_PORT_C;
return hasPCH ? PCH_HDMI_PORT_C : INTEL_HDMI_PORT_C;
case INTEL_PORT_D:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_CHV))
return CHV_HDMI_PORT_D;
return hasPCH ? PCH_HDMI_PORT_D : 0;
default:
return 0;
}
return 0;
}
DisplayPort::DisplayPort(port_index index, const char* baseName)
:
Port(index, baseName)
{
}
pipe_index
DisplayPort::PipePreference()
{
CALLED();
if (gInfo->shared_info->device_type.Generation() <= 4)
return INTEL_PIPE_ANY;
uint32 TranscoderPort = INTEL_TRANS_DP_PORT_NONE;
switch (PortIndex()) {
case INTEL_PORT_A:
if (gInfo->shared_info->device_type.Generation() == 6) {
if (((read32(INTEL_DISPLAY_PORT_A) & INTEL_DISP_PORTA_SNB_PIPE_MASK)
>> INTEL_DISP_PORTA_SNB_PIPE_SHIFT) == INTEL_DISP_PORTA_SNB_PIPE_A) {
return INTEL_PIPE_A;
} else {
return INTEL_PIPE_B;
}
}
if (gInfo->shared_info->device_type.Generation() == 7) {
uint32 Pipe = (read32(INTEL_DISPLAY_PORT_A) & INTEL_DISP_PORTA_IVB_PIPE_MASK)
>> INTEL_DISP_PORTA_IVB_PIPE_SHIFT;
switch (Pipe) {
case INTEL_DISP_PORTA_IVB_PIPE_A:
return INTEL_PIPE_A;
case INTEL_DISP_PORTA_IVB_PIPE_B:
return INTEL_PIPE_B;
case INTEL_DISP_PORTA_IVB_PIPE_C:
return INTEL_PIPE_C;
default:
return INTEL_PIPE_ANY;
}
}
return INTEL_PIPE_ANY;
case INTEL_PORT_B:
TranscoderPort = INTEL_TRANS_DP_PORT_B;
break;
case INTEL_PORT_C:
TranscoderPort = INTEL_TRANS_DP_PORT_C;
break;
case INTEL_PORT_D:
TranscoderPort = INTEL_TRANS_DP_PORT_D;
break;
default:
return INTEL_PIPE_ANY;
}
for (uint32 Transcoder = 0; Transcoder < 3; Transcoder++) {
if ((read32(INTEL_TRANSCODER_A_DP_CTL + (Transcoder << 12)) & INTEL_TRANS_DP_PORT_MASK) ==
INTEL_TRANS_DP_PORT(TranscoderPort)) {
switch (Transcoder) {
case 0:
return INTEL_PIPE_A;
case 1:
return INTEL_PIPE_B;
case 2:
return INTEL_PIPE_C;
}
}
}
return INTEL_PIPE_ANY;
}
status_t
DisplayPort::SetPipe(Pipe* pipe)
{
CALLED();
if (pipe == NULL) {
ERROR("%s: Invalid pipe provided!\n", __PRETTY_FUNCTION__);
return B_ERROR;
}
if (fPipe != NULL) {
ERROR("%s: Can't reassign display pipe (yet)\n", __PRETTY_FUNCTION__);
return B_ERROR;
}
TRACE("%s: Assuming pipe %d is assigned by BIOS to port %d (fixme)\n", __PRETTY_FUNCTION__,
pipe->Index(), PortIndex());
fPipe = pipe;
if (fPipe == NULL)
return B_NO_MEMORY;
if (fPipe->IsEnabled())
fPipe->Enable(false);
return B_OK;
}
status_t
DisplayPort::SetupI2c(i2c_bus *bus)
{
CALLED();
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
if (gInfo->shared_info->device_type.Generation() >= 6 && deviceConfigCount > 0) {
if (!_IsDisplayPortInVBT())
return Port::SetupI2c(bus);
}
return _SetupDpAuxI2c(bus);
}
bool
DisplayPort::IsConnected()
{
addr_t portRegister = _PortRegister();
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
portRegister);
if (portRegister == 0)
return false;
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
if (gInfo->shared_info->device_type.Generation() >= 6 && deviceConfigCount > 0) {
if (!_IsPortInVBT()) {
TRACE("%s: %s: port not found in VBT\n", __func__, PortName());
return false;
} else
TRACE("%s: %s: port found in VBT\n", __func__, PortName());
}
if ((read32(portRegister) & DISPLAY_MONITOR_PORT_ENABLED) == 0) {
TRACE("%s: %s link not detected\n", __func__, PortName());
return false;
}
TRACE("%s: %s link detected\n", __func__, PortName());
bool edidDetected = HasEDID();
if ((gInfo->shared_info->device_type.IsMobile() || _IsEDPPort())
&& (PortIndex() == INTEL_PORT_A) && !edidDetected) {
if (gInfo->shared_info->has_vesa_edid_info) {
TRACE("%s: Laptop. Using VESA edid info\n", __func__);
memcpy(&fEDIDInfo, &gInfo->shared_info->vesa_edid_info,
sizeof(edid1_info));
if (fEDIDState != B_OK) {
fEDIDState = B_OK;
edid_dump(&fEDIDInfo);
}
return true;
} else if (gInfo->shared_info->got_vbt) {
TRACE("%s: Laptop. No EDID, but force enabled as we have a VBT\n", __func__);
return true;
}
}
return true;
}
addr_t
DisplayPort::_DDCRegister()
{
return 0;
}
addr_t
DisplayPort::_PortRegister()
{
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV)
|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_CHV)) {
ERROR("TODO: DisplayPort on ValleyView / CherryView");
return 0;
}
switch (PortIndex()) {
case INTEL_PORT_A:
return INTEL_DISPLAY_PORT_A;
case INTEL_PORT_B:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV))
return VLV_DISPLAY_PORT_B;
return INTEL_DISPLAY_PORT_B;
case INTEL_PORT_C:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV))
return VLV_DISPLAY_PORT_C;
return INTEL_DISPLAY_PORT_C;
case INTEL_PORT_D:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_CHV))
return CHV_DISPLAY_PORT_D;
else if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV))
return 0;
return INTEL_DISPLAY_PORT_D;
default:
return 0;
}
return 0;
}
status_t
DisplayPort::_SetPortLinkGen4(const display_timing& timing)
{
uint32 linkBandwidth = 270000;
uint32 fPipeOffset = 0;
if (fPipe->Index() == INTEL_PIPE_B)
fPipeOffset = 0x1000;
TRACE("%s: DP M1 data before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_DATA_M + fPipeOffset));
TRACE("%s: DP N1 data before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_DATA_N + fPipeOffset));
TRACE("%s: DP M1 link before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_LINK_M + fPipeOffset));
TRACE("%s: DP N1 link before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_LINK_N + fPipeOffset));
uint32 bitsPerPixel = 24;
uint32 lanes = 4;
uint64 linkspeed = lanes * linkBandwidth * 8;
uint64 ret_n = 1;
while(ret_n < linkspeed) {
ret_n *= 2;
}
if (ret_n > 0x800000) {
ret_n = 0x800000;
}
uint64 ret_m = timing.pixel_clock * ret_n * bitsPerPixel / linkspeed;
while ((ret_n > 0xffffff) || (ret_m > 0xffffff)) {
ret_m >>= 1;
ret_n >>= 1;
}
write32(INTEL_PIPE_A_DATA_M + fPipeOffset, ret_m | FDI_PIPE_MN_TU_SIZE_MASK);
write32(INTEL_PIPE_A_DATA_N + fPipeOffset, ret_n);
linkspeed = linkBandwidth;
ret_n = 1;
while(ret_n < linkspeed) {
ret_n *= 2;
}
if (ret_n > 0x800000) {
ret_n = 0x800000;
}
ret_m = timing.pixel_clock * ret_n / linkspeed;
while ((ret_n > 0xffffff) || (ret_m > 0xffffff)) {
ret_m >>= 1;
ret_n >>= 1;
}
write32(INTEL_PIPE_A_LINK_M + fPipeOffset, ret_m);
write32(INTEL_PIPE_A_LINK_N + fPipeOffset, ret_n);
TRACE("%s: DP M1 data after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_DATA_M + fPipeOffset));
TRACE("%s: DP N1 data after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_DATA_N + fPipeOffset));
TRACE("%s: DP M1 link after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_LINK_M + fPipeOffset));
TRACE("%s: DP N1 link after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_PIPE_A_LINK_N + fPipeOffset));
return B_OK;
}
status_t
DisplayPort::_SetPortLinkGen6(const display_timing& timing)
{
uint32 linkBandwidth = 270000;
TRACE("%s: DP link reference clock is %gMhz\n", __func__, linkBandwidth / 1000.0f);
uint32 fPipeOffset = 0;
switch (fPipe->Index()) {
case INTEL_PIPE_B:
fPipeOffset = 0x1000;
break;
case INTEL_PIPE_C:
fPipeOffset = 0x2000;
break;
default:
break;
}
TRACE("%s: DP M1 data before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_DATA_M1 + fPipeOffset));
TRACE("%s: DP N1 data before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_DATA_N1 + fPipeOffset));
TRACE("%s: DP M1 link before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_LINK_M1 + fPipeOffset));
TRACE("%s: DP N1 link before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_LINK_N1 + fPipeOffset));
uint32 bitsPerPixel =
(read32(INTEL_TRANSCODER_A_DP_CTL + fPipeOffset) & INTEL_TRANS_DP_BPC_MASK) >> INTEL_TRANS_DP_COLOR_SHIFT;
switch (bitsPerPixel) {
case PIPE_DDI_8BPC:
bitsPerPixel = 24;
break;
case PIPE_DDI_10BPC:
bitsPerPixel = 30;
break;
case PIPE_DDI_6BPC:
bitsPerPixel = 18;
break;
case PIPE_DDI_12BPC:
bitsPerPixel = 36;
break;
default:
ERROR("%s: DP illegal link colordepth set.\n", __func__);
return B_ERROR;
}
TRACE("%s: DP link colordepth: %" B_PRIu32 "\n", __func__, bitsPerPixel);
uint32 lanes = ((read32(_PortRegister()) & INTEL_DISP_PORT_WIDTH_MASK) >> INTEL_DISP_PORT_WIDTH_SHIFT) + 1;
if (lanes > 4) {
ERROR("%s: DP illegal number of lanes set.\n", __func__);
return B_ERROR;
}
TRACE("%s: DP mode with %" B_PRIx32 " lane(s) in use\n", __func__, lanes);
uint32 bps = timing.pixel_clock * bitsPerPixel * 21 / 20;
uint32 required_lanes = (bps + (linkBandwidth * 8) - 1) / (linkBandwidth * 8);
TRACE("%s: DP mode needs %" B_PRIx32 " lane(s) in use\n", __func__, required_lanes);
if (required_lanes > lanes) {
ERROR("%s: DP not enough lanes active for requested mode.\n", __func__);
return B_ERROR;
}
uint64 linkspeed = lanes * linkBandwidth * 8;
uint64 ret_n = 1;
while(ret_n < linkspeed) {
ret_n *= 2;
}
if (ret_n > 0x800000) {
ret_n = 0x800000;
}
uint64 ret_m = timing.pixel_clock * ret_n * bitsPerPixel / linkspeed;
while ((ret_n > 0xffffff) || (ret_m > 0xffffff)) {
ret_m >>= 1;
ret_n >>= 1;
}
write32(INTEL_TRANSCODER_A_DATA_M1 + fPipeOffset, ret_m | INTEL_TRANSCODER_MN_TU_SIZE_MASK);
write32(INTEL_TRANSCODER_A_DATA_N1 + fPipeOffset, ret_n);
linkspeed = linkBandwidth;
ret_n = 1;
while(ret_n < linkspeed) {
ret_n *= 2;
}
if (ret_n > 0x800000) {
ret_n = 0x800000;
}
ret_m = timing.pixel_clock * ret_n / linkspeed;
while ((ret_n > 0xffffff) || (ret_m > 0xffffff)) {
ret_m >>= 1;
ret_n >>= 1;
}
write32(INTEL_TRANSCODER_A_LINK_M1 + fPipeOffset, ret_m);
write32(INTEL_TRANSCODER_A_LINK_N1 + fPipeOffset, ret_n);
TRACE("%s: DP M1 data after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_DATA_M1 + fPipeOffset));
TRACE("%s: DP N1 data after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_DATA_N1 + fPipeOffset));
TRACE("%s: DP M1 link after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_LINK_M1 + fPipeOffset));
TRACE("%s: DP N1 link after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_TRANSCODER_A_LINK_N1 + fPipeOffset));
return B_OK;
}
status_t
DisplayPort::SetDisplayMode(display_mode* target, uint32 colorMode)
{
CALLED();
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->timing.h_display,
target->timing.v_display);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
status_t result = B_OK;
if (gInfo->shared_info->device_type.Generation() <= 4) {
fPipe->ConfigureTimings(target);
result = _SetPortLinkGen4(target->timing);
} else {
display_timing hardwareTarget = target->timing;
bool needsScaling = false;
if ((PortIndex() == INTEL_PORT_A)
&& (gInfo->shared_info->device_type.IsMobile() || _IsEDPPort())) {
if (gInfo->shared_info->got_vbt) {
hardwareTarget = gInfo->shared_info->panel_timing;
if (hardwareTarget.h_display == target->timing.h_display
&& hardwareTarget.v_display == target->timing.v_display) {
TRACE("%s: Requested mode is panel's native resolution: disabling scaling\n", __func__);
} else {
TRACE("%s: Requested mode is not panel's native resolution: enabling scaling\n", __func__);
needsScaling = true;
}
} else {
TRACE("%s: Setting internal panel mode without VBT info generation, scaling may not work\n",
__func__);
hardwareTarget = target->timing;
}
}
result = B_OK;
if (PortIndex() != INTEL_PORT_A)
result = _SetPortLinkGen6(hardwareTarget);
if (result == B_OK) {
PanelFitter* fitter = fPipe->PFT();
if (fitter != NULL)
fitter->Enable(hardwareTarget);
uint32 lanes = 0;
uint32 linkBandwidth = 0;
uint32 bitsPerPixel = 0;
if (PortIndex() != INTEL_PORT_A) {
FDILink* link = fPipe->FDI();
if (link != NULL) {
link->PreTrain(&hardwareTarget, &linkBandwidth, &lanes, &bitsPerPixel);
fPipe->SetFDILink(hardwareTarget, linkBandwidth, lanes, bitsPerPixel);
link->Train(&hardwareTarget, lanes);
}
} else {
linkBandwidth =
(read32(INTEL_DISPLAY_PORT_A) & INTEL_DISP_EDP_PLL_FREQ_MASK) >> INTEL_DISP_EDP_PLL_FREQ_SHIFT;
switch (linkBandwidth) {
case INTEL_DISP_EDP_PLL_FREQ_270:
linkBandwidth = 270000;
break;
case INTEL_DISP_EDP_PLL_FREQ_162:
linkBandwidth = 162000;
break;
default:
TRACE("%s: eDP illegal reference clock ID set, assuming 270Mhz.\n", __func__);
linkBandwidth = 270000;
}
bitsPerPixel =
(read32(INTEL_DISPLAY_A_PIPE_CONTROL) & INTEL_PIPE_BPC_MASK) >> INTEL_PIPE_COLOR_SHIFT;
switch (bitsPerPixel) {
case INTEL_PIPE_8BPC:
bitsPerPixel = 24;
break;
case INTEL_PIPE_10BPC:
bitsPerPixel = 30;
break;
case INTEL_PIPE_6BPC:
bitsPerPixel = 18;
break;
case INTEL_PIPE_12BPC:
bitsPerPixel = 36;
break;
default:
bitsPerPixel = 0;
}
lanes =
((read32(INTEL_DISPLAY_PORT_A) & INTEL_DISP_PORT_WIDTH_MASK) >> INTEL_DISP_PORT_WIDTH_SHIFT) + 1;
fPipe->SetFDILink(hardwareTarget, linkBandwidth, lanes, bitsPerPixel);
}
fPipe->Configure(target);
fPipe->ConfigureTimings(target, !needsScaling, PortIndex());
} else {
TRACE("%s: Setting display mode via fallback: using scaling!\n", __func__);
fPipe->ConfigureScalePos(target);
}
}
memcpy(&fCurrentMode, target, sizeof(display_mode));
return result;
}
EmbeddedDisplayPort::EmbeddedDisplayPort()
:
DisplayPort(INTEL_PORT_A, "Embedded DisplayPort")
{
}
bool
EmbeddedDisplayPort::IsConnected()
{
addr_t portRegister = _PortRegister();
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
portRegister);
if (!gInfo->shared_info->device_type.IsMobile()) {
TRACE("%s: skipping eDP on non-mobile GPU\n", __func__);
return false;
}
if ((read32(portRegister) & DISPLAY_MONITOR_PORT_DETECTED) == 0) {
TRACE("%s: %s link not detected\n", __func__, PortName());
return false;
}
HasEDID();
return true;
}
DigitalDisplayInterface::DigitalDisplayInterface(port_index index,
const char* baseName)
:
Port(index, baseName)
{
}
addr_t
DigitalDisplayInterface::_PortRegister()
{
switch (PortIndex()) {
case INTEL_PORT_A:
return DDI_BUF_CTL_A;
case INTEL_PORT_B:
return DDI_BUF_CTL_B;
case INTEL_PORT_C:
return DDI_BUF_CTL_C;
case INTEL_PORT_D:
return DDI_BUF_CTL_D;
case INTEL_PORT_E:
return DDI_BUF_CTL_E;
case INTEL_PORT_F:
if ((gInfo->shared_info->device_type.Generation() > 8) &&
!gInfo->shared_info->device_type.InGroup(INTEL_GROUP_SKY))
return DDI_BUF_CTL_F;
return 0;
case INTEL_PORT_G:
if (gInfo->shared_info->device_type.Generation() >= 12)
return DDI_BUF_CTL_G;
return 0;
default:
return 0;
}
return 0;
}
addr_t
DigitalDisplayInterface::_DDCRegister()
{
return Port::_DDCPin();
}
status_t
DigitalDisplayInterface::Power(bool enabled)
{
if (fPipe == NULL) {
ERROR("%s: Setting power without assigned pipe!\n", __func__);
return B_ERROR;
}
TRACE("%s: %s DDI enabled: %s\n", __func__, PortName(),
enabled ? "true" : "false");
fPipe->Enable(enabled);
#if 0
addr_t portRegister = _PortRegister();
uint32 state = read32(portRegister);
write32(portRegister,
enabled ? (state | DDI_BUF_CTL_ENABLE) : (state & ~DDI_BUF_CTL_ENABLE));
read32(portRegister);
#endif
return B_OK;
}
status_t
DigitalDisplayInterface::SetPipe(Pipe* pipe)
{
CALLED();
if (pipe == NULL) {
ERROR("%s: Invalid pipe provided!\n", __PRETTY_FUNCTION__);
return B_ERROR;
}
if (fPipe != NULL) {
ERROR("%s: Can't reassign display pipe (yet)\n", __PRETTY_FUNCTION__);
return B_ERROR;
}
TRACE("%s: Assuming pipe %d is assigned by BIOS to port %d (fixme)\n", __PRETTY_FUNCTION__,
pipe->Index(), PortIndex());
fPipe = pipe;
if (fPipe == NULL)
return B_NO_MEMORY;
if (fPipe->IsEnabled())
fPipe->Enable(false);
return B_OK;
}
status_t
DigitalDisplayInterface::SetupI2c(i2c_bus *bus)
{
CALLED();
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
if (gInfo->shared_info->device_type.Generation() >= 6 && deviceConfigCount > 0) {
if (!_IsDisplayPortInVBT())
return Port::SetupI2c(bus);
}
return _SetupDpAuxI2c(bus);
}
status_t
DigitalDisplayInterface::SetupI2cFallback(i2c_bus *bus)
{
CALLED();
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
if (gInfo->shared_info->device_type.Generation() >= 6 && deviceConfigCount > 0
&& _IsDisplayPortInVBT() && _IsHdmiInVBT()) {
return Port::SetupI2c(bus);
}
return B_ERROR;
}
bool
DigitalDisplayInterface::IsConnected()
{
addr_t portRegister = _PortRegister();
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
portRegister);
if (portRegister == 0) {
TRACE("%s: Port not implemented\n", __func__);
return false;
}
fMaxLanes = 4;
if ((gInfo->shared_info->device_type.Generation() < 9) ||
gInfo->shared_info->device_type.InGroup(INTEL_GROUP_SKY)) {
if ((read32(DDI_BUF_CTL_A) & DDI_A_4_LANES) != 0) {
switch (PortIndex()) {
case INTEL_PORT_A:
fMaxLanes = 4;
break;
case INTEL_PORT_E:
fMaxLanes = 0;
break;
default:
fMaxLanes = 4;
break;
}
} else {
switch (PortIndex()) {
case INTEL_PORT_A:
fMaxLanes = 2;
break;
case INTEL_PORT_E:
fMaxLanes = 2;
break;
default:
fMaxLanes = 4;
break;
}
}
}
const uint32 deviceConfigCount = gInfo->shared_info->device_config_count;
if (gInfo->shared_info->device_type.Generation() >= 6 && deviceConfigCount > 0) {
if (!_IsPortInVBT()) {
TRACE("%s: %s: port not found in VBT\n", __func__, PortName());
return false;
} else
TRACE("%s: %s: port found in VBT\n", __func__, PortName());
}
TRACE("%s: %s Maximum Lanes: %" B_PRId8 "\n", __func__,
PortName(), fMaxLanes);
bool edidDetected = HasEDID();
uint32 pipeState = 0;
if ((gInfo->shared_info->device_type.IsMobile() || _IsEDPPort())
&& (PortIndex() == INTEL_PORT_A)) {
if (gInfo->shared_info->device_type.Generation() < 12) {
pipeState = read32(PIPE_DDI_FUNC_CTL_EDP);
TRACE("%s: PIPE_DDI_FUNC_CTL_EDP: 0x%" B_PRIx32 "\n", __func__, pipeState);
if (!(pipeState & PIPE_DDI_FUNC_CTL_ENABLE)) {
TRACE("%s: Laptop, but eDP port down\n", __func__);
return false;
}
}
if (edidDetected)
return true;
else if (gInfo->shared_info->has_vesa_edid_info) {
TRACE("%s: Laptop. Using VESA edid info\n", __func__);
memcpy(&fEDIDInfo, &gInfo->shared_info->vesa_edid_info, sizeof(edid1_info));
if (fEDIDState != B_OK) {
fEDIDState = B_OK;
edid_dump(&fEDIDInfo);
}
return true;
} else if (gInfo->shared_info->got_vbt) {
TRACE("%s: Laptop. No VESA EDID, but force enabled as we have a VBT\n", __func__);
return true;
}
TRACE("%s: No (panel) type info found, assuming not connected\n", __func__);
return false;
}
for (uint32 pipeCnt = 0; pipeCnt < 3; pipeCnt++) {
switch (pipeCnt) {
case 1:
pipeState = read32(PIPE_DDI_FUNC_CTL_B);
break;
case 2:
pipeState = read32(PIPE_DDI_FUNC_CTL_C);
break;
default:
pipeState = read32(PIPE_DDI_FUNC_CTL_A);
break;
}
if ((((pipeState & PIPE_DDI_SELECT_MASK) >> PIPE_DDI_SELECT_SHIFT) + 1) == (uint32)PortIndex()) {
TRACE("%s: PIPE_DDI_FUNC_CTL nr %" B_PRIx32 ": 0x%" B_PRIx32 "\n", __func__, pipeCnt + 1, pipeState);
if (pipeState & PIPE_DDI_FUNC_CTL_ENABLE) {
TRACE("%s: Connected\n", __func__);
return true;
}
}
}
if (edidDetected) {
for (uint32 pipeCnt = 0; pipeCnt < 3; pipeCnt++) {
uint32 pipeReg = 0;
switch (pipeCnt) {
case 1:
pipeReg = PIPE_DDI_FUNC_CTL_B;
break;
case 2:
pipeReg = PIPE_DDI_FUNC_CTL_C;
break;
default:
pipeReg = PIPE_DDI_FUNC_CTL_A;
break;
}
pipeState = read32(pipeReg);
if ((pipeState & PIPE_DDI_FUNC_CTL_ENABLE) == 0) {
TRACE("%s: Connected but port down\n", __func__);
return false;
}
return true;
}
TRACE("%s: No pipe available, ignoring connected screen\n", __func__);
}
TRACE("%s: Not connected\n", __func__);
return false;
}
status_t
DigitalDisplayInterface::_SetPortLinkGen8(const display_timing& timing, uint32 pllSel)
{
uint32 linkBandwidth = 270000;
if (gInfo->shared_info->device_type.Generation() >= 11) {
ERROR("%s: DDI PLL selection not implemented for Gen11, "
"assuming default DP-link reference\n", __func__);
} else if (gInfo->shared_info->device_type.Generation() >= 9) {
if (pllSel != 0xff) {
linkBandwidth = (read32(SKL_DPLL_CTRL1) >> (1 + 6 * pllSel)) & SKL_DPLL_DP_LINKRATE_MASK;
switch (linkBandwidth) {
case SKL_DPLL_CTRL1_2700:
linkBandwidth = 2700000 / 5;
break;
case SKL_DPLL_CTRL1_1350:
linkBandwidth = 1350000 / 5;
break;
case SKL_DPLL_CTRL1_810:
linkBandwidth = 810000 / 5;
break;
case SKL_DPLL_CTRL1_1620:
linkBandwidth = 1620000 / 5;
break;
case SKL_DPLL_CTRL1_1080:
linkBandwidth = 1080000 / 5;
break;
case SKL_DPLL_CTRL1_2160:
linkBandwidth = 2160000 / 5;
break;
default:
linkBandwidth = 270000;
ERROR("%s: DDI No known DP-link reference clock selected, assuming default\n", __func__);
break;
}
} else {
ERROR("%s: DDI No known PLL selected, assuming default DP-link reference\n", __func__);
}
}
TRACE("%s: DDI DP-link reference clock is %gMhz\n", __func__, linkBandwidth / 1000.0f);
uint32 fPipeOffset = 0;
switch (fPipe->Index()) {
case INTEL_PIPE_B:
fPipeOffset = 0x1000;
break;
case INTEL_PIPE_C:
fPipeOffset = 0x2000;
break;
case INTEL_PIPE_D:
fPipeOffset = 0xf000;
break;
default:
break;
}
TRACE("%s: DDI M1 data before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_DATA_M + fPipeOffset));
TRACE("%s: DDI N1 data before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_DATA_N + fPipeOffset));
TRACE("%s: DDI M1 link before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_LINK_M + fPipeOffset));
TRACE("%s: DDI N1 link before: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_LINK_N + fPipeOffset));
uint32 pipeFunc = read32(PIPE_DDI_FUNC_CTL_A + fPipeOffset);
uint32 bitsPerPixel = (pipeFunc & PIPE_DDI_BPC_MASK) >> PIPE_DDI_COLOR_SHIFT;
switch (bitsPerPixel) {
case PIPE_DDI_8BPC:
bitsPerPixel = 24;
break;
case PIPE_DDI_10BPC:
bitsPerPixel = 30;
break;
case PIPE_DDI_6BPC:
bitsPerPixel = 18;
break;
case PIPE_DDI_12BPC:
bitsPerPixel = 36;
break;
default:
ERROR("%s: DDI illegal link colordepth set.\n", __func__);
return B_ERROR;
}
TRACE("%s: DDI Link Colordepth: %" B_PRIu32 "\n", __func__, bitsPerPixel);
uint32 lanes = 4;
if (((pipeFunc & PIPE_DDI_MODESEL_MASK) >> PIPE_DDI_MODESEL_SHIFT) >= PIPE_DDI_MODE_DP_SST) {
lanes = ((pipeFunc & PIPE_DDI_DP_WIDTH_MASK) >> PIPE_DDI_DP_WIDTH_SHIFT) + 1;
TRACE("%s: DDI in DP mode with %" B_PRIx32 " lane(s) in use\n", __func__, lanes);
} else {
TRACE("%s: DDI in non-DP mode with %" B_PRIx32 " lane(s) in use\n", __func__, lanes);
}
uint64 linkspeed = lanes * linkBandwidth * 8;
uint64 ret_n = 1;
while(ret_n < linkspeed) {
ret_n *= 2;
}
if (ret_n > 0x800000) {
ret_n = 0x800000;
}
uint64 ret_m = timing.pixel_clock * ret_n * bitsPerPixel / linkspeed;
while ((ret_n > 0xffffff) || (ret_m > 0xffffff)) {
ret_m >>= 1;
ret_n >>= 1;
}
write32(INTEL_DDI_PIPE_A_DATA_M + fPipeOffset, ret_m | FDI_PIPE_MN_TU_SIZE_MASK);
write32(INTEL_DDI_PIPE_A_DATA_N + fPipeOffset, ret_n);
linkspeed = linkBandwidth;
ret_n = 1;
while(ret_n < linkspeed) {
ret_n *= 2;
}
if (ret_n > 0x800000) {
ret_n = 0x800000;
}
ret_m = timing.pixel_clock * ret_n / linkspeed;
while ((ret_n > 0xffffff) || (ret_m > 0xffffff)) {
ret_m >>= 1;
ret_n >>= 1;
}
write32(INTEL_DDI_PIPE_A_LINK_M + fPipeOffset, ret_m);
write32(INTEL_DDI_PIPE_A_LINK_N + fPipeOffset, ret_n);
TRACE("%s: DDI M1 data after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_DATA_M + fPipeOffset));
TRACE("%s: DDI N1 data after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_DATA_N + fPipeOffset));
TRACE("%s: DDI M1 link after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_LINK_M + fPipeOffset));
TRACE("%s: DDI N1 link after: 0x%" B_PRIx32 "\n", __func__, read32(INTEL_DDI_PIPE_A_LINK_N + fPipeOffset));
return B_OK;
}
status_t
DigitalDisplayInterface::SetDisplayMode(display_mode* target, uint32 colorMode)
{
CALLED();
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->timing.h_display,
target->timing.v_display);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
display_timing hardwareTarget = target->timing;
bool needsScaling = false;
if ((PortIndex() == INTEL_PORT_A)
&& (gInfo->shared_info->device_type.IsMobile() || _IsEDPPort())) {
if (gInfo->shared_info->got_vbt || HasEDID()) {
hardwareTarget = gInfo->shared_info->panel_timing;
if (HasEDID()) {
int i;
for (i = 0; i < EDID1_NUM_DETAILED_MONITOR_DESC; ++i) {
edid1_detailed_monitor *monitor = &fEDIDInfo.detailed_monitor[i];
if (monitor->monitor_desc_type == EDID1_IS_DETAILED_TIMING)
break;
}
if (i < EDID1_NUM_DETAILED_MONITOR_DESC) {
TRACE("%s: Using EDID detailed timing %d for the internal panel\n",
__func__, i);
const edid1_detailed_timing& timing
= fEDIDInfo.detailed_monitor[i].data.detailed_timing;
hardwareTarget.pixel_clock = timing.pixel_clock * 10;
hardwareTarget.h_display = timing.h_active;
hardwareTarget.h_sync_start = timing.h_active + timing.h_sync_off;
hardwareTarget.h_sync_end = hardwareTarget.h_sync_start + timing.h_sync_width;
hardwareTarget.h_total = timing.h_active + timing.h_blank;
hardwareTarget.v_display = timing.v_active;
hardwareTarget.v_sync_start = timing.v_active + timing.v_sync_off;
hardwareTarget.v_sync_end = hardwareTarget.v_sync_start + timing.v_sync_width;
hardwareTarget.v_total = timing.v_active + timing.v_blank;
hardwareTarget.flags = 0;
if (timing.sync == 3) {
if (timing.misc & 1)
hardwareTarget.flags |= B_POSITIVE_HSYNC;
if (timing.misc & 2)
hardwareTarget.flags |= B_POSITIVE_VSYNC;
}
if (timing.interlaced)
hardwareTarget.flags |= B_TIMING_INTERLACED;
}
}
if (hardwareTarget.h_display == target->timing.h_display
&& hardwareTarget.v_display == target->timing.v_display) {
hardwareTarget = target->timing;
TRACE("%s: Setting internal panel to native resolution at %" B_PRIu32 "Hz\n", __func__,
hardwareTarget.pixel_clock * 1000 / (hardwareTarget.h_total * hardwareTarget.v_total));
} else {
TRACE("%s: Hardware mode will actually be %dx%d at %" B_PRIu32 "Hz\n", __func__,
hardwareTarget.h_display, hardwareTarget.v_display,
hardwareTarget.pixel_clock * 1000 / (hardwareTarget.h_total * hardwareTarget.v_total));
needsScaling = true;
}
} else {
TRACE("%s: Setting internal panel mode without VBT info generation, scaling may not work\n",
__func__);
hardwareTarget = target->timing;
}
}
PanelFitter* fitter = fPipe->PFT();
if (fitter != NULL)
fitter->Enable(hardwareTarget);
fPipe->Configure(target);
uint32 pllSel = 0xff;
if (gInfo->shared_info->device_type.Generation() <= 8) {
unsigned int r2_out, n2_out, p_out;
hsw_ddi_calculate_wrpll(
hardwareTarget.pixel_clock * 1000 ,
&r2_out, &n2_out, &p_out);
} else if (gInfo->shared_info->device_type.Generation() <= 11) {
skl_wrpll_params wrpll_params;
skl_ddi_calculate_wrpll(
hardwareTarget.pixel_clock * 1000 ,
gInfo->shared_info->pll_info.reference_frequency,
&wrpll_params);
fPipe->ConfigureClocksSKL(wrpll_params,
hardwareTarget.pixel_clock,
PortIndex(),
&pllSel);
} else {
int p, q, k;
float dco;
uint32 mode = fPipe->TranscoderMode();
if ((mode == PIPE_DDI_MODE_DVI || mode == PIPE_DDI_MODE_HDMI)
&& ComputeHdmiDpll(hardwareTarget.pixel_clock, &p, &q, &k, &dco)) {
TRACE("PLL settings: DCO=%f, P,Q,K=%d,%d,%d\n", dco, p, q, k);
} else if ((mode == PIPE_DDI_MODE_DP_SST || mode == PIPE_DDI_MODE_DP_MST)
&& ComputeDisplayPortDpll(hardwareTarget.pixel_clock, &p, &q, &k, &dco)) {
TRACE("PLL settings: DCO=%f, P,Q,K=%d,%d,%d\n", dco, p, q, k);
} else {
ERROR("%s: Could not find a matching PLL setting\n", __func__);
return B_ERROR;
}
int chosenPLL = 0;
if (PortIndex() == 7)
chosenPLL = 1;
TRACE("Using DPLL %d for port %d. PLL settings: DCO=%f, P,Q,K=%d,%d,%d\n", chosenPLL,
PortIndex(), dco, p, q, k);
ProgramPLL(chosenPLL, p, q, k, dco);
uint32 config = read32(TGL_DPCLKA_CFGCR0);
TRACE("PLL configuration before changes: %" B_PRIx32 "\n", config);
if (chosenPLL == 0) {
config |= TGL_DPCLKA_DDIA_CLOCK_OFF;
config &= TGL_DPCLKA_DDIA_CLOCK_SELECT;
write32(TGL_DPCLKA_CFGCR0, config);
config &= ~TGL_DPCLKA_DDIA_CLOCK_OFF;
write32(TGL_DPCLKA_CFGCR0, config);
} else {
config |= TGL_DPCLKA_DDIB_CLOCK_OFF;
config &= TGL_DPCLKA_DDIB_CLOCK_SELECT;
config |= 1 << TGL_DPCLKA_DDIB_CLOCK_SELECT_SHIFT;
write32(TGL_DPCLKA_CFGCR0, config);
config &= ~TGL_DPCLKA_DDIB_CLOCK_OFF;
write32(TGL_DPCLKA_CFGCR0, config);
}
TRACE("PLL configuration after changes: %" B_PRIx32 "\n", config);
}
fPipe->ConfigureTimings(target, !needsScaling);
_SetPortLinkGen8(hardwareTarget, pllSel);
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}