summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_edid.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/drm_edid.c')
-rw-r--r--drivers/gpu/drm/drm_edid.c147
1 files changed, 114 insertions, 33 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 923c4423151c..ea77577a3786 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -102,6 +102,11 @@ struct detailed_mode_closure {
int modes;
};
+struct drm_edid_match_closure {
+ const struct drm_edid_ident *ident;
+ bool matched;
+};
+
#define LEVEL_DMT 0
#define LEVEL_GTF 1
#define LEVEL_GTF2 2
@@ -109,13 +114,15 @@ struct detailed_mode_closure {
#define EDID_QUIRK(vend_chr_0, vend_chr_1, vend_chr_2, product_id, _quirks) \
{ \
- .panel_id = drm_edid_encode_panel_id(vend_chr_0, vend_chr_1, vend_chr_2, \
- product_id), \
+ .ident = { \
+ .panel_id = drm_edid_encode_panel_id(vend_chr_0, vend_chr_1, \
+ vend_chr_2, product_id), \
+ }, \
.quirks = _quirks \
}
static const struct edid_quirk {
- u32 panel_id;
+ const struct drm_edid_ident ident;
u32 quirks;
} edid_quirk_list[] = {
/* Acer AL1706 */
@@ -2749,8 +2756,27 @@ const struct drm_edid *drm_edid_read(struct drm_connector *connector)
}
EXPORT_SYMBOL(drm_edid_read);
-static u32 edid_extract_panel_id(const struct edid *edid)
+/**
+ * drm_edid_get_panel_id - Get a panel's ID from EDID
+ * @drm_edid: EDID that contains panel ID.
+ *
+ * This function uses the first block of the EDID of a panel and (assuming
+ * that the EDID is valid) extracts the ID out of it. The ID is a 32-bit value
+ * (16 bits of manufacturer ID and 16 bits of per-manufacturer ID) that's
+ * supposed to be different for each different modem of panel.
+ *
+ * Return: A 32-bit ID that should be different for each make/model of panel.
+ * See the functions drm_edid_encode_panel_id() and
+ * drm_edid_decode_panel_id() for some details on the structure of this
+ * ID. Return 0 if the EDID size is less than a base block.
+ */
+u32 drm_edid_get_panel_id(const struct drm_edid *drm_edid)
{
+ const struct edid *edid = drm_edid->edid;
+
+ if (drm_edid->size < EDID_LENGTH)
+ return 0;
+
/*
* We represent the ID as a 32-bit number so it can easily be compared
* with "==".
@@ -2768,60 +2794,54 @@ static u32 edid_extract_panel_id(const struct edid *edid)
(u32)edid->mfg_id[1] << 16 |
(u32)EDID_PRODUCT_ID(edid);
}
+EXPORT_SYMBOL(drm_edid_get_panel_id);
/**
- * drm_edid_get_panel_id - Get a panel's ID through DDC
+ * drm_edid_read_base_block - Get a panel's EDID base block
* @adapter: I2C adapter to use for DDC
*
- * This function reads the first block of the EDID of a panel and (assuming
- * that the EDID is valid) extracts the ID out of it. The ID is a 32-bit value
- * (16 bits of manufacturer ID and 16 bits of per-manufacturer ID) that's
- * supposed to be different for each different modem of panel.
+ * This function returns the drm_edid containing the first block of the EDID of
+ * a panel.
*
* This function is intended to be used during early probing on devices where
* more than one panel might be present. Because of its intended use it must
- * assume that the EDID of the panel is correct, at least as far as the ID
- * is concerned (in other words, we don't process any overrides here).
+ * assume that the EDID of the panel is correct, at least as far as the base
+ * block is concerned (in other words, we don't process any overrides here).
+ *
+ * Caller should call drm_edid_free() after use.
*
* NOTE: it's expected that this function and drm_do_get_edid() will both
* be read the EDID, but there is no caching between them. Since we're only
* reading the first block, hopefully this extra overhead won't be too big.
*
- * Return: A 32-bit ID that should be different for each make/model of panel.
- * See the functions drm_edid_encode_panel_id() and
- * drm_edid_decode_panel_id() for some details on the structure of this
- * ID.
+ * WARNING: Only use this function when the connector is unknown. For example,
+ * during the early probe of panel. The EDID read from the function is temporary
+ * and should be replaced by the full EDID returned from other drm_edid_read.
+ *
+ * Return: Pointer to allocated EDID base block, or NULL on any failure.
*/
-
-u32 drm_edid_get_panel_id(struct i2c_adapter *adapter)
+const struct drm_edid *drm_edid_read_base_block(struct i2c_adapter *adapter)
{
enum edid_block_status status;
void *base_block;
- u32 panel_id = 0;
-
- /*
- * There are no manufacturer IDs of 0, so if there is a problem reading
- * the EDID then we'll just return 0.
- */
base_block = kzalloc(EDID_LENGTH, GFP_KERNEL);
if (!base_block)
- return 0;
+ return NULL;
status = edid_block_read(base_block, 0, drm_do_probe_ddc_edid, adapter);
edid_block_status_print(status, base_block, 0);
- if (edid_block_status_valid(status, edid_block_tag(base_block)))
- panel_id = edid_extract_panel_id(base_block);
- else
+ if (!edid_block_status_valid(status, edid_block_tag(base_block))) {
edid_block_dump(KERN_NOTICE, base_block, 0);
+ kfree(base_block);
+ return NULL;
+ }
- kfree(base_block);
-
- return panel_id;
+ return _drm_edid_alloc(base_block, EDID_LENGTH);
}
-EXPORT_SYMBOL(drm_edid_get_panel_id);
+EXPORT_SYMBOL(drm_edid_read_base_block);
/**
* drm_get_edid_switcheroo - get EDID data for a vga_switcheroo output
@@ -2903,16 +2923,17 @@ EXPORT_SYMBOL(drm_edid_duplicate);
* @drm_edid: EDID to process
*
* This tells subsequent routines what fixes they need to apply.
+ *
+ * Return: A u32 represents the quirks to apply.
*/
static u32 edid_get_quirks(const struct drm_edid *drm_edid)
{
- u32 panel_id = edid_extract_panel_id(drm_edid->edid);
const struct edid_quirk *quirk;
int i;
for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {
quirk = &edid_quirk_list[i];
- if (quirk->panel_id == panel_id)
+ if (drm_edid_match(drm_edid, &quirk->ident))
return quirk->quirks;
}
@@ -5443,6 +5464,66 @@ drm_parse_hdmi_vsdb_audio(struct drm_connector *connector, const u8 *db)
}
static void
+match_identity(const struct detailed_timing *timing, void *data)
+{
+ struct drm_edid_match_closure *closure = data;
+ unsigned int i;
+ const char *name = closure->ident->name;
+ unsigned int name_len = strlen(name);
+ const char *desc = timing->data.other_data.data.str.str;
+ unsigned int desc_len = ARRAY_SIZE(timing->data.other_data.data.str.str);
+
+ if (name_len > desc_len ||
+ !(is_display_descriptor(timing, EDID_DETAIL_MONITOR_NAME) ||
+ is_display_descriptor(timing, EDID_DETAIL_MONITOR_STRING)))
+ return;
+
+ if (strncmp(name, desc, name_len))
+ return;
+
+ for (i = name_len; i < desc_len; i++) {
+ if (desc[i] == '\n')
+ break;
+ /* Allow white space before EDID string terminator. */
+ if (!isspace(desc[i]))
+ return;
+ }
+
+ closure->matched = true;
+}
+
+/**
+ * drm_edid_match - match drm_edid with given identity
+ * @drm_edid: EDID
+ * @ident: the EDID identity to match with
+ *
+ * Check if the EDID matches with the given identity.
+ *
+ * Return: True if the given identity matched with EDID, false otherwise.
+ */
+bool drm_edid_match(const struct drm_edid *drm_edid,
+ const struct drm_edid_ident *ident)
+{
+ if (!drm_edid || drm_edid_get_panel_id(drm_edid) != ident->panel_id)
+ return false;
+
+ /* Match with name only if it's not NULL. */
+ if (ident->name) {
+ struct drm_edid_match_closure closure = {
+ .ident = ident,
+ .matched = false,
+ };
+
+ drm_for_each_detailed_block(drm_edid, match_identity, &closure);
+
+ return closure.matched;
+ }
+
+ return true;
+}
+EXPORT_SYMBOL(drm_edid_match);
+
+static void
monitor_name(const struct detailed_timing *timing, void *data)
{
const char **res = data;