From 12725a37af691345e74fe22d53300abec2581852 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Fri, 9 May 2008 14:19:00 -0700 Subject: i915: add basic VBT support Map the VBIOS (and therefore VBT) at init time for use by various output initialization routines. --- linux-core/intel_bios.c | 244 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 linux-core/intel_bios.c (limited to 'linux-core/intel_bios.c') diff --git a/linux-core/intel_bios.c b/linux-core/intel_bios.c new file mode 100644 index 00000000..7f8e8513 --- /dev/null +++ b/linux-core/intel_bios.c @@ -0,0 +1,244 @@ +/* + * Copyright © 2006 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Eric Anholt + * + */ +#include "drmP.h" +#include "drm.h" +#include "i915_drm.h" +#include "i915_drv.h" +#include "intel_bios.h" + +#define VBT_OFFSET 0x1a + +/** + * intel_find_vbt - find the VBT + * @dev: DRM device + * + * Loads the Video BIOS and checks that the VBT exists. + * + * VBT existence is a sanity check that is relied on by other i830_bios.c code. + * Note that it would be better to use a BIOS call to get the VBT, as BIOSes may + * feed an updated VBT back through that, compared to what we'll fetch using + * this method of groping around in the BIOS data. + * + * Returns 0 on success, nonzero on failure. + */ +bool +intel_find_bios(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct pci_dev *pdev = dev->pdev; + void __iomem *bios; + size_t size; + + bios = pci_map_rom(pdev, &size); + if (!bios) + return NULL; + + dev_priv->vbt = (struct vbt_header *)((u8 *)bios + VBT_OFFSET); + dev_priv->bdb = (struct bdb_header *)((u8 *)bios + VBT_OFFSET + + dev_priv->vbt->bdb_offset); + + if (memcmp(dev_priv->vbt->signature, "$VBT", 4) != 0) { + DRM_ERROR("Bad VBT signature\n"); + pci_unmap_rom(pdev, bios); + return NULL; + } + + return bios; +} + +#if 0 +/** + * Returns the BIOS's fixed panel mode. + * + * Note that many BIOSes will have the appropriate tables for a panel even when + * a panel is not attached. Additionally, many BIOSes adjust table sizes or + * offsets, such that this parsing fails. Thus, almost any other method for + * detecting the panel mode is preferable. + */ +struct drm_display_mode * +i830_bios_get_panel_mode(struct drm_device *dev, bool *wants_dither) +{ + I830Ptr pI830 = I830PTR(pScrn); + struct vbt_header *vbt; + struct bdb_header *bdb; + int vbt_off, bdb_off, bdb_block_off, block_size; + int panel_type = -1; + unsigned char *bios; + + bios = i830_bios_get (pScrn); + + if (bios == NULL) + return NULL; + + vbt_off = INTEL_BIOS_16(0x1a); + vbt = (struct vbt_header *)(bios + vbt_off); + bdb_off = vbt_off + vbt->bdb_offset; + bdb = (struct bdb_header *)(bios + bdb_off); + + if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Bad BDB signature\n"); + xfree(bios); + return NULL; + } + + *wants_dither = FALSE; + for (bdb_block_off = bdb->header_size; bdb_block_off < bdb->bdb_size; + bdb_block_off += block_size) + { + int start = bdb_off + bdb_block_off; + int id; + struct lvds_bdb_1 *lvds1; + struct lvds_bdb_2 *lvds2; + struct lvds_bdb_2_fp_params *fpparam; + struct lvds_bdb_2_fp_edid_dtd *fptiming; + DisplayModePtr fixed_mode; + CARD8 *timing_ptr; + + id = INTEL_BIOS_8(start); + block_size = INTEL_BIOS_16(start + 1) + 3; + switch (id) { + case 40: + lvds1 = (struct lvds_bdb_1 *)(bios + start); + panel_type = lvds1->panel_type; + if (lvds1->caps & LVDS_CAP_DITHER) + *wants_dither = TRUE; + break; + case 41: + if (panel_type == -1) + break; + + lvds2 = (struct lvds_bdb_2 *)(bios + start); + fpparam = (struct lvds_bdb_2_fp_params *)(bios + + bdb_off + lvds2->panels[panel_type].fp_params_offset); + fptiming = (struct lvds_bdb_2_fp_edid_dtd *)(bios + + bdb_off + lvds2->panels[panel_type].fp_edid_dtd_offset); + timing_ptr = bios + bdb_off + + lvds2->panels[panel_type].fp_edid_dtd_offset; + + if (fpparam->terminator != 0xffff) { + /* Apparently the offsets are wrong for some BIOSes, so we + * try the other offsets if we find a bad terminator. + */ + fpparam = (struct lvds_bdb_2_fp_params *)(bios + + bdb_off + lvds2->panels[panel_type].fp_params_offset + 8); + fptiming = (struct lvds_bdb_2_fp_edid_dtd *)(bios + + bdb_off + lvds2->panels[panel_type].fp_edid_dtd_offset + 8); + timing_ptr = bios + bdb_off + + lvds2->panels[panel_type].fp_edid_dtd_offset + 8; + + if (fpparam->terminator != 0xffff) + continue; + } + + fixed_mode = xnfalloc(sizeof(DisplayModeRec)); + memset(fixed_mode, 0, sizeof(*fixed_mode)); + + /* Since lvds_bdb_2_fp_edid_dtd is just an EDID detailed timing + * block, pull the contents out using EDID macros. + */ + fixed_mode->HDisplay = _H_ACTIVE(timing_ptr); + fixed_mode->VDisplay = _V_ACTIVE(timing_ptr); + fixed_mode->HSyncStart = fixed_mode->HDisplay + + _H_SYNC_OFF(timing_ptr); + fixed_mode->HSyncEnd = fixed_mode->HSyncStart + + _H_SYNC_WIDTH(timing_ptr); + fixed_mode->HTotal = fixed_mode->HDisplay + + _H_BLANK(timing_ptr); + fixed_mode->VSyncStart = fixed_mode->VDisplay + + _V_SYNC_OFF(timing_ptr); + fixed_mode->VSyncEnd = fixed_mode->VSyncStart + + _V_SYNC_WIDTH(timing_ptr); + fixed_mode->VTotal = fixed_mode->VDisplay + + _V_BLANK(timing_ptr); + fixed_mode->Clock = _PIXEL_CLOCK(timing_ptr) / 1000; + fixed_mode->type = M_T_PREFERRED; + + xf86SetModeDefaultName(fixed_mode); + + if (pI830->debug_modes) { + xf86DrvMsg(pScrn->scrnIndex, X_INFO, + "Found panel mode in BIOS VBT tables:\n"); + xf86PrintModeline(pScrn->scrnIndex, fixed_mode); + } + + xfree(bios); + return fixed_mode; + } + } + + xfree(bios); + return NULL; +} + +unsigned char * +i830_bios_get_aim_data_block (ScrnInfoPtr pScrn, int aim, int data_block) +{ + unsigned char *bios; + int bdb_off; + int vbt_off; + int aim_off; + struct vbt_header *vbt; + struct aimdb_header *aimdb; + struct aimdb_block *aimdb_block; + + bios = i830_bios_get (pScrn); + if (!bios) + return NULL; + + vbt_off = INTEL_BIOS_16(0x1a); + vbt = (struct vbt_header *)(bios + vbt_off); + + aim_off = vbt->aim_offset[aim]; + if (!aim_off) + { + free (bios); + return NULL; + } + xf86DrvMsg(pScrn->scrnIndex, X_INFO, "aim_off %d\n", aim_off); + aimdb = (struct aimdb_header *) (bios + vbt_off + aim_off); + bdb_off = aimdb->aimdb_header_size; + while (bdb_off < aimdb->aimdb_size) + { + aimdb_block = (struct aimdb_block *) (bios + vbt_off + aim_off + bdb_off); + if (aimdb_block->aimdb_id == data_block) + { + unsigned char *aim = malloc (aimdb_block->aimdb_size + sizeof (struct aimdb_block)); + if (!aim) + { + free (bios); + return NULL; + } + memcpy (aim, aimdb_block, aimdb_block->aimdb_size + sizeof (struct aimdb_block)); + free (bios); + return aim; + } + bdb_off += aimdb_block->aimdb_size + sizeof (struct aimdb_block); + } + free (bios); + return NULL; +} +#endif -- cgit v1.2.3 From 9fc4ea5c00dfb91ebff893fb5092e768155cc2e2 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Fri, 23 May 2008 18:42:47 -0700 Subject: i915: do a better job of parsing VBIOS data Add code to get panel modes from the VBIOS if present and check whether certain outputs exist. Should make our display detection code a little more robust. --- linux-core/intel_bios.c | 322 ++++++++++++++++++++++++------------------------ 1 file changed, 159 insertions(+), 163 deletions(-) (limited to 'linux-core/intel_bios.c') diff --git a/linux-core/intel_bios.c b/linux-core/intel_bios.c index 7f8e8513..f124fa1a 100644 --- a/linux-core/intel_bios.c +++ b/linux-core/intel_bios.c @@ -30,170 +30,7 @@ #include "i915_drv.h" #include "intel_bios.h" -#define VBT_OFFSET 0x1a - -/** - * intel_find_vbt - find the VBT - * @dev: DRM device - * - * Loads the Video BIOS and checks that the VBT exists. - * - * VBT existence is a sanity check that is relied on by other i830_bios.c code. - * Note that it would be better to use a BIOS call to get the VBT, as BIOSes may - * feed an updated VBT back through that, compared to what we'll fetch using - * this method of groping around in the BIOS data. - * - * Returns 0 on success, nonzero on failure. - */ -bool -intel_find_bios(struct drm_device *dev) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - struct pci_dev *pdev = dev->pdev; - void __iomem *bios; - size_t size; - - bios = pci_map_rom(pdev, &size); - if (!bios) - return NULL; - - dev_priv->vbt = (struct vbt_header *)((u8 *)bios + VBT_OFFSET); - dev_priv->bdb = (struct bdb_header *)((u8 *)bios + VBT_OFFSET + - dev_priv->vbt->bdb_offset); - - if (memcmp(dev_priv->vbt->signature, "$VBT", 4) != 0) { - DRM_ERROR("Bad VBT signature\n"); - pci_unmap_rom(pdev, bios); - return NULL; - } - - return bios; -} - #if 0 -/** - * Returns the BIOS's fixed panel mode. - * - * Note that many BIOSes will have the appropriate tables for a panel even when - * a panel is not attached. Additionally, many BIOSes adjust table sizes or - * offsets, such that this parsing fails. Thus, almost any other method for - * detecting the panel mode is preferable. - */ -struct drm_display_mode * -i830_bios_get_panel_mode(struct drm_device *dev, bool *wants_dither) -{ - I830Ptr pI830 = I830PTR(pScrn); - struct vbt_header *vbt; - struct bdb_header *bdb; - int vbt_off, bdb_off, bdb_block_off, block_size; - int panel_type = -1; - unsigned char *bios; - - bios = i830_bios_get (pScrn); - - if (bios == NULL) - return NULL; - - vbt_off = INTEL_BIOS_16(0x1a); - vbt = (struct vbt_header *)(bios + vbt_off); - bdb_off = vbt_off + vbt->bdb_offset; - bdb = (struct bdb_header *)(bios + bdb_off); - - if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) { - xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Bad BDB signature\n"); - xfree(bios); - return NULL; - } - - *wants_dither = FALSE; - for (bdb_block_off = bdb->header_size; bdb_block_off < bdb->bdb_size; - bdb_block_off += block_size) - { - int start = bdb_off + bdb_block_off; - int id; - struct lvds_bdb_1 *lvds1; - struct lvds_bdb_2 *lvds2; - struct lvds_bdb_2_fp_params *fpparam; - struct lvds_bdb_2_fp_edid_dtd *fptiming; - DisplayModePtr fixed_mode; - CARD8 *timing_ptr; - - id = INTEL_BIOS_8(start); - block_size = INTEL_BIOS_16(start + 1) + 3; - switch (id) { - case 40: - lvds1 = (struct lvds_bdb_1 *)(bios + start); - panel_type = lvds1->panel_type; - if (lvds1->caps & LVDS_CAP_DITHER) - *wants_dither = TRUE; - break; - case 41: - if (panel_type == -1) - break; - - lvds2 = (struct lvds_bdb_2 *)(bios + start); - fpparam = (struct lvds_bdb_2_fp_params *)(bios + - bdb_off + lvds2->panels[panel_type].fp_params_offset); - fptiming = (struct lvds_bdb_2_fp_edid_dtd *)(bios + - bdb_off + lvds2->panels[panel_type].fp_edid_dtd_offset); - timing_ptr = bios + bdb_off + - lvds2->panels[panel_type].fp_edid_dtd_offset; - - if (fpparam->terminator != 0xffff) { - /* Apparently the offsets are wrong for some BIOSes, so we - * try the other offsets if we find a bad terminator. - */ - fpparam = (struct lvds_bdb_2_fp_params *)(bios + - bdb_off + lvds2->panels[panel_type].fp_params_offset + 8); - fptiming = (struct lvds_bdb_2_fp_edid_dtd *)(bios + - bdb_off + lvds2->panels[panel_type].fp_edid_dtd_offset + 8); - timing_ptr = bios + bdb_off + - lvds2->panels[panel_type].fp_edid_dtd_offset + 8; - - if (fpparam->terminator != 0xffff) - continue; - } - - fixed_mode = xnfalloc(sizeof(DisplayModeRec)); - memset(fixed_mode, 0, sizeof(*fixed_mode)); - - /* Since lvds_bdb_2_fp_edid_dtd is just an EDID detailed timing - * block, pull the contents out using EDID macros. - */ - fixed_mode->HDisplay = _H_ACTIVE(timing_ptr); - fixed_mode->VDisplay = _V_ACTIVE(timing_ptr); - fixed_mode->HSyncStart = fixed_mode->HDisplay + - _H_SYNC_OFF(timing_ptr); - fixed_mode->HSyncEnd = fixed_mode->HSyncStart + - _H_SYNC_WIDTH(timing_ptr); - fixed_mode->HTotal = fixed_mode->HDisplay + - _H_BLANK(timing_ptr); - fixed_mode->VSyncStart = fixed_mode->VDisplay + - _V_SYNC_OFF(timing_ptr); - fixed_mode->VSyncEnd = fixed_mode->VSyncStart + - _V_SYNC_WIDTH(timing_ptr); - fixed_mode->VTotal = fixed_mode->VDisplay + - _V_BLANK(timing_ptr); - fixed_mode->Clock = _PIXEL_CLOCK(timing_ptr) / 1000; - fixed_mode->type = M_T_PREFERRED; - - xf86SetModeDefaultName(fixed_mode); - - if (pI830->debug_modes) { - xf86DrvMsg(pScrn->scrnIndex, X_INFO, - "Found panel mode in BIOS VBT tables:\n"); - xf86PrintModeline(pScrn->scrnIndex, fixed_mode); - } - - xfree(bios); - return fixed_mode; - } - } - - xfree(bios); - return NULL; -} - unsigned char * i830_bios_get_aim_data_block (ScrnInfoPtr pScrn, int aim, int data_block) { @@ -242,3 +79,162 @@ i830_bios_get_aim_data_block (ScrnInfoPtr pScrn, int aim, int data_block) return NULL; } #endif + +static void * +find_section(struct bdb_header *bdb, int section_id) +{ + u8 *base = (u8 *)bdb; + int index = 0; + u16 total, current_size; + u8 current_id; + + /* skip to first section */ + index += bdb->header_size; + total = bdb->bdb_size; + + /* walk the sections looking for section_id */ + while (index < total) { + current_id = *(base + index); + index++; + current_size = *((u16 *)(base + index)); + index += 2; + if (current_id == section_id) + return base + index; + index += current_size; + } + + return NULL; +} + +/* Try to find panel data */ +static void +parse_panel_data(struct drm_i915_private *dev_priv, struct bdb_header *bdb) +{ + struct bdb_lvds_options *lvds_options; + struct bdb_lvds_lfp_data *lvds_lfp_data; + struct bdb_lvds_lfp_data_entry *entry; + struct lvds_dvo_timing *dvo_timing; + struct drm_display_mode *panel_fixed_mode; + + /* Defaults if we can't find VBT info */ + dev_priv->lvds_dither = 0; + dev_priv->lvds_vbt = 0; + + lvds_options = find_section(bdb, BDB_LVDS_OPTIONS); + if (!lvds_options) + return; + + dev_priv->lvds_dither = lvds_options->pixel_dither; + if (lvds_options->panel_type == 0xff) + return; + + lvds_lfp_data = find_section(bdb, BDB_LVDS_LFP_DATA); + if (!lvds_lfp_data) + return; + + dev_priv->lvds_vbt = 1; + + entry = &lvds_lfp_data->data[lvds_options->panel_type]; + dvo_timing = &entry->dvo_timing; + + panel_fixed_mode = drm_calloc(1, sizeof(*panel_fixed_mode), + DRM_MEM_DRIVER); + + panel_fixed_mode->hdisplay = (dvo_timing->hactive_hi << 8) | + dvo_timing->hactive_lo; + panel_fixed_mode->hsync_start = panel_fixed_mode->hdisplay + + ((dvo_timing->hsync_off_hi << 8) | dvo_timing->hsync_off_lo); + panel_fixed_mode->hsync_end = panel_fixed_mode->hsync_start + + dvo_timing->hsync_pulse_width; + panel_fixed_mode->htotal = panel_fixed_mode->hdisplay + + ((dvo_timing->hblank_hi << 8) | dvo_timing->hblank_lo); + + panel_fixed_mode->vdisplay = (dvo_timing->vactive_hi << 8) | + dvo_timing->vactive_lo; + panel_fixed_mode->vsync_start = panel_fixed_mode->vdisplay + + dvo_timing->vsync_off; + panel_fixed_mode->vsync_end = panel_fixed_mode->vsync_start + + dvo_timing->vsync_pulse_width; + panel_fixed_mode->vtotal = panel_fixed_mode->vdisplay + + ((dvo_timing->vblank_hi << 8) | dvo_timing->vblank_lo); + panel_fixed_mode->clock = dvo_timing->clock * 10; + panel_fixed_mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(panel_fixed_mode); + + dev_priv->vbt_mode = panel_fixed_mode; + + DRM_DEBUG("Found panel mode in BIOS VBT tables:\n"); + drm_mode_debug_printmodeline(panel_fixed_mode); + + return; +} + +static void +parse_general_features(struct drm_i915_private *dev_priv, + struct bdb_header *bdb) +{ + struct bdb_general_features *general; + + /* Set sensible defaults in case we can't find the general block */ + dev_priv->int_tv_support = 1; + dev_priv->int_crt_support = 1; + + general = find_section(bdb, BDB_GENERAL_FEATURES); + if (general) { + dev_priv->int_tv_support = general->int_tv_support; + dev_priv->int_crt_support = general->int_crt_support; + } +} + +/** + * intel_init_bios - initialize VBIOS settings & find VBT + * @dev: DRM device + * + * Loads the Video BIOS and checks that the VBT exists. Sets scratch registers + * to appropriate values. + * + * VBT existence is a sanity check that is relied on by other i830_bios.c code. + * Note that it would be better to use a BIOS call to get the VBT, as BIOSes may + * feed an updated VBT back through that, compared to what we'll fetch using + * this method of groping around in the BIOS data. + * + * Returns 0 on success, nonzero on failure. + */ +bool +intel_init_bios(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct pci_dev *pdev = dev->pdev; + struct vbt_header *vbt = NULL; + struct bdb_header *bdb; + u8 __iomem *bios; + size_t size; + int i; + + bios = pci_map_rom(pdev, &size); + if (!bios) + return -1; + + /* Scour memory looking for the VBT signature */ + for (i = 0; i + 4 < size; i++) { + if (!memcmp(bios + i, "$VBT", 4)) { + vbt = (struct vbt_header *)(bios + i); + break; + } + } + + if (!vbt) { + DRM_ERROR("VBT signature missing\n"); + pci_unmap_rom(pdev, bios); + return -1; + } + + bdb = (struct bdb_header *)(bios + i + vbt->bdb_offset); + + /* Grab useful general definitions */ + parse_general_features(dev_priv, bdb); + parse_panel_data(dev_priv, bdb); + + return 0; +} -- cgit v1.2.3 From 070755af3fecefb6b09e8ef98738e4926e4148a7 Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Wed, 28 May 2008 08:24:42 -0700 Subject: i915: unmap BIOS when we're done with it At the moment, we only read it at startup time, so we can just unmap it there when we're done. --- linux-core/intel_bios.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'linux-core/intel_bios.c') diff --git a/linux-core/intel_bios.c b/linux-core/intel_bios.c index f124fa1a..0cdc915c 100644 --- a/linux-core/intel_bios.c +++ b/linux-core/intel_bios.c @@ -236,5 +236,7 @@ intel_init_bios(struct drm_device *dev) parse_general_features(dev_priv, bdb); parse_panel_data(dev_priv, bdb); + pci_unmap_rom(pdev, bios); + return 0; } -- cgit v1.2.3