diff options
Diffstat (limited to 'drivers/gpu/drm/drm_edid.c')
-rw-r--r-- | drivers/gpu/drm/drm_edid.c | 215 |
1 files changed, 166 insertions, 49 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 6bb6337be920..4f751a9d71a3 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -82,6 +82,8 @@ #define EDID_QUIRK_FORCE_6BPC (1 << 10) /* Force 10bpc */ #define EDID_QUIRK_FORCE_10BPC (1 << 11) +/* Non desktop display (i.e. HMD) */ +#define EDID_QUIRK_NON_DESKTOP (1 << 12) struct detailed_mode_closure { struct drm_connector *connector; @@ -111,6 +113,9 @@ static const struct edid_quirk { /* AEO model 0 reports 8 bpc, but is a 6 bpc panel */ { "AEO", 0, EDID_QUIRK_FORCE_6BPC }, + /* CPT panel of Asus UX303LA reports 8 bpc, but is a 6 bpc panel */ + { "CPT", 0x17df, EDID_QUIRK_FORCE_6BPC }, + /* Belinea 10 15 55 */ { "MAX", 1516, EDID_QUIRK_PREFER_LARGE_60 }, { "MAX", 0x77e, EDID_QUIRK_PREFER_LARGE_60 }, @@ -157,6 +162,27 @@ static const struct edid_quirk { /* Rotel RSX-1058 forwards sink's EDID but only does HDMI 1.1*/ { "ETR", 13896, EDID_QUIRK_FORCE_8BPC }, + + /* HTC Vive VR Headset */ + { "HVR", 0xaa01, EDID_QUIRK_NON_DESKTOP }, + + /* Oculus Rift DK1, DK2, and CV1 VR Headsets */ + { "OVR", 0x0001, EDID_QUIRK_NON_DESKTOP }, + { "OVR", 0x0003, EDID_QUIRK_NON_DESKTOP }, + { "OVR", 0x0004, EDID_QUIRK_NON_DESKTOP }, + + /* Windows Mixed Reality Headsets */ + { "ACR", 0x7fce, EDID_QUIRK_NON_DESKTOP }, + { "HPN", 0x3515, EDID_QUIRK_NON_DESKTOP }, + { "LEN", 0x0408, EDID_QUIRK_NON_DESKTOP }, + { "LEN", 0xb800, EDID_QUIRK_NON_DESKTOP }, + { "FUJ", 0x1970, EDID_QUIRK_NON_DESKTOP }, + { "DEL", 0x7fce, EDID_QUIRK_NON_DESKTOP }, + { "SEC", 0x144a, EDID_QUIRK_NON_DESKTOP }, + { "AUS", 0xc102, EDID_QUIRK_NON_DESKTOP }, + + /* Sony PlayStation VR Headset */ + { "SNY", 0x0704, EDID_QUIRK_NON_DESKTOP }, }; /* @@ -1533,6 +1559,10 @@ static void connector_bad_edid(struct drm_connector *connector, * level, drivers must make all reasonable efforts to expose it as an I2C * adapter and use drm_get_edid() instead of abusing this function. * + * The EDID may be overridden using debugfs override_edid or firmare EDID + * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority + * order. Having either of them bypasses actual EDID reads. + * * Return: Pointer to valid EDID or NULL if we couldn't find any. */ struct edid *drm_do_get_edid(struct drm_connector *connector, @@ -1542,6 +1572,17 @@ struct edid *drm_do_get_edid(struct drm_connector *connector, { int i, j = 0, valid_extensions = 0; u8 *edid, *new; + struct edid *override = NULL; + + if (connector->override_edid) + override = drm_edid_duplicate((const struct edid *) + connector->edid_blob_ptr->data); + + if (!override) + override = drm_load_edid_firmware(connector); + + if (!IS_ERR_OR_NULL(override)) + return override; if ((edid = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) return NULL; @@ -1711,7 +1752,7 @@ EXPORT_SYMBOL(drm_edid_duplicate); * * Returns true if @vendor is in @edid, false otherwise */ -static bool edid_vendor(struct edid *edid, const char *vendor) +static bool edid_vendor(const struct edid *edid, const char *vendor) { char edid_vendor[3]; @@ -1729,7 +1770,7 @@ static bool edid_vendor(struct edid *edid, const char *vendor) * * This tells subsequent routines what fixes they need to apply. */ -static u32 edid_get_quirks(struct edid *edid) +static u32 edid_get_quirks(const struct edid *edid) { const struct edid_quirk *quirk; int i; @@ -2793,7 +2834,7 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid, /* * Search EDID for CEA extension block. */ -static u8 *drm_find_edid_extension(struct edid *edid, int ext_id) +static u8 *drm_find_edid_extension(const struct edid *edid, int ext_id) { u8 *edid_ext = NULL; int i; @@ -2815,12 +2856,12 @@ static u8 *drm_find_edid_extension(struct edid *edid, int ext_id) return edid_ext; } -static u8 *drm_find_cea_extension(struct edid *edid) +static u8 *drm_find_cea_extension(const struct edid *edid) { return drm_find_edid_extension(edid, CEA_EXT); } -static u8 *drm_find_displayid_extension(struct edid *edid) +static u8 *drm_find_displayid_extension(const struct edid *edid) { return drm_find_edid_extension(edid, DISPLAYID_EXT); } @@ -3378,6 +3419,7 @@ static int do_hdmi_vsdb_modes(struct drm_connector *connector, const u8 *db, u8 len, const u8 *video_db, u8 video_len) { + struct drm_display_info *info = &connector->display_info; int modes = 0, offset = 0, i, multi_present = 0, multi_len; u8 vic_len, hdmi_3d_len = 0; u16 mask; @@ -3505,6 +3547,8 @@ do_hdmi_vsdb_modes(struct drm_connector *connector, const u8 *db, u8 len, } out: + if (modes > 0) + info->has_hdmi_infoframe = true; return modes; } @@ -3741,8 +3785,8 @@ drm_parse_hdmi_vsdb_audio(struct drm_connector *connector, const u8 *db) { u8 len = cea_db_payload_len(db); - if (len >= 6) - connector->eld[5] |= (db[6] >> 7) << 1; /* Supports_AI */ + if (len >= 6 && (db[6] & (1 << 7))) + connector->eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= DRM_ELD_SUPPORTS_AI; if (len >= 8) { connector->latency_present[0] = db[8] >> 7; connector->latency_present[1] = (db[8] >> 6) & 1; @@ -3814,16 +3858,27 @@ void drm_edid_get_monitor_name(struct edid *edid, char *name, int bufsize) } EXPORT_SYMBOL(drm_edid_get_monitor_name); -/** +static void clear_eld(struct drm_connector *connector) +{ + memset(connector->eld, 0, sizeof(connector->eld)); + + connector->latency_present[0] = false; + connector->latency_present[1] = false; + connector->video_latency[0] = 0; + connector->audio_latency[0] = 0; + connector->video_latency[1] = 0; + connector->audio_latency[1] = 0; +} + +/* * drm_edid_to_eld - build ELD from EDID * @connector: connector corresponding to the HDMI/DP sink * @edid: EDID to parse * * Fill the ELD (EDID-Like Data) buffer for passing to the audio driver. The - * Conn_Type, HDCP and Port_ID ELD fields are left for the graphics driver to - * fill in. + * HDCP and Port_ID ELD fields are left for the graphics driver to fill in. */ -void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) +static void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) { uint8_t *eld = connector->eld; u8 *cea; @@ -3832,14 +3887,7 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) int mnl; int dbl; - memset(eld, 0, sizeof(connector->eld)); - - connector->latency_present[0] = false; - connector->latency_present[1] = false; - connector->video_latency[0] = 0; - connector->audio_latency[0] = 0; - connector->video_latency[1] = 0; - connector->audio_latency[1] = 0; + clear_eld(connector); if (!edid) return; @@ -3850,17 +3898,18 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) return; } - mnl = get_monitor_name(edid, eld + 20); + mnl = get_monitor_name(edid, &eld[DRM_ELD_MONITOR_NAME_STRING]); + DRM_DEBUG_KMS("ELD monitor %s\n", &eld[DRM_ELD_MONITOR_NAME_STRING]); - eld[4] = (cea[1] << 5) | mnl; - DRM_DEBUG_KMS("ELD monitor %s\n", eld + 20); + eld[DRM_ELD_CEA_EDID_VER_MNL] = cea[1] << DRM_ELD_CEA_EDID_VER_SHIFT; + eld[DRM_ELD_CEA_EDID_VER_MNL] |= mnl; - eld[0] = 2 << 3; /* ELD version: 2 */ + eld[DRM_ELD_VER] = DRM_ELD_VER_CEA861D; - eld[16] = edid->mfg_id[0]; - eld[17] = edid->mfg_id[1]; - eld[18] = edid->prod_code[0]; - eld[19] = edid->prod_code[1]; + eld[DRM_ELD_MANUFACTURER_NAME0] = edid->mfg_id[0]; + eld[DRM_ELD_MANUFACTURER_NAME1] = edid->mfg_id[1]; + eld[DRM_ELD_PRODUCT_CODE0] = edid->prod_code[0]; + eld[DRM_ELD_PRODUCT_CODE1] = edid->prod_code[1]; if (cea_revision(cea) >= 3) { int i, start, end; @@ -3881,14 +3930,14 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) /* Audio Data Block, contains SADs */ sad_count = min(dbl / 3, 15 - total_sad_count); if (sad_count >= 1) - memcpy(eld + 20 + mnl + total_sad_count * 3, + memcpy(&eld[DRM_ELD_CEA_SAD(mnl, total_sad_count)], &db[1], sad_count * 3); total_sad_count += sad_count; break; case SPEAKER_BLOCK: /* Speaker Allocation Data Block */ if (dbl >= 1) - eld[7] = db[1]; + eld[DRM_ELD_SPEAKER] = db[1]; break; case VENDOR_BLOCK: /* HDMI Vendor-Specific Data Block */ @@ -3900,7 +3949,13 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) } } } - eld[5] |= total_sad_count << 4; + eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= total_sad_count << DRM_ELD_SAD_COUNT_SHIFT; + + if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort || + connector->connector_type == DRM_MODE_CONNECTOR_eDP) + eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= DRM_ELD_CONN_TYPE_DP; + else + eld[DRM_ELD_SAD_COUNT_CONN_TYPE] |= DRM_ELD_CONN_TYPE_HDMI; eld[DRM_ELD_BASELINE_ELD_LEN] = DIV_ROUND_UP(drm_eld_calc_baseline_block_size(eld), 4); @@ -3908,7 +3963,6 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) DRM_DEBUG_KMS("ELD size %d, SAD count %d\n", drm_eld_size(eld), total_sad_count); } -EXPORT_SYMBOL(drm_edid_to_eld); /** * drm_edid_to_sad - extracts SADs from EDID @@ -4218,6 +4272,8 @@ static void drm_parse_hdmi_forum_vsdb(struct drm_connector *connector, struct drm_display_info *display = &connector->display_info; struct drm_hdmi_info *hdmi = &display->hdmi; + display->has_hdmi_infoframe = true; + if (hf_vsdb[6] & 0x80) { hdmi->scdc.supported = true; if (hf_vsdb[6] & 0x40) @@ -4343,7 +4399,7 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db) } static void drm_parse_cea_ext(struct drm_connector *connector, - struct edid *edid) + const struct edid *edid) { struct drm_display_info *info = &connector->display_info; const u8 *edid_ext; @@ -4377,11 +4433,34 @@ static void drm_parse_cea_ext(struct drm_connector *connector, } } -static void drm_add_display_info(struct drm_connector *connector, - struct edid *edid) +/* A connector has no EDID information, so we've got no EDID to compute quirks from. Reset + * all of the values which would have been set from EDID + */ +void +drm_reset_display_info(struct drm_connector *connector) +{ + struct drm_display_info *info = &connector->display_info; + + info->width_mm = 0; + info->height_mm = 0; + + info->bpc = 0; + info->color_formats = 0; + info->cea_rev = 0; + info->max_tmds_clock = 0; + info->dvi_dual = false; + info->has_hdmi_infoframe = false; + + info->non_desktop = 0; +} +EXPORT_SYMBOL_GPL(drm_reset_display_info); + +u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edid) { struct drm_display_info *info = &connector->display_info; + u32 quirks = edid_get_quirks(edid); + info->width_mm = edid->width_cm * 10; info->height_mm = edid->height_cm * 10; @@ -4391,12 +4470,17 @@ static void drm_add_display_info(struct drm_connector *connector, info->cea_rev = 0; info->max_tmds_clock = 0; info->dvi_dual = false; + info->has_hdmi_infoframe = false; + + info->non_desktop = !!(quirks & EDID_QUIRK_NON_DESKTOP); + + DRM_DEBUG_KMS("non_desktop set to %d\n", info->non_desktop); if (edid->revision < 3) - return; + return quirks; if (!(edid->input & DRM_EDID_INPUT_DIGITAL)) - return; + return quirks; drm_parse_cea_ext(connector, edid); @@ -4416,7 +4500,7 @@ static void drm_add_display_info(struct drm_connector *connector, /* Only defined for 1.4 with digital displays */ if (edid->revision < 4) - return; + return quirks; switch (edid->input & DRM_EDID_DIGITAL_DEPTH_MASK) { case DRM_EDID_DIGITAL_DEPTH_6: @@ -4451,7 +4535,9 @@ static void drm_add_display_info(struct drm_connector *connector, info->color_formats |= DRM_COLOR_FORMAT_YCRCB444; if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) info->color_formats |= DRM_COLOR_FORMAT_YCRCB422; + return quirks; } +EXPORT_SYMBOL_GPL(drm_add_display_info); static int validate_displayid(u8 *displayid, int length, int idx) { @@ -4586,8 +4672,8 @@ static int add_displayid_detailed_modes(struct drm_connector *connector, * @edid: EDID data * * Add the specified modes to the connector's mode list. Also fills out the - * &drm_display_info structure in @connector with any information which can be - * derived from the edid. + * &drm_display_info structure and ELD in @connector with any information which + * can be derived from the edid. * * Return: The number of modes added or 0 if we couldn't find any. */ @@ -4597,22 +4683,24 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) u32 quirks; if (edid == NULL) { + clear_eld(connector); return 0; } if (!drm_edid_is_valid(edid)) { + clear_eld(connector); dev_warn(connector->dev->dev, "%s: EDID invalid.\n", connector->name); return 0; } - quirks = edid_get_quirks(edid); + drm_edid_to_eld(connector, edid); /* * CEA-861-F adds ycbcr capability map block, for HDMI 2.0 sinks. * To avoid multiple parsing of same block, lets parse that map * from sink info, before parsing CEA modes. */ - drm_add_display_info(connector, edid); + quirks = drm_add_display_info(connector, edid); /* * EDID spec says modes should be preferred in this order: @@ -4804,12 +4892,18 @@ EXPORT_SYMBOL(drm_hdmi_avi_infoframe_from_display_mode); * @mode: DRM display mode * @rgb_quant_range: RGB quantization range (Q) * @rgb_quant_range_selectable: Sink support selectable RGB quantization range (QS) + * @is_hdmi2_sink: HDMI 2.0 sink, which has different default recommendations + * + * Note that @is_hdmi2_sink can be derived by looking at the + * &drm_scdc.supported flag stored in &drm_hdmi_info.scdc, + * &drm_display_info.hdmi, which can be found in &drm_connector.display_info. */ void drm_hdmi_avi_infoframe_quant_range(struct hdmi_avi_infoframe *frame, const struct drm_display_mode *mode, enum hdmi_quantization_range rgb_quant_range, - bool rgb_quant_range_selectable) + bool rgb_quant_range_selectable, + bool is_hdmi2_sink) { /* * CEA-861: @@ -4833,8 +4927,15 @@ drm_hdmi_avi_infoframe_quant_range(struct hdmi_avi_infoframe *frame, * YQ-field to match the RGB Quantization Range being transmitted * (e.g., when Limited Range RGB, set YQ=0 or when Full Range RGB, * set YQ=1) and the Sink shall ignore the YQ-field." + * + * Unfortunate certain sinks (eg. VIZ Model 67/E261VA) get confused + * by non-zero YQ when receiving RGB. There doesn't seem to be any + * good way to tell which version of CEA-861 the sink supports, so + * we limit non-zero YQ to HDMI 2.0 sinks only as HDMI 2.0 is based + * on on CEA-861-F. */ - if (rgb_quant_range == HDMI_QUANTIZATION_RANGE_LIMITED) + if (!is_hdmi2_sink || + rgb_quant_range == HDMI_QUANTIZATION_RANGE_LIMITED) frame->ycc_quantization_range = HDMI_YCC_QUANTIZATION_RANGE_LIMITED; else @@ -4874,6 +4975,7 @@ s3d_structure_from_display_mode(const struct drm_display_mode *mode) * drm_hdmi_vendor_infoframe_from_display_mode() - fill an HDMI infoframe with * data from a DRM display mode * @frame: HDMI vendor infoframe + * @connector: the connector * @mode: DRM display mode * * Note that there's is a need to send HDMI vendor infoframes only when using a @@ -4884,8 +4986,15 @@ s3d_structure_from_display_mode(const struct drm_display_mode *mode) */ int drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, + struct drm_connector *connector, const struct drm_display_mode *mode) { + /* + * FIXME: sil-sii8620 doesn't have a connector around when + * we need one, so we have to be prepared for a NULL connector. + */ + bool has_hdmi_infoframe = connector ? + connector->display_info.has_hdmi_infoframe : false; int err; u32 s3d_flags; u8 vic; @@ -4893,11 +5002,21 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, if (!frame || !mode) return -EINVAL; + if (!has_hdmi_infoframe) + return -EINVAL; + vic = drm_match_hdmi_mode(mode); s3d_flags = mode->flags & DRM_MODE_FLAG_3D_MASK; - if (!vic && !s3d_flags) - return -EINVAL; + /* + * Even if it's not absolutely necessary to send the infoframe + * (ie.vic==0 and s3d_struct==0) we will still send it if we + * know that the sink can handle it. This is based on a + * suggestion in HDMI 2.0 Appendix F. Apparently some sinks + * have trouble realizing that they shuld switch from 3D to 2D + * mode if the source simply stops sending the infoframe when + * it wants to switch from 3D to 2D. + */ if (vic && s3d_flags) return -EINVAL; @@ -4906,10 +5025,8 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, if (err < 0) return err; - if (vic) - frame->vic = vic; - else - frame->s3d_struct = s3d_structure_from_display_mode(mode); + frame->vic = vic; + frame->s3d_struct = s3d_structure_from_display_mode(mode); return 0; } |