Intel_extreme: improve i855 support.

https://github.com/druga/haiku-stuff/tree/master/intel_extreme
Rebased against current sources.

* The BIOS video mode sometimes reports a scaled mode instead of the
physical panel dimensions. Get the data from the VBT table as well, and
use it if the reported resolution is bigger.
* On first boot, force the panel native mode so the user doesn't have to
set it manually.
* Only allow a single head at a time on i855gm, as the card can't drive
both heads at the same time.
* Detect when a new requested mode is the same as the current one, and
skip modesetting in that case. Avoids screen flickering when changing
workspaces.
* Fix some cases of misdetecting which pipes to enable
This commit is contained in:
Adrien Destugues
2014-01-17 12:42:20 +01:00
parent 9e9ccf69c0
commit ef726c687a
5 changed files with 399 additions and 31 deletions
@@ -173,6 +173,9 @@ struct intel_shared_info {
addr_t frame_buffer;
uint32 frame_buffer_offset;
bool got_vbt;
bool single_head_locked;
struct lock accelerant_lock;
struct lock engine_lock;
+91 -31
View File
@@ -74,6 +74,20 @@ struct pll_limits {
};
static struct display_mode_hook {
bool active;
display_mode *dm;
struct {
uint16 width;
uint16 height;
uint16 space;
} mode;
} display_mode_hook;
static void mode_fill_missing_bits(display_mode *, uint32);
static status_t
get_i2c_signals(void* cookie, int* _clock, int* _data)
{
@@ -282,6 +296,31 @@ compute_pll_divisors(const display_mode &current, pll_divisors& divisors,
}
static void
mode_fill_missing_bits(display_mode *mode, uint32 cntrl)
{
uint32 value = read32(cntrl);
switch (value & DISPLAY_CONTROL_COLOR_MASK) {
case DISPLAY_CONTROL_RGB32:
default:
mode->space = B_RGB32;
break;
case DISPLAY_CONTROL_RGB16:
mode->space = B_RGB16;
break;
case DISPLAY_CONTROL_RGB15:
mode->space = B_RGB15;
break;
case DISPLAY_CONTROL_CMAP8:
mode->space = B_CMAP8;
break;
}
mode->flags = B_8_BIT_DAC | B_HARDWARE_CURSOR | B_PARALLEL_ACCESS | B_DPMS;
}
static void
retrieve_current_mode(display_mode& mode, uint32 pllRegister)
{
@@ -411,27 +450,10 @@ retrieve_current_mode(display_mode& mode, uint32 pllRegister)
if (mode.virtual_height < mode.timing.v_display)
mode.virtual_height = mode.timing.v_display;
value = read32(controlRegister);
switch (value & DISPLAY_CONTROL_COLOR_MASK) {
case DISPLAY_CONTROL_RGB32:
default:
mode.space = B_RGB32;
break;
case DISPLAY_CONTROL_RGB16:
mode.space = B_RGB16;
break;
case DISPLAY_CONTROL_RGB15:
mode.space = B_RGB15;
break;
case DISPLAY_CONTROL_CMAP8:
mode.space = B_CMAP8;
break;
}
mode_fill_missing_bits(&mode, controlRegister);
mode.h_display_start = 0;
mode.v_display_start = 0;
mode.flags = B_8_BIT_DAC | B_HARDWARE_CURSOR | B_PARALLEL_ACCESS
| B_DPMS;
if (gInfo->overlay_registers != NULL) {
mode.flags |= B_SUPPORTS_OVERLAYS;
}
@@ -535,12 +557,12 @@ set_frame_buffer_base()
uint32 baseRegister;
uint32 surfaceRegister;
if (gInfo->head_mode & HEAD_MODE_A_ANALOG) {
baseRegister = INTEL_DISPLAY_A_BASE;
surfaceRegister = INTEL_DISPLAY_A_SURFACE;
} else {
if (gInfo->head_mode & HEAD_MODE_B_DIGITAL) {
baseRegister = INTEL_DISPLAY_B_BASE;
surfaceRegister = INTEL_DISPLAY_B_SURFACE;
} else {
baseRegister = INTEL_DISPLAY_A_BASE;
surfaceRegister = INTEL_DISPLAY_A_SURFACE;
}
if (sharedInfo.device_type.InGroup(INTEL_TYPE_96x)
@@ -578,6 +600,8 @@ create_mode_list(void)
if (error == B_OK) {
edid_dump(&gInfo->edid_info);
gInfo->has_edid = true;
if (gInfo->shared_info->single_head_locked)
gInfo->head_mode = HEAD_MODE_A_ANALOG;
} else {
TRACE("getting EDID on port A (analog) failed : %s. "
"Trying on port C (lvds)\n", strerror(error));
@@ -593,8 +617,10 @@ create_mode_list(void)
// We could not read any EDID info. Fallback to creating a list with
// only the mode set up by the BIOS.
// TODO: support lower modes via scaling and windowing
if ((gInfo->head_mode & HEAD_MODE_LVDS_PANEL) != 0
&& (gInfo->head_mode & HEAD_MODE_A_ANALOG) == 0) {
if (((gInfo->head_mode & HEAD_MODE_LVDS_PANEL) != 0
&& (gInfo->head_mode & HEAD_MODE_A_ANALOG) == 0)
|| ((gInfo->head_mode & HEAD_MODE_LVDS_PANEL) != 0
&& gInfo->shared_info->got_vbt)) {
size_t size = (sizeof(display_mode) + B_PAGE_SIZE - 1)
& ~(B_PAGE_SIZE - 1);
@@ -605,7 +631,29 @@ create_mode_list(void)
if (area < B_OK)
return area;
memcpy(list, &gInfo->lvds_panel_mode, sizeof(display_mode));
// Prefer information dumped directly from VBT, as the BIOS
// one may have display scaling, but only do this if the VBT
// resolution is higher than the BIOS one.
if (gInfo->shared_info->got_vbt
&& gInfo->shared_info->current_mode.virtual_width
>= gInfo->lvds_panel_mode.virtual_width
&& gInfo->shared_info->current_mode.virtual_height
>= gInfo->lvds_panel_mode.virtual_height) {
memcpy(list, &gInfo->shared_info->current_mode,
sizeof(display_mode));
mode_fill_missing_bits(list, INTEL_DISPLAY_B_CONTROL);
} else {
memcpy(list, &gInfo->lvds_panel_mode,
sizeof(display_mode));
if (gInfo->shared_info->got_vbt)
TRACE("intel_extreme: ignoring VBT mode.");
}
// We can also make this the default resolution, if the user
// didn't pick one yet.
display_mode_hook.active = true;
display_mode_hook.dm = list;
gInfo->mode_list_area = area;
gInfo->mode_list = list;
@@ -717,12 +765,17 @@ intel_propose_display_mode(display_mode* target, const display_mode* low,
status_t
intel_set_display_mode(display_mode* mode)
{
TRACE("%s(%" B_PRIu16 "x%" B_PRIu16 ")\n", __func__,
mode->virtual_width, mode->virtual_height);
if (display_mode_hook.active) {
mode = display_mode_hook.dm;
display_mode_hook.active = false;
}
if (mode == NULL)
return B_BAD_VALUE;
TRACE("%s(%" B_PRIu16 "x%" B_PRIu16 ")\n", __func__,
mode->virtual_width, mode->virtual_height);
display_mode target = *mode;
// TODO: it may be acceptable to continue when using panel fitting or
@@ -736,12 +789,15 @@ intel_set_display_mode(display_mode* mode)
uint32 colorMode, bytesPerRow, bitsPerPixel;
get_color_space_format(target, colorMode, bytesPerRow, bitsPerPixel);
// TODO: do not go further if the mode is identical to the current one.
// This would avoid the screen being off when switching workspaces when they
// have the same resolution.
// avoid screen being off when switching workspaces when they have the same
// resolution.
if (target.virtual_width == display_mode_hook.mode.width
&& target.virtual_height == display_mode_hook.mode.height
&& target.space == display_mode_hook.mode.space)
return B_OK;
#if 0
static bool first = true;
static bool first = tru;
if (first) {
int fd = open("/boot/home/ie_.regs", O_CREAT | O_WRONLY, 0644);
if (fd >= 0) {
@@ -1174,6 +1230,10 @@ if (first) {
sharedInfo.current_mode = target;
sharedInfo.bits_per_pixel = bitsPerPixel;
display_mode_hook.mode.width = target.virtual_width;
display_mode_hook.mode.height = target.virtual_height;
display_mode_hook.mode.space = target.space;
return B_OK;
}
@@ -8,6 +8,7 @@ UsePrivateHeaders graphics kernel ;
UsePrivateHeaders shared ;
KernelAddon intel_extreme :
bios.cpp
driver.cpp
device.cpp
intel_extreme.cpp
@@ -0,0 +1,298 @@
/* Written by Artem Falcon <[email protected]> */
#include <KernelExport.h>
#include "intel_extreme.h"
#include <string.h>
#define TRACE_BIOS 1
#ifdef TRACE_BIOS
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
/* for moving into intel_extreme_private.h */
#define VBIOS 0xc0000
#define INTEL_VBIOS_SIZE (64 * 1024) /* XXX */
#define INTEL_BIOS_16(_addr) (vbios.memory[_addr] | \
(vbios.memory[_addr + 1] << 8))
/* */
/* subject of including into private/graphics/common/edid* */
#define _PIXEL_CLOCK_MHZ(x) (x[0] + (x[1] << 8)) / 100
#define _PIXEL_CLOCK(x) (x[0] + (x[1] << 8)) * 10000
#define _H_ACTIVE(x) (x[2] + ((x[4] & 0xF0) << 4))
#define _H_BLANK(x) (x[3] + ((x[4] & 0x0F) << 8))
#define _H_SYNC_OFF(x) (x[8] + ((x[11] & 0xC0) << 2))
#define _H_SYNC_WIDTH(x) (x[9] + ((x[11] & 0x30) << 4))
#define _V_ACTIVE(x) (x[5] + ((x[7] & 0xF0) << 4))
#define _V_BLANK(x) (x[6] + ((x[7] & 0x0F) << 8))
#define _V_SYNC_OFF(x) ((x[10] >> 4) + ((x[11] & 0x0C) << 2))
#define _V_SYNC_WIDTH(x) ((x[10] & 0x0F) + ((x[11] & 0x03) << 4))
/* */
struct vbt_header {
uint8 signature[20];
uint16 version;
uint16 header_size;
uint16 vbt_size;
uint8 vbt_checksum;
uint8 reserved0;
uint32 bdb_offset;
uint32 aim_offset[4];
} __attribute__((packed));
struct bdb_header {
uint8 signature[16];
uint16 version;
uint16 header_size;
uint16 bdb_size;
/* cutted */
} __attribute__((packed));
struct lvds_bdb1 {
uint8 id;
uint16 size;
uint8 panel_type;
uint8 reserved0;
uint16 caps;
} __attribute__((packed));
struct lvds_bdb2_entry {
uint16 lfp_info_offset;
uint8 lfp_info_size;
uint16 lfp_edid_dtd_offset;
uint8 lfp_edid_dtd_size;
uint16 lfp_edid_pid_offset;
uint8 lfp_edid_pid_size;
} __attribute__((packed));
struct lvds_bdb2 {
uint8 id;
uint16 size;
uint8 table_size; /* unapproved */
struct lvds_bdb2_entry panels[16];
} __attribute__((packed));
struct lvds_bdb2_lfp_info {
uint16 x_res;
uint16 y_res;
uint32 lvds_reg;
uint32 lvds_reg_val;
uint32 pp_on_reg;
uint32 pp_on_reg_val;
uint32 pp_off_reg;
uint32 pp_off_reg_val;
uint32 pp_cycle_reg;
uint32 pp_cycle_reg_val;
uint32 pfit_reg;
uint32 pfit_reg_val;
uint16 terminator;
} __attribute__((packed));
static struct vbios {
area_id area;
uint8* memory;
display_mode *shared_info;
struct {
uint16 hsync_start;
uint16 hsync_end;
uint16 hsync_total;
uint16 vsync_start;
uint16 vsync_end;
uint16 vsync_total;
} timings_common;
} vbios;
static inline bool unmap_bios(area_id area, uint8* mem) {
delete_area(area);
mem = NULL;
return false;
}
/* TO-DO: move to accelerant code, if possible */
/* this is reimplementation, Haiku uses BIOS call and gets most
* current panel info, we're, otherwise, digging in VBIOS memory
* and parsing VBT tables to get native panel timings. This will
* allow to get non-updated, PROM-programmed timings info when
* compensation mode is off on your machine */
static bool
get_bios(void)
{
int vbt_offset;
struct vbt_header *vbt;
/* !!!DANGER!!!: mapping of BIOS using legacy location for now,
* hence, if panel mode will be set using info from VBT, it will
* be taken from primary card's VBIOS */
vbios.area = map_physical_memory("VBIOS mapping",
VBIOS, INTEL_VBIOS_SIZE, B_ANY_KERNEL_ADDRESS,
B_READ_AREA, (void**)&(vbios.memory));
if (vbios.area < 0)
return false;
TRACE((DEVICE_NAME ": mapping VBIOS: 0x%x -> %p\n",
VBIOS, vbios.memory));
vbt_offset = INTEL_BIOS_16(0x1a);
if (vbt_offset >= INTEL_VBIOS_SIZE) {
TRACE(("intel_extreme: bad VBT offset : 0x%x\n",
vbt_offset));
return unmap_bios(vbios.area, vbios.memory);
}
vbt = (struct vbt_header *)(vbios.memory + vbt_offset);
if (memcmp(vbt->signature, "$VBT", 4) != 0) {
TRACE(("intel_extreme: bad VBT signature: %20s\n",
vbt->signature));
return unmap_bios(vbios.area, vbios.memory);
}
return true;
}
static bool feed_shared_info(uint8* data)
{
bool bogus = false;
/* handle bogus h/vtotal values, if got such */
if (vbios.timings_common.hsync_end > vbios.timings_common.hsync_total) {
vbios.timings_common.hsync_total =
vbios.timings_common.hsync_end + 1;
bogus = true;
TRACE(("intel_extreme: got bogus htotal. Fixing\n"));
}
if (vbios.timings_common.vsync_end > vbios.timings_common.vsync_total) {
vbios.timings_common.vsync_total =
vbios.timings_common.vsync_end + 1;
bogus = true;
TRACE(("intel_extreme: got bogus vtotal. Fixing\n"));
}
/* */
if (bogus)
TRACE(("intel_extreme: adjusted LFP modeline: x%d Hz,\t%d "
"%d %d %d %d %d %d %d %d\n",
_PIXEL_CLOCK(data) / (
(_H_ACTIVE(data) + _H_BLANK(data))
* (_V_ACTIVE(data) + _V_BLANK(data))
),
_PIXEL_CLOCK_MHZ(data),
_H_ACTIVE(data), vbios.timings_common.hsync_start,
vbios.timings_common.hsync_end, vbios.timings_common.hsync_total,
_V_ACTIVE(data), vbios.timings_common.vsync_start,
vbios.timings_common.vsync_end, vbios.timings_common.vsync_total
));
/* TO-DO: add retrieved info to edid info struct, not fixed mode struct */
/* struct display_timing is not packed, so we're end setting of every single elm of it */
vbios.shared_info->timing.pixel_clock = _PIXEL_CLOCK(data) / 1000;
vbios.shared_info->timing.h_display = vbios.shared_info->virtual_width = _H_ACTIVE(data);
vbios.shared_info->timing.h_sync_start = vbios.timings_common.hsync_start;
vbios.shared_info->timing.h_sync_end = vbios.timings_common.hsync_end;
vbios.shared_info->timing.h_total = vbios.timings_common.hsync_total;
vbios.shared_info->timing.v_display = vbios.shared_info->virtual_height = _V_ACTIVE(data);
vbios.shared_info->timing.v_sync_start = vbios.timings_common.vsync_start;
vbios.shared_info->timing.v_sync_end = vbios.timings_common.vsync_end;
vbios.shared_info->timing.v_total = vbios.timings_common.vsync_total;
unmap_bios(vbios.area, vbios.memory);
return true;
}
bool get_lvds_mode_from_bios(display_mode *shared_info)
{
struct vbt_header *vbt;
struct bdb_header *bdb;
int vbt_offset, bdb_offset, bdb_block_offset, block_size;
int panel_type = -1;
if (!get_bios())
return false;
vbt_offset = INTEL_BIOS_16(0x1a);
vbt = (struct vbt_header *)(vbios.memory + vbt_offset);
bdb_offset = vbt_offset + vbt->bdb_offset;
bdb = (struct bdb_header *)(vbios.memory + bdb_offset);
if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) {
TRACE(("intel_extreme: bad BDB signature\n"));
return unmap_bios(vbios.area, vbios.memory);
}
TRACE(("intel_extreme: parsing BDB blocks\n"));
for (bdb_block_offset = bdb->header_size; bdb_block_offset < bdb->bdb_size;
bdb_block_offset += block_size) {
int start = bdb_offset + bdb_block_offset;
int id;
struct lvds_bdb1 *lvds1;
struct lvds_bdb2 *lvds2;
struct lvds_bdb2_lfp_info *lvds2_lfp_info;
uint8_t *timing_data;
id = vbios.memory[start];
block_size = INTEL_BIOS_16(start + 1) + 3;
//TRACE(("intel_extreme: found BDB block type %d\n", id));
switch (id) {
case 40:
lvds1 = (struct lvds_bdb1 *)(vbios.memory + start);
panel_type = lvds1->panel_type;
break;
case 41:
if (panel_type == -1)
break;
lvds2 = (struct lvds_bdb2 *)(vbios.memory + start);
lvds2_lfp_info = (struct lvds_bdb2_lfp_info *)
(vbios.memory + bdb_offset +
lvds2->panels[panel_type].lfp_info_offset);
/* found bad one terminator */
if (lvds2_lfp_info->terminator != 0xffff) {
return unmap_bios(vbios.area, vbios.memory);
}
timing_data = vbios.memory + bdb_offset +
lvds2->panels[panel_type].lfp_edid_dtd_offset;
TRACE(("intel_extreme: found LFP of size %d x %d "
"in BIOS VBT tables\n",
lvds2_lfp_info->x_res, lvds2_lfp_info->y_res));
vbios.timings_common.hsync_start = _H_ACTIVE(timing_data) +
_H_SYNC_OFF(timing_data);
vbios.timings_common.hsync_end = vbios.timings_common.hsync_start
+ _H_SYNC_WIDTH(timing_data);
vbios.timings_common.hsync_total = _H_ACTIVE(timing_data) +
_H_BLANK(timing_data);
vbios.timings_common.vsync_start = _V_ACTIVE(timing_data) +
_V_SYNC_OFF(timing_data);
vbios.timings_common.vsync_end = vbios.timings_common.vsync_start
+ _V_SYNC_WIDTH(timing_data);
vbios.timings_common.vsync_total = _V_ACTIVE(timing_data) +
_V_BLANK(timing_data);
/* Xfree86 compatible modeline */
/*TRACE(("intel_extreme: LFP modeline: x%d Hz,\t%d "
"%d %d %d %d %d %d %d %d\n",
_PIXEL_CLOCK(timing_data) / (
(_H_ACTIVE(timing_data) + _H_BLANK(timing_data))
* (_V_ACTIVE(timing_data) + _V_BLANK(timing_data))
),
_PIXEL_CLOCK_MHZ(timing_data),
_H_ACTIVE(timing_data), vbios.timings_common.hsync_start,
vbios.timings_common.hsync_end, vbios.timings_common.hsync_total,
_V_ACTIVE(timing_data), vbios.timings_common.vsync_start,
vbios.timings_common.vsync_end, vbios.timings_common.vsync_total
));*/
vbios.shared_info = shared_info;
return feed_shared_info(timing_data);
break;
}
}
unmap_bios(vbios.area, vbios.memory);
return true;
}
@@ -333,6 +333,12 @@ intel_extreme_init(intel_info &info)
info.shared_info->frame_buffer = 0;
info.shared_info->dpms_mode = B_DPMS_ON;
info.shared_info->got_vbt = get_lvds_mode_from_bios(
&info.shared_info->current_mode);
/* at least 855gm can't drive more than one head at time */
if (info.device_type.InFamily(INTEL_TYPE_8xx))
info.shared_info->single_head_locked = 1;
if (info.device_type.InFamily(INTEL_TYPE_9xx)) {
info.shared_info->pll_info.reference_frequency = 96000; // 96 kHz
info.shared_info->pll_info.max_frequency = 400000;