#include <drm/drmP.h>
#include <drm/drm_edid.h>
#include <sys/bus.h>
#include <sys/firmware.h>
#ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE
static u8 *do_edid_fw_load(struct drm_connector *connector, const char *fwname,
const char *connector_name);
static char edidfw_tun[256];
TUNABLE_STR("drm.edid_firmware", edidfw_tun, sizeof(edidfw_tun));
struct edid *drm_load_edid_firmware(struct drm_connector *connector)
{
const char *connector_name = connector->name;
char *cp, *fwname = edidfw_tun;
struct edid *fwedid;
if (*fwname == '\0')
return ERR_PTR(-ENOENT);
if ((cp = strchr(fwname, ':')) != NULL) {
if (strncmp(connector_name, fwname, cp - fwname))
return ERR_PTR(-ENOENT);
fwname = cp + 1;
if (*fwname == '\0')
return ERR_PTR(-ENOENT);
}
fwedid = (struct edid *)do_edid_fw_load(connector, fwname, connector_name);
return fwedid;
}
static u8 *
do_edid_fw_load(struct drm_connector *connector, const char *fwname,
const char *connector_name)
{
const struct firmware *fw = NULL;
const u8 *fwdata;
u8 *block = NULL, *new = NULL;
int fwsize, expected;
int j, valid_extensions = 0;
bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS);
fw = firmware_get(fwname);
if (fw == NULL) {
DRM_ERROR("Requesting EDID firmware %s failed\n", fwname);
return (NULL);
}
fwdata = fw->data;
fwsize = fw->datasize;
if (fwsize < EDID_LENGTH)
goto fw_out;
expected = (fwdata[0x7e] + 1) * EDID_LENGTH;
if (expected != fwsize) {
DRM_ERROR("Size of EDID firmware %s is invalid: %d vs %d(got)\n",
fwname, expected, fwsize);
goto fw_out;
}
block = kmalloc(fwsize, M_DRM, GFP_KERNEL);
if (block == NULL) {
goto fw_out;
}
memcpy(block, fwdata, fwsize);
fw_out:
fwdata = NULL;
if (fw != NULL) {
firmware_put(fw, 0);
}
if (block == NULL)
return (NULL);
if (!drm_edid_block_valid(block, 0, print_bad_edid, NULL)) {
connector->bad_edid_counter++;
DRM_ERROR("EDID firmware %s base block is invalid ", fwname);
goto out;
}
DRM_INFO("Got EDID base block from %s for connector %s\n", fwname, connector_name);
if (block[0x7e] == 0)
return block;
WARN(1, "Loading EDID firmware with extensions is untested!\n");
for (j = 1; j <= block[0x7e]; j++) {
if (j != valid_extensions + 1) {
memcpy(block + (valid_extensions + 1) * EDID_LENGTH,
block + (j * EDID_LENGTH), EDID_LENGTH);
}
if (drm_edid_block_valid(block + j * EDID_LENGTH, j, print_bad_edid, NULL)) {
valid_extensions++;
}
}
if (valid_extensions != block[0x7e]) {
block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
block[0x7e] = valid_extensions;
new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, M_DRM, M_WAITOK);
if (new == NULL)
goto out;
block = new;
}
if (valid_extensions > 0) {
DRM_INFO("Got %d extensions in EDID firmware from %s for connector %s\n",
valid_extensions, fwname, connector_name);
}
return block;
out:
kfree(block);
return (NULL);
}
#endif