From ef726c687a8cb1779a9a78d43a6b6fa8167a7ab4 Mon Sep 17 00:00:00 2001 From: Adrien Destugues Date: Fri, 17 Jan 2014 12:42:20 +0100 Subject: [PATCH] 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 --- .../graphics/intel_extreme/intel_extreme.h | 3 + .../accelerants/intel_extreme/mode.cpp | 122 +++++-- .../drivers/graphics/intel_extreme/Jamfile | 1 + .../drivers/graphics/intel_extreme/bios.cpp | 298 ++++++++++++++++++ .../graphics/intel_extreme/intel_extreme.cpp | 6 + 5 files changed, 399 insertions(+), 31 deletions(-) create mode 100644 src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp diff --git a/headers/private/graphics/intel_extreme/intel_extreme.h b/headers/private/graphics/intel_extreme/intel_extreme.h index b8e969fba7..7c4c957bef 100644 --- a/headers/private/graphics/intel_extreme/intel_extreme.h +++ b/headers/private/graphics/intel_extreme/intel_extreme.h @@ -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; diff --git a/src/add-ons/accelerants/intel_extreme/mode.cpp b/src/add-ons/accelerants/intel_extreme/mode.cpp index 00182b8040..f484863fbc 100644 --- a/src/add-ons/accelerants/intel_extreme/mode.cpp +++ b/src/add-ons/accelerants/intel_extreme/mode.cpp @@ -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 ¤t, 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; } diff --git a/src/add-ons/kernel/drivers/graphics/intel_extreme/Jamfile b/src/add-ons/kernel/drivers/graphics/intel_extreme/Jamfile index 95bad2c46c..8ac97c6715 100644 --- a/src/add-ons/kernel/drivers/graphics/intel_extreme/Jamfile +++ b/src/add-ons/kernel/drivers/graphics/intel_extreme/Jamfile @@ -8,6 +8,7 @@ UsePrivateHeaders graphics kernel ; UsePrivateHeaders shared ; KernelAddon intel_extreme : + bios.cpp driver.cpp device.cpp intel_extreme.cpp diff --git a/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp b/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp new file mode 100644 index 0000000000..f62992892f --- /dev/null +++ b/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp @@ -0,0 +1,298 @@ +/* Written by Artem Falcon */ + +#include +#include "intel_extreme.h" +#include + +#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; +} diff --git a/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp b/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp index 9c087b7e92..5adb4b3f4c 100644 --- a/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp +++ b/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp @@ -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;