/* * 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 * */ #ifdef HAVE_CONFIG_H #include "config.h" #undef VERSION /* XXX edid.h has a VERSION too */ #endif #include #include #include #define _PARSE_EDID_ #include "xf86.h" #include "i830.h" #include "i830_bios.h" #include "edid.h" #define INTEL_BIOS_8(_addr) (bios[_addr]) #define INTEL_BIOS_16(_addr) (bios[_addr] | \ (bios[_addr + 1] << 8)) #define INTEL_BIOS_32(_addr) (bios[_addr] | \ (bios[_addr + 1] << 8) \ (bios[_addr + 2] << 16) \ (bios[_addr + 3] << 24)) /* XXX */ #define INTEL_VBIOS_SIZE (64 * 1024) static void i830DumpBIOSToFile(ScrnInfoPtr pScrn, unsigned char *bios) { const char *filename = "/tmp/xf86-video-intel-VBIOS"; FILE *f; f = fopen(filename, "w"); if (f == NULL) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Couldn't open %s\n", filename); return; } if (fwrite(bios, INTEL_VBIOS_SIZE, 1, f) != 1) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Couldn't write BIOS data\n"); } xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Wrote BIOS contents to %s\n", filename); fclose(f); } /** * 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. */ unsigned char * i830_bios_get (ScrnInfoPtr pScrn) { I830Ptr pI830 = I830PTR(pScrn); struct vbt_header *vbt; int vbt_off; unsigned char *bios; vbeInfoPtr pVbe; bios = xalloc(INTEL_VBIOS_SIZE); if (bios == NULL) return NULL; pVbe = VBEInit (NULL, pI830->pEnt->index); if (pVbe != NULL) { memcpy(bios, xf86int10Addr(pVbe->pInt10, pVbe->pInt10->BIOSseg << 4), INTEL_VBIOS_SIZE); vbeFree (pVbe); } else { #if XSERVER_LIBPCIACCESS pci_device_read_rom (pI830->PciInfo, bios); #else xf86ReadPciBIOS(0, pI830->PciTag, 0, bios, INTEL_VBIOS_SIZE); #endif } if (0) i830DumpBIOSToFile(pScrn, bios); vbt_off = INTEL_BIOS_16(0x1a); if (vbt_off >= INTEL_VBIOS_SIZE) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Bad VBT offset: 0x%x\n", vbt_off); xfree(bios); return NULL; } vbt = (struct vbt_header *)(bios + vbt_off); if (memcmp(vbt->signature, "$VBT", 4) != 0) { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Bad VBT signature\n"); xfree(bios); return NULL; } return bios; } /** * 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. */ DisplayModePtr i830_bios_get_panel_mode(ScrnInfoPtr pScrn, 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; uint8_t *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; }