From patchwork Wed Aug 21 18:50:02 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 11108039 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 6C2801399 for ; Wed, 21 Aug 2019 18:50:37 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id B09F322DD3 for ; Wed, 21 Aug 2019 18:50:35 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org B09F322DD3 Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=dri-devel-bounces@lists.freedesktop.org Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id CD5B26E99A; Wed, 21 Aug 2019 18:50:34 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by gabe.freedesktop.org (Postfix) with ESMTPS id 46D506E970 for ; Wed, 21 Aug 2019 18:50:17 +0000 (UTC) Received: from pendragon.bb.dnainternet.fi (dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi [IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EF060816; Wed, 21 Aug 2019 20:50:14 +0200 (CEST) From: Laurent Pinchart To: dri-devel@lists.freedesktop.org Subject: [PATCH/RFC 2/5] drm/edid: Move functions to avoid forward declaration Date: Wed, 21 Aug 2019 21:50:02 +0300 Message-Id: <20190821185005.9789-3-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190821185005.9789-1-laurent.pinchart@ideasonboard.com> References: <20190821185005.9789-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 X-Mailman-Original-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1566413415; bh=BrGRKBv/3AMeX3lrW9J2Vq2pVFa3BaqbrJqkulHT7Bg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HygFy53sei7w0OCmydPwiKcVepvWDBbTCMPaPaDro8A0+cQw0HcitJT4BoacmdRK+ eyD7w7MfwALmsU0Z72MrB6Acs9Hbmqs3e/gpluDaH/lUDPHIG5J3oxjwfZvbkXKocj BZDGe8/MZOuD428jymNKf8V2XNS0In3Wc9XEnSdE= X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Tomi Valkeinen Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Move the EDID retrieval functions to the end of the file to avoid forward declarations. While at it fix a typo in a comment (s/firmare/firmware/). No functional change is included. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/drm_edid.c | 637 ++++++++++++++++++------------------- 1 file changed, 317 insertions(+), 320 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 7c6bc5183b60..bfcb232b9760 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1340,9 +1340,6 @@ module_param_named(edid_fixup, edid_fixup, int, 0400); MODULE_PARM_DESC(edid_fixup, "Minimum number of valid EDID header bytes (0-8, default 6)"); -static void drm_get_displayid(struct drm_connector *connector, - struct edid *edid); - static int drm_edid_block_checksum(const u8 *raw_edid) { int i; @@ -1481,323 +1478,6 @@ bool drm_edid_is_valid(struct edid *edid) } EXPORT_SYMBOL(drm_edid_is_valid); -#define DDC_SEGMENT_ADDR 0x30 -/** - * drm_do_probe_ddc_edid() - get EDID information via I2C - * @data: I2C device adapter - * @buf: EDID data buffer to be filled - * @block: 128 byte EDID block to start fetching from - * @len: EDID data buffer length to fetch - * - * Try to fetch EDID information by calling I2C driver functions. - * - * Return: 0 on success or -1 on failure. - */ -static int -drm_do_probe_ddc_edid(void *data, u8 *buf, unsigned int block, size_t len) -{ - struct i2c_adapter *adapter = data; - unsigned char start = block * EDID_LENGTH; - unsigned char segment = block >> 1; - unsigned char xfers = segment ? 3 : 2; - int ret, retries = 5; - - /* - * The core I2C driver will automatically retry the transfer if the - * adapter reports EAGAIN. However, we find that bit-banging transfers - * are susceptible to errors under a heavily loaded machine and - * generate spurious NAKs and timeouts. Retrying the transfer - * of the individual block a few times seems to overcome this. - */ - do { - struct i2c_msg msgs[] = { - { - .addr = DDC_SEGMENT_ADDR, - .flags = 0, - .len = 1, - .buf = &segment, - }, { - .addr = DDC_ADDR, - .flags = 0, - .len = 1, - .buf = &start, - }, { - .addr = DDC_ADDR, - .flags = I2C_M_RD, - .len = len, - .buf = buf, - } - }; - - /* - * Avoid sending the segment addr to not upset non-compliant - * DDC monitors. - */ - ret = i2c_transfer(adapter, &msgs[3 - xfers], xfers); - - if (ret == -ENXIO) { - DRM_DEBUG_KMS("drm: skipping non-existent adapter %s\n", - adapter->name); - break; - } - } while (ret != xfers && --retries); - - return ret == xfers ? 0 : -1; -} - -static void connector_bad_edid(struct drm_connector *connector, - u8 *edid, int num_blocks) -{ - int i; - - if (connector->bad_edid_counter++ && !(drm_debug & DRM_UT_KMS)) - return; - - dev_warn(connector->dev->dev, - "%s: EDID is invalid:\n", - connector->name); - for (i = 0; i < num_blocks; i++) { - u8 *block = edid + i * EDID_LENGTH; - char prefix[20]; - - if (drm_edid_is_zero(block, EDID_LENGTH)) - sprintf(prefix, "\t[%02x] ZERO ", i); - else if (!drm_edid_block_valid(block, i, false, NULL)) - sprintf(prefix, "\t[%02x] BAD ", i); - else - sprintf(prefix, "\t[%02x] GOOD ", i); - - print_hex_dump(KERN_WARNING, - prefix, DUMP_PREFIX_NONE, 16, 1, - block, EDID_LENGTH, false); - } -} - -/* Get override or firmware EDID */ -static struct edid *drm_get_override_edid(struct drm_connector *connector) -{ - struct edid *override = NULL; - - if (connector->override_edid) - override = drm_edid_duplicate(connector->edid_blob_ptr->data); - - if (!override) - override = drm_load_edid_firmware(connector); - - return IS_ERR(override) ? NULL : override; -} - -/** - * drm_add_override_edid_modes - add modes from override/firmware EDID - * @connector: connector we're probing - * - * Add modes from the override/firmware EDID, if available. Only to be used from - * drm_helper_probe_single_connector_modes() as a fallback for when DDC probe - * failed during drm_get_edid() and caused the override/firmware EDID to be - * skipped. - * - * Return: The number of modes added or 0 if we couldn't find any. - */ -int drm_add_override_edid_modes(struct drm_connector *connector) -{ - struct edid *override; - int num_modes = 0; - - override = drm_get_override_edid(connector); - if (override) { - drm_connector_update_edid_property(connector, override); - num_modes = drm_add_edid_modes(connector, override); - kfree(override); - - DRM_DEBUG_KMS("[CONNECTOR:%d:%s] adding %d modes via fallback override/firmware EDID\n", - connector->base.id, connector->name, num_modes); - } - - return num_modes; -} -EXPORT_SYMBOL(drm_add_override_edid_modes); - -/** - * drm_do_get_edid - get EDID data using a custom EDID block read function - * @connector: connector we're probing - * @get_edid_block: EDID block read function - * @data: private data passed to the block read function - * - * When the I2C adapter connected to the DDC bus is hidden behind a device that - * exposes a different interface to read EDID blocks this function can be used - * to get EDID data using a custom block read function. - * - * As in the general case the DDC bus is accessible by the kernel at the I2C - * level, drivers must make all reasonable efforts to expose it as an I2C - * adapter and use drm_get_edid() instead of abusing this function. - * - * The EDID may be overridden using debugfs override_edid or firmare EDID - * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority - * order. Having either of them bypasses actual EDID reads. - * - * Return: Pointer to valid EDID or NULL if we couldn't find any. - */ -struct edid *drm_do_get_edid(struct drm_connector *connector, - int (*get_edid_block)(void *data, u8 *buf, unsigned int block, - size_t len), - void *data) -{ - int i, j = 0, valid_extensions = 0; - u8 *edid, *new; - struct edid *override; - - override = drm_get_override_edid(connector); - if (override) - return override; - - if ((edid = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) - return NULL; - - /* base block fetch */ - for (i = 0; i < 4; i++) { - if (get_edid_block(data, edid, 0, EDID_LENGTH)) - goto out; - if (drm_edid_block_valid(edid, 0, false, - &connector->edid_corrupt)) - break; - if (i == 0 && drm_edid_is_zero(edid, EDID_LENGTH)) { - connector->null_edid_counter++; - goto carp; - } - } - if (i == 4) - goto carp; - - /* if there's no extensions, we're done */ - valid_extensions = edid[0x7e]; - if (valid_extensions == 0) - return (struct edid *)edid; - - new = krealloc(edid, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL); - if (!new) - goto out; - edid = new; - - for (j = 1; j <= edid[0x7e]; j++) { - u8 *block = edid + j * EDID_LENGTH; - - for (i = 0; i < 4; i++) { - if (get_edid_block(data, block, j, EDID_LENGTH)) - goto out; - if (drm_edid_block_valid(block, j, false, NULL)) - break; - } - - if (i == 4) - valid_extensions--; - } - - if (valid_extensions != edid[0x7e]) { - u8 *base; - - connector_bad_edid(connector, edid, edid[0x7e] + 1); - - edid[EDID_LENGTH-1] += edid[0x7e] - valid_extensions; - edid[0x7e] = valid_extensions; - - new = kmalloc_array(valid_extensions + 1, EDID_LENGTH, - GFP_KERNEL); - if (!new) - goto out; - - base = new; - for (i = 0; i <= edid[0x7e]; i++) { - u8 *block = edid + i * EDID_LENGTH; - - if (!drm_edid_block_valid(block, i, false, NULL)) - continue; - - memcpy(base, block, EDID_LENGTH); - base += EDID_LENGTH; - } - - kfree(edid); - edid = new; - } - - return (struct edid *)edid; - -carp: - connector_bad_edid(connector, edid, 1); -out: - kfree(edid); - return NULL; -} -EXPORT_SYMBOL_GPL(drm_do_get_edid); - -/** - * drm_probe_ddc() - probe DDC presence - * @adapter: I2C adapter to probe - * - * Return: True on success, false on failure. - */ -bool -drm_probe_ddc(struct i2c_adapter *adapter) -{ - unsigned char out; - - return (drm_do_probe_ddc_edid(adapter, &out, 0, 1) == 0); -} -EXPORT_SYMBOL(drm_probe_ddc); - -/** - * drm_get_edid - get EDID data, if available - * @connector: connector we're probing - * @adapter: I2C adapter to use for DDC - * - * Poke the given I2C channel to grab EDID data if possible. If found, - * attach it to the connector. - * - * Return: Pointer to valid EDID or NULL if we couldn't find any. - */ -struct edid *drm_get_edid(struct drm_connector *connector, - struct i2c_adapter *adapter) -{ - struct edid *edid; - - if (connector->force == DRM_FORCE_OFF) - return NULL; - - if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter)) - return NULL; - - edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter); - if (edid) - drm_get_displayid(connector, edid); - return edid; -} -EXPORT_SYMBOL(drm_get_edid); - -/** - * drm_get_edid_switcheroo - get EDID data for a vga_switcheroo output - * @connector: connector we're probing - * @adapter: I2C adapter to use for DDC - * - * Wrapper around drm_get_edid() for laptops with dual GPUs using one set of - * outputs. The wrapper adds the requisite vga_switcheroo calls to temporarily - * switch DDC to the GPU which is retrieving EDID. - * - * Return: Pointer to valid EDID or %NULL if we couldn't find any. - */ -struct edid *drm_get_edid_switcheroo(struct drm_connector *connector, - struct i2c_adapter *adapter) -{ - struct pci_dev *pdev = connector->dev->pdev; - struct edid *edid; - - vga_switcheroo_lock_ddc(pdev); - edid = drm_get_edid(connector, adapter); - vga_switcheroo_unlock_ddc(pdev); - - return edid; -} -EXPORT_SYMBOL(drm_get_edid_switcheroo); - /** * drm_edid_duplicate - duplicate an EDID and the extensions * @edid: EDID to duplicate @@ -5490,3 +5170,320 @@ static void drm_get_displayid(struct drm_connector *connector, connector->tile_group = NULL; } } + +#define DDC_SEGMENT_ADDR 0x30 +/** + * drm_do_probe_ddc_edid() - get EDID information via I2C + * @data: I2C device adapter + * @buf: EDID data buffer to be filled + * @block: 128 byte EDID block to start fetching from + * @len: EDID data buffer length to fetch + * + * Try to fetch EDID information by calling I2C driver functions. + * + * Return: 0 on success or -1 on failure. + */ +static int +drm_do_probe_ddc_edid(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct i2c_adapter *adapter = data; + unsigned char start = block * EDID_LENGTH; + unsigned char segment = block >> 1; + unsigned char xfers = segment ? 3 : 2; + int ret, retries = 5; + + /* + * The core I2C driver will automatically retry the transfer if the + * adapter reports EAGAIN. However, we find that bit-banging transfers + * are susceptible to errors under a heavily loaded machine and + * generate spurious NAKs and timeouts. Retrying the transfer + * of the individual block a few times seems to overcome this. + */ + do { + struct i2c_msg msgs[] = { + { + .addr = DDC_SEGMENT_ADDR, + .flags = 0, + .len = 1, + .buf = &segment, + }, { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + /* + * Avoid sending the segment addr to not upset non-compliant + * DDC monitors. + */ + ret = i2c_transfer(adapter, &msgs[3 - xfers], xfers); + + if (ret == -ENXIO) { + DRM_DEBUG_KMS("drm: skipping non-existent adapter %s\n", + adapter->name); + break; + } + } while (ret != xfers && --retries); + + return ret == xfers ? 0 : -1; +} + +static void connector_bad_edid(struct drm_connector *connector, + u8 *edid, int num_blocks) +{ + int i; + + if (connector->bad_edid_counter++ && !(drm_debug & DRM_UT_KMS)) + return; + + dev_warn(connector->dev->dev, + "%s: EDID is invalid:\n", + connector->name); + for (i = 0; i < num_blocks; i++) { + u8 *block = edid + i * EDID_LENGTH; + char prefix[20]; + + if (drm_edid_is_zero(block, EDID_LENGTH)) + sprintf(prefix, "\t[%02x] ZERO ", i); + else if (!drm_edid_block_valid(block, i, false, NULL)) + sprintf(prefix, "\t[%02x] BAD ", i); + else + sprintf(prefix, "\t[%02x] GOOD ", i); + + print_hex_dump(KERN_WARNING, + prefix, DUMP_PREFIX_NONE, 16, 1, + block, EDID_LENGTH, false); + } +} + +/* Get override or firmware EDID */ +static struct edid *drm_get_override_edid(struct drm_connector *connector) +{ + struct edid *override = NULL; + + if (connector->override_edid) + override = drm_edid_duplicate(connector->edid_blob_ptr->data); + + if (!override) + override = drm_load_edid_firmware(connector); + + return IS_ERR(override) ? NULL : override; +} + +/** + * drm_add_override_edid_modes - add modes from override/firmware EDID + * @connector: connector we're probing + * + * Add modes from the override/firmware EDID, if available. Only to be used from + * drm_helper_probe_single_connector_modes() as a fallback for when DDC probe + * failed during drm_get_edid() and caused the override/firmware EDID to be + * skipped. + * + * Return: The number of modes added or 0 if we couldn't find any. + */ +int drm_add_override_edid_modes(struct drm_connector *connector) +{ + struct edid *override; + int num_modes = 0; + + override = drm_get_override_edid(connector); + if (override) { + drm_connector_update_edid_property(connector, override); + num_modes = drm_add_edid_modes(connector, override); + kfree(override); + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] adding %d modes via fallback override/firmware EDID\n", + connector->base.id, connector->name, num_modes); + } + + return num_modes; +} +EXPORT_SYMBOL(drm_add_override_edid_modes); + +/** + * drm_do_get_edid - get EDID data using a custom EDID block read function + * @connector: connector we're probing + * @get_edid_block: EDID block read function + * @data: private data passed to the block read function + * + * When the I2C adapter connected to the DDC bus is hidden behind a device that + * exposes a different interface to read EDID blocks this function can be used + * to get EDID data using a custom block read function. + * + * As in the general case the DDC bus is accessible by the kernel at the I2C + * level, drivers must make all reasonable efforts to expose it as an I2C + * adapter and use drm_get_edid() instead of abusing this function. + * + * The EDID may be overridden using debugfs override_edid or firmware EDID + * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority + * order. Having either of them bypasses actual EDID reads. + * + * Return: Pointer to valid EDID or NULL if we couldn't find any. + */ +struct edid *drm_do_get_edid(struct drm_connector *connector, + int (*get_edid_block)(void *data, u8 *buf, unsigned int block, + size_t len), + void *data) +{ + int i, j = 0, valid_extensions = 0; + u8 *edid, *new; + struct edid *override; + + override = drm_get_override_edid(connector); + if (override) + return override; + + if ((edid = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) + return NULL; + + /* base block fetch */ + for (i = 0; i < 4; i++) { + if (get_edid_block(data, edid, 0, EDID_LENGTH)) + goto out; + if (drm_edid_block_valid(edid, 0, false, + &connector->edid_corrupt)) + break; + if (i == 0 && drm_edid_is_zero(edid, EDID_LENGTH)) { + connector->null_edid_counter++; + goto carp; + } + } + if (i == 4) + goto carp; + + /* if there's no extensions, we're done */ + valid_extensions = edid[0x7e]; + if (valid_extensions == 0) + return (struct edid *)edid; + + new = krealloc(edid, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL); + if (!new) + goto out; + edid = new; + + for (j = 1; j <= edid[0x7e]; j++) { + u8 *block = edid + j * EDID_LENGTH; + + for (i = 0; i < 4; i++) { + if (get_edid_block(data, block, j, EDID_LENGTH)) + goto out; + if (drm_edid_block_valid(block, j, false, NULL)) + break; + } + + if (i == 4) + valid_extensions--; + } + + if (valid_extensions != edid[0x7e]) { + u8 *base; + + connector_bad_edid(connector, edid, edid[0x7e] + 1); + + edid[EDID_LENGTH-1] += edid[0x7e] - valid_extensions; + edid[0x7e] = valid_extensions; + + new = kmalloc_array(valid_extensions + 1, EDID_LENGTH, + GFP_KERNEL); + if (!new) + goto out; + + base = new; + for (i = 0; i <= edid[0x7e]; i++) { + u8 *block = edid + i * EDID_LENGTH; + + if (!drm_edid_block_valid(block, i, false, NULL)) + continue; + + memcpy(base, block, EDID_LENGTH); + base += EDID_LENGTH; + } + + kfree(edid); + edid = new; + } + + return (struct edid *)edid; + +carp: + connector_bad_edid(connector, edid, 1); +out: + kfree(edid); + return NULL; +} +EXPORT_SYMBOL_GPL(drm_do_get_edid); + +/** + * drm_probe_ddc() - probe DDC presence + * @adapter: I2C adapter to probe + * + * Return: True on success, false on failure. + */ +bool +drm_probe_ddc(struct i2c_adapter *adapter) +{ + unsigned char out; + + return (drm_do_probe_ddc_edid(adapter, &out, 0, 1) == 0); +} +EXPORT_SYMBOL(drm_probe_ddc); + +/** + * drm_get_edid - get EDID data, if available + * @connector: connector we're probing + * @adapter: I2C adapter to use for DDC + * + * Poke the given I2C channel to grab EDID data if possible. If found, + * attach it to the connector. + * + * Return: Pointer to valid EDID or NULL if we couldn't find any. + */ +struct edid *drm_get_edid(struct drm_connector *connector, + struct i2c_adapter *adapter) +{ + struct edid *edid; + + if (connector->force == DRM_FORCE_OFF) + return NULL; + + if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter)) + return NULL; + + edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter); + if (edid) + drm_get_displayid(connector, edid); + return edid; +} +EXPORT_SYMBOL(drm_get_edid); + +/** + * drm_get_edid_switcheroo - get EDID data for a vga_switcheroo output + * @connector: connector we're probing + * @adapter: I2C adapter to use for DDC + * + * Wrapper around drm_get_edid() for laptops with dual GPUs using one set of + * outputs. The wrapper adds the requisite vga_switcheroo calls to temporarily + * switch DDC to the GPU which is retrieving EDID. + * + * Return: Pointer to valid EDID or %NULL if we couldn't find any. + */ +struct edid *drm_get_edid_switcheroo(struct drm_connector *connector, + struct i2c_adapter *adapter) +{ + struct pci_dev *pdev = connector->dev->pdev; + struct edid *edid; + + vga_switcheroo_lock_ddc(pdev); + edid = drm_get_edid(connector, adapter); + vga_switcheroo_unlock_ddc(pdev); + + return edid; +} +EXPORT_SYMBOL(drm_get_edid_switcheroo);