drm: Implement simple broken EDID override by loading firmware (v16)
[dragonfly.git] / sys / dev / drm / drm_edid_load.c
1 /*
2  * Copyright (c) 2015 Rimvydas Jasinskas
3  *
4  * Simple EDID firmware handling routines derived from
5  * drm_edid.c:drm_do_get_edid()
6  *
7  * Copyright (c) 2007-2008 Intel Corporation
8  *   Jesse Barnes <jesse.barnes@intel.com>
9  * Copyright 2010 Red Hat, Inc.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sub license,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice (including the
19  * next paragraph) shall be included in all copies or substantial portions
20  * of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  */
30
31 #include <drm/drmP.h>
32 #include <drm/drm_edid.h>
33 #include <sys/bus.h>
34 #include <sys/firmware.h>
35
36 #ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE
37 static u8 *do_edid_fw_load(struct drm_connector *connector, const char *fwname,
38                            const char *connector_name);
39
40 static char edidfw_tun[256];
41 TUNABLE_STR("drm.edid_firmware", edidfw_tun, sizeof(edidfw_tun));
42
43 int drm_load_edid_firmware(struct drm_connector *connector)
44 {
45         const char *connector_name = connector->name;
46         char *cp, *fwname = edidfw_tun;
47         struct edid *fwedid;
48         int ret;
49
50         if (*fwname == '\0')
51                 return 0;
52
53         /* Check for connector specifier presence */
54         if ((cp = strchr(fwname, ':')) != NULL) {
55                 /* if connector name doesn't match, we're done */
56                 if (strncmp(connector_name, fwname, cp - fwname))
57                         return 0;
58                 fwname = cp + 1;
59                 if (*fwname == '\0')
60                         return 0;
61         }
62
63         fwedid = (struct edid *)do_edid_fw_load(connector, fwname, connector_name);
64         if (fwedid == NULL)
65                 return 0;
66
67         drm_mode_connector_update_edid_property(connector, fwedid);
68         ret = drm_add_edid_modes(connector, fwedid);
69         kfree(fwedid);
70
71         return ret;
72 }
73
74 static u8 *
75 do_edid_fw_load(struct drm_connector *connector, const char *fwname,
76                         const char *connector_name)
77 {
78         const struct firmware *fw = NULL;
79         const u8 *fwdata;
80         u8 *block = NULL, *new = NULL;
81         int fwsize, expected;
82         int j, valid_extensions = 0;
83         bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS);
84
85         fw = firmware_get(fwname);
86
87         if (fw == NULL) {
88                 DRM_ERROR("Requesting EDID firmware %s failed\n", fwname);
89                 return (NULL);
90         }
91
92         fwdata = fw->data;
93         fwsize = fw->datasize;
94
95         if (fwsize < EDID_LENGTH)
96                 goto fw_out;
97
98         expected = (fwdata[0x7e] + 1) * EDID_LENGTH;
99         if (expected != fwsize) {
100                 DRM_ERROR("Size of EDID firmware %s is invalid: %d vs %d(got)\n",
101                           fwname, expected, fwsize);
102                 goto fw_out;
103         }
104
105         block = kmalloc(fwsize, M_DRM, GFP_KERNEL);
106         if (block == NULL) {
107                 goto fw_out;
108         }
109         memcpy(block, fwdata, fwsize);
110
111         /* now it is safe to release the firmware */
112 fw_out:
113         fwdata = NULL;
114         if (fw != NULL) {
115                 firmware_put(fw, FIRMWARE_UNLOAD);
116         }
117
118         if (block == NULL)
119                 return (NULL);
120
121         /* first check the base block */
122         if (!drm_edid_block_valid(block, 0, print_bad_edid)) {
123                 connector->bad_edid_counter++;
124                 DRM_ERROR("EDID firmware %s base block is invalid ", fwname);
125                 goto out;
126         }
127
128         DRM_INFO("Got EDID base block from %s for connector %s\n", fwname, connector_name);
129
130         /* if there's no extensions, we're done */
131         if (block[0x7e] == 0)
132                 return block;
133
134         /* XXX then extension blocks */
135         WARN(1, "Loading EDID firmware with extensions is untested!\n");
136
137         for (j = 1; j <= block[0x7e]; j++) {
138                 /* if we skiped any extension block we have to shuffle good ones */
139                 if (j != valid_extensions + 1) {
140                         memcpy(block + (valid_extensions + 1) * EDID_LENGTH,
141                                block + (j * EDID_LENGTH), EDID_LENGTH);
142                 }
143                 if (drm_edid_block_valid(block + j * EDID_LENGTH, j, print_bad_edid)) {
144                         valid_extensions++;
145                 }
146         }
147
148         if (valid_extensions != block[0x7e]) {
149                 block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
150                 block[0x7e] = valid_extensions;
151                 new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, M_DRM, M_WAITOK);
152                 if (new == NULL)
153                         goto out;
154                 block = new;
155         }
156
157         if (valid_extensions > 0) {
158                 DRM_INFO("Got %d extensions in EDID firmware from %s for connector %s\n",
159                          valid_extensions, fwname, connector_name);
160         }
161
162         /* if got to here return edid block */
163         return block;
164
165 out:
166         kfree(block);
167         return (NULL);
168 }
169
170 #endif /* CONFIG_DRM_LOAD_EDID_FIRMWARE */