1819060d5Szrj /*
2819060d5Szrj * Copyright (c) 2015 Rimvydas Jasinskas
3819060d5Szrj *
4819060d5Szrj * Simple EDID firmware handling routines derived from
5819060d5Szrj * drm_edid.c:drm_do_get_edid()
6819060d5Szrj *
7819060d5Szrj * Copyright (c) 2007-2008 Intel Corporation
8819060d5Szrj * Jesse Barnes <jesse.barnes@intel.com>
9819060d5Szrj * Copyright 2010 Red Hat, Inc.
10819060d5Szrj *
11819060d5Szrj * Permission is hereby granted, free of charge, to any person obtaining a
12819060d5Szrj * copy of this software and associated documentation files (the "Software"),
13819060d5Szrj * to deal in the Software without restriction, including without limitation
14819060d5Szrj * the rights to use, copy, modify, merge, publish, distribute, sub license,
15819060d5Szrj * and/or sell copies of the Software, and to permit persons to whom the
16819060d5Szrj * Software is furnished to do so, subject to the following conditions:
17819060d5Szrj *
18819060d5Szrj * The above copyright notice and this permission notice (including the
19819060d5Szrj * next paragraph) shall be included in all copies or substantial portions
20819060d5Szrj * of the Software.
21819060d5Szrj *
22819060d5Szrj * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23819060d5Szrj * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24819060d5Szrj * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
25819060d5Szrj * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26819060d5Szrj * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27819060d5Szrj * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28819060d5Szrj * DEALINGS IN THE SOFTWARE.
29819060d5Szrj */
30819060d5Szrj
31819060d5Szrj #include <drm/drmP.h>
32819060d5Szrj #include <drm/drm_edid.h>
33819060d5Szrj #include <sys/bus.h>
34819060d5Szrj #include <sys/firmware.h>
35819060d5Szrj
36819060d5Szrj #ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE
37819060d5Szrj static u8 *do_edid_fw_load(struct drm_connector *connector, const char *fwname,
38819060d5Szrj const char *connector_name);
39819060d5Szrj
40819060d5Szrj static char edidfw_tun[256];
41819060d5Szrj TUNABLE_STR("drm.edid_firmware", edidfw_tun, sizeof(edidfw_tun));
42819060d5Szrj
drm_load_edid_firmware(struct drm_connector * connector)43*a85cb24fSFrançois Tigeot struct edid *drm_load_edid_firmware(struct drm_connector *connector)
44819060d5Szrj {
45819060d5Szrj const char *connector_name = connector->name;
46819060d5Szrj char *cp, *fwname = edidfw_tun;
47819060d5Szrj struct edid *fwedid;
48819060d5Szrj
49819060d5Szrj if (*fwname == '\0')
50*a85cb24fSFrançois Tigeot return ERR_PTR(-ENOENT);
51819060d5Szrj
52819060d5Szrj /* Check for connector specifier presence */
53819060d5Szrj if ((cp = strchr(fwname, ':')) != NULL) {
54819060d5Szrj /* if connector name doesn't match, we're done */
55819060d5Szrj if (strncmp(connector_name, fwname, cp - fwname))
56*a85cb24fSFrançois Tigeot return ERR_PTR(-ENOENT);
57819060d5Szrj fwname = cp + 1;
58819060d5Szrj if (*fwname == '\0')
59*a85cb24fSFrançois Tigeot return ERR_PTR(-ENOENT);
60819060d5Szrj }
61819060d5Szrj
62819060d5Szrj fwedid = (struct edid *)do_edid_fw_load(connector, fwname, connector_name);
63819060d5Szrj
64*a85cb24fSFrançois Tigeot return fwedid;
65819060d5Szrj }
66819060d5Szrj
67819060d5Szrj static u8 *
do_edid_fw_load(struct drm_connector * connector,const char * fwname,const char * connector_name)68819060d5Szrj do_edid_fw_load(struct drm_connector *connector, const char *fwname,
69819060d5Szrj const char *connector_name)
70819060d5Szrj {
71819060d5Szrj const struct firmware *fw = NULL;
72819060d5Szrj const u8 *fwdata;
73819060d5Szrj u8 *block = NULL, *new = NULL;
74819060d5Szrj int fwsize, expected;
75819060d5Szrj int j, valid_extensions = 0;
76819060d5Szrj bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS);
77819060d5Szrj
78819060d5Szrj fw = firmware_get(fwname);
79819060d5Szrj
80819060d5Szrj if (fw == NULL) {
81819060d5Szrj DRM_ERROR("Requesting EDID firmware %s failed\n", fwname);
82819060d5Szrj return (NULL);
83819060d5Szrj }
84819060d5Szrj
85819060d5Szrj fwdata = fw->data;
86819060d5Szrj fwsize = fw->datasize;
87819060d5Szrj
88819060d5Szrj if (fwsize < EDID_LENGTH)
89819060d5Szrj goto fw_out;
90819060d5Szrj
91819060d5Szrj expected = (fwdata[0x7e] + 1) * EDID_LENGTH;
92819060d5Szrj if (expected != fwsize) {
93819060d5Szrj DRM_ERROR("Size of EDID firmware %s is invalid: %d vs %d(got)\n",
94819060d5Szrj fwname, expected, fwsize);
95819060d5Szrj goto fw_out;
96819060d5Szrj }
97819060d5Szrj
98819060d5Szrj block = kmalloc(fwsize, M_DRM, GFP_KERNEL);
99819060d5Szrj if (block == NULL) {
100819060d5Szrj goto fw_out;
101819060d5Szrj }
102819060d5Szrj memcpy(block, fwdata, fwsize);
103819060d5Szrj
104819060d5Szrj /* now it is safe to release the firmware */
105819060d5Szrj fw_out:
106819060d5Szrj fwdata = NULL;
107819060d5Szrj if (fw != NULL) {
108794ee81fSzrj /*
109794ee81fSzrj * Don't release edid fw right away, useful if / is
110794ee81fSzrj * still not mounted and/or we performing early kms
111794ee81fSzrj */
112794ee81fSzrj firmware_put(fw, 0);
113819060d5Szrj }
114819060d5Szrj
115819060d5Szrj if (block == NULL)
116819060d5Szrj return (NULL);
117819060d5Szrj
118819060d5Szrj /* first check the base block */
11919c468b4SFrançois Tigeot if (!drm_edid_block_valid(block, 0, print_bad_edid, NULL)) {
120819060d5Szrj connector->bad_edid_counter++;
121819060d5Szrj DRM_ERROR("EDID firmware %s base block is invalid ", fwname);
122819060d5Szrj goto out;
123819060d5Szrj }
124819060d5Szrj
125819060d5Szrj DRM_INFO("Got EDID base block from %s for connector %s\n", fwname, connector_name);
126819060d5Szrj
127819060d5Szrj /* if there's no extensions, we're done */
128819060d5Szrj if (block[0x7e] == 0)
129819060d5Szrj return block;
130819060d5Szrj
131819060d5Szrj /* XXX then extension blocks */
132819060d5Szrj WARN(1, "Loading EDID firmware with extensions is untested!\n");
133819060d5Szrj
134819060d5Szrj for (j = 1; j <= block[0x7e]; j++) {
135819060d5Szrj /* if we skiped any extension block we have to shuffle good ones */
136819060d5Szrj if (j != valid_extensions + 1) {
137819060d5Szrj memcpy(block + (valid_extensions + 1) * EDID_LENGTH,
138819060d5Szrj block + (j * EDID_LENGTH), EDID_LENGTH);
139819060d5Szrj }
14019c468b4SFrançois Tigeot if (drm_edid_block_valid(block + j * EDID_LENGTH, j, print_bad_edid, NULL)) {
141819060d5Szrj valid_extensions++;
142819060d5Szrj }
143819060d5Szrj }
144819060d5Szrj
145819060d5Szrj if (valid_extensions != block[0x7e]) {
146819060d5Szrj block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
147819060d5Szrj block[0x7e] = valid_extensions;
148819060d5Szrj new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, M_DRM, M_WAITOK);
149819060d5Szrj if (new == NULL)
150819060d5Szrj goto out;
151819060d5Szrj block = new;
152819060d5Szrj }
153819060d5Szrj
154819060d5Szrj if (valid_extensions > 0) {
155819060d5Szrj DRM_INFO("Got %d extensions in EDID firmware from %s for connector %s\n",
156819060d5Szrj valid_extensions, fwname, connector_name);
157819060d5Szrj }
158819060d5Szrj
159819060d5Szrj /* if got to here return edid block */
160819060d5Szrj return block;
161819060d5Szrj
162819060d5Szrj out:
163819060d5Szrj kfree(block);
164819060d5Szrj return (NULL);
165819060d5Szrj }
166819060d5Szrj
167819060d5Szrj #endif /* CONFIG_DRM_LOAD_EDID_FIRMWARE */
168