drm/i915/opregion: add support for mailbox #5 EDID
authorAnisse Astier <anisse@astier.eu>
Wed, 29 Dec 2021 22:21:59 +0000 (23:21 +0100)
committerJani Nikula <jani.nikula@intel.com>
Fri, 31 Dec 2021 10:13:54 +0000 (12:13 +0200)
The ACPI OpRegion Mailbox #5 ASLE extension may contain an EDID to be
used for the embedded display. Add support for using it via by adding
the EDID to the list of available modes on the connector, and use it for
eDP when available.

If a panel's EDID is broken, there may be an override EDID set in the
ACPI OpRegion mailbox #5. Use it if available.

Fixes the GPD Win Max display.

Based on original patch series by: Jani Nikula <jani.nikula@intel.com>
https://patchwork.kernel.org/project/intel-gfx/patch/20200828061941.17051-1-jani.nikula@intel.com/

Changes:
 - EDID is copied and validated with drm_edid_is_valid
 - EDID is now only used as a fallback.
 - squashed the two patches

Cc: Jani Nikula <jani.nikula@intel.com>
Cc: Uma Shankar <uma.shankar@intel.com>
Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Co-developed-by: Jani Nikula <jani.nikula@intel.com>
Signed-off-by: Jani Nikula <jani.nikula@intel.com>
Signed-off-by: Anisse Astier <anisse@astier.eu>
Link: https://patchwork.freedesktop.org/patch/msgid/20211229222200.53128-2-anisse@astier.eu
drivers/gpu/drm/i915/display/intel_dp.c
drivers/gpu/drm/i915/display/intel_opregion.c
drivers/gpu/drm/i915/display/intel_opregion.h

index b5e2508db1cfef93d0cfd1b257649fc9c66be7a1..d6d8c9922feb61eab520e3541f42e0721057c867 100644 (file)
@@ -4974,6 +4974,14 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp,
 
        mutex_lock(&dev->mode_config.mutex);
        edid = drm_get_edid(connector, &intel_dp->aux.ddc);
+       if (!edid) {
+               /* Fallback to EDID from ACPI OpRegion, if any */
+               edid = intel_opregion_get_edid(intel_connector);
+               if (edid)
+                       drm_dbg_kms(&dev_priv->drm,
+                                   "[CONNECTOR:%d:%s] Using OpRegion EDID\n",
+                                   connector->base.id, connector->name);
+       }
        if (edid) {
                if (drm_add_edid_modes(connector, edid)) {
                        drm_connector_update_edid_property(connector, edid);
index 0065111593a6029e4d4169eb5c822632fff52522..985790a66a4dc91c1145d62748329fc5f4da4d8a 100644 (file)
@@ -195,6 +195,8 @@ struct opregion_asle_ext {
 #define ASLE_IUER_WINDOWS_BTN          (1 << 1)
 #define ASLE_IUER_POWER_BTN            (1 << 0)
 
+#define ASLE_PHED_EDID_VALID_MASK      0x3
+
 /* Software System Control Interrupt (SWSCI) */
 #define SWSCI_SCIC_INDICATOR           (1 << 0)
 #define SWSCI_SCIC_MAIN_FUNCTION_SHIFT 1
@@ -908,8 +910,10 @@ int intel_opregion_setup(struct drm_i915_private *dev_priv)
                opregion->asle->ardy = ASLE_ARDY_NOT_READY;
        }
 
-       if (mboxes & MBOX_ASLE_EXT)
+       if (mboxes & MBOX_ASLE_EXT) {
                drm_dbg(&dev_priv->drm, "ASLE extension supported\n");
+               opregion->asle_ext = base + OPREGION_ASLE_EXT_OFFSET;
+       }
 
        if (intel_load_vbt_firmware(dev_priv) == 0)
                goto out;
@@ -1036,6 +1040,54 @@ intel_opregion_get_panel_type(struct drm_i915_private *dev_priv)
        return ret - 1;
 }
 
+/**
+ * intel_opregion_get_edid - Fetch EDID from ACPI OpRegion mailbox #5
+ * @intel_connector: eDP connector
+ *
+ * This reads the ACPI Opregion mailbox #5 to extract the EDID that is passed
+ * to it.
+ *
+ * Returns:
+ * The EDID in the OpRegion, or NULL if there is none or it's invalid.
+ *
+ */
+struct edid *intel_opregion_get_edid(struct intel_connector *intel_connector)
+{
+       struct drm_connector *connector = &intel_connector->base;
+       struct drm_i915_private *i915 = to_i915(connector->dev);
+       struct intel_opregion *opregion = &i915->opregion;
+       const void *in_edid;
+       const struct edid *edid;
+       struct edid *new_edid;
+       int len;
+
+       if (!opregion->asle_ext)
+               return NULL;
+
+       in_edid = opregion->asle_ext->bddc;
+
+       /* Validity corresponds to number of 128-byte blocks */
+       len = (opregion->asle_ext->phed & ASLE_PHED_EDID_VALID_MASK) * 128;
+       if (!len || !memchr_inv(in_edid, 0, len))
+               return NULL;
+
+       edid = in_edid;
+
+       if (len < EDID_LENGTH * (1 + edid->extensions)) {
+               drm_dbg_kms(&i915->drm, "Invalid EDID in ACPI OpRegion (Mailbox #5): too short\n");
+               return NULL;
+       }
+       new_edid = drm_edid_duplicate(edid);
+       if (!new_edid)
+               return NULL;
+       if (!drm_edid_is_valid(new_edid)) {
+               kfree(new_edid);
+               drm_dbg_kms(&i915->drm, "Invalid EDID in ACPI OpRegion (Mailbox #5)\n");
+               return NULL;
+       }
+       return new_edid;
+}
+
 void intel_opregion_register(struct drm_i915_private *i915)
 {
        struct intel_opregion *opregion = &i915->opregion;
@@ -1129,6 +1181,7 @@ void intel_opregion_unregister(struct drm_i915_private *i915)
        opregion->acpi = NULL;
        opregion->swsci = NULL;
        opregion->asle = NULL;
+       opregion->asle_ext = NULL;
        opregion->vbt = NULL;
        opregion->lid_state = NULL;
 }
index 4aa68ffbd30eba9d895517b1cbe0c2c48c825578..82cc0ba34af7eaa07c360a9078a9f62f78b3ba9d 100644 (file)
 #include <linux/pci.h>
 
 struct drm_i915_private;
+struct intel_connector;
 struct intel_encoder;
 
 struct opregion_header;
 struct opregion_acpi;
 struct opregion_swsci;
 struct opregion_asle;
+struct opregion_asle_ext;
 
 struct intel_opregion {
        struct opregion_header *header;
@@ -43,6 +45,7 @@ struct intel_opregion {
        u32 swsci_gbda_sub_functions;
        u32 swsci_sbcb_sub_functions;
        struct opregion_asle *asle;
+       struct opregion_asle_ext *asle_ext;
        void *rvda;
        void *vbt_firmware;
        const void *vbt;
@@ -71,6 +74,7 @@ int intel_opregion_notify_encoder(struct intel_encoder *intel_encoder,
 int intel_opregion_notify_adapter(struct drm_i915_private *dev_priv,
                                  pci_power_t state);
 int intel_opregion_get_panel_type(struct drm_i915_private *dev_priv);
+struct edid *intel_opregion_get_edid(struct intel_connector *connector);
 
 #else /* CONFIG_ACPI*/
 
@@ -117,6 +121,12 @@ static inline int intel_opregion_get_panel_type(struct drm_i915_private *dev)
        return -ENODEV;
 }
 
+static inline struct edid *
+intel_opregion_get_edid(struct intel_connector *connector)
+{
+       return NULL;
+}
+
 #endif /* CONFIG_ACPI */
 
 #endif