/*
* Copyright 2006-2015, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
* Michael Lotz, mmlr@mlotz.ch
* Alexander von Gluck IV, kallisti5@unixzen.com
*/
#include "Ports.h"
#include <ddc.h>
#include <stdlib.h>
#include <string.h>
#include <Debug.h>
#include <KernelExport.h>
#include "accelerant.h"
#include "accelerant_protos.h"
#include "FlexibleDisplayInterface.h"
#include "intel_extreme.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;
}
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;
}
// TODO: UnAssignPipe? This likely needs reworked a little
if (fPipe != NULL) {
ERROR("%s: Can't reassign display pipe (yet)\n", __func__);
return B_ERROR;
}
TRACE("%s: Assigning %s (0x%" B_PRIx32 ") to pipe %s\n", __func__,
PortName(), portRegister, (pipe->Index() == INTEL_PIPE_A) ? "A" : "B");
uint32 portState = read32(portRegister);
if (gInfo->shared_info->pch_info == INTEL_PCH_CPT) {
portState &= PORT_TRANS_SEL_MASK;
if (pipe->Index() == INTEL_PIPE_A)
write32(portRegister, portState | PORT_TRANS_A_SEL_CPT);
else
write32(portRegister, portState | PORT_TRANS_B_SEL_CPT);
} 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;
// Disable display pipe until modesetting enables it
if (fPipe->IsEnabled())
fPipe->Enable(false);
read32(portRegister);
return B_OK;
}
status_t
Port::Power(bool enabled)
{
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());
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);
i2c_bus bus;
bus.cookie = (void*)ddcRegister;
bus.set_signals = &_SetI2CSignals;
bus.get_signals = &_GetI2CSignals;
ddc2_init_timing(&bus);
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::GetPLLLimits(pll_limits& limits)
{
return B_ERROR;
}
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)) {
// on these chips, the reserved values are fixed
value = 0;
} else {
// on all others, we have to preserve them manually
value = read32(ioRegister) & I2C_RESERVED;
}
if (data != 0)
value |= I2C_DATA_DIRECTION_MASK;
else {
value |= I2C_DATA_DIRECTION_MASK | I2C_DATA_DIRECTION_OUT
| I2C_DATA_VALUE_MASK;
}
if (clock != 0)
value |= I2C_CLOCK_DIRECTION_MASK;
else {
value |= I2C_CLOCK_DIRECTION_MASK | I2C_CLOCK_DIRECTION_OUT
| I2C_CLOCK_VALUE_MASK;
}
write32(ioRegister, value);
read32(ioRegister);
// make sure the PCI bus has flushed the write
return B_OK;
}
// #pragma mark - Analog Port
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()
{
// always fixed
return INTEL_I2C_IO_A;
}
addr_t
AnalogPort::_PortRegister()
{
// always fixed
return INTEL_ANALOG_PORT;
}
status_t
AnalogPort::SetDisplayMode(display_mode* target, uint32 colorMode)
{
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->virtual_width,
target->virtual_height);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
// Train FDI if it exists
FDILink* link = fPipe->FDI();
if (link != NULL)
link->Train(target);
pll_divisors divisors;
compute_pll_divisors(target, &divisors, false);
uint32 extraPLLFlags = 0;
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_NORMAL;
// Program general pipe config
fPipe->Configure(target);
// Program pipe PLL's
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));
// Program target display mode
fPipe->ConfigureTimings(target);
// Set fCurrentMode to our set display mode
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
// #pragma mark - LVDS Panel
LVDSPort::LVDSPort()
:
Port(INTEL_PORT_C, "LVDS")
{
// Always unlock LVDS port as soon as we start messing with it.
uint32 panelControl = INTEL_PANEL_CONTROL;
if (gInfo->shared_info->pch_info != INTEL_PCH_NONE) {
// FIXME writing there results in black screen on SandyBridge
return;
// panelControl = PCH_PANEL_CONTROL;
}
write32(panelControl, read32(panelControl) | PANEL_REGISTER_UNLOCK);
}
pipe_index
LVDSPort::PipePreference()
{
// TODO: Technically INTEL_PIPE_B is only required on < gen 4
// otherwise we can use INTEL_PIPE_ANY, however it seems to break
// modesetting atm. (likely due to a bug on our end)
//if (gInfo->shared_info->device_type.Generation() < 4)
// return INTEL_PIPE_B;
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());
// there's a detection bit we can use
if ((registerValue & PCH_LVDS_DETECTED) == 0) {
TRACE("LVDS: Not detected\n");
return false;
}
// TODO: Skip if eDP support
} else if (gInfo->shared_info->device_type.Generation() <= 4) {
// Older generations don't have LVDS detection. If not mobile skip.
if (!gInfo->shared_info->device_type.IsMobile()) {
TRACE("LVDS: Skipping LVDS detection due to gen and not mobile\n");
return false;
}
// If mobile, try to grab EDID
// Linux seems to look at lid status for LVDS port detection
// If we don't get EDID, we can use vbios native mode or vesa?
if (!HasEDID()) {
#if 0
if (gInfo->shared_info->got_vbt) {
// TODO: Fake EDID from vbios native mode?
// I feel like this would be more accurate
} else if...
#endif
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));
fEDIDState = B_OK;
// HasEDID now true
} else {
TRACE("LVDS: Couldn't find any valid EDID!\n");
return false;
}
}
}
// Try getting EDID, as the LVDS port doesn't overlap with anything else,
// we don't run the risk of getting someone else's data.
return HasEDID();
}
addr_t
LVDSPort::_DDCRegister()
{
// always fixed
return INTEL_I2C_IO_C;
}
addr_t
LVDSPort::_PortRegister()
{
// always fixed
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->virtual_width, target->virtual_height);
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;
}
// Power off Panel
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());
// Train FDI if it exists
FDILink* link = fPipe->FDI();
if (link != NULL)
link->Train(target);
#if 0
// Disable PanelFitter for now
addr_t panelFitterControl = PCH_PANEL_FITTER_BASE_REGISTER
+ PCH_PANEL_FITTER_CONTROL;
if (fPipe->Index() == INTEL_PIPE_B)
panelFitterControl += PCH_PANEL_FITTER_PIPE_OFFSET;
write32(panelFitterControl, (read32(panelFitterControl) & ~PANEL_FITTER_ENABLED));
read32(panelFitterControl);
#endif
// For LVDS panels, we actually always set the native mode in hardware
// Then we use the panel fitter to scale the picture to that.
display_mode hardwareTarget;
bool needsScaling = false;
// Try to get the panel preferred screen mode from EDID info
if (gInfo->shared_info->got_vbt) {
// Set vbios hardware panel mode as base
memcpy(&hardwareTarget, &gInfo->shared_info->panel_mode,
sizeof(display_mode));
hardwareTarget.space = target->space;
if ((hardwareTarget.virtual_width <= target->virtual_width
&& hardwareTarget.virtual_height <= target->virtual_height
&& hardwareTarget.space <= target->space)
|| intel_propose_display_mode(&hardwareTarget, target, target)) {
hardwareTarget = *target;
} else
needsScaling = true;
TRACE("%s: hardware mode will actually be %dx%d (%s)\n", __func__,
hardwareTarget.virtual_width, hardwareTarget.virtual_height,
needsScaling ? "scaled" : "unscaled");
} else {
// We don't have EDID data, try to set the requested mode directly
hardwareTarget = *target;
}
pll_divisors divisors;
if (needsScaling)
compute_pll_divisors(&hardwareTarget, &divisors, true);
else
compute_pll_divisors(target, &divisors, true);
uint32 lvds = read32(_PortRegister())
| LVDS_PORT_EN | LVDS_A0A2_CLKA_POWER_UP;
if (gInfo->shared_info->device_type.Generation() == 4) {
// LVDS_A3_POWER_UP == 24bpp
// otherwise, 18bpp
if ((lvds & LVDS_A3_POWER_MASK) != LVDS_A3_POWER_UP)
lvds |= LVDS_18BIT_DITHER;
}
// LVDS on PCH needs set before display enable
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;
}
// Set the B0-B3 data pairs corresponding to whether we're going to
// set the DPLLs for dual-channel mode or not.
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 port control moves polarity bits because Intel hates you.
// Set LVDS sync polarity
lvds &= ~(LVDS_HSYNC_POLARITY | LVDS_VSYNC_POLARITY);
// set on - 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;
// DPLL mode LVDS for i915+
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_LVDS;
// Program general pipe config
fPipe->Configure(target);
// Program pipe PLL's (pixel_clock is *always* the hardware pixel clock)
fPipe->ConfigureClocks(divisors, hardwareTarget.timing.pixel_clock,
extraPLLFlags);
// Disable panel fitting, but enable 8 to 6-bit dithering
write32(INTEL_PANEL_FIT_CONTROL, 0x4);
// TODO: do not do this if the connected panel is 24-bit
// (I don't know how to detect that)
// Power on Panel
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 1000ms!\n", __func__, PortName());
// Program target display mode
fPipe->ConfigureTimings(target);
#if 0
// update timing parameters
if (needsScaling) {
// TODO: Alternatively, it should be possible to use the panel
// fitter and scale the picture.
// TODO: Perform some sanity check, for example if the target is
// wider than the hardware mode we end up with negative borders and
// broken timings
uint32 borderWidth = hardwareTarget.timing.h_display
- target->timing.h_display;
uint32 syncWidth = hardwareTarget.timing.h_sync_end
- hardwareTarget.timing.h_sync_start;
uint32 syncCenter = target->timing.h_display
+ (hardwareTarget.timing.h_total
- target->timing.h_display) / 2;
write32(INTEL_DISPLAY_B_HTOTAL,
((uint32)(hardwareTarget.timing.h_total - 1) << 16)
| ((uint32)target->timing.h_display - 1));
write32(INTEL_DISPLAY_B_HBLANK,
((uint32)(hardwareTarget.timing.h_total - borderWidth / 2 - 1)
<< 16)
| ((uint32)target->timing.h_display + borderWidth / 2 - 1));
write32(INTEL_DISPLAY_B_HSYNC,
((uint32)(syncCenter + syncWidth / 2 - 1) << 16)
| ((uint32)syncCenter - syncWidth / 2 - 1));
uint32 borderHeight = hardwareTarget.timing.v_display
- target->timing.v_display;
uint32 syncHeight = hardwareTarget.timing.v_sync_end
- hardwareTarget.timing.v_sync_start;
syncCenter = target->timing.v_display
+ (hardwareTarget.timing.v_total
- target->timing.v_display) / 2;
write32(INTEL_DISPLAY_B_VTOTAL,
((uint32)(hardwareTarget.timing.v_total - 1) << 16)
| ((uint32)target->timing.v_display - 1));
write32(INTEL_DISPLAY_B_VBLANK,
((uint32)(hardwareTarget.timing.v_total - borderHeight / 2 - 1)
<< 16)
| ((uint32)target->timing.v_display
+ borderHeight / 2 - 1));
write32(INTEL_DISPLAY_B_VSYNC,
((uint32)(syncCenter + syncHeight / 2 - 1) << 16)
| ((uint32)syncCenter - syncHeight / 2 - 1));
// This is useful for debugging: it sets the border to red, so you
// can see what is border and what is porch (black area around the
// sync)
// write32(0x61020, 0x00FF0000);
} else {
write32(INTEL_DISPLAY_B_HTOTAL,
((uint32)(target->timing.h_total - 1) << 16)
| ((uint32)target->timing.h_display - 1));
write32(INTEL_DISPLAY_B_HBLANK,
((uint32)(target->timing.h_total - 1) << 16)
| ((uint32)target->timing.h_display - 1));
write32(INTEL_DISPLAY_B_HSYNC,
((uint32)(target->timing.h_sync_end - 1) << 16)
| ((uint32)target->timing.h_sync_start - 1));
write32(INTEL_DISPLAY_B_VTOTAL,
((uint32)(target->timing.v_total - 1) << 16)
| ((uint32)target->timing.v_display - 1));
write32(INTEL_DISPLAY_B_VBLANK,
((uint32)(target->timing.v_total - 1) << 16)
| ((uint32)target->timing.v_display - 1));
write32(INTEL_DISPLAY_B_VSYNC, (
(uint32)(target->timing.v_sync_end - 1) << 16)
| ((uint32)target->timing.v_sync_start - 1));
}
#endif
// Set fCurrentMode to our set display mode
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
// #pragma mark - DVI/SDVO/generic
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());
// As this port overlaps with pretty much everything, this must be called
// after having ruled out all other port types.
return HasEDID();
}
addr_t
DigitalPort::_DDCRegister()
{
//TODO: IS BROXTON, B = B, C = C, D = NIL
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)
{
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->virtual_width,
target->virtual_height);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
// Train FDI if it exists
FDILink* link = fPipe->FDI();
if (link != NULL)
link->Train(target);
pll_divisors divisors;
compute_pll_divisors(target, &divisors, false);
uint32 extraPLLFlags = 0;
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_NORMAL;
// Program general pipe config
fPipe->Configure(target);
// Program pipe PLL's
fPipe->ConfigureClocks(divisors, target->timing.pixel_clock, extraPLLFlags);
// Program target display mode
fPipe->ConfigureTimings(target);
// Set fCurrentMode to our set display mode
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
// #pragma mark - LVDS Panel
// #pragma mark - HDMI
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;
bool hasPCH = (gInfo->shared_info->pch_info != INTEL_PCH_NONE);
if (!hasPCH && PortIndex() == INTEL_PORT_C) {
// there's no detection bit on this port
} else if ((read32(portRegister) & DISPLAY_MONITOR_PORT_DETECTED) == 0)
return false;
return HasEDID();
}
addr_t
HDMIPort::_PortRegister()
{
// on PCH there's an additional port sandwiched in
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;
}
// #pragma mark - DisplayPort
DisplayPort::DisplayPort(port_index index, const char* baseName)
:
Port(index, baseName)
{
}
bool
DisplayPort::IsConnected()
{
addr_t portRegister = _PortRegister();
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
portRegister);
if (portRegister == 0)
return false;
if ((read32(portRegister) & DISPLAY_MONITOR_PORT_DETECTED) == 0) {
TRACE("%s: %s link not detected\n", __func__, PortName());
return false;
}
return HasEDID();
}
addr_t
DisplayPort::_DDCRegister()
{
// TODO: Do VLV + CHV use the VLV_DP_AUX_CTL_B + VLV_DP_AUX_CTL_C?
switch (PortIndex()) {
case INTEL_PORT_A:
return INTEL_DP_AUX_CTL_A;
case INTEL_PORT_B:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV))
return VLV_DP_AUX_CTL_B;
return INTEL_DP_AUX_CTL_B;
case INTEL_PORT_C:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV))
return VLV_DP_AUX_CTL_C;
return INTEL_DP_AUX_CTL_C;
case INTEL_PORT_D:
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_CHV))
return CHV_DP_AUX_CTL_D;
else if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_VLV))
return 0;
return INTEL_DP_AUX_CTL_D;
default:
return 0;
}
return 0;
}
addr_t
DisplayPort::_PortRegister()
{
// There are 6000 lines of intel linux code probing DP registers
// to properly detect DP vs eDP to then in-turn properly figure out
// what is DP and what is HDMI. It only takes 3 lines to
// ignore DisplayPort on ValleyView / CherryView
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;
}
// Intel, are humans even involved anymore?
// This is a lot more complex than this code makes it look. (see defines)
// INTEL_DISPLAY_PORT_X moves around a lot based on PCH
// except on ValleyView and CherryView.
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::SetDisplayMode(display_mode* target, uint32 colorMode)
{
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->virtual_width,
target->virtual_height);
ERROR("TODO: DisplayPort\n");
return B_ERROR;
}
// #pragma mark - Embedded DisplayPort
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();
// If eDP has EDID, awesome. We use it.
// No EDID? The modesetting code falls back to VBIOS panel_mode
return true;
}
// #pragma mark - Digital Display Port
DigitalDisplayInterface::DigitalDisplayInterface(port_index index,
const char* baseName)
:
Port(index, baseName)
{
// As of Haswell, Intel decided to change eDP ports to a "DDI" bus...
// on a dare because the hardware engineers were drunk one night.
}
addr_t
DigitalDisplayInterface::_PortRegister()
{
// TODO: Linux does a DDI_BUF_CTL(INTEL_PORT_A) which is cleaner
// (but we have to ensure the offsets + region base is correct)
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;
default:
return 0;
}
return 0;
}
addr_t
DigitalDisplayInterface::_DDCRegister()
{
// TODO: No idea, does DDI have DDC?
return 0;
}
status_t
DigitalDisplayInterface::Power(bool enabled)
{
TRACE("%s: %s DDI enabled: %s\n", __func__, PortName(),
enabled ? "true" : "false");
fPipe->Enable(enabled);
addr_t portRegister = _PortRegister();
uint32 state = read32(portRegister);
write32(portRegister,
enabled ? (state | DDI_BUF_CTL_ENABLE) : (state & ~DDI_BUF_CTL_ENABLE));
read32(portRegister);
return B_OK;
}
bool
DigitalDisplayInterface::IsConnected()
{
addr_t portRegister = _PortRegister();
TRACE("%s: %s PortRegister: 0x%" B_PRIxADDR "\n", __func__, PortName(),
portRegister);
if (portRegister == 0)
return false;
if ((read32(portRegister) & DDI_INIT_DISPLAY_DETECTED) == 0) {
TRACE("%s: %s link not detected\n", __func__, PortName());
return false;
}
// Probe a little port info.
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;
}
}
TRACE("%s: %s Maximum Lanes: %" B_PRId8 "\n", __func__,
PortName(), fMaxLanes);
HasEDID();
return true;
}
status_t
DigitalDisplayInterface::SetDisplayMode(display_mode* target, uint32 colorMode)
{
TRACE("%s: %s %dx%d\n", __func__, PortName(), target->virtual_width,
target->virtual_height);
if (fPipe == NULL) {
ERROR("%s: Setting display mode without assigned pipe!\n", __func__);
return B_ERROR;
}
// Train FDI if it exists
FDILink* link = fPipe->FDI();
if (link != NULL)
link->Train(target);
pll_divisors divisors;
compute_pll_divisors(target, &divisors, false);
uint32 extraPLLFlags = 0;
if (gInfo->shared_info->device_type.Generation() >= 3)
extraPLLFlags |= DISPLAY_PLL_MODE_NORMAL;
// Program general pipe config
fPipe->Configure(target);
// Program pipe PLL's
fPipe->ConfigureClocks(divisors, target->timing.pixel_clock, extraPLLFlags);
// Program target display mode
fPipe->ConfigureTimings(target);
// Set fCurrentMode to our set display mode
memcpy(&fCurrentMode, target, sizeof(display_mode));
return B_OK;
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fMaxLanes.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fCurrentMode, fEDIDInfo.