diff options
Diffstat (limited to 'drivers/gpu/drm/i915/intel_bios.c')
-rw-r--r-- | drivers/gpu/drm/i915/intel_bios.c | 383 |
1 files changed, 344 insertions, 39 deletions
diff --git a/drivers/gpu/drm/i915/intel_bios.c b/drivers/gpu/drm/i915/intel_bios.c index fa486c5fbb0..1ee98f121a0 100644 --- a/drivers/gpu/drm/i915/intel_bios.c +++ b/drivers/gpu/drm/i915/intel_bios.c @@ -49,13 +49,19 @@ find_section(struct bdb_header *bdb, int section_id) total = bdb->bdb_size; /* walk the sections looking for section_id */ - while (index < total) { + while (index + 3 < total) { current_id = *(base + index); index++; + current_size = *((u16 *)(base + index)); index += 2; + + if (index + current_size > total) + return NULL; + if (current_id == section_id) return base + index; + index += current_size; } @@ -206,7 +212,7 @@ parse_lfp_panel_data(struct drm_i915_private *dev_priv, const struct lvds_dvo_timing *panel_dvo_timing; const struct lvds_fp_timing *fp_timing; struct drm_display_mode *panel_fixed_mode; - int i, downclock; + int i, downclock, drrs_mode; lvds_options = find_section(bdb, BDB_LVDS_OPTIONS); if (!lvds_options) @@ -218,6 +224,28 @@ parse_lfp_panel_data(struct drm_i915_private *dev_priv, panel_type = lvds_options->panel_type; + drrs_mode = (lvds_options->dps_panel_type_bits + >> (panel_type * 2)) & MODE_MASK; + /* + * VBT has static DRRS = 0 and seamless DRRS = 2. + * The below piece of code is required to adjust vbt.drrs_type + * to match the enum drrs_support_type. + */ + switch (drrs_mode) { + case 0: + dev_priv->vbt.drrs_type = STATIC_DRRS_SUPPORT; + DRM_DEBUG_KMS("DRRS supported mode is static\n"); + break; + case 2: + dev_priv->vbt.drrs_type = SEAMLESS_DRRS_SUPPORT; + DRM_DEBUG_KMS("DRRS supported mode is seamless\n"); + break; + default: + dev_priv->vbt.drrs_type = DRRS_NOT_SUPPORTED; + DRM_DEBUG_KMS("DRRS not supported (VBT input)\n"); + break; + } + lvds_lfp_data = find_section(bdb, BDB_LVDS_LFP_DATA); if (!lvds_lfp_data) return; @@ -526,6 +554,16 @@ parse_driver_features(struct drm_i915_private *dev_priv, if (driver->dual_frequency) dev_priv->render_reclock_avail = true; + + DRM_DEBUG_KMS("DRRS State Enabled:%d\n", driver->drrs_enabled); + /* + * If DRRS is not supported, drrs_type has to be set to 0. + * This is because, VBT is configured in such a way that + * static DRRS is 0 and DRRS not supported is represented by + * driver->drrs_enabled=false + */ + if (!driver->drrs_enabled) + dev_priv->vbt.drrs_type = DRRS_NOT_SUPPORTED; } static void @@ -560,63 +598,289 @@ parse_edp(struct drm_i915_private *dev_priv, struct bdb_header *bdb) dev_priv->vbt.edp_pps = *edp_pps; - dev_priv->vbt.edp_rate = edp_link_params->rate ? DP_LINK_BW_2_7 : - DP_LINK_BW_1_62; + switch (edp_link_params->rate) { + case EDP_RATE_1_62: + dev_priv->vbt.edp_rate = DP_LINK_BW_1_62; + break; + case EDP_RATE_2_7: + dev_priv->vbt.edp_rate = DP_LINK_BW_2_7; + break; + default: + DRM_DEBUG_KMS("VBT has unknown eDP link rate value %u\n", + edp_link_params->rate); + break; + } + switch (edp_link_params->lanes) { - case 0: + case EDP_LANE_1: dev_priv->vbt.edp_lanes = 1; break; - case 1: + case EDP_LANE_2: dev_priv->vbt.edp_lanes = 2; break; - case 3: - default: + case EDP_LANE_4: dev_priv->vbt.edp_lanes = 4; break; + default: + DRM_DEBUG_KMS("VBT has unknown eDP lane count value %u\n", + edp_link_params->lanes); + break; } + switch (edp_link_params->preemphasis) { - case 0: + case EDP_PREEMPHASIS_NONE: dev_priv->vbt.edp_preemphasis = DP_TRAIN_PRE_EMPHASIS_0; break; - case 1: + case EDP_PREEMPHASIS_3_5dB: dev_priv->vbt.edp_preemphasis = DP_TRAIN_PRE_EMPHASIS_3_5; break; - case 2: + case EDP_PREEMPHASIS_6dB: dev_priv->vbt.edp_preemphasis = DP_TRAIN_PRE_EMPHASIS_6; break; - case 3: + case EDP_PREEMPHASIS_9_5dB: dev_priv->vbt.edp_preemphasis = DP_TRAIN_PRE_EMPHASIS_9_5; break; + default: + DRM_DEBUG_KMS("VBT has unknown eDP pre-emphasis value %u\n", + edp_link_params->preemphasis); + break; } + switch (edp_link_params->vswing) { - case 0: + case EDP_VSWING_0_4V: dev_priv->vbt.edp_vswing = DP_TRAIN_VOLTAGE_SWING_400; break; - case 1: + case EDP_VSWING_0_6V: dev_priv->vbt.edp_vswing = DP_TRAIN_VOLTAGE_SWING_600; break; - case 2: + case EDP_VSWING_0_8V: dev_priv->vbt.edp_vswing = DP_TRAIN_VOLTAGE_SWING_800; break; - case 3: + case EDP_VSWING_1_2V: dev_priv->vbt.edp_vswing = DP_TRAIN_VOLTAGE_SWING_1200; break; + default: + DRM_DEBUG_KMS("VBT has unknown eDP voltage swing value %u\n", + edp_link_params->vswing); + break; + } +} + +static u8 *goto_next_sequence(u8 *data, int *size) +{ + u16 len; + int tmp = *size; + + if (--tmp < 0) + return NULL; + + /* goto first element */ + data++; + while (1) { + switch (*data) { + case MIPI_SEQ_ELEM_SEND_PKT: + /* + * skip by this element payload size + * skip elem id, command flag and data type + */ + tmp -= 5; + if (tmp < 0) + return NULL; + + data += 3; + len = *((u16 *)data); + + tmp -= len; + if (tmp < 0) + return NULL; + + /* skip by len */ + data = data + 2 + len; + break; + case MIPI_SEQ_ELEM_DELAY: + /* skip by elem id, and delay is 4 bytes */ + tmp -= 5; + if (tmp < 0) + return NULL; + + data += 5; + break; + case MIPI_SEQ_ELEM_GPIO: + tmp -= 3; + if (tmp < 0) + return NULL; + + data += 3; + break; + default: + DRM_ERROR("Unknown element\n"); + return NULL; + } + + /* end of sequence ? */ + if (*data == 0) + break; } + + /* goto next sequence or end of block byte */ + if (--tmp < 0) + return NULL; + + data++; + + /* update amount of data left for the sequence block to be parsed */ + *size = tmp; + return data; } static void parse_mipi(struct drm_i915_private *dev_priv, struct bdb_header *bdb) { - struct bdb_mipi *mipi; + struct bdb_mipi_config *start; + struct bdb_mipi_sequence *sequence; + struct mipi_config *config; + struct mipi_pps_data *pps; + u8 *data, *seq_data; + int i, panel_id, seq_size; + u16 block_size; + + /* parse MIPI blocks only if LFP type is MIPI */ + if (!dev_priv->vbt.has_mipi) + return; + + /* Initialize this to undefined indicating no generic MIPI support */ + dev_priv->vbt.dsi.panel_id = MIPI_DSI_UNDEFINED_PANEL_ID; - mipi = find_section(bdb, BDB_MIPI_CONFIG); - if (!mipi) { - DRM_DEBUG_KMS("No MIPI BDB found"); + /* Block #40 is already parsed and panel_fixed_mode is + * stored in dev_priv->lfp_lvds_vbt_mode + * resuse this when needed + */ + + /* Parse #52 for panel index used from panel_type already + * parsed + */ + start = find_section(bdb, BDB_MIPI_CONFIG); + if (!start) { + DRM_DEBUG_KMS("No MIPI config BDB found"); return; } - /* XXX: add more info */ + DRM_DEBUG_DRIVER("Found MIPI Config block, panel index = %d\n", + panel_type); + + /* + * get hold of the correct configuration block and pps data as per + * the panel_type as index + */ + config = &start->config[panel_type]; + pps = &start->pps[panel_type]; + + /* store as of now full data. Trim when we realise all is not needed */ + dev_priv->vbt.dsi.config = kmemdup(config, sizeof(struct mipi_config), GFP_KERNEL); + if (!dev_priv->vbt.dsi.config) + return; + + dev_priv->vbt.dsi.pps = kmemdup(pps, sizeof(struct mipi_pps_data), GFP_KERNEL); + if (!dev_priv->vbt.dsi.pps) { + kfree(dev_priv->vbt.dsi.config); + return; + } + + /* We have mandatory mipi config blocks. Initialize as generic panel */ dev_priv->vbt.dsi.panel_id = MIPI_DSI_GENERIC_PANEL_ID; + + /* Check if we have sequence block as well */ + sequence = find_section(bdb, BDB_MIPI_SEQUENCE); + if (!sequence) { + DRM_DEBUG_KMS("No MIPI Sequence found, parsing complete\n"); + return; + } + + DRM_DEBUG_DRIVER("Found MIPI sequence block\n"); + + block_size = get_blocksize(sequence); + + /* + * parse the sequence block for individual sequences + */ + dev_priv->vbt.dsi.seq_version = sequence->version; + + seq_data = &sequence->data[0]; + + /* + * sequence block is variable length and hence we need to parse and + * get the sequence data for specific panel id + */ + for (i = 0; i < MAX_MIPI_CONFIGURATIONS; i++) { + panel_id = *seq_data; + seq_size = *((u16 *) (seq_data + 1)); + if (panel_id == panel_type) + break; + + /* skip the sequence including seq header of 3 bytes */ + seq_data = seq_data + 3 + seq_size; + if ((seq_data - &sequence->data[0]) > block_size) { + DRM_ERROR("Sequence start is beyond sequence block size, corrupted sequence block\n"); + return; + } + } + + if (i == MAX_MIPI_CONFIGURATIONS) { + DRM_ERROR("Sequence block detected but no valid configuration\n"); + return; + } + + /* check if found sequence is completely within the sequence block + * just being paranoid */ + if (seq_size > block_size) { + DRM_ERROR("Corrupted sequence/size, bailing out\n"); + return; + } + + /* skip the panel id(1 byte) and seq size(2 bytes) */ + dev_priv->vbt.dsi.data = kmemdup(seq_data + 3, seq_size, GFP_KERNEL); + if (!dev_priv->vbt.dsi.data) + return; + + /* + * loop into the sequence data and split into multiple sequneces + * There are only 5 types of sequences as of now + */ + data = dev_priv->vbt.dsi.data; + dev_priv->vbt.dsi.size = seq_size; + + /* two consecutive 0x00 indicate end of all sequences */ + while (1) { + int seq_id = *data; + if (MIPI_SEQ_MAX > seq_id && seq_id > MIPI_SEQ_UNDEFINED) { + dev_priv->vbt.dsi.sequence[seq_id] = data; + DRM_DEBUG_DRIVER("Found mipi sequence - %d\n", seq_id); + } else { + DRM_ERROR("undefined sequence\n"); + goto err; + } + + /* partial parsing to skip elements */ + data = goto_next_sequence(data, &seq_size); + + if (data == NULL) { + DRM_ERROR("Sequence elements going beyond block itself. Sequence block parsing failed\n"); + goto err; + } + + if (*data == 0) + break; /* end of sequence reached */ + } + + DRM_DEBUG_DRIVER("MIPI related vbt parsing complete\n"); + return; +err: + kfree(dev_priv->vbt.dsi.data); + dev_priv->vbt.dsi.data = NULL; + + /* error during parsing so set all pointers to null + * because of partial parsing */ + memset(dev_priv->vbt.dsi.sequence, 0, MIPI_SEQ_MAX); } static void parse_ddi_port(struct drm_i915_private *dev_priv, enum port port, @@ -799,6 +1063,15 @@ parse_device_mapping(struct drm_i915_private *dev_priv, /* skip the device block if device type is invalid */ continue; } + + if (p_child->common.dvo_port >= DVO_PORT_MIPIA + && p_child->common.dvo_port <= DVO_PORT_MIPID + &&p_child->common.device_type & DEVICE_TYPE_MIPI_OUTPUT) { + DRM_DEBUG_KMS("Found MIPI as LFP\n"); + dev_priv->vbt.has_mipi = 1; + dev_priv->vbt.dsi.port = p_child->common.dvo_port; + } + child_dev_ptr = dev_priv->vbt.child_dev + count; count++; memcpy((void *)child_dev_ptr, (void *)p_child, @@ -869,6 +1142,46 @@ static const struct dmi_system_id intel_no_opregion_vbt[] = { { } }; +static struct bdb_header *validate_vbt(char *base, size_t size, + struct vbt_header *vbt, + const char *source) +{ + size_t offset; + struct bdb_header *bdb; + + if (vbt == NULL) { + DRM_DEBUG_DRIVER("VBT signature missing\n"); + return NULL; + } + + offset = (char *)vbt - base; + if (offset + sizeof(struct vbt_header) > size) { + DRM_DEBUG_DRIVER("VBT header incomplete\n"); + return NULL; + } + + if (memcmp(vbt->signature, "$VBT", 4)) { + DRM_DEBUG_DRIVER("VBT invalid signature\n"); + return NULL; + } + + offset += vbt->bdb_offset; + if (offset + sizeof(struct bdb_header) > size) { + DRM_DEBUG_DRIVER("BDB header incomplete\n"); + return NULL; + } + + bdb = (struct bdb_header *)(base + offset); + if (offset + bdb->bdb_size > size) { + DRM_DEBUG_DRIVER("BDB incomplete\n"); + return NULL; + } + + DRM_DEBUG_KMS("Using VBT from %s: %20s\n", + source, vbt->signature); + return bdb; +} + /** * intel_parse_bios - find VBT and initialize settings from the BIOS * @dev: DRM device @@ -892,20 +1205,13 @@ intel_parse_bios(struct drm_device *dev) init_vbt_defaults(dev_priv); /* XXX Should this validation be moved to intel_opregion.c? */ - if (!dmi_check_system(intel_no_opregion_vbt) && dev_priv->opregion.vbt) { - struct vbt_header *vbt = dev_priv->opregion.vbt; - if (memcmp(vbt->signature, "$VBT", 4) == 0) { - DRM_DEBUG_KMS("Using VBT from OpRegion: %20s\n", - vbt->signature); - bdb = (struct bdb_header *)((char *)vbt + vbt->bdb_offset); - } else - dev_priv->opregion.vbt = NULL; - } + if (!dmi_check_system(intel_no_opregion_vbt) && dev_priv->opregion.vbt) + bdb = validate_vbt((char *)dev_priv->opregion.header, OPREGION_SIZE, + (struct vbt_header *)dev_priv->opregion.vbt, + "OpRegion"); if (bdb == NULL) { - struct vbt_header *vbt = NULL; - size_t size; - int i; + size_t i, size; bios = pci_map_rom(pdev, &size); if (!bios) @@ -913,19 +1219,18 @@ intel_parse_bios(struct drm_device *dev) /* Scour memory looking for the VBT signature */ for (i = 0; i + 4 < size; i++) { - if (!memcmp(bios + i, "$VBT", 4)) { - vbt = (struct vbt_header *)(bios + i); + if (memcmp(bios + i, "$VBT", 4) == 0) { + bdb = validate_vbt(bios, size, + (struct vbt_header *)(bios + i), + "PCI ROM"); break; } } - if (!vbt) { - DRM_DEBUG_DRIVER("VBT signature missing\n"); + if (!bdb) { pci_unmap_rom(pdev, bios); return -1; } - - bdb = (struct bdb_header *)(bios + i + vbt->bdb_offset); } /* Grab useful general definitions */ |