* Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Support for i915 chipset and up based on the X driver,
* Copyright 2006-2007 Intel Corporation.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
*/
#include <algorithm>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <Debug.h>
#include <create_display_modes.h>
#include <ddc.h>
#include <edid.h>
#include <validate_display_mode.h>
#include "accelerant_protos.h"
#include "accelerant.h"
#include "pll.h"
#include "Ports.h"
#include "utility.h"
#undef TRACE
#define TRACE_MODE
#ifdef TRACE_MODE
# 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 void
get_color_space_format(const display_mode &mode, uint32 &colorMode,
uint32 &bytesPerRow, uint32 &bitsPerPixel)
{
uint32 bytesPerPixel;
switch (mode.space) {
case B_RGB32_LITTLE:
if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
colorMode = DISPLAY_CONTROL_RGB32_SKY;
} else {
colorMode = DISPLAY_CONTROL_RGB32;
}
bytesPerPixel = 4;
bitsPerPixel = 32;
break;
case B_RGB16_LITTLE:
if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
colorMode = DISPLAY_CONTROL_RGB16_SKY;
} else {
colorMode = DISPLAY_CONTROL_RGB16;
}
bytesPerPixel = 2;
bitsPerPixel = 16;
break;
case B_RGB15_LITTLE:
if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
colorMode = DISPLAY_CONTROL_RGB15_SKY;
} else {
colorMode = DISPLAY_CONTROL_RGB15;
}
bytesPerPixel = 2;
bitsPerPixel = 15;
break;
case B_CMAP8:
default:
if (gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_LAKE)) {
colorMode = DISPLAY_CONTROL_CMAP8_SKY;
} else {
colorMode = DISPLAY_CONTROL_CMAP8;
}
bytesPerPixel = 1;
bitsPerPixel = 8;
break;
}
bytesPerRow = mode.virtual_width * bytesPerPixel;
if ((bytesPerRow & 63) != 0)
bytesPerRow = (bytesPerRow + 63) & ~63;
}
static bool
sanitize_display_mode(display_mode& mode)
{
uint16 pixelCount = 1;
if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_Gxx)
|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x)
|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_94x)
|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_91x)
|| gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_8xx)) {
pixelCount = 2;
}
display_constraints constraints = {
320, 4096, 200, 4096,
gInfo->shared_info->pll_info.min_frequency,
gInfo->shared_info->pll_info.max_frequency,
{pixelCount, 0, 8160, 32, 8192, 0, 8192},
{1, 1, 8190, 2, 8192, 1, 8192}
};
return sanitize_display_mode(mode, constraints,
gInfo->has_edid ? &gInfo->edid_info : NULL);
}
static void
set_frame_buffer_registers(uint32 offset)
{
intel_shared_info &sharedInfo = *gInfo->shared_info;
display_mode &mode = sharedInfo.current_mode;
uint32 bytes_per_pixel = (sharedInfo.bits_per_pixel + 7) / 8;
if (sharedInfo.device_type.InGroup(INTEL_GROUP_96x)
|| sharedInfo.device_type.InGroup(INTEL_GROUP_G4x)
|| sharedInfo.device_type.InGroup(INTEL_GROUP_ILK)
|| sharedInfo.device_type.InFamily(INTEL_FAMILY_SER5)
|| sharedInfo.device_type.InFamily(INTEL_FAMILY_LAKE)
|| sharedInfo.device_type.InFamily(INTEL_FAMILY_SOC0)) {
if (sharedInfo.device_type.InGroup(INTEL_GROUP_HAS)) {
write32(INTEL_DISPLAY_A_OFFSET_HAS + offset,
((uint32)mode.v_display_start << 16)
| (uint32)mode.h_display_start);
read32(INTEL_DISPLAY_A_OFFSET_HAS + offset);
} else {
write32(INTEL_DISPLAY_A_BASE + offset,
mode.v_display_start * sharedInfo.bytes_per_row
+ mode.h_display_start * bytes_per_pixel);
read32(INTEL_DISPLAY_A_BASE + offset);
}
write32(INTEL_DISPLAY_A_SURFACE + offset, sharedInfo.frame_buffer_offset);
read32(INTEL_DISPLAY_A_SURFACE + offset);
} else {
write32(INTEL_DISPLAY_A_BASE + offset, sharedInfo.frame_buffer_offset
+ mode.v_display_start * sharedInfo.bytes_per_row
+ mode.h_display_start * bytes_per_pixel);
read32(INTEL_DISPLAY_A_BASE + offset);
}
}
void
set_frame_buffer_base()
{
set_frame_buffer_registers(0);
set_frame_buffer_registers(INTEL_DISPLAY_OFFSET);
}
static bool
limit_modes_for_gen3_lvds(display_mode* mode)
{
if (gInfo->shared_info->panel_timing.h_display < mode->timing.h_display)
return false;
if (gInfo->shared_info->panel_timing.v_display < mode->timing.v_display)
return false;
return true;
}
It's called from intel_init_accelerant().
*/
status_t
create_mode_list(void)
{
CALLED();
for (uint32 i = 0; i < gInfo->port_count; i++) {
if (gInfo->ports[i] == NULL)
continue;
status_t status = gInfo->ports[i]->GetEDID(&gInfo->edid_info);
if (status == B_OK) {
gInfo->has_edid = true;
break;
}
}
if (!gInfo->has_edid && gInfo->shared_info->has_vesa_edid_info) {
TRACE("%s: Using VESA edid info\n", __func__);
memcpy(&gInfo->edid_info, &gInfo->shared_info->vesa_edid_info,
sizeof(edid1_info));
edid_dump(&gInfo->edid_info);
gInfo->has_edid = true;
}
display_mode* list;
uint32 count = 0;
const color_space kSupportedSpaces[] = {B_RGB32_LITTLE, B_RGB16_LITTLE,
B_CMAP8};
const color_space* supportedSpaces;
int colorSpaceCount;
if (gInfo->shared_info->device_type.Generation() >= 4) {
supportedSpaces = kSupportedSpaces;
colorSpaceCount = B_COUNT_OF(kSupportedSpaces);
} else {
supportedSpaces = NULL;
colorSpaceCount = 0;
}
if (!gInfo->has_edid && gInfo->shared_info->got_vbt) {
check_display_mode_hook limitModes = NULL;
if (gInfo->shared_info->device_type.Generation() < 4)
limitModes = limit_modes_for_gen3_lvds;
display_mode mode;
mode.timing = gInfo->shared_info->panel_timing;
mode.space = B_RGB32;
mode.virtual_width = mode.timing.h_display;
mode.virtual_height = mode.timing.v_display;
mode.h_display_start = 0;
mode.v_display_start = 0;
mode.flags = 0;
gInfo->mode_list_area = create_display_modes("intel extreme modes", NULL, &mode, 1,
supportedSpaces, colorSpaceCount, limitModes, &list, &count);
} else {
gInfo->mode_list_area = create_display_modes("intel extreme modes",
gInfo->has_edid ? &gInfo->edid_info : NULL, NULL, 0,
supportedSpaces, colorSpaceCount, NULL, &list, &count);
}
if (gInfo->mode_list_area < B_OK)
return gInfo->mode_list_area;
gInfo->mode_list = list;
gInfo->shared_info->mode_list_area = gInfo->mode_list_area;
gInfo->shared_info->mode_count = count;
return B_OK;
}
void
wait_for_vblank(void)
{
acquire_sem_etc(gInfo->shared_info->vblank_sem, 1, B_RELATIVE_TIMEOUT,
21000);
}
uint32
intel_accelerant_mode_count(void)
{
CALLED();
return gInfo->shared_info->mode_count;
}
status_t
intel_get_mode_list(display_mode* modeList)
{
CALLED();
memcpy(modeList, gInfo->mode_list,
gInfo->shared_info->mode_count * sizeof(display_mode));
return B_OK;
}
status_t
intel_propose_display_mode(display_mode* target, const display_mode* low,
const display_mode* high)
{
CALLED();
display_mode mode = *target;
if (sanitize_display_mode(*target)) {
TRACE("Video mode was adjusted by sanitize_display_mode\n");
TRACE("Initial mode: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
mode.timing.h_display, mode.timing.h_sync_start,
mode.timing.h_sync_end, mode.timing.h_total,
mode.timing.v_display, mode.timing.v_sync_start,
mode.timing.v_sync_end, mode.timing.v_total);
TRACE("Sanitized: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
target->timing.h_display, target->timing.h_sync_start,
target->timing.h_sync_end, target->timing.h_total,
target->timing.v_display, target->timing.v_sync_start,
target->timing.v_sync_end, target->timing.v_total);
}
target->flags |= B_SCROLL;
return is_display_mode_within_bounds(*target, *low, *high)
? B_OK : B_BAD_VALUE;
}
status_t
intel_set_display_mode(display_mode* mode)
{
if (mode == NULL)
return B_BAD_VALUE;
TRACE("%s(%" B_PRIu16 "x%" B_PRIu16 ", virtual: %" B_PRIu16 "x%" B_PRIu16 ")\n", __func__,
mode->timing.h_display, mode->timing.v_display, mode->virtual_width, mode->virtual_height);
display_mode target = *mode;
if (intel_propose_display_mode(&target, &target, &target) != B_OK)
return B_BAD_VALUE;
uint32 colorMode, bytesPerRow, bitsPerPixel;
get_color_space_format(target, colorMode, bytesPerRow, bitsPerPixel);
intel_shared_info &sharedInfo = *gInfo->shared_info;
Autolock locker(sharedInfo.accelerant_lock);
set_display_power_mode(B_DPMS_OFF);
intel_free_memory(sharedInfo.frame_buffer);
addr_t base;
if (intel_allocate_memory(bytesPerRow * target.virtual_height, 0,
base) < B_OK) {
if (intel_allocate_memory(sharedInfo.current_mode.virtual_height
* sharedInfo.bytes_per_row, 0, base) == B_OK) {
sharedInfo.frame_buffer = base;
sharedInfo.frame_buffer_offset = base
- (addr_t)sharedInfo.graphics_memory;
set_frame_buffer_base();
}
ERROR("%s: Failed to allocate framebuffer !\n", __func__);
return B_NO_MEMORY;
}
memset((uint8*)base, 0, bytesPerRow * target.virtual_height);
sharedInfo.frame_buffer = base;
sharedInfo.frame_buffer_offset = base - (addr_t)sharedInfo.graphics_memory;
#if 0
if ((gInfo->head_mode & HEAD_MODE_TESTING) != 0) {
FDILink* link = pipe->FDILink();
if (link != NULL) {
link->Receiver().EnablePLL();
link->Receiver().SwitchClock(true);
link->Transmitter().EnablePLL();
}
PanelFitter* fitter = pipe->PanelFitter();
if (fitter != NULL)
fitter->Enable(mode);
pll_divisors divisors;
compute_pll_divisors(target, divisors, false);
pipe->ConfigureTimings(divisors);
pipe->Enable();
8. Configure and enable CPU planes (VGA or hires)
9. If enabling port on PCH:
ii. Enable CPU FDI Transmitter and PCH FDI Receiver with Training Pattern 1 enabled.
iii. Wait for FDI training pattern 1 time
iv. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for bit lock in bit 8 (retry at least once if no lock)
v. Enable training pattern 2 on CPU FDI Transmitter and PCH FDI Receiver
vi. Wait for FDI training pattern 2 time
vii. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for symbol lock in bit 9 (retry at least once if no
lock)
viii. Enable normal pixel output on CPU FDI Transmitter and PCH FDI Receiver
ix. Wait for FDI idle pattern time for link to become active
c. Configure and enable PCH DPLL, wait for PCH DPLL warmup (Can be done anytime before enabling
PCH transcoder)
d. [DevCPT] Configure DPLL SEL to set the DPLL to transcoder mapping and enable DPLL to the
transcoder.
e. [DevCPT] Configure DPLL_CTL DPLL_HDMI_multipler.
f. Configure PCH transcoder timings, M/N/TU, and other transcoder settings (should match CPU settings).
g. [DevCPT] Configure and enable Transcoder DisplayPort Control if DisplayPort will be used
h. Enable PCH transcoder
10. Enable ports (DisplayPort must enable in training pattern 1)
11. Enable panel power through panel power sequencing
12. Wait for panel power sequencing to reach enabled steady state
13. Disable panel power override
14. If DisplayPort, complete link training
15. Enable panel backlight
}
#endif
write32(INTEL_VGA_DISPLAY_CONTROL, VGA_DISPLAY_DISABLED);
read32(INTEL_VGA_DISPLAY_CONTROL);
for (uint32 i = 0; i < gInfo->port_count; i++) {
if (gInfo->ports[i] == NULL)
continue;
if (!gInfo->ports[i]->IsConnected())
continue;
status_t status = gInfo->ports[i]->SetDisplayMode(&target, colorMode);
if (status != B_OK)
ERROR("%s: Unable to set display mode!\n", __func__);
}
TRACE("%s: Port configuration completed successfully!\n", __func__);
program_pipe_color_modes(colorMode);
set_display_power_mode(sharedInfo.dpms_mode);
if (sharedInfo.device_type.InFamily(INTEL_FAMILY_LAKE)) {
write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow >> 6);
write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow >> 6);
} else {
write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow);
write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow);
}
sharedInfo.current_mode = target;
sharedInfo.bytes_per_row = bytesPerRow;
sharedInfo.bits_per_pixel = bitsPerPixel;
set_frame_buffer_base();
return B_OK;
}
status_t
intel_get_display_mode(display_mode* _currentMode)
{
CALLED();
*_currentMode = gInfo->shared_info->current_mode;
return B_OK;
}
status_t
intel_get_preferred_mode(display_mode* preferredMode)
{
TRACE("%s\n", __func__);
display_mode mode;
if (gInfo->has_edid || !gInfo->shared_info->got_vbt
|| !gInfo->shared_info->device_type.IsMobile()) {
return B_ERROR;
}
mode.timing = gInfo->shared_info->panel_timing;
mode.space = B_RGB32;
mode.virtual_width = mode.timing.h_display;
mode.virtual_height = mode.timing.v_display;
mode.h_display_start = 0;
mode.v_display_start = 0;
mode.flags = 0;
memcpy(preferredMode, &mode, sizeof(mode));
return B_OK;
}
status_t
intel_get_edid_info(void* info, size_t size, uint32* _version)
{
if (!gInfo->has_edid)
return B_ERROR;
if (size < sizeof(struct edid1_info))
return B_BUFFER_OVERFLOW;
memcpy(info, &gInfo->edid_info, sizeof(struct edid1_info));
*_version = EDID_VERSION_1;
return B_OK;
}
static int32_t
intel_get_backlight_register(bool period)
{
if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
if (period)
return PCH_SOUTH_BLC_PWM_PERIOD;
else
return PCH_SOUTH_BLC_PWM_DUTY_CYCLE;
} else if (gInfo->shared_info->pch_info >= INTEL_PCH_SPT)
return BLC_PWM_PCH_CTL2;
if (gInfo->shared_info->pch_info == INTEL_PCH_NONE)
return MCH_BLC_PWM_CTL;
if (period)
return PCH_SOUTH_BLC_PWM_PERIOD;
else
return PCH_BLC_PWM_CTL;
}
status_t
intel_set_brightness(float brightness)
{
CALLED();
if (brightness < 0 || brightness > 1)
return B_BAD_VALUE;
if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
uint32_t period = read32(intel_get_backlight_register(true));
uint32_t duty = (uint32_t)(period * brightness);
duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
write32(intel_get_backlight_register(false), duty);
} else if (gInfo->shared_info->pch_info >= INTEL_PCH_SPT) {
uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
uint32_t duty = (uint32_t)(period * brightness) & 0xffff;
duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
write32(intel_get_backlight_register(false), duty | (period << 16));
} else {
uint32 tmp = read32(intel_get_backlight_register(true));
bool legacyMode = false;
if (gInfo->shared_info->device_type.Generation() == 2
|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_915M)
|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_945M)) {
legacyMode = (tmp & BLM_LEGACY_MODE) != 0;
}
uint32_t period = tmp >> 16;
uint32_t mask = 0xffff;
uint32_t shift = 0;
if (gInfo->shared_info->device_type.Generation() < 4) {
mask = 0xfffe;
shift = 1;
period = tmp >> 17;
}
if (legacyMode)
period *= 0xfe;
uint32_t duty = (uint32_t)(period * brightness);
if (legacyMode) {
uint8 lpc = duty / 0xff + 1;
duty /= lpc;
intel_brightness_legacy brightnessLegacy;
brightnessLegacy.magic = INTEL_PRIVATE_DATA_MAGIC;
brightnessLegacy.lpc = lpc;
ioctl(gInfo->device, INTEL_SET_BRIGHTNESS_LEGACY, &brightnessLegacy,
sizeof(brightnessLegacy));
}
duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
duty <<= shift;
write32(intel_get_backlight_register(false), (duty & mask) | (tmp & ~mask));
}
return B_OK;
}
status_t
intel_get_brightness(float* brightness)
{
CALLED();
if (brightness == NULL)
return B_BAD_VALUE;
uint32_t duty;
uint32_t period;
if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
period = read32(intel_get_backlight_register(true));
duty = read32(intel_get_backlight_register(false));
} else {
uint32 tmp = read32(intel_get_backlight_register(true));
bool legacyMode = false;
if (gInfo->shared_info->device_type.Generation() == 2
|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_915M)
|| gInfo->shared_info->device_type.IsModel(INTEL_MODEL_945M)) {
legacyMode = (tmp & BLM_LEGACY_MODE) != 0;
}
period = tmp >> 16;
duty = read32(intel_get_backlight_register(false)) & 0xffff;
if (legacyMode) {
period *= 0xff;
intel_brightness_legacy brightnessLegacy;
brightnessLegacy.magic = INTEL_PRIVATE_DATA_MAGIC;
ioctl(gInfo->device, INTEL_GET_BRIGHTNESS_LEGACY, &brightnessLegacy,
sizeof(brightnessLegacy));
duty *= brightnessLegacy.lpc;
}
if (gInfo->shared_info->device_type.Generation() < 4) {
period >>= 1;
duty >>= 1;
}
}
*brightness = (float)duty / period;
return B_OK;
}
status_t
intel_get_frame_buffer_config(frame_buffer_config* config)
{
CALLED();
uint32 offset = gInfo->shared_info->frame_buffer_offset;
config->frame_buffer = gInfo->shared_info->graphics_memory + offset;
config->frame_buffer_dma
= (uint8*)gInfo->shared_info->physical_graphics_memory + offset;
config->bytes_per_row = gInfo->shared_info->bytes_per_row;
return B_OK;
}
status_t
intel_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high)
{
CALLED();
if (_low != NULL) {
uint32 totalClocks = (uint32)mode->timing.h_total
* (uint32)mode->timing.v_total;
uint32 low = (totalClocks * 48L) / 1000L;
if (low < gInfo->shared_info->pll_info.min_frequency)
low = gInfo->shared_info->pll_info.min_frequency;
else if (low > gInfo->shared_info->pll_info.max_frequency)
return B_ERROR;
*_low = low;
}
if (_high != NULL)
*_high = gInfo->shared_info->pll_info.max_frequency;
return B_OK;
}
status_t
intel_move_display(uint16 horizontalStart, uint16 verticalStart)
{
intel_shared_info &sharedInfo = *gInfo->shared_info;
Autolock locker(sharedInfo.accelerant_lock);
display_mode &mode = sharedInfo.current_mode;
if (horizontalStart + mode.timing.h_display > mode.virtual_width
|| verticalStart + mode.timing.v_display > mode.virtual_height)
return B_BAD_VALUE;
mode.h_display_start = horizontalStart;
mode.v_display_start = verticalStart;
set_frame_buffer_base();
return B_OK;
}
status_t
intel_get_timing_constraints(display_timing_constraints* constraints)
{
CALLED();
return B_ERROR;
}
void
intel_set_indexed_colors(uint count, uint8 first, uint8* colors, uint32 flags)
{
TRACE("%s(colors = %p, first = %u)\n", __func__, colors, first);
if (colors == NULL)
return;
Autolock locker(gInfo->shared_info->accelerant_lock);
for (; count-- > 0; first++) {
uint32 color = colors[0] << 16 | colors[1] << 8 | colors[2];
colors += 3;
write32(INTEL_DISPLAY_A_PALETTE + first * sizeof(uint32), color);
write32(INTEL_DISPLAY_B_PALETTE + first * sizeof(uint32), color);
}
}