drm/dp: Introduce EDID-based quirks
authorLyude Paul <lyude@redhat.com>
Tue, 11 Feb 2020 18:33:46 +0000 (13:33 -0500)
committerLyude Paul <lyude@redhat.com>
Wed, 4 Mar 2020 01:34:31 +0000 (20:34 -0500)
The whole point of using OUIs is so that we can recognize certain
devices and potentially apply quirks for them. Normally this should work
quite well, but there appears to be quite a number of laptop panels out
there that will fill the OUI but not the device ID. As such, for devices
like this I can't imagine it's a very good idea to try relying on OUIs
for applying quirks. As well, some laptop vendors have confirmed to us
that their panels have this exact issue.

So, let's introduce the ability to apply DP quirks based on EDID
identification. We reuse the same quirk bits for OUI-based quirks, so
that callers can simply check all possible quirks using
drm_dp_has_quirk().

Signed-off-by: Lyude Paul <lyude@redhat.com>
Cc: Jani Nikula <jani.nikula@intel.com>
Reviewed-by: Adam Jackson <ajax@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200211183358.157448-2-lyude@redhat.com
drivers/gpu/drm/drm_dp_helper.c
drivers/gpu/drm/drm_dp_mst_topology.c
drivers/gpu/drm/i915/display/intel_display_types.h
drivers/gpu/drm/i915/display/intel_dp.c
drivers/gpu/drm/i915/display/intel_dp_mst.c
drivers/gpu/drm/i915/display/intel_psr.c
include/drm/drm_dp_helper.h

index 5a103e9b3c86d8ab04f6bf4280e1a95465d17f07..db23308f4d8b57e7a43c8ea26d4a4ba09c502acf 100644 (file)
@@ -1221,6 +1221,67 @@ drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch)
 #undef DEVICE_ID_ANY
 #undef DEVICE_ID
 
+struct edid_quirk {
+       u8 mfg_id[2];
+       u8 prod_id[2];
+       u32 quirks;
+};
+
+#define MFG(first, second) { (first), (second) }
+#define PROD_ID(first, second) { (first), (second) }
+
+/*
+ * Some devices have unreliable OUIDs where they don't set the device ID
+ * correctly, and as a result we need to use the EDID for finding additional
+ * DP quirks in such cases.
+ */
+static const struct edid_quirk edid_quirk_list[] = {
+};
+
+#undef MFG
+#undef PROD_ID
+
+/**
+ * drm_dp_get_edid_quirks() - Check the EDID of a DP device to find additional
+ * DP-specific quirks
+ * @edid: The EDID to check
+ *
+ * While OUIDs are meant to be used to recognize a DisplayPort device, a lot
+ * of manufacturers don't seem to like following standards and neglect to fill
+ * the dev-ID in, making it impossible to only use OUIDs for determining
+ * quirks in some cases. This function can be used to check the EDID and look
+ * up any additional DP quirks. The bits returned by this function correspond
+ * to the quirk bits in &drm_dp_quirk.
+ *
+ * Returns: a bitmask of quirks, if any. The driver can check this using
+ * drm_dp_has_quirk().
+ */
+u32 drm_dp_get_edid_quirks(const struct edid *edid)
+{
+       const struct edid_quirk *quirk;
+       u32 quirks = 0;
+       int i;
+
+       if (!edid)
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {
+               quirk = &edid_quirk_list[i];
+               if (memcmp(quirk->mfg_id, edid->mfg_id,
+                          sizeof(edid->mfg_id)) == 0 &&
+                   memcmp(quirk->prod_id, edid->prod_code,
+                          sizeof(edid->prod_code)) == 0)
+                       quirks |= quirk->quirks;
+       }
+
+       DRM_DEBUG_KMS("DP sink: EDID mfg %*phD prod-ID %*phD quirks: 0x%04x\n",
+                     (int)sizeof(edid->mfg_id), edid->mfg_id,
+                     (int)sizeof(edid->prod_code), edid->prod_code, quirks);
+
+       return quirks;
+}
+EXPORT_SYMBOL(drm_dp_get_edid_quirks);
+
 /**
  * drm_dp_read_desc - read sink/branch descriptor from DPCD
  * @aux: DisplayPort AUX channel
index a811247cecfefe7237bd421410ce73b24b8ee532..68247670427a16294ddc57e4b7234f81c630dd14 100644 (file)
@@ -5452,7 +5452,8 @@ struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port)
        if (drm_dp_read_desc(port->mgr->aux, &desc, true))
                return NULL;
 
-       if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) &&
+       if (drm_dp_has_quirk(&desc, 0,
+                            DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) &&
            port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 &&
            port->parent == port->mgr->mst_primary) {
                u8 downstreamport;
index bd37c7058f93f6fb0d73430a4c7111bf7a9c36b8..5e00e611f077f80a29fe048551f7b732f6b78a7c 100644 (file)
@@ -1280,6 +1280,7 @@ struct intel_dp {
        int max_link_rate;
        /* sink or branch descriptor */
        struct drm_dp_desc desc;
+       u32 edid_quirks;
        struct drm_dp_aux aux;
        u32 aux_busy_last_status;
        u8 train_set[4];
index 7bf2b6878677ab56329164111dae885a75b638d8..0a417cd2af2bc02dd8f992b28b6b810c6f3e2232 100644 (file)
@@ -2398,7 +2398,7 @@ intel_dp_compute_config(struct intel_encoder *encoder,
        struct intel_connector *intel_connector = intel_dp->attached_connector;
        struct intel_digital_connector_state *intel_conn_state =
                to_intel_digital_connector_state(conn_state);
-       bool constant_n = drm_dp_has_quirk(&intel_dp->desc,
+       bool constant_n = drm_dp_has_quirk(&intel_dp->desc, 0,
                                           DP_DPCD_QUIRK_CONSTANT_N);
        int ret = 0, output_bpp;
 
@@ -4514,7 +4514,8 @@ intel_dp_get_dpcd(struct intel_dp *intel_dp)
         * it don't care about read it here and in intel_edp_init_dpcd().
         */
        if (!intel_dp_is_edp(intel_dp) &&
-           !drm_dp_has_quirk(&intel_dp->desc, DP_DPCD_QUIRK_NO_SINK_COUNT)) {
+           !drm_dp_has_quirk(&intel_dp->desc, 0,
+                             DP_DPCD_QUIRK_NO_SINK_COUNT)) {
                u8 count;
                ssize_t r;
 
@@ -5681,6 +5682,7 @@ intel_dp_set_edid(struct intel_dp *intel_dp)
 
        intel_dp->has_audio = drm_detect_monitor_audio(edid);
        drm_dp_cec_set_edid(&intel_dp->aux, edid);
+       intel_dp->edid_quirks = drm_dp_get_edid_quirks(edid);
 }
 
 static void
@@ -5693,6 +5695,7 @@ intel_dp_unset_edid(struct intel_dp *intel_dp)
        intel_connector->detect_edid = NULL;
 
        intel_dp->has_audio = false;
+       intel_dp->edid_quirks = 0;
 }
 
 static int
@@ -7565,8 +7568,8 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp,
        edid = drm_get_edid(connector, &intel_dp->aux.ddc);
        if (edid) {
                if (drm_add_edid_modes(connector, edid)) {
-                       drm_connector_update_edid_property(connector,
-                                                               edid);
+                       drm_connector_update_edid_property(connector, edid);
+                       intel_dp->edid_quirks = drm_dp_get_edid_quirks(edid);
                } else {
                        kfree(edid);
                        edid = ERR_PTR(-EINVAL);
index d7bfa7c350e99c0e278405e6c62aa1d9bc46aefc..39f5de9a8c7ce46b5678c55d8c1f17ee280fc3d6 100644 (file)
@@ -50,7 +50,7 @@ static int intel_dp_mst_compute_link_config(struct intel_encoder *encoder,
        const struct drm_display_mode *adjusted_mode =
                &crtc_state->hw.adjusted_mode;
        void *port = connector->port;
-       bool constant_n = drm_dp_has_quirk(&intel_dp->desc,
+       bool constant_n = drm_dp_has_quirk(&intel_dp->desc, 0,
                                           DP_DPCD_QUIRK_CONSTANT_N);
        int bpp, slots = -EINVAL;
 
index 7e754201f54d1313fcad2fb567d000196acdff46..fd9b146e3abac6146c0ae8867f7a0de68e44aac9 100644 (file)
@@ -304,7 +304,7 @@ void intel_psr_init_dpcd(struct intel_dp *intel_dp)
        drm_dbg_kms(&dev_priv->drm, "eDP panel supports PSR version %x\n",
                    intel_dp->psr_dpcd[0]);
 
-       if (drm_dp_has_quirk(&intel_dp->desc, DP_DPCD_QUIRK_NO_PSR)) {
+       if (drm_dp_has_quirk(&intel_dp->desc, 0, DP_DPCD_QUIRK_NO_PSR)) {
                drm_dbg_kms(&dev_priv->drm,
                            "PSR support not currently available for this panel\n");
                return;
index 262faf9e5e9400522325e47dd9ab0fa35e6a845d..7f5dd2ee4a9415d85d395938716d0898444419ee 100644 (file)
@@ -1495,13 +1495,16 @@ struct drm_dp_desc {
 
 int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc,
                     bool is_branch);
+u32 drm_dp_get_edid_quirks(const struct edid *edid);
 
 /**
  * enum drm_dp_quirk - Display Port sink/branch device specific quirks
  *
  * Display Port sink and branch devices in the wild have a variety of bugs, try
  * to collect them here. The quirks are shared, but it's up to the drivers to
- * implement workarounds for them.
+ * implement workarounds for them. Note that because some devices have
+ * unreliable OUIDs, the EDID of sinks should also be checked for quirks using
+ * drm_dp_get_edid_quirks().
  */
 enum drm_dp_quirk {
        /**
@@ -1537,14 +1540,16 @@ enum drm_dp_quirk {
 /**
  * drm_dp_has_quirk() - does the DP device have a specific quirk
  * @desc: Device decriptor filled by drm_dp_read_desc()
+ * @edid_quirks: Optional quirk bitmask filled by drm_dp_get_edid_quirks()
  * @quirk: Quirk to query for
  *
  * Return true if DP device identified by @desc has @quirk.
  */
 static inline bool
-drm_dp_has_quirk(const struct drm_dp_desc *desc, enum drm_dp_quirk quirk)
+drm_dp_has_quirk(const struct drm_dp_desc *desc, u32 edid_quirks,
+                enum drm_dp_quirk quirk)
 {
-       return desc->quirks & BIT(quirk);
+       return (desc->quirks | edid_quirks) & BIT(quirk);
 }
 
 #ifdef CONFIG_DRM_DP_CEC