400 lines
12 KiB
C
400 lines
12 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))
|
|
|
|
#define SLAVE_ADDR1 0x70
|
|
#define SLAVE_ADDR2 0x72
|
|
static void *
|
|
find_section(struct bdb_header *bdb, int section_id)
|
|
{
|
|
unsigned char *base = (unsigned char *)bdb;
|
|
int index = 0;
|
|
uint16_t total, current_size;
|
|
unsigned char 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 = *((uint16_t *)(base + index));
|
|
index += 2;
|
|
if (current_id == section_id)
|
|
return base + index;
|
|
index += current_size;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
fill_detail_timing_data(DisplayModePtr fixed_mode, unsigned char *timing_ptr)
|
|
{
|
|
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;
|
|
|
|
/* Some VBTs have bogus h/vtotal values */
|
|
if (fixed_mode->HSyncEnd > fixed_mode->HTotal)
|
|
fixed_mode->HTotal = fixed_mode->HSyncEnd + 1;
|
|
if (fixed_mode->VSyncEnd > fixed_mode->VTotal)
|
|
fixed_mode->VTotal = fixed_mode->VSyncEnd + 1;
|
|
|
|
xf86SetModeDefaultName(fixed_mode);
|
|
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static void
|
|
parse_integrated_panel_data(I830Ptr pI830, struct bdb_header *bdb)
|
|
{
|
|
struct bdb_lvds_options *lvds_options;
|
|
struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs;
|
|
struct bdb_lvds_lfp_data *lvds_data;
|
|
struct bdb_lvds_lfp_data_entry *entry;
|
|
DisplayModePtr fixed_mode;
|
|
unsigned char *timing_ptr;
|
|
int lfp_data_size;
|
|
|
|
/* Defaults if we can't find VBT info */
|
|
pI830->lvds_dither = 0;
|
|
|
|
lvds_options = find_section(bdb, BDB_LVDS_OPTIONS);
|
|
if (!lvds_options)
|
|
return;
|
|
|
|
pI830->lvds_dither = lvds_options->pixel_dither;
|
|
if (lvds_options->panel_type == 0xff)
|
|
return;
|
|
|
|
lvds_data = find_section(bdb, BDB_LVDS_LFP_DATA);
|
|
if (!lvds_data) {
|
|
return;
|
|
}
|
|
|
|
lvds_lfp_data_ptrs = find_section(bdb, BDB_LVDS_LFP_DATA_PTRS);
|
|
if (!lvds_lfp_data_ptrs)
|
|
return;
|
|
|
|
lfp_data_size = lvds_lfp_data_ptrs->ptr[1].dvo_timing_offset -
|
|
lvds_lfp_data_ptrs->ptr[0].dvo_timing_offset;
|
|
entry = (struct bdb_lvds_lfp_data_entry *)((uint8_t *)lvds_data->data +
|
|
(lfp_data_size * lvds_options->panel_type));
|
|
timing_ptr = (unsigned char *)&entry->dvo_timing;
|
|
|
|
if (pI830->skip_panel_detect)
|
|
return;
|
|
|
|
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.
|
|
*/
|
|
fill_detail_timing_data(fixed_mode, timing_ptr);
|
|
pI830->lvds_fixed_mode = fixed_mode;
|
|
}
|
|
|
|
static void
|
|
parse_sdvo_panel_data(I830Ptr pI830, struct bdb_header *bdb)
|
|
{
|
|
DisplayModePtr fixed_mode;
|
|
struct bdb_sdvo_lvds_options *sdvo_lvds_options;
|
|
unsigned char *timing_ptr;
|
|
|
|
pI830->sdvo_lvds_fixed_mode = NULL;
|
|
|
|
sdvo_lvds_options = find_section(bdb, BDB_SDVO_LVDS_OPTIONS);
|
|
if (sdvo_lvds_options == NULL)
|
|
return;
|
|
|
|
timing_ptr = find_section(bdb, BDB_SDVO_PANEL_DTDS);
|
|
if (timing_ptr == NULL)
|
|
return;
|
|
|
|
fixed_mode = xnfalloc(sizeof(DisplayModeRec));
|
|
if (fixed_mode == NULL)
|
|
return;
|
|
|
|
memset(fixed_mode, 0, sizeof(*fixed_mode));
|
|
fill_detail_timing_data(fixed_mode, timing_ptr +
|
|
(sdvo_lvds_options->panel_type * DET_TIMING_INFO_LEN));
|
|
pI830->sdvo_lvds_fixed_mode = fixed_mode;
|
|
|
|
}
|
|
|
|
static void
|
|
parse_panel_data(I830Ptr pI830, struct bdb_header *bdb)
|
|
{
|
|
parse_integrated_panel_data(pI830, bdb);
|
|
parse_sdvo_panel_data(pI830, bdb);
|
|
}
|
|
|
|
static void
|
|
parse_general_features(I830Ptr pI830, struct bdb_header *bdb)
|
|
{
|
|
struct bdb_general_features *general;
|
|
|
|
/* Set sensible defaults in case we can't find the general block */
|
|
pI830->tv_present = 1;
|
|
|
|
general = find_section(bdb, BDB_GENERAL_FEATURES);
|
|
if (!general)
|
|
return;
|
|
|
|
pI830->tv_present = general->int_tv_support;
|
|
pI830->lvds_use_ssc = general->enable_ssc;
|
|
if (pI830->lvds_use_ssc) {
|
|
if (IS_I85X(pI830))
|
|
pI830->lvds_ssc_freq = general->ssc_freq ? 66 : 48;
|
|
else
|
|
pI830->lvds_ssc_freq = general->ssc_freq ? 100 : 96;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_driver_feature(I830Ptr pI830, struct bdb_header *bdb)
|
|
{
|
|
struct bdb_driver_feature *feature;
|
|
|
|
/* For mobile chip, set default as true */
|
|
if (IS_MOBILE(pI830) && !IS_I830(pI830))
|
|
pI830->integrated_lvds = TRUE;
|
|
|
|
/* skip pre-9xx chips which is broken to parse this block. */
|
|
if (!IS_I9XX(pI830))
|
|
return;
|
|
|
|
/* XXX Disable this parsing, as it looks doesn't work for all
|
|
VBIOS. Reenable it if we could find out the reliable VBT parsing
|
|
for LVDS config later. */
|
|
if (1)
|
|
return;
|
|
|
|
feature = find_section(bdb, BDB_DRIVER_FEATURES);
|
|
if (!feature)
|
|
return;
|
|
|
|
if (feature->lvds_config != BDB_DRIVER_INT_LVDS)
|
|
pI830->integrated_lvds = FALSE;
|
|
}
|
|
|
|
static
|
|
void parse_sdvo_mapping(ScrnInfoPtr pScrn, struct bdb_header *bdb)
|
|
{
|
|
unsigned int block_size;
|
|
uint16_t *block_ptr;
|
|
struct bdb_general_definitions *defs;
|
|
struct child_device_config *child;
|
|
int i, child_device_num, count;
|
|
struct sdvo_device_mapping *p_mapping;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
|
|
defs = find_section(bdb, BDB_GENERAL_DEFINITIONS);
|
|
if (!defs) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"can't find the general definition blocks\n");
|
|
return;
|
|
}
|
|
/* Get the block size of general defintion block */
|
|
block_ptr = (uint16_t *)((char *)defs - 2);
|
|
block_size = *block_ptr;
|
|
child_device_num = (block_size - sizeof(*defs)) / sizeof(*child);
|
|
count = 0;
|
|
|
|
for (i = 0; i < child_device_num; i++) {
|
|
child = &defs->devices[i];
|
|
if (!child->device_type) {
|
|
/* skip invalid child device type*/
|
|
continue;
|
|
}
|
|
if (child->slave_addr == SLAVE_ADDR1 ||
|
|
child->slave_addr == SLAVE_ADDR2) {
|
|
if (child->dvo_port != DEVICE_PORT_DVOB &&
|
|
child->dvo_port != DEVICE_PORT_DVOC) {
|
|
/* skip the incorrect sdvo port */
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"Incorrect SDVO port\n");
|
|
continue;
|
|
}
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"the SDVO device with slave addr %x "
|
|
"is found on DVO %x port\n",
|
|
child->slave_addr, child->dvo_port);
|
|
/* fill the primary dvo port */
|
|
p_mapping = &(pI830->sdvo_mappings[child->dvo_port - 1]);
|
|
if (!p_mapping->initialized) {
|
|
p_mapping->dvo_port = child->dvo_port;
|
|
p_mapping->dvo_wiring = child->dvo_wiring;
|
|
p_mapping->initialized = 1;
|
|
p_mapping->slave_addr = child->slave_addr;
|
|
} else {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"One DVO port is shared by two slave "
|
|
"address. Maybe it can't be handled\n");
|
|
}
|
|
/* If there exists the slave2_addr, maybe it is a sdvo
|
|
* device that contain multiple inputs. And it can't
|
|
* handled by SDVO driver.
|
|
* Ignore the dvo mapping of slave2_addr
|
|
* of course its mapping info won't be added.
|
|
*/
|
|
if (child->slave2_addr) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"Two DVO ports uses the same slave address."
|
|
"Maybe it can't be handled by SDVO driver\n");
|
|
}
|
|
count++;
|
|
} else {
|
|
/* if the slave address is neither 0x70 nor 0x72, skip it. */
|
|
continue;
|
|
}
|
|
}
|
|
/* If the count is zero, it indicates that no sdvo device is found */
|
|
if (!count)
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"No SDVO device is found in VBT\n");
|
|
|
|
return;
|
|
}
|
|
#define INTEL_VBIOS_SIZE (64 * 1024) /* XXX */
|
|
|
|
/**
|
|
* i830_bios_init - map VBIOS, find VBT
|
|
*
|
|
* 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.
|
|
*/
|
|
int
|
|
i830_bios_init(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
struct vbt_header *vbt;
|
|
struct bdb_header *bdb;
|
|
int vbt_off, bdb_off;
|
|
unsigned char *bios;
|
|
int ret;
|
|
int size;
|
|
|
|
size = pI830->PciInfo->rom_size;
|
|
if (size == 0) {
|
|
size = INTEL_VBIOS_SIZE;
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"libpciaccess reported 0 rom size, guessing %dkB\n",
|
|
size / 1024);
|
|
}
|
|
bios = xalloc(size);
|
|
if (bios == NULL)
|
|
return -1;
|
|
|
|
ret = pci_device_read_rom (pI830->PciInfo, bios);
|
|
if (ret != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"libpciaccess failed to read %dkB video BIOS: %s\n",
|
|
size / 1024, strerror(-ret));
|
|
xfree (bios);
|
|
return -1;
|
|
}
|
|
|
|
vbt_off = INTEL_BIOS_16(0x1a);
|
|
if (vbt_off >= size) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Bad VBT offset: 0x%x\n",
|
|
vbt_off);
|
|
xfree(bios);
|
|
return -1;
|
|
}
|
|
|
|
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 -1;
|
|
}
|
|
|
|
/* Now that we've found the VBIOS, go scour the VBTs */
|
|
bdb_off = vbt_off + vbt->bdb_offset;
|
|
bdb = (struct bdb_header *)(bios + bdb_off);
|
|
|
|
parse_general_features(pI830, bdb);
|
|
parse_panel_data(pI830, bdb);
|
|
parse_driver_feature(pI830, bdb);
|
|
parse_sdvo_mapping(pScrn, bdb);
|
|
|
|
xfree(bios);
|
|
|
|
return 0;
|
|
}
|