300 lines
8.6 KiB
C
300 lines
8.6 KiB
C
/*
|
|
* 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 <eric@anholt.net>
|
|
*
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#undef VERSION /* XXX edid.h has a VERSION too */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#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;
|
|
}
|