summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/i915/display/intel_bios.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/i915/display/intel_bios.c')
-rw-r--r--drivers/gpu/drm/i915/display/intel_bios.c828
1 files changed, 647 insertions, 181 deletions
diff --git a/drivers/gpu/drm/i915/display/intel_bios.c b/drivers/gpu/drm/i915/display/intel_bios.c
index 40b5e7ed12c2..0c5638f5b72b 100644
--- a/drivers/gpu/drm/i915/display/intel_bios.c
+++ b/drivers/gpu/drm/i915/display/intel_bios.c
@@ -25,7 +25,8 @@
*
*/
-#include <drm/dp/drm_dp_helper.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_dsc_helper.h>
#include "display/intel_display.h"
#include "display/intel_display_types.h"
@@ -88,7 +89,7 @@ static u32 get_blocksize(const void *block_data)
}
static const void *
-find_section(const void *_bdb, enum bdb_block_id section_id)
+find_raw_section(const void *_bdb, enum bdb_block_id section_id)
{
const struct bdb_header *bdb = _bdb;
const u8 *base = _bdb;
@@ -118,6 +119,406 @@ find_section(const void *_bdb, enum bdb_block_id section_id)
return NULL;
}
+/*
+ * Offset from the start of BDB to the start of the
+ * block data (just past the block header).
+ */
+static u32 block_offset(const void *bdb, enum bdb_block_id section_id)
+{
+ const void *block;
+
+ block = find_raw_section(bdb, section_id);
+ if (!block)
+ return 0;
+
+ return block - bdb;
+}
+
+/* size of the block excluding the header */
+static u32 block_size(const void *bdb, enum bdb_block_id section_id)
+{
+ const void *block;
+
+ block = find_raw_section(bdb, section_id);
+ if (!block)
+ return 0;
+
+ return get_blocksize(block);
+}
+
+struct bdb_block_entry {
+ struct list_head node;
+ enum bdb_block_id section_id;
+ u8 data[];
+};
+
+static const void *
+find_section(struct drm_i915_private *i915,
+ enum bdb_block_id section_id)
+{
+ struct bdb_block_entry *entry;
+
+ list_for_each_entry(entry, &i915->vbt.bdb_blocks, node) {
+ if (entry->section_id == section_id)
+ return entry->data + 3;
+ }
+
+ return NULL;
+}
+
+static const struct {
+ enum bdb_block_id section_id;
+ size_t min_size;
+} bdb_blocks[] = {
+ { .section_id = BDB_GENERAL_FEATURES,
+ .min_size = sizeof(struct bdb_general_features), },
+ { .section_id = BDB_GENERAL_DEFINITIONS,
+ .min_size = sizeof(struct bdb_general_definitions), },
+ { .section_id = BDB_PSR,
+ .min_size = sizeof(struct bdb_psr), },
+ { .section_id = BDB_DRIVER_FEATURES,
+ .min_size = sizeof(struct bdb_driver_features), },
+ { .section_id = BDB_SDVO_LVDS_OPTIONS,
+ .min_size = sizeof(struct bdb_sdvo_lvds_options), },
+ { .section_id = BDB_SDVO_PANEL_DTDS,
+ .min_size = sizeof(struct bdb_sdvo_panel_dtds), },
+ { .section_id = BDB_EDP,
+ .min_size = sizeof(struct bdb_edp), },
+ { .section_id = BDB_LVDS_OPTIONS,
+ .min_size = sizeof(struct bdb_lvds_options), },
+ /*
+ * BDB_LVDS_LFP_DATA depends on BDB_LVDS_LFP_DATA_PTRS,
+ * so keep the two ordered.
+ */
+ { .section_id = BDB_LVDS_LFP_DATA_PTRS,
+ .min_size = sizeof(struct bdb_lvds_lfp_data_ptrs), },
+ { .section_id = BDB_LVDS_LFP_DATA,
+ .min_size = 0, /* special case */ },
+ { .section_id = BDB_LVDS_BACKLIGHT,
+ .min_size = sizeof(struct bdb_lfp_backlight_data), },
+ { .section_id = BDB_LFP_POWER,
+ .min_size = sizeof(struct bdb_lfp_power), },
+ { .section_id = BDB_MIPI_CONFIG,
+ .min_size = sizeof(struct bdb_mipi_config), },
+ { .section_id = BDB_MIPI_SEQUENCE,
+ .min_size = sizeof(struct bdb_mipi_sequence) },
+ { .section_id = BDB_COMPRESSION_PARAMETERS,
+ .min_size = sizeof(struct bdb_compression_parameters), },
+ { .section_id = BDB_GENERIC_DTD,
+ .min_size = sizeof(struct bdb_generic_dtd), },
+};
+
+static size_t lfp_data_min_size(struct drm_i915_private *i915)
+{
+ const struct bdb_lvds_lfp_data_ptrs *ptrs;
+ size_t size;
+
+ ptrs = find_section(i915, BDB_LVDS_LFP_DATA_PTRS);
+ if (!ptrs)
+ return 0;
+
+ size = sizeof(struct bdb_lvds_lfp_data);
+ if (ptrs->panel_name.table_size)
+ size = max(size, ptrs->panel_name.offset +
+ sizeof(struct bdb_lvds_lfp_data_tail));
+
+ return size;
+}
+
+static bool validate_lfp_data_ptrs(const void *bdb,
+ const struct bdb_lvds_lfp_data_ptrs *ptrs)
+{
+ int fp_timing_size, dvo_timing_size, panel_pnp_id_size, panel_name_size;
+ int data_block_size, lfp_data_size;
+ int i;
+
+ data_block_size = block_size(bdb, BDB_LVDS_LFP_DATA);
+ if (data_block_size == 0)
+ return false;
+
+ /* always 3 indicating the presence of fp_timing+dvo_timing+panel_pnp_id */
+ if (ptrs->lvds_entries != 3)
+ return false;
+
+ fp_timing_size = ptrs->ptr[0].fp_timing.table_size;
+ dvo_timing_size = ptrs->ptr[0].dvo_timing.table_size;
+ panel_pnp_id_size = ptrs->ptr[0].panel_pnp_id.table_size;
+ panel_name_size = ptrs->panel_name.table_size;
+
+ /* fp_timing has variable size */
+ if (fp_timing_size < 32 ||
+ dvo_timing_size != sizeof(struct lvds_dvo_timing) ||
+ panel_pnp_id_size != sizeof(struct lvds_pnp_id))
+ return false;
+
+ /* panel_name is not present in old VBTs */
+ if (panel_name_size != 0 &&
+ panel_name_size != sizeof(struct lvds_lfp_panel_name))
+ return false;
+
+ lfp_data_size = ptrs->ptr[1].fp_timing.offset - ptrs->ptr[0].fp_timing.offset;
+ if (16 * lfp_data_size > data_block_size)
+ return false;
+
+ /*
+ * Except for vlv/chv machines all real VBTs seem to have 6
+ * unaccounted bytes in the fp_timing table. And it doesn't
+ * appear to be a really intentional hole as the fp_timing
+ * 0xffff terminator is always within those 6 missing bytes.
+ */
+ if (fp_timing_size + dvo_timing_size + panel_pnp_id_size != lfp_data_size &&
+ fp_timing_size + 6 + dvo_timing_size + panel_pnp_id_size != lfp_data_size)
+ return false;
+
+ if (ptrs->ptr[0].fp_timing.offset + fp_timing_size > ptrs->ptr[0].dvo_timing.offset ||
+ ptrs->ptr[0].dvo_timing.offset + dvo_timing_size != ptrs->ptr[0].panel_pnp_id.offset ||
+ ptrs->ptr[0].panel_pnp_id.offset + panel_pnp_id_size != lfp_data_size)
+ return false;
+
+ /* make sure the table entries have uniform size */
+ for (i = 1; i < 16; i++) {
+ if (ptrs->ptr[i].fp_timing.table_size != fp_timing_size ||
+ ptrs->ptr[i].dvo_timing.table_size != dvo_timing_size ||
+ ptrs->ptr[i].panel_pnp_id.table_size != panel_pnp_id_size)
+ return false;
+
+ if (ptrs->ptr[i].fp_timing.offset - ptrs->ptr[i-1].fp_timing.offset != lfp_data_size ||
+ ptrs->ptr[i].dvo_timing.offset - ptrs->ptr[i-1].dvo_timing.offset != lfp_data_size ||
+ ptrs->ptr[i].panel_pnp_id.offset - ptrs->ptr[i-1].panel_pnp_id.offset != lfp_data_size)
+ return false;
+ }
+
+ /* make sure the tables fit inside the data block */
+ for (i = 0; i < 16; i++) {
+ if (ptrs->ptr[i].fp_timing.offset + fp_timing_size > data_block_size ||
+ ptrs->ptr[i].dvo_timing.offset + dvo_timing_size > data_block_size ||
+ ptrs->ptr[i].panel_pnp_id.offset + panel_pnp_id_size > data_block_size)
+ return false;
+ }
+
+ if (ptrs->panel_name.offset + 16 * panel_name_size > data_block_size)
+ return false;
+
+ return true;
+}
+
+/* make the data table offsets relative to the data block */
+static bool fixup_lfp_data_ptrs(const void *bdb, void *ptrs_block)
+{
+ struct bdb_lvds_lfp_data_ptrs *ptrs = ptrs_block;
+ u32 offset;
+ int i;
+
+ offset = block_offset(bdb, BDB_LVDS_LFP_DATA);
+
+ for (i = 0; i < 16; i++) {
+ if (ptrs->ptr[i].fp_timing.offset < offset ||
+ ptrs->ptr[i].dvo_timing.offset < offset ||
+ ptrs->ptr[i].panel_pnp_id.offset < offset)
+ return false;
+
+ ptrs->ptr[i].fp_timing.offset -= offset;
+ ptrs->ptr[i].dvo_timing.offset -= offset;
+ ptrs->ptr[i].panel_pnp_id.offset -= offset;
+ }
+
+ if (ptrs->panel_name.table_size) {
+ if (ptrs->panel_name.offset < offset)
+ return false;
+
+ ptrs->panel_name.offset -= offset;
+ }
+
+ return validate_lfp_data_ptrs(bdb, ptrs);
+}
+
+static const void *find_fp_timing_terminator(const u8 *data, int size)
+{
+ int i;
+
+ for (i = 0; i < size - 1; i++) {
+ if (data[i] == 0xff && data[i+1] == 0xff)
+ return &data[i];
+ }
+
+ return NULL;
+}
+
+static int make_lfp_data_ptr(struct lvds_lfp_data_ptr_table *table,
+ int table_size, int total_size)
+{
+ if (total_size < table_size)
+ return total_size;
+
+ table->table_size = table_size;
+ table->offset = total_size - table_size;
+
+ return total_size - table_size;
+}
+
+static void next_lfp_data_ptr(struct lvds_lfp_data_ptr_table *next,
+ const struct lvds_lfp_data_ptr_table *prev,
+ int size)
+{
+ next->table_size = prev->table_size;
+ next->offset = prev->offset + size;
+}
+
+static void *generate_lfp_data_ptrs(struct drm_i915_private *i915,
+ const void *bdb)
+{
+ int i, size, table_size, block_size, offset;
+ const void *t0, *t1, *block;
+ struct bdb_lvds_lfp_data_ptrs *ptrs;
+ void *ptrs_block;
+
+ block = find_raw_section(bdb, BDB_LVDS_LFP_DATA);
+ if (!block)
+ return NULL;
+
+ drm_dbg_kms(&i915->drm, "Generating LFP data table pointers\n");
+
+ block_size = get_blocksize(block);
+
+ size = block_size;
+ t0 = find_fp_timing_terminator(block, size);
+ if (!t0)
+ return NULL;
+
+ size -= t0 - block - 2;
+ t1 = find_fp_timing_terminator(t0 + 2, size);
+ if (!t1)
+ return NULL;
+
+ size = t1 - t0;
+ if (size * 16 > block_size)
+ return NULL;
+
+ ptrs_block = kzalloc(sizeof(*ptrs) + 3, GFP_KERNEL);
+ if (!ptrs_block)
+ return NULL;
+
+ *(u8 *)(ptrs_block + 0) = BDB_LVDS_LFP_DATA_PTRS;
+ *(u16 *)(ptrs_block + 1) = sizeof(*ptrs);
+ ptrs = ptrs_block + 3;
+
+ table_size = sizeof(struct lvds_pnp_id);
+ size = make_lfp_data_ptr(&ptrs->ptr[0].panel_pnp_id, table_size, size);
+
+ table_size = sizeof(struct lvds_dvo_timing);
+ size = make_lfp_data_ptr(&ptrs->ptr[0].dvo_timing, table_size, size);
+
+ table_size = t0 - block + 2;
+ size = make_lfp_data_ptr(&ptrs->ptr[0].fp_timing, table_size, size);
+
+ if (ptrs->ptr[0].fp_timing.table_size)
+ ptrs->lvds_entries++;
+ if (ptrs->ptr[0].dvo_timing.table_size)
+ ptrs->lvds_entries++;
+ if (ptrs->ptr[0].panel_pnp_id.table_size)
+ ptrs->lvds_entries++;
+
+ if (size != 0 || ptrs->lvds_entries != 3) {
+ kfree(ptrs);
+ return NULL;
+ }
+
+ size = t1 - t0;
+ for (i = 1; i < 16; i++) {
+ next_lfp_data_ptr(&ptrs->ptr[i].fp_timing, &ptrs->ptr[i-1].fp_timing, size);
+ next_lfp_data_ptr(&ptrs->ptr[i].dvo_timing, &ptrs->ptr[i-1].dvo_timing, size);
+ next_lfp_data_ptr(&ptrs->ptr[i].panel_pnp_id, &ptrs->ptr[i-1].panel_pnp_id, size);
+ }
+
+ size = t1 - t0;
+ table_size = sizeof(struct lvds_lfp_panel_name);
+
+ if (16 * (size + table_size) <= block_size) {
+ ptrs->panel_name.table_size = table_size;
+ ptrs->panel_name.offset = size * 16;
+ }
+
+ offset = block - bdb;
+
+ for (i = 0; i < 16; i++) {
+ ptrs->ptr[i].fp_timing.offset += offset;
+ ptrs->ptr[i].dvo_timing.offset += offset;
+ ptrs->ptr[i].panel_pnp_id.offset += offset;
+ }
+
+ if (ptrs->panel_name.table_size)
+ ptrs->panel_name.offset += offset;
+
+ return ptrs_block;
+}
+
+static void
+init_bdb_block(struct drm_i915_private *i915,
+ const void *bdb, enum bdb_block_id section_id,
+ size_t min_size)
+{
+ struct bdb_block_entry *entry;
+ void *temp_block = NULL;
+ const void *block;
+ size_t block_size;
+
+ block = find_raw_section(bdb, section_id);
+
+ /* Modern VBTs lack the LFP data table pointers block, make one up */
+ if (!block && section_id == BDB_LVDS_LFP_DATA_PTRS) {
+ temp_block = generate_lfp_data_ptrs(i915, bdb);
+ if (temp_block)
+ block = temp_block + 3;
+ }
+ if (!block)
+ return;
+
+ drm_WARN(&i915->drm, min_size == 0,
+ "Block %d min_size is zero\n", section_id);
+
+ block_size = get_blocksize(block);
+
+ entry = kzalloc(struct_size(entry, data, max(min_size, block_size) + 3),
+ GFP_KERNEL);
+ if (!entry) {
+ kfree(temp_block);
+ return;
+ }
+
+ entry->section_id = section_id;
+ memcpy(entry->data, block - 3, block_size + 3);
+
+ kfree(temp_block);
+
+ drm_dbg_kms(&i915->drm, "Found BDB block %d (size %zu, min size %zu)\n",
+ section_id, block_size, min_size);
+
+ if (section_id == BDB_LVDS_LFP_DATA_PTRS &&
+ !fixup_lfp_data_ptrs(bdb, entry->data + 3)) {
+ drm_err(&i915->drm, "VBT has malformed LFP data table pointers\n");
+ kfree(entry);
+ return;
+ }
+
+ list_add_tail(&entry->node, &i915->vbt.bdb_blocks);
+}
+
+static void init_bdb_blocks(struct drm_i915_private *i915,
+ const void *bdb)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bdb_blocks); i++) {
+ enum bdb_block_id section_id = bdb_blocks[i].section_id;
+ size_t min_size = bdb_blocks[i].min_size;
+
+ if (section_id == BDB_LVDS_LFP_DATA)
+ min_size = lfp_data_min_size(i915);
+
+ init_bdb_block(i915, bdb, section_id, min_size);
+ }
+}
+
static void
fill_detail_timing_data(struct drm_display_mode *panel_fixed_mode,
const struct lvds_dvo_timing *dvo_timing)
@@ -169,82 +570,124 @@ fill_detail_timing_data(struct drm_display_mode *panel_fixed_mode,
}
static const struct lvds_dvo_timing *
-get_lvds_dvo_timing(const struct bdb_lvds_lfp_data *lvds_lfp_data,
- const struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs,
+get_lvds_dvo_timing(const struct bdb_lvds_lfp_data *data,
+ const struct bdb_lvds_lfp_data_ptrs *ptrs,
int index)
{
- /*
- * the size of fp_timing varies on the different platform.
- * So calculate the DVO timing relative offset in LVDS data
- * entry to get the DVO timing entry
- */
-
- int lfp_data_size =
- lvds_lfp_data_ptrs->ptr[1].dvo_timing_offset -
- lvds_lfp_data_ptrs->ptr[0].dvo_timing_offset;
- int dvo_timing_offset =
- lvds_lfp_data_ptrs->ptr[0].dvo_timing_offset -
- lvds_lfp_data_ptrs->ptr[0].fp_timing_offset;
- char *entry = (char *)lvds_lfp_data->data + lfp_data_size * index;
-
- return (struct lvds_dvo_timing *)(entry + dvo_timing_offset);
+ return (const void *)data + ptrs->ptr[index].dvo_timing.offset;
}
-/* get lvds_fp_timing entry
- * this function may return NULL if the corresponding entry is invalid
- */
static const struct lvds_fp_timing *
-get_lvds_fp_timing(const struct bdb_header *bdb,
- const struct bdb_lvds_lfp_data *data,
+get_lvds_fp_timing(const struct bdb_lvds_lfp_data *data,
const struct bdb_lvds_lfp_data_ptrs *ptrs,
int index)
{
- size_t data_ofs = (const u8 *)data - (const u8 *)bdb;
- u16 data_size = ((const u16 *)data)[-1]; /* stored in header */
- size_t ofs;
+ return (const void *)data + ptrs->ptr[index].fp_timing.offset;
+}
- if (index >= ARRAY_SIZE(ptrs->ptr))
- return NULL;
- ofs = ptrs->ptr[index].fp_timing_offset;
- if (ofs < data_ofs ||
- ofs + sizeof(struct lvds_fp_timing) > data_ofs + data_size)
+static const struct bdb_lvds_lfp_data_tail *
+get_lfp_data_tail(const struct bdb_lvds_lfp_data *data,
+ const struct bdb_lvds_lfp_data_ptrs *ptrs)
+{
+ if (ptrs->panel_name.table_size)
+ return (const void *)data + ptrs->panel_name.offset;
+ else
return NULL;
- return (const struct lvds_fp_timing *)((const u8 *)bdb + ofs);
+}
+
+static int opregion_get_panel_type(struct drm_i915_private *i915)
+{
+ return intel_opregion_get_panel_type(i915);
+}
+
+static int vbt_get_panel_type(struct drm_i915_private *i915)
+{
+ const struct bdb_lvds_options *lvds_options;
+
+ lvds_options = find_section(i915, BDB_LVDS_OPTIONS);
+ if (!lvds_options)
+ return -1;
+
+ if (lvds_options->panel_type > 0xf) {
+ drm_dbg_kms(&i915->drm, "Invalid VBT panel type 0x%x\n",
+ lvds_options->panel_type);
+ return -1;
+ }
+
+ return lvds_options->panel_type;
+}
+
+static int fallback_get_panel_type(struct drm_i915_private *i915)
+{
+ return 0;
+}
+
+enum panel_type {
+ PANEL_TYPE_OPREGION,
+ PANEL_TYPE_VBT,
+ PANEL_TYPE_FALLBACK,
+};
+
+static int get_panel_type(struct drm_i915_private *i915)
+{
+ struct {
+ const char *name;
+ int (*get_panel_type)(struct drm_i915_private *i915);
+ int panel_type;
+ } panel_types[] = {
+ [PANEL_TYPE_OPREGION] = {
+ .name = "OpRegion",
+ .get_panel_type = opregion_get_panel_type,
+ },
+ [PANEL_TYPE_VBT] = {
+ .name = "VBT",
+ .get_panel_type = vbt_get_panel_type,
+ },
+ [PANEL_TYPE_FALLBACK] = {
+ .name = "fallback",
+ .get_panel_type = fallback_get_panel_type,
+ },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(panel_types); i++) {
+ panel_types[i].panel_type = panel_types[i].get_panel_type(i915);
+
+ drm_WARN_ON(&i915->drm, panel_types[i].panel_type > 0xf);
+
+ if (panel_types[i].panel_type >= 0)
+ drm_dbg_kms(&i915->drm, "Panel type (%s): %d\n",
+ panel_types[i].name, panel_types[i].panel_type);
+ }
+
+ if (panel_types[PANEL_TYPE_OPREGION].panel_type >= 0)
+ i = PANEL_TYPE_OPREGION;
+ else if (panel_types[PANEL_TYPE_VBT].panel_type >= 0)
+ i = PANEL_TYPE_VBT;
+ else
+ i = PANEL_TYPE_FALLBACK;
+
+ drm_dbg_kms(&i915->drm, "Selected panel type (%s): %d\n",
+ panel_types[i].name, panel_types[i].panel_type);
+
+ return panel_types[i].panel_type;
}
/* Parse general panel options */
static void
-parse_panel_options(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_panel_options(struct drm_i915_private *i915)
{
const struct bdb_lvds_options *lvds_options;
int panel_type;
int drrs_mode;
- int ret;
- lvds_options = find_section(bdb, BDB_LVDS_OPTIONS);
+ lvds_options = find_section(i915, BDB_LVDS_OPTIONS);
if (!lvds_options)
return;
i915->vbt.lvds_dither = lvds_options->pixel_dither;
- ret = intel_opregion_get_panel_type(i915);
- if (ret >= 0) {
- drm_WARN_ON(&i915->drm, ret > 0xf);
- panel_type = ret;
- drm_dbg_kms(&i915->drm, "Panel type: %d (OpRegion)\n",
- panel_type);
- } else {
- if (lvds_options->panel_type > 0xf) {
- drm_dbg_kms(&i915->drm,
- "Invalid VBT panel type 0x%x\n",
- lvds_options->panel_type);
- return;
- }
- panel_type = lvds_options->panel_type;
- drm_dbg_kms(&i915->drm, "Panel type: %d (VBT)\n",
- panel_type);
- }
+ panel_type = get_panel_type(i915);
i915->vbt.panel_type = panel_type;
@@ -257,42 +700,32 @@ parse_panel_options(struct drm_i915_private *i915,
*/
switch (drrs_mode) {
case 0:
- i915->vbt.drrs_type = STATIC_DRRS_SUPPORT;
+ i915->vbt.drrs_type = DRRS_TYPE_STATIC;
drm_dbg_kms(&i915->drm, "DRRS supported mode is static\n");
break;
case 2:
- i915->vbt.drrs_type = SEAMLESS_DRRS_SUPPORT;
+ i915->vbt.drrs_type = DRRS_TYPE_SEAMLESS;
drm_dbg_kms(&i915->drm,
"DRRS supported mode is seamless\n");
break;
default:
- i915->vbt.drrs_type = DRRS_NOT_SUPPORTED;
+ i915->vbt.drrs_type = DRRS_TYPE_NONE;
drm_dbg_kms(&i915->drm,
"DRRS not supported (VBT input)\n");
break;
}
}
-/* Try to find integrated panel timing data */
static void
parse_lfp_panel_dtd(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+ const struct bdb_lvds_lfp_data *lvds_lfp_data,
+ const struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs)
{
- const struct bdb_lvds_lfp_data *lvds_lfp_data;
- const struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs;
const struct lvds_dvo_timing *panel_dvo_timing;
const struct lvds_fp_timing *fp_timing;
struct drm_display_mode *panel_fixed_mode;
int panel_type = i915->vbt.panel_type;
- lvds_lfp_data = find_section(bdb, BDB_LVDS_LFP_DATA);
- if (!lvds_lfp_data)
- return;
-
- lvds_lfp_data_ptrs = find_section(bdb, BDB_LVDS_LFP_DATA_PTRS);
- if (!lvds_lfp_data_ptrs)
- return;
-
panel_dvo_timing = get_lvds_dvo_timing(lvds_lfp_data,
lvds_lfp_data_ptrs,
panel_type);
@@ -306,34 +739,75 @@ parse_lfp_panel_dtd(struct drm_i915_private *i915,
i915->vbt.lfp_lvds_vbt_mode = panel_fixed_mode;
drm_dbg_kms(&i915->drm,
- "Found panel mode in BIOS VBT legacy lfp table:\n");
- drm_mode_debug_printmodeline(panel_fixed_mode);
+ "Found panel mode in BIOS VBT legacy lfp table: " DRM_MODE_FMT "\n",
+ DRM_MODE_ARG(panel_fixed_mode));
- fp_timing = get_lvds_fp_timing(bdb, lvds_lfp_data,
+ fp_timing = get_lvds_fp_timing(lvds_lfp_data,
lvds_lfp_data_ptrs,
panel_type);
- if (fp_timing) {
- /* check the resolution, just to be sure */
- if (fp_timing->x_res == panel_fixed_mode->hdisplay &&
- fp_timing->y_res == panel_fixed_mode->vdisplay) {
- i915->vbt.bios_lvds_val = fp_timing->lvds_reg_val;
- drm_dbg_kms(&i915->drm,
- "VBT initial LVDS value %x\n",
- i915->vbt.bios_lvds_val);
- }
+
+ /* check the resolution, just to be sure */
+ if (fp_timing->x_res == panel_fixed_mode->hdisplay &&
+ fp_timing->y_res == panel_fixed_mode->vdisplay) {
+ i915->vbt.bios_lvds_val = fp_timing->lvds_reg_val;
+ drm_dbg_kms(&i915->drm,
+ "VBT initial LVDS value %x\n",
+ i915->vbt.bios_lvds_val);
+ }
+}
+
+static void
+parse_lfp_data(struct drm_i915_private *i915)
+{
+ const struct bdb_lvds_lfp_data *data;
+ const struct bdb_lvds_lfp_data_tail *tail;
+ const struct bdb_lvds_lfp_data_ptrs *ptrs;
+ int panel_type = i915->vbt.panel_type;
+
+ ptrs = find_section(i915, BDB_LVDS_LFP_DATA_PTRS);
+ if (!ptrs)
+ return;
+
+ data = find_section(i915, BDB_LVDS_LFP_DATA);
+ if (!data)
+ return;
+
+ if (!i915->vbt.lfp_lvds_vbt_mode)
+ parse_lfp_panel_dtd(i915, data, ptrs);
+
+ tail = get_lfp_data_tail(data, ptrs);
+ if (!tail)
+ return;
+
+ if (i915->vbt.version >= 188) {
+ i915->vbt.seamless_drrs_min_refresh_rate =
+ tail->seamless_drrs_min_refresh_rate[panel_type];
+ drm_dbg_kms(&i915->drm,
+ "Seamless DRRS min refresh rate: %d Hz\n",
+ i915->vbt.seamless_drrs_min_refresh_rate);
}
}
static void
-parse_generic_dtd(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_generic_dtd(struct drm_i915_private *i915)
{
const struct bdb_generic_dtd *generic_dtd;
const struct generic_dtd_entry *dtd;
struct drm_display_mode *panel_fixed_mode;
int num_dtd;
- generic_dtd = find_section(bdb, BDB_GENERIC_DTD);
+ /*
+ * Older VBTs provided DTD information for internal displays through
+ * the "LFP panel tables" block (42). As of VBT revision 229 the
+ * DTD information should be provided via a newer "generic DTD"
+ * block (58). Just to be safe, we'll try the new generic DTD block
+ * first on VBT >= 229, but still fall back to trying the old LFP
+ * block if that fails.
+ */
+ if (i915->vbt.version < 229)
+ return;
+
+ generic_dtd = find_section(i915, BDB_GENERIC_DTD);
if (!generic_dtd)
return;
@@ -397,40 +871,21 @@ parse_generic_dtd(struct drm_i915_private *i915,
panel_fixed_mode->flags |= DRM_MODE_FLAG_NVSYNC;
drm_dbg_kms(&i915->drm,
- "Found panel mode in BIOS VBT generic dtd table:\n");
- drm_mode_debug_printmodeline(panel_fixed_mode);
+ "Found panel mode in BIOS VBT generic dtd table: " DRM_MODE_FMT "\n",
+ DRM_MODE_ARG(panel_fixed_mode));
i915->vbt.lfp_lvds_vbt_mode = panel_fixed_mode;
}
static void
-parse_panel_dtd(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
-{
- /*
- * Older VBTs provided provided DTD information for internal displays
- * through the "LFP panel DTD" block (42). As of VBT revision 229,
- * that block is now deprecated and DTD information should be provided
- * via a newer "generic DTD" block (58). Just to be safe, we'll
- * try the new generic DTD block first on VBT >= 229, but still fall
- * back to trying the old LFP block if that fails.
- */
- if (bdb->version >= 229)
- parse_generic_dtd(i915, bdb);
- if (!i915->vbt.lfp_lvds_vbt_mode)
- parse_lfp_panel_dtd(i915, bdb);
-}
-
-static void
-parse_lfp_backlight(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_lfp_backlight(struct drm_i915_private *i915)
{
const struct bdb_lfp_backlight_data *backlight_data;
const struct lfp_backlight_data_entry *entry;
int panel_type = i915->vbt.panel_type;
u16 level;
- backlight_data = find_section(bdb, BDB_LVDS_BACKLIGHT);
+ backlight_data = find_section(i915, BDB_LVDS_BACKLIGHT);
if (!backlight_data)
return;
@@ -452,12 +907,12 @@ parse_lfp_backlight(struct drm_i915_private *i915,
}
i915->vbt.backlight.type = INTEL_BACKLIGHT_DISPLAY_DDI;
- if (bdb->version >= 191) {
+ if (i915->vbt.version >= 191) {
size_t exp_size;
- if (bdb->version >= 236)
+ if (i915->vbt.version >= 236)
exp_size = sizeof(struct bdb_lfp_backlight_data);
- else if (bdb->version >= 234)
+ else if (i915->vbt.version >= 234)
exp_size = EXP_BDB_LFP_BL_DATA_SIZE_REV_234;
else
exp_size = EXP_BDB_LFP_BL_DATA_SIZE_REV_191;
@@ -474,14 +929,14 @@ parse_lfp_backlight(struct drm_i915_private *i915,
i915->vbt.backlight.pwm_freq_hz = entry->pwm_freq_hz;
i915->vbt.backlight.active_low_pwm = entry->active_low_pwm;
- if (bdb->version >= 234) {
+ if (i915->vbt.version >= 234) {
u16 min_level;
bool scale;
level = backlight_data->brightness_level[panel_type].level;
min_level = backlight_data->brightness_min_level[panel_type].level;
- if (bdb->version >= 236)
+ if (i915->vbt.version >= 236)
scale = backlight_data->brightness_precision_bits[panel_type] == 16;
else
scale = level > 255;
@@ -514,8 +969,7 @@ parse_lfp_backlight(struct drm_i915_private *i915,
/* Try to find sdvo panel data */
static void
-parse_sdvo_panel_data(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_sdvo_panel_data(struct drm_i915_private *i915)
{
const struct bdb_sdvo_panel_dtds *dtds;
struct drm_display_mode *panel_fixed_mode;
@@ -531,14 +985,14 @@ parse_sdvo_panel_data(struct drm_i915_private *i915,
if (index == -1) {
const struct bdb_sdvo_lvds_options *sdvo_lvds_options;
- sdvo_lvds_options = find_section(bdb, BDB_SDVO_LVDS_OPTIONS);
+ sdvo_lvds_options = find_section(i915, BDB_SDVO_LVDS_OPTIONS);
if (!sdvo_lvds_options)
return;
index = sdvo_lvds_options->panel_type;
}
- dtds = find_section(bdb, BDB_SDVO_PANEL_DTDS);
+ dtds = find_section(i915, BDB_SDVO_PANEL_DTDS);
if (!dtds)
return;
@@ -551,8 +1005,8 @@ parse_sdvo_panel_data(struct drm_i915_private *i915,
i915->vbt.sdvo_lvds_vbt_mode = panel_fixed_mode;
drm_dbg_kms(&i915->drm,
- "Found SDVO panel mode in BIOS VBT tables:\n");
- drm_mode_debug_printmodeline(panel_fixed_mode);
+ "Found SDVO panel mode in BIOS VBT tables: " DRM_MODE_FMT "\n",
+ DRM_MODE_ARG(panel_fixed_mode));
}
static int intel_bios_ssc_frequency(struct drm_i915_private *i915,
@@ -570,18 +1024,17 @@ static int intel_bios_ssc_frequency(struct drm_i915_private *i915,
}
static void
-parse_general_features(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_general_features(struct drm_i915_private *i915)
{
const struct bdb_general_features *general;
- general = find_section(bdb, BDB_GENERAL_FEATURES);
+ general = find_section(i915, BDB_GENERAL_FEATURES);
if (!general)
return;
i915->vbt.int_tv_support = general->int_tv_support;
/* int_crt_support can't be trusted on earlier platforms */
- if (bdb->version >= 155 &&
+ if (i915->vbt.version >= 155 &&
(HAS_DDI(i915) || IS_VALLEYVIEW(i915)))
i915->vbt.int_crt_support = general->int_crt_support;
i915->vbt.lvds_use_ssc = general->enable_ssc;
@@ -589,7 +1042,7 @@ parse_general_features(struct drm_i915_private *i915,
intel_bios_ssc_frequency(i915, general->ssc_freq);
i915->vbt.display_clock_mode = general->display_clock_mode;
i915->vbt.fdi_rx_polarity_inverted = general->fdi_rx_polarity_inverted;
- if (bdb->version >= 181) {
+ if (i915->vbt.version >= 181) {
i915->vbt.orientation = general->rotate_180 ?
DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP :
DRM_MODE_PANEL_ORIENTATION_NORMAL;
@@ -597,7 +1050,7 @@ parse_general_features(struct drm_i915_private *i915,
i915->vbt.orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
}
- if (bdb->version >= 249 && general->afc_startup_config) {
+ if (i915->vbt.version >= 249 && general->afc_startup_config) {
i915->vbt.override_afc_startup = true;
i915->vbt.override_afc_startup_val = general->afc_startup_config == 0x1 ? 0x0 : 0x7;
}
@@ -695,12 +1148,11 @@ parse_sdvo_device_mapping(struct drm_i915_private *i915)
}
static void
-parse_driver_features(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_driver_features(struct drm_i915_private *i915)
{
const struct bdb_driver_features *driver;
- driver = find_section(bdb, BDB_DRIVER_FEATURES);
+ driver = find_section(i915, BDB_DRIVER_FEATURES);
if (!driver)
return;
@@ -724,13 +1176,13 @@ parse_driver_features(struct drm_i915_private *i915,
* in the wild with the bits correctly populated. Version
* 108 (on i85x) does not have the bits correctly populated.
*/
- if (bdb->version >= 134 &&
+ if (i915->vbt.version >= 134 &&
driver->lvds_config != BDB_DRIVER_FEATURE_INT_LVDS &&
driver->lvds_config != BDB_DRIVER_FEATURE_INT_SDVO_LVDS)
i915->vbt.int_lvds_support = 0;
}
- if (bdb->version < 228) {
+ if (i915->vbt.version < 228) {
drm_dbg_kms(&i915->drm, "DRRS State Enabled:%d\n",
driver->drrs_enabled);
/*
@@ -740,23 +1192,22 @@ parse_driver_features(struct drm_i915_private *i915,
* driver->drrs_enabled=false
*/
if (!driver->drrs_enabled)
- i915->vbt.drrs_type = DRRS_NOT_SUPPORTED;
+ i915->vbt.drrs_type = DRRS_TYPE_NONE;
i915->vbt.psr.enable = driver->psr_enabled;
}
}
static void
-parse_power_conservation_features(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_power_conservation_features(struct drm_i915_private *i915)
{
const struct bdb_lfp_power *power;
u8 panel_type = i915->vbt.panel_type;
- if (bdb->version < 228)
+ if (i915->vbt.version < 228)
return;
- power = find_section(bdb, BDB_LFP_POWER);
+ power = find_section(i915, BDB_LFP_POWER);
if (!power)
return;
@@ -769,21 +1220,21 @@ parse_power_conservation_features(struct drm_i915_private *i915,
* power->drrs & BIT(panel_type)=false
*/
if (!(power->drrs & BIT(panel_type)))
- i915->vbt.drrs_type = DRRS_NOT_SUPPORTED;
+ i915->vbt.drrs_type = DRRS_TYPE_NONE;
- if (bdb->version >= 232)
+ if (i915->vbt.version >= 232)
i915->vbt.edp.hobl = power->hobl & BIT(panel_type);
}
static void
-parse_edp(struct drm_i915_private *i915, const struct bdb_header *bdb)
+parse_edp(struct drm_i915_private *i915)
{
const struct bdb_edp *edp;
const struct edp_power_seq *edp_pps;
const struct edp_fast_link_params *edp_link_params;
int panel_type = i915->vbt.panel_type;
- edp = find_section(bdb, BDB_EDP);
+ edp = find_section(i915, BDB_EDP);
if (!edp)
return;
@@ -876,7 +1327,7 @@ parse_edp(struct drm_i915_private *i915, const struct bdb_header *bdb)
break;
}
- if (bdb->version >= 173) {
+ if (i915->vbt.version >= 173) {
u8 vswing;
/* Don't read from VBT if module parameter has valid value*/
@@ -888,16 +1339,19 @@ parse_edp(struct drm_i915_private *i915, const struct bdb_header *bdb)
i915->vbt.edp.low_vswing = vswing == 0;
}
}
+
+ i915->vbt.edp.drrs_msa_timing_delay =
+ (edp->sdrrs_msa_timing_delay >> (panel_type * 2)) & 3;
}
static void
-parse_psr(struct drm_i915_private *i915, const struct bdb_header *bdb)
+parse_psr(struct drm_i915_private *i915)
{
const struct bdb_psr *psr;
const struct psr_table *psr_table;
int panel_type = i915->vbt.panel_type;
- psr = find_section(bdb, BDB_PSR);
+ psr = find_section(i915, BDB_PSR);
if (!psr) {
drm_dbg_kms(&i915->drm, "No PSR BDB found.\n");
return;
@@ -916,7 +1370,7 @@ parse_psr(struct drm_i915_private *i915, const struct bdb_header *bdb)
* New psr options 0=500us, 1=100us, 2=2500us, 3=0us
* Old decimal value is wake up time in multiples of 100 us.
*/
- if (bdb->version >= 205 &&
+ if (i915->vbt.version >= 205 &&
(DISPLAY_VER(i915) >= 9 && !IS_BROXTON(i915))) {
switch (psr_table->tp1_wakeup_time) {
case 0:
@@ -962,7 +1416,7 @@ parse_psr(struct drm_i915_private *i915, const struct bdb_header *bdb)
i915->vbt.psr.tp2_tp3_wakeup_time_us = psr_table->tp2_tp3_wakeup_time * 100;
}
- if (bdb->version >= 226) {
+ if (i915->vbt.version >= 226) {
u32 wakeup_time = psr->psr2_tp2_tp3_wakeup_time;
wakeup_time = (wakeup_time >> (2 * panel_type)) & 0x3;
@@ -1031,8 +1485,7 @@ static void parse_dsi_backlight_ports(struct drm_i915_private *i915,
}
static void
-parse_mipi_config(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_mipi_config(struct drm_i915_private *i915)
{
const struct bdb_mipi_config *start;
const struct mipi_config *config;
@@ -1055,7 +1508,7 @@ parse_mipi_config(struct drm_i915_private *i915,
/* Parse #52 for panel index used from panel_type already
* parsed
*/
- start = find_section(bdb, BDB_MIPI_CONFIG);
+ start = find_section(i915, BDB_MIPI_CONFIG);
if (!start) {
drm_dbg_kms(&i915->drm, "No MIPI config BDB found");
return;
@@ -1082,7 +1535,7 @@ parse_mipi_config(struct drm_i915_private *i915,
return;
}
- parse_dsi_backlight_ports(i915, bdb->version, port);
+ parse_dsi_backlight_ports(i915, i915->vbt.version, port);
/* FIXME is the 90 vs. 270 correct? */
switch (config->rotation) {
@@ -1351,8 +1804,7 @@ static void fixup_mipi_sequences(struct drm_i915_private *i915)
}
static void
-parse_mipi_sequence(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_mipi_sequence(struct drm_i915_private *i915)
{
int panel_type = i915->vbt.panel_type;
const struct bdb_mipi_sequence *sequence;
@@ -1365,7 +1817,7 @@ parse_mipi_sequence(struct drm_i915_private *i915,
if (i915->vbt.dsi.panel_id != MIPI_DSI_GENERIC_PANEL_ID)
return;
- sequence = find_section(bdb, BDB_MIPI_SEQUENCE);
+ sequence = find_section(i915, BDB_MIPI_SEQUENCE);
if (!sequence) {
drm_dbg_kms(&i915->drm,
"No MIPI Sequence found, parsing complete\n");
@@ -1436,8 +1888,7 @@ err:
}
static void
-parse_compression_parameters(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_compression_parameters(struct drm_i915_private *i915)
{
const struct bdb_compression_parameters *params;
struct intel_bios_encoder_data *devdata;
@@ -1445,10 +1896,10 @@ parse_compression_parameters(struct drm_i915_private *i915,
u16 block_size;
int index;
- if (bdb->version < 198)
+ if (i915->vbt.version < 198)
return;
- params = find_section(bdb, BDB_COMPRESSION_PARAMETERS);
+ params = find_section(i915, BDB_COMPRESSION_PARAMETERS);
if (params) {
/* Sanity checks */
if (params->entry_size != sizeof(params->data[0])) {
@@ -1955,6 +2406,12 @@ static int _intel_bios_max_tmds_clock(const struct intel_bios_encoder_data *devd
fallthrough;
case HDMI_MAX_DATA_RATE_PLATFORM:
return 0;
+ case HDMI_MAX_DATA_RATE_594:
+ return 594000;
+ case HDMI_MAX_DATA_RATE_340:
+ return 340000;
+ case HDMI_MAX_DATA_RATE_300:
+ return 300000;
case HDMI_MAX_DATA_RATE_297:
return 297000;
case HDMI_MAX_DATA_RATE_165:
@@ -2077,8 +2534,7 @@ static void parse_ddi_ports(struct drm_i915_private *i915)
}
static void
-parse_general_definitions(struct drm_i915_private *i915,
- const struct bdb_header *bdb)
+parse_general_definitions(struct drm_i915_private *i915)
{
const struct bdb_general_definitions *defs;
struct intel_bios_encoder_data *devdata;
@@ -2088,7 +2544,7 @@ parse_general_definitions(struct drm_i915_private *i915,
u16 block_size;
int bus_pin;
- defs = find_section(bdb, BDB_GENERAL_DEFINITIONS);
+ defs = find_section(i915, BDB_GENERAL_DEFINITIONS);
if (!defs) {
drm_dbg_kms(&i915->drm,
"No general definition block is found, no devices defined.\n");
@@ -2108,31 +2564,31 @@ parse_general_definitions(struct drm_i915_private *i915,
if (intel_gmbus_is_valid_pin(i915, bus_pin))
i915->vbt.crt_ddc_pin = bus_pin;
- if (bdb->version < 106) {
+ if (i915->vbt.version < 106) {
expected_size = 22;
- } else if (bdb->version < 111) {
+ } else if (i915->vbt.version < 111) {
expected_size = 27;
- } else if (bdb->version < 195) {
+ } else if (i915->vbt.version < 195) {
expected_size = LEGACY_CHILD_DEVICE_CONFIG_SIZE;
- } else if (bdb->version == 195) {
+ } else if (i915->vbt.version == 195) {
expected_size = 37;
- } else if (bdb->version <= 215) {
+ } else if (i915->vbt.version <= 215) {
expected_size = 38;
- } else if (bdb->version <= 237) {
+ } else if (i915->vbt.version <= 237) {
expected_size = 39;
} else {
expected_size = sizeof(*child);
BUILD_BUG_ON(sizeof(*child) < 39);
drm_dbg(&i915->drm,
"Expected child device config size for VBT version %u not known; assuming %u\n",
- bdb->version, expected_size);
+ i915->vbt.version, expected_size);
}
/* Flag an error for unexpected size, but continue anyway. */
if (defs->child_dev_size != expected_size)
drm_err(&i915->drm,
"Unexpected child device config size %u (expected %u for VBT version %u)\n",
- defs->child_dev_size, expected_size, bdb->version);
+ defs->child_dev_size, expected_size, i915->vbt.version);
/* The legacy sized child device config is the minimum we need. */
if (defs->child_dev_size < LEGACY_CHILD_DEVICE_CONFIG_SIZE) {
@@ -2457,6 +2913,7 @@ void intel_bios_init(struct drm_i915_private *i915)
const struct bdb_header *bdb;
INIT_LIST_HEAD(&i915->vbt.display_devices);
+ INIT_LIST_HEAD(&i915->vbt.bdb_blocks);
if (!HAS_DISPLAY(i915)) {
drm_dbg_kms(&i915->drm,
@@ -2488,24 +2945,27 @@ void intel_bios_init(struct drm_i915_private *i915)
drm_dbg_kms(&i915->drm,
"VBT signature \"%.*s\", BDB version %d\n",
- (int)sizeof(vbt->signature), vbt->signature, bdb->version);
+ (int)sizeof(vbt->signature), vbt->signature, i915->vbt.version);
+
+ init_bdb_blocks(i915, bdb);
/* Grab useful general definitions */
- parse_general_features(i915, bdb);
- parse_general_definitions(i915, bdb);
- parse_panel_options(i915, bdb);
- parse_panel_dtd(i915, bdb);
- parse_lfp_backlight(i915, bdb);
- parse_sdvo_panel_data(i915, bdb);
- parse_driver_features(i915, bdb);
- parse_power_conservation_features(i915, bdb);
- parse_edp(i915, bdb);
- parse_psr(i915, bdb);
- parse_mipi_config(i915, bdb);
- parse_mipi_sequence(i915, bdb);
+ parse_general_features(i915);
+ parse_general_definitions(i915);
+ parse_panel_options(i915);
+ parse_generic_dtd(i915);
+ parse_lfp_data(i915);
+ parse_lfp_backlight(i915);
+ parse_sdvo_panel_data(i915);
+ parse_driver_features(i915);
+ parse_power_conservation_features(i915);
+ parse_edp(i915);
+ parse_psr(i915);
+ parse_mipi_config(i915);
+ parse_mipi_sequence(i915);
/* Depends on child device list */
- parse_compression_parameters(i915, bdb);
+ parse_compression_parameters(i915);
out:
if (!vbt) {
@@ -2527,14 +2987,20 @@ out:
*/
void intel_bios_driver_remove(struct drm_i915_private *i915)
{
- struct intel_bios_encoder_data *devdata, *n;
+ struct intel_bios_encoder_data *devdata, *nd;
+ struct bdb_block_entry *entry, *ne;
- list_for_each_entry_safe(devdata, n, &i915->vbt.display_devices, node) {
+ list_for_each_entry_safe(devdata, nd, &i915->vbt.display_devices, node) {
list_del(&devdata->node);
kfree(devdata->dsc);
kfree(devdata);
}
+ list_for_each_entry_safe(entry, ne, &i915->vbt.bdb_blocks, node) {
+ list_del(&entry->node);
+ kfree(entry);
+ }
+
kfree(i915->vbt.sdvo_lvds_vbt_mode);
i915->vbt.sdvo_lvds_vbt_mode = NULL;
kfree(i915->vbt.lfp_lvds_vbt_mode);