aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/drm_edid_load.c
blob: d1c7e8298702e1605a89241a13843c0ce1aa6c2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// SPDX-License-Identifier: GPL-2.0-or-later
/*
   drm_edid_load.c: use a built-in EDID data set or load it via the firmware
		    interface

   Copyright (C) 2012 Carsten Emde <C.Emde@osadl.org>

*/

#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <drm/drm_connector.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>

#include "drm_crtc_internal.h"

static char edid_firmware[PATH_MAX];
module_param_string(edid_firmware, edid_firmware, sizeof(edid_firmware), 0644);
MODULE_PARM_DESC(edid_firmware,
		 "Do not probe monitor, use specified EDID blob from /lib/firmware instead.");

static const struct drm_edid *edid_load(struct drm_connector *connector, const char *name)
{
	const struct firmware *fw = NULL;
	const struct drm_edid *drm_edid;
	int err;

	err = request_firmware(&fw, name, connector->dev->dev);
	if (err) {
		drm_err(connector->dev,
			"[CONNECTOR:%d:%s] Requesting EDID firmware \"%s\" failed (err=%d)\n",
			connector->base.id, connector->name,
			name, err);
		return ERR_PTR(err);
	}

	drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Loaded external firmware EDID \"%s\"\n",
		    connector->base.id, connector->name, name);

	drm_edid = drm_edid_alloc(fw->data, fw->size);
	if (!drm_edid_valid(drm_edid)) {
		drm_err(connector->dev, "Invalid firmware EDID \"%s\"\n", name);
		drm_edid_free(drm_edid);
		drm_edid = ERR_PTR(-EINVAL);
	}

	release_firmware(fw);

	return drm_edid;
}

const struct drm_edid *drm_edid_load_firmware(struct drm_connector *connector)
{
	char *edidname, *last, *colon, *fwstr, *edidstr, *fallback = NULL;
	const struct drm_edid *drm_edid;

	if (edid_firmware[0] == '\0')
		return ERR_PTR(-ENOENT);

	/*
	 * If there are multiple edid files specified and separated
	 * by commas, search through the list looking for one that
	 * matches the connector.
	 *
	 * If there's one or more that doesn't specify a connector, keep
	 * the last one found one as a fallback.
	 */
	fwstr = kstrdup(edid_firmware, GFP_KERNEL);
	if (!fwstr)
		return ERR_PTR(-ENOMEM);
	edidstr = fwstr;

	while ((edidname = strsep(&edidstr, ","))) {
		colon = strchr(edidname, ':');
		if (colon != NULL) {
			if (strncmp(connector->name, edidname, colon - edidname))
				continue;
			edidname = colon + 1;
			break;
		}

		if (*edidname != '\0') /* corner case: multiple ',' */
			fallback = edidname;
	}

	if (!edidname) {
		if (!fallback) {
			kfree(fwstr);
			return ERR_PTR(-ENOENT);
		}
		edidname = fallback;
	}

	last = edidname + strlen(edidname) - 1;
	if (*last == '\n')
		*last = '\0';

	drm_edid = edid_load(connector, edidname);

	kfree(fwstr);

	return drm_edid;
}