diff --git a/src/bios_reader/bios_reader.c b/src/bios_reader/bios_reader.c index ffa27f0..41b35eb 100644 --- a/src/bios_reader/bios_reader.c +++ b/src/bios_reader/bios_reader.c @@ -25,11 +25,16 @@ * */ +#include +#include #include #include #include +#include +#include #include + #include "../i830_bios.h" #define _PARSE_EDID_ @@ -51,108 +56,211 @@ struct _fake_i830 *pI830 = &I830; (pI830->VBIOS[_addr + 2] << 16) \ (pI830->VBIOS[_addr + 3] << 24)) +#define YESNO(val) ((val) ? "yes" : "no") + +static int tv_present; +static int lvds_present; +static int panel_type; + +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 dump_general_features(void *data) +{ + struct bdb_general_features *features = data; + + if (!data) + return; + + printf("General features block:\n"); + + printf("\tPanel fitting: "); + switch (features->panel_fitting) { + case 0: + printf("disabled\n"); + break; + case 1: + printf("text only\n"); + break; + case 2: + printf("graphics only\n"); + break; + case 3: + printf("text & graphics\n"); + break; + } + printf("\tFlexaim: %s\n", YESNO(features->flexaim)); + printf("\tMessage: %s\n", YESNO(features->msg_enable)); + printf("\tClear screen: %d\n", features->clear_screen); + printf("\tDVO color flip required: %s\n", YESNO(features->color_flip)); + printf("\tExternal VBT: %s\n", YESNO(features->download_ext_vbt)); + printf("\tEnable SSC: %s\n", YESNO(features->enable_ssc)); + if (features->enable_ssc) + printf("\tSSC frequency: %s\n", features->ssc_freq ? + "100 MHz (66 MHz on 855)" : "96 MHz (48 MHz on 855)"); + printf("\tLFP on override: %s\n", YESNO(features->enable_lfp_on_override)); + printf("\tDisable SSC on clone: %s\n", YESNO(features->disable_ssc_ddt)); + printf("\tDisable smooth vision: %s\n", + YESNO(features->disable_smooth_vision)); + printf("\tSingle DVI for CRT/DVI: %s\n", YESNO(features->single_dvi)); + printf("\tLegacy monitor detect: %s\n", + YESNO(features->legacy_monitor_detect)); + printf("\tIntegrated CRT: %s\n", YESNO(features->int_crt_support)); + printf("\tIntegrated TV: %s\n", YESNO(features->int_tv_support)); + + tv_present = 1; /* should be based on whether TV DAC exists */ + lvds_present = 1; /* should be based on IS_MOBILE() */ +} + +static void dump_general_definitions(void *data) +{ + struct bdb_general_definitions *defs = data; + unsigned char *lvds_data = defs->tv_or_lvds_info; + + if (!data) + return; + + printf("General definitions block:\n"); + + printf("\tCRT DDC GMBUS addr: 0x%02x\n", defs->crt_ddc_gmbus_pin); + printf("\tUse ACPI DPMS CRT power states: %s\n", YESNO(defs->dpms_acpi)); + printf("\tSkip CRT detect at boot: %s\n", + YESNO(defs->skip_boot_crt_detect)); + printf("\tUse DPMS on AIM devices: %s\n", YESNO(defs->dpms_aim)); + printf("\tBoot display type: 0x%02x%02x\n", defs->boot_display[1], + defs->boot_display[0]); + printf("\tTV data block present: %s\n", YESNO(tv_present)); + if (tv_present) + lvds_data += 33; + if (lvds_present) + printf("\tLFP DDC GMBUS addr: 0x%02x\n", lvds_data[19]); +} + +static void dump_lvds_options(void *data) +{ + struct bdb_lvds_options *options = data; + + if (!data) + return; + + printf("LVDS options block:\n"); + + panel_type = options->panel_type; + printf("\tPanel type: %d\n", panel_type); + printf("\tLVDS EDID available: %s\n", YESNO(options->lvds_edid)); + printf("\tPixel dither: %s\n", YESNO(options->pixel_dither)); + printf("\tPFIT auto ratio: %s\n", YESNO(options->pfit_ratio_auto)); + printf("\tPFIT enhanced graphics mode: %s\n", + YESNO(options->pfit_gfx_mode_enhanced)); + printf("\tPFIT enhanced text mode: %s\n", + YESNO(options->pfit_text_mode_enhanced)); + printf("\tPFIT mode: %d\n", options->pfit_mode); +} + +static void dump_lvds_data(void *data, unsigned char *base) +{ + struct bdb_lvds_lfp_data *lvds_data = data; + int i; + + if (!data) + return; + + printf("LVDS panel data block (preferred block marked with '*'):\n"); + + for (i = 0; i < 16; i++) { + struct bdb_lvds_lfp_data_entry *lfp_data = &lvds_data->data[i]; + uint8_t *timing_data = (uint8_t *)&lfp_data->dvo_timing; + char marker; + + if (i == panel_type) + marker = '*'; + else + marker = ' '; + + printf("%c\tpanel type %02i: %dx%d clock %d\n", marker, + i, lfp_data->fp_timing.x_res, lfp_data->fp_timing.y_res, + _PIXEL_CLOCK(timing_data)); + printf("\t\ttimings: %d %d %d %d %d %d %d %d\n", + _H_ACTIVE(timing_data), + _H_BLANK(timing_data), + _H_SYNC_OFF(timing_data), + _H_SYNC_WIDTH(timing_data), + _V_ACTIVE(timing_data), + _V_BLANK(timing_data), + _V_SYNC_OFF(timing_data), + _V_SYNC_WIDTH(timing_data)); + } + +} + int main(int argc, char **argv) { - FILE *f; + int fd; int bios_size = 65536; struct vbt_header *vbt; struct bdb_header *bdb; - int vbt_off, bdb_off, bdb_block_off, block_size; - int panel_type = -1, i; + int vbt_off, bdb_off; char *filename = "bios"; - if (argc == 2) - filename = argv[1]; + if (argc != 2) { + printf("usage: %s \n", argv[0]); + return 1; + } + + filename = argv[1]; - f = fopen(filename, "r"); - if (!f) { - printf("Couldn't open %s\n", filename); + fd = open(filename, O_RDONLY); + if (fd == -1) { + printf("Couldn't open \"%s\": %s\n", filename, strerror(errno)); return 1; } pI830->VBIOS = calloc(1, bios_size); - if (fread(pI830->VBIOS, 1, bios_size, f) != bios_size) + if (read(fd, pI830->VBIOS, bios_size) < 0) { + printf("Read of \"%s\" failed: %s\n", filename, strerror(errno)); return 1; + } vbt_off = INTEL_BIOS_16(0x1a); - printf("VBT offset: %08x\n", vbt_off); vbt = (struct vbt_header *)(pI830->VBIOS + vbt_off); printf("VBT sig: %20s\n", vbt->signature); + if (memcmp(vbt->signature, "$VBT", 4)) { + printf("bad VBT sig, exiting\n"); + return 1; + } printf("VBT vers: %d.%d\n", vbt->version / 100, vbt->version % 100); bdb_off = vbt_off + vbt->bdb_offset; bdb = (struct bdb_header *)(pI830->VBIOS + bdb_off); printf("BDB sig: %16s\n", bdb->signature); printf("BDB vers: %d.%d\n", bdb->version / 100, bdb->version % 100); - 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; - uint8_t *timing_ptr; - - id = INTEL_BIOS_8(start); - block_size = INTEL_BIOS_16(start + 1) + 3; - printf("BDB block type %03d size %d\n", id, block_size); - switch (id) { - case 40: - lvds1 = (struct lvds_bdb_1 *)(pI830->VBIOS + start); - panel_type = lvds1->panel_type; - printf("Panel type: %d, caps %04x\n", panel_type, lvds1->caps); - break; - case 41: - if (panel_type == -1) { - printf("Found panel block with no panel type\n"); - break; - } - - lvds2 = (struct lvds_bdb_2 *)(pI830->VBIOS + start); - - printf("Entries per table: %d\n", lvds2->table_size); - for (i = 0; i < 16; i++) { - char marker; - fpparam = (struct lvds_bdb_2_fp_params *)(pI830->VBIOS + - bdb_off + lvds2->panels[i].fp_params_offset); - fptiming = (struct lvds_bdb_2_fp_edid_dtd *)(pI830->VBIOS + - bdb_off + lvds2->panels[i].fp_edid_dtd_offset); - timing_ptr = pI830->VBIOS + bdb_off + - lvds2->panels[i].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 *)(pI830->VBIOS + - bdb_off + lvds2->panels[i].fp_params_offset + 8); - fptiming = (struct lvds_bdb_2_fp_edid_dtd *)(pI830->VBIOS + - bdb_off + lvds2->panels[i].fp_edid_dtd_offset + 8); - timing_ptr = pI830->VBIOS + bdb_off + - lvds2->panels[i].fp_edid_dtd_offset + 8; - - if (fpparam->terminator != 0xffff) - continue; - } - if (i == panel_type) - marker = '*'; - else - marker = ' '; - printf("%c Panel index %02i xres %d yres %d clock %d\n", marker, - i, fpparam->x_res, fpparam->y_res, - _PIXEL_CLOCK(timing_ptr)); - printf(" %d %d %d %d %d %d %d %d\n", - _H_ACTIVE(timing_ptr), _H_BLANK(timing_ptr), - _H_SYNC_OFF(timing_ptr), _H_SYNC_WIDTH(timing_ptr), - _V_ACTIVE(timing_ptr), _V_BLANK(timing_ptr), - _V_SYNC_OFF(timing_ptr), _V_SYNC_WIDTH(timing_ptr)); - } - - printf("Panel of size %dx%d\n", fpparam->x_res, fpparam->y_res); - break; - } - } + + dump_general_features(find_section(bdb, BDB_GENERAL_FEATURES)); + dump_general_definitions(find_section(bdb, BDB_GENERAL_DEFINITIONS)); + dump_lvds_options(find_section(bdb, BDB_LVDS_OPTIONS)); + dump_lvds_data(find_section(bdb, BDB_LVDS_LFP_DATA), bdb); return 0; } diff --git a/src/i830.h b/src/i830.h index 570db13..68690f7 100644 --- a/src/i830.h +++ b/src/i830.h @@ -554,6 +554,10 @@ typedef struct _I830Rec { OptionInfoPtr Options; Bool lvds_24_bit_mode; + Bool lvds_use_ssc; + int lvds_ssc_freq; /* in MHz */ + + Bool tv_present; /* TV connector present (from VBIOS) */ Bool StolenOnly; diff --git a/src/i830_bios.c b/src/i830_bios.c index 57ee278..e99c1b5 100644 --- a/src/i830_bios.c +++ b/src/i830_bios.c @@ -70,6 +70,32 @@ i830DumpBIOSToFile(ScrnInfoPtr pScrn, unsigned char *bios) fclose(f); } +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; +} + /** * Loads the Video BIOS and checks that the VBT exists. * @@ -127,6 +153,70 @@ i830_bios_get (ScrnInfoPtr pScrn) return bios; } +void +i830_bios_get_ssc(ScrnInfoPtr pScrn) +{ + I830Ptr pI830 = I830PTR(pScrn); + struct vbt_header *vbt; + struct bdb_header *bdb; + struct bdb_general_features *bdb_features; + int vbt_off, bdb_off; + unsigned char *bios; + + bios = i830_bios_get(pScrn); + + if (bios == NULL) + return; + + 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); + + bdb_features = find_section(bdb, BDB_GENERAL_FEATURES); + if (!bdb_features) + return; + + pI830->lvds_use_ssc = bdb_features->enable_ssc; + if (pI830->lvds_use_ssc) { + if (IS_I855(pI830)) + pI830->lvds_ssc_freq = bdb_features->ssc_freq ? 66 : 48; + else + pI830->lvds_ssc_freq = bdb_features->ssc_freq ? 100 : 96; + } + + xfree(bios); +} + +void +i830_bios_get_tv(ScrnInfoPtr pScrn) +{ + I830Ptr pI830 = I830PTR(pScrn); + struct vbt_header *vbt; + struct bdb_header *bdb; + struct bdb_general_features *bdb_features; + int vbt_off, bdb_off; + unsigned char *bios; + + bios = i830_bios_get(pScrn); + + if (bios == NULL) + return; + + 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); + + bdb_features = find_section(bdb, BDB_GENERAL_FEATURES); + if (!bdb_features) + return; + + pI830->tv_present = bdb_features->int_tv_support; + + xfree(bios); +} +#if 0 /** * Returns the BIOS's fixed panel mode. * @@ -297,3 +387,4 @@ i830_bios_get_aim_data_block (ScrnInfoPtr pScrn, int aim, int data_block) free (bios); return NULL; } +#endif diff --git a/src/i830_bios.h b/src/i830_bios.h index 95230f5..db9dafa 100644 --- a/src/i830_bios.h +++ b/src/i830_bios.h @@ -49,6 +49,97 @@ struct bdb_header { uint16_t bdb_size; /**< in bytes */ } __attribute__((packed)); +/* + * There are several types of BIOS data blocks (BDBs), each block has + * an ID and size in the first 3 bytes (ID in first, size in next 2). + * Known types are listed below. + */ +#define BDB_GENERAL_FEATURES 1 +#define BDB_GENERAL_DEFINITIONS 2 +#define BDB_OLD_TOGGLE_LIST 3 +#define BDB_MODE_SUPPORT_LIST 4 +#define BDB_GENERIC_MODE_TABLE 5 +#define BDB_EXT_MMIO_REGS 6 +#define BDB_SWF_IO 7 +#define BDB_SWF_MMIO 8 +#define BDB_DOT_CLOCK_TABLE 9 +#define BDB_MODE_REMOVAL_TABLE 10 +#define BDB_CHILD_DEVICE_TABLE 11 +#define BDB_DRIVER_FEATURES 12 +#define BDB_DRIVER_PERSISTENCE 13 +#define BDB_EXT_TABLE_PTRS 14 +#define BDB_DOT_CLOCK_OVERRIDE 15 +#define BDB_DISPLAY_SELECT 16 +/* 17 rsvd */ +#define BDB_DRIVER_ROTATION 18 +#define BDB_DISPLAY_REMOVE 19 +#define BDB_OEM_CUSTOM 20 +#define BDB_EFP_LIST 21 /* workarounds for VGA hsync/vsync */ +#define BDB_SDVO_LVDS_OPTIONS 22 +#define BDB_SDVO_PANEL_DTDS 23 +#define BDB_SDVO_LVDS_PNP_IDS 24 +#define BDB_SDVO_LVDS_POWER_SEQ 25 +#define BDB_TV_OPTIONS 26 +#define BDB_LVDS_OPTIONS 40 +#define BDB_LVDS_LFP_DATA_PTRS 41 +#define BDB_LVDS_LFP_DATA 42 +#define BDB_LVDS_BACKLIGHT 43 +#define BDB_LVDS_POWER 44 +#define BDB_SKIP 254 /* VBIOS private block, ignore */ + +struct bdb_general_features { + /* bits 1 */ + unsigned char panel_fitting:2; + unsigned char flexaim:1; + unsigned char msg_enable:1; + unsigned char clear_screen:3; + unsigned char color_flip:1; + + /* bits 2 */ + unsigned char download_ext_vbt:1; + unsigned char enable_ssc:1; + unsigned char ssc_freq:1; + unsigned char enable_lfp_on_override:1; + unsigned char disable_ssc_ddt:1; + unsigned char rsvd8:3; /* finish byte */ + + /* bits 3 */ + unsigned char disable_smooth_vision:1; + unsigned char single_dvi:1; + unsigned char rsvd9:6; /* finish byte */ + + /* bits 4 */ + unsigned char legacy_monitor_detect; + + /* bits 5 */ + unsigned char int_crt_support:1; + unsigned char int_tv_support:1; + unsigned char rsvd11:6; /* finish byte */ +} __attribute__((packed)); + +struct bdb_general_definitions { + /* DDC GPIO */ + unsigned char crt_ddc_gmbus_pin; + + /* DPMS bits */ + unsigned char dpms_acpi:1; + unsigned char skip_boot_crt_detect:1; + unsigned char dpms_aim:1; + unsigned char rsvd1:5; /* finish byte */ + + /* boot device bits */ + unsigned char boot_display[2]; + unsigned char child_dev_size; + + /* device info */ + unsigned char tv_or_lvds_info[33]; + unsigned char dev1[33]; + unsigned char dev2[33]; + unsigned char dev3[33]; + unsigned char dev4[33]; + /* may be another device block here on some platforms */ +}; + #define LVDS_CAP_EDID (1 << 6) #define LVDS_CAP_DITHER (1 << 5) #define LVDS_CAP_PFIT_AUTO_RATIO (1 << 4) @@ -56,15 +147,22 @@ struct bdb_header { #define LVDS_CAP_PFIT_TEXT_MODE (1 << 2) #define LVDS_CAP_PFIT_GRAPHICS (1 << 1) #define LVDS_CAP_PFIT_TEXT (1 << 0) -struct lvds_bdb_1 { - uint8_t id; /**< 40 */ - uint16_t size; + +struct bdb_lvds_options { uint8_t panel_type; - uint8_t reserved0; - uint16_t caps; + uint8_t rsvd1; + /* LVDS capabilities, stored in a dword */ + uint8_t rsvd2:1; + uint8_t lvds_edid:1; + uint8_t pixel_dither:1; + uint8_t pfit_ratio_auto:1; + uint8_t pfit_gfx_mode_enhanced:1; + uint8_t pfit_text_mode_enhanced:1; + uint8_t pfit_mode:2; + uint8_t rsvd4; } __attribute__((packed)); -struct lvds_bdb_2_fp_params { +struct lvds_fp_timing { uint16_t x_res; uint16_t y_res; uint32_t lvds_reg; @@ -80,7 +178,7 @@ struct lvds_bdb_2_fp_params { uint16_t terminator; } __attribute__((packed)); -struct lvds_bdb_2_fp_edid_dtd { +struct lvds_dvo_timing { uint16_t dclk; /**< In 10khz */ uint8_t hactive; uint8_t hblank; @@ -102,20 +200,37 @@ struct lvds_bdb_2_fp_edid_dtd { #define FP_EDID_FLAG_HSYNC_POSITIVE (1 << 1) } __attribute__((packed)); -struct lvds_bdb_2_entry { - uint16_t fp_params_offset; /**< From beginning of BDB */ - uint8_t fp_params_size; - uint16_t fp_edid_dtd_offset; - uint8_t fp_edid_dtd_size; - uint16_t fp_edid_pid_offset; - uint8_t fp_edid_pid_size; +struct lvds_pnp_id { + uint16_t mfg_name; + uint16_t product_code; + uint32_t serial; + uint8_t mfg_week; + uint8_t mfg_year; +} __attribute__((packed));; + +/* LFP pointer table contains entries to the struct below */ +struct bdb_lvds_lfp_data_ptr { + uint16_t fp_timing_offset; /* offsets are from start of bdb */ + uint8_t fp_table_size; + uint16_t dvo_timing_offset; + uint8_t dvo_table_size; + uint16_t panel_pnp_id_offset; + uint8_t pnp_table_size; +} __attribute__((packed)); + +struct bdb_lvds_lfp_data_ptrs { + uint8_t lvds_entries; + struct bdb_lvds_lfp_data_ptr ptr[16]; +} __attribute__((packed)); + +struct bdb_lvds_lfp_data_entry { + struct lvds_fp_timing fp_timing; + struct lvds_dvo_timing dvo_timing; + struct lvds_pnp_id pnp_id; } __attribute__((packed)); -struct lvds_bdb_2 { - uint8_t id; /**< 41 */ - uint16_t size; - uint8_t table_size; /* not sure on this one */ - struct lvds_bdb_2_entry panels[16]; +struct bdb_lvds_lfp_data { + struct bdb_lvds_lfp_data_entry data[16]; } __attribute__((packed)); struct aimdb_header { @@ -150,6 +265,8 @@ struct vch_bdb_22 { unsigned char * i830_bios_get (ScrnInfoPtr pScrn); +void i830_bios_get_ssc(ScrnInfoPtr pScrn); +void i830_bios_get_tv(ScrnInfoPtr pScrn); DisplayModePtr i830_bios_get_panel_mode(ScrnInfoPtr pScrn, Bool *wants_dither); unsigned char * diff --git a/src/i830_display.c b/src/i830_display.c index abe9875..5844a75 100644 --- a/src/i830_display.c +++ b/src/i830_display.c @@ -1114,7 +1114,7 @@ i830_crtc_mode_set(xf86CrtcPtr crtc, DisplayModePtr mode, int dsppos_reg = (plane == 0) ? DSPAPOS : DSPBPOS; int dspsize_reg = (plane == 0) ? DSPASIZE : DSPBSIZE; int pipestat_reg = (pipe == 0) ? PIPEASTAT : PIPEBSTAT; - int i; + int i, num_outputs = 0; int refclk; intel_clock_t clock; uint32_t dpll = 0, fp = 0, dspcntr, pipeconf, lvds_bits = 0; @@ -1154,9 +1154,19 @@ i830_crtc_mode_set(xf86CrtcPtr crtc, DisplayModePtr mode, is_crt = TRUE; break; } + + num_outputs++; } - if (IS_I9XX(pI830)) { + if (num_outputs > 1) + xf86DrvMsg(pScrn->scrnIndex, X_INFO, "clone detected, disabling SSC\n"); + + /* Don't use SSC when cloned */ + if (pI830->lvds_use_ssc && num_outputs < 2) { + refclk = pI830->lvds_ssc_freq * 1000; + xf86DrvMsg(pScrn->scrnIndex, X_INFO, + "using SSC reference clock of %d MHz\n", refclk / 1000); + } else if (IS_I9XX(pI830)) { refclk = 96000; } else { refclk = 48000; @@ -1232,10 +1242,8 @@ i830_crtc_mode_set(xf86CrtcPtr crtc, DisplayModePtr mode, /* dpll |= PLL_REF_INPUT_TVCLKINBC; */ dpll |= 3; } -#if 0 - else if (is_lvds) + else if (is_lvds && pI830->lvds_use_ssc && num_outputs < 2) dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; -#endif else dpll |= PLL_REF_INPUT_DREFCLK; diff --git a/src/i830_lvds.c b/src/i830_lvds.c index b5e0c45..9880b18 100644 --- a/src/i830_lvds.c +++ b/src/i830_lvds.c @@ -1341,6 +1341,9 @@ i830_lvds_init(ScrnInfoPtr pScrn) goto disable_exit; } + /* Update pI830 w/SSC info, if any */ + i830_bios_get_ssc(pScrn); + skip_panel_fixed_mode_setup: /* Blacklist machines with BIOSes that list an LVDS panel without actually diff --git a/src/i830_tv.c b/src/i830_tv.c index cde929a..651f77b 100644 --- a/src/i830_tv.c +++ b/src/i830_tv.c @@ -1715,6 +1715,10 @@ i830_tv_init(ScrnInfoPtr pScrn) (tv_dac_off & TVDAC_STATE_CHG_EN) != 0) return; + i830_bios_get_tv(pScrn); + if (!pI830->tv_present) /* VBIOS claims no TV connector */ + return; + output = xf86OutputCreate (pScrn, &i830_tv_output_funcs, "TV"); if (!output)