Merge tag 'topic/phy-compliance-2020-04-08' of git://anongit.freedesktop.org/drm...
[platform/kernel/linux-rpi.git] / drivers / gpu / drm / drm_dp_helper.c
index a5364b5..612a59e 100644 (file)
@@ -362,6 +362,65 @@ int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
 EXPORT_SYMBOL(drm_dp_dpcd_read_link_status);
 
 /**
+ * drm_dp_send_real_edid_checksum() - send back real edid checksum value
+ * @aux: DisplayPort AUX channel
+ * @real_edid_checksum: real edid checksum for the last block
+ *
+ * Returns:
+ * True on success
+ */
+bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux,
+                                   u8 real_edid_checksum)
+{
+       u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0;
+
+       if (drm_dp_dpcd_read(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
+                            &auto_test_req, 1) < 1) {
+               DRM_ERROR("DPCD failed read at register 0x%x\n",
+                         DP_DEVICE_SERVICE_IRQ_VECTOR);
+               return false;
+       }
+       auto_test_req &= DP_AUTOMATED_TEST_REQUEST;
+
+       if (drm_dp_dpcd_read(aux, DP_TEST_REQUEST, &link_edid_read, 1) < 1) {
+               DRM_ERROR("DPCD failed read at register 0x%x\n",
+                         DP_TEST_REQUEST);
+               return false;
+       }
+       link_edid_read &= DP_TEST_LINK_EDID_READ;
+
+       if (!auto_test_req || !link_edid_read) {
+               DRM_DEBUG_KMS("Source DUT does not support TEST_EDID_READ\n");
+               return false;
+       }
+
+       if (drm_dp_dpcd_write(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
+                             &auto_test_req, 1) < 1) {
+               DRM_ERROR("DPCD failed write at register 0x%x\n",
+                         DP_DEVICE_SERVICE_IRQ_VECTOR);
+               return false;
+       }
+
+       /* send back checksum for the last edid extension block data */
+       if (drm_dp_dpcd_write(aux, DP_TEST_EDID_CHECKSUM,
+                             &real_edid_checksum, 1) < 1) {
+               DRM_ERROR("DPCD failed write at register 0x%x\n",
+                         DP_TEST_EDID_CHECKSUM);
+               return false;
+       }
+
+       test_resp |= DP_TEST_EDID_CHECKSUM_WRITE;
+       if (drm_dp_dpcd_write(aux, DP_TEST_RESPONSE, &test_resp, 1) < 1) {
+               DRM_ERROR("DPCD failed write at register 0x%x\n",
+                         DP_TEST_RESPONSE);
+               return false;
+       }
+
+       return true;
+}
+EXPORT_SYMBOL(drm_dp_send_real_edid_checksum);
+
+/**
  * drm_dp_downstream_max_clock() - extract branch device max
  *                                 pixel rate for legacy VGA
  *                                 converter or max TMDS clock
@@ -470,8 +529,7 @@ void drm_dp_downstream_debug(struct seq_file *m,
        int len;
        uint8_t rev[2];
        int type = port_cap[0] & DP_DS_PORT_TYPE_MASK;
-       bool branch_device = dpcd[DP_DOWNSTREAMPORT_PRESENT] &
-                            DP_DWN_STRM_PORT_PRESENT;
+       bool branch_device = drm_dp_is_branch(dpcd);
 
        seq_printf(m, "\tDP branch device present: %s\n",
                   branch_device ? "yes" : "no");
@@ -1180,6 +1238,8 @@ static const struct dpcd_quirk dpcd_quirk_list[] = {
        { OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) },
        /* Synaptics DP1.4 MST hubs can support DSC without virtual DPCD */
        { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) },
+       /* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */
+       { OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) },
 };
 
 #undef OUI
@@ -1222,6 +1282,85 @@ 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[] = {
+       /* Optional 4K AMOLED panel in the ThinkPad X1 Extreme 2nd Generation
+        * only supports DPCD backlight controls
+        */
+       { MFG(0x4c, 0x83), PROD_ID(0x41, 0x41), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
+       /*
+        * Some Dell CML 2020 systems have panels support both AUX and PWM
+        * backlight control, and some only support AUX backlight control. All
+        * said panels start up in AUX mode by default, and we don't have any
+        * support for disabling HDR mode on these panels which would be
+        * required to switch to PWM backlight control mode (plus, I'm not
+        * even sure we want PWM backlight controls over DPCD backlight
+        * controls anyway...). Until we have a better way of detecting these,
+        * force DPCD backlight mode on all of them.
+        */
+       { MFG(0x06, 0xaf), PROD_ID(0x9b, 0x32), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
+       { MFG(0x06, 0xaf), PROD_ID(0xeb, 0x41), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
+       { MFG(0x4d, 0x10), PROD_ID(0xc7, 0x14), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
+       { MFG(0x4d, 0x10), PROD_ID(0xe6, 0x14), BIT(DP_QUIRK_FORCE_DPCD_BACKLIGHT) },
+};
+
+#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
@@ -1396,3 +1535,97 @@ int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_S
        return num_bpc;
 }
 EXPORT_SYMBOL(drm_dp_dsc_sink_supported_input_bpcs);
+
+/**
+ * drm_dp_get_phy_test_pattern() - get the requested pattern from the sink.
+ * @aux: DisplayPort AUX channel
+ * @data: DP phy compliance test parameters.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux,
+                               struct drm_dp_phy_test_params *data)
+{
+       int err;
+       u8 rate, lanes;
+
+       err = drm_dp_dpcd_readb(aux, DP_TEST_LINK_RATE, &rate);
+       if (err < 0)
+               return err;
+       data->link_rate = drm_dp_bw_code_to_link_rate(rate);
+
+       err = drm_dp_dpcd_readb(aux, DP_TEST_LANE_COUNT, &lanes);
+       if (err < 0)
+               return err;
+       data->num_lanes = lanes & DP_MAX_LANE_COUNT_MASK;
+
+       if (lanes & DP_ENHANCED_FRAME_CAP)
+               data->enhanced_frame_cap = true;
+
+       err = drm_dp_dpcd_readb(aux, DP_PHY_TEST_PATTERN, &data->phy_pattern);
+       if (err < 0)
+               return err;
+
+       switch (data->phy_pattern) {
+       case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
+               err = drm_dp_dpcd_read(aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0,
+                                      &data->custom80, sizeof(data->custom80));
+               if (err < 0)
+                       return err;
+
+               break;
+       case DP_PHY_TEST_PATTERN_CP2520:
+               err = drm_dp_dpcd_read(aux, DP_TEST_HBR2_SCRAMBLER_RESET,
+                                      &data->hbr2_reset,
+                                      sizeof(data->hbr2_reset));
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_dp_get_phy_test_pattern);
+
+/**
+ * drm_dp_set_phy_test_pattern() - set the pattern to the sink.
+ * @aux: DisplayPort AUX channel
+ * @data: DP phy compliance test parameters.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux,
+                               struct drm_dp_phy_test_params *data, u8 dp_rev)
+{
+       int err, i;
+       u8 link_config[2];
+       u8 test_pattern;
+
+       link_config[0] = drm_dp_link_rate_to_bw_code(data->link_rate);
+       link_config[1] = data->num_lanes;
+       if (data->enhanced_frame_cap)
+               link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+       err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, link_config, 2);
+       if (err < 0)
+               return err;
+
+       test_pattern = data->phy_pattern;
+       if (dp_rev < 0x12) {
+               test_pattern = (test_pattern << 2) &
+                              DP_LINK_QUAL_PATTERN_11_MASK;
+               err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET,
+                                        test_pattern);
+               if (err < 0)
+                       return err;
+       } else {
+               for (i = 0; i < data->num_lanes; i++) {
+                       err = drm_dp_dpcd_writeb(aux,
+                                                DP_LINK_QUAL_LANE0_SET + i,
+                                                test_pattern);
+                       if (err < 0)
+                               return err;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_dp_set_phy_test_pattern);