diff options
Diffstat (limited to 'drivers/gpu/drm/drm_probe_helper.c')
| -rw-r--r-- | drivers/gpu/drm/drm_probe_helper.c | 426 | 
1 files changed, 426 insertions, 0 deletions
| diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c new file mode 100644 index 000000000000..e70f54d4a581 --- /dev/null +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * + * DRM core CRTC related functions + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission.  The copyright holders make no representations + * about the suitability of this software for any purpose.  It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + * + * Authors: + *      Keith Packard + *	Eric Anholt <eric@anholt.net> + *      Dave Airlie <airlied@linux.ie> + *      Jesse Barnes <jesse.barnes@intel.com> + */ + +#include <linux/export.h> +#include <linux/moduleparam.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_edid.h> + +/** + * DOC: output probing helper overview + * + * This library provides some helper code for output probing. It provides an + * implementation of the core connector->fill_modes interface with + * drm_helper_probe_single_connector_modes. + * + * It also provides support for polling connectors with a work item and for + * generic hotplug interrupt handling where the driver doesn't or cannot keep + * track of a per-connector hpd interrupt. + * + * This helper library can be used independently of the modeset helper library. + * Drivers can also overwrite different parts e.g. use their own hotplug + * handling code to avoid probing unrelated outputs. + */ + +static bool drm_kms_helper_poll = true; +module_param_named(poll, drm_kms_helper_poll, bool, 0600); + +static void drm_mode_validate_flag(struct drm_connector *connector, +				   int flags) +{ +	struct drm_display_mode *mode; + +	if (flags == (DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_INTERLACE | +		      DRM_MODE_FLAG_3D_MASK)) +		return; + +	list_for_each_entry(mode, &connector->modes, head) { +		if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && +				!(flags & DRM_MODE_FLAG_INTERLACE)) +			mode->status = MODE_NO_INTERLACE; +		if ((mode->flags & DRM_MODE_FLAG_DBLSCAN) && +				!(flags & DRM_MODE_FLAG_DBLSCAN)) +			mode->status = MODE_NO_DBLESCAN; +		if ((mode->flags & DRM_MODE_FLAG_3D_MASK) && +				!(flags & DRM_MODE_FLAG_3D_MASK)) +			mode->status = MODE_NO_STEREO; +	} + +	return; +} + +/** + * drm_helper_probe_single_connector_modes - get complete set of display modes + * @connector: connector to probe + * @maxX: max width for modes + * @maxY: max height for modes + * + * Based on the helper callbacks implemented by @connector try to detect all + * valid modes.  Modes will first be added to the connector's probed_modes list, + * then culled (based on validity and the @maxX, @maxY parameters) and put into + * the normal modes list. + * + * Intended to be use as a generic implementation of the ->fill_modes() + * @connector vfunc for drivers that use the crtc helpers for output mode + * filtering and detection. + * + * Returns: + * The number of modes found on @connector. + */ +int drm_helper_probe_single_connector_modes(struct drm_connector *connector, +					    uint32_t maxX, uint32_t maxY) +{ +	struct drm_device *dev = connector->dev; +	struct drm_display_mode *mode; +	struct drm_connector_helper_funcs *connector_funcs = +		connector->helper_private; +	int count = 0; +	int mode_flags = 0; +	bool verbose_prune = true; + +	WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); + +	DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, +			drm_get_connector_name(connector)); +	/* set all modes to the unverified state */ +	list_for_each_entry(mode, &connector->modes, head) +		mode->status = MODE_UNVERIFIED; + +	if (connector->force) { +		if (connector->force == DRM_FORCE_ON) +			connector->status = connector_status_connected; +		else +			connector->status = connector_status_disconnected; +		if (connector->funcs->force) +			connector->funcs->force(connector); +	} else { +		connector->status = connector->funcs->detect(connector, true); +	} + +	/* Re-enable polling in case the global poll config changed. */ +	if (drm_kms_helper_poll != dev->mode_config.poll_running) +		drm_kms_helper_poll_enable(dev); + +	dev->mode_config.poll_running = drm_kms_helper_poll; + +	if (connector->status == connector_status_disconnected) { +		DRM_DEBUG_KMS("[CONNECTOR:%d:%s] disconnected\n", +			connector->base.id, drm_get_connector_name(connector)); +		drm_mode_connector_update_edid_property(connector, NULL); +		verbose_prune = false; +		goto prune; +	} + +#ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE +	count = drm_load_edid_firmware(connector); +	if (count == 0) +#endif +		count = (*connector_funcs->get_modes)(connector); + +	if (count == 0 && connector->status == connector_status_connected) +		count = drm_add_modes_noedid(connector, 1024, 768); +	if (count == 0) +		goto prune; + +	drm_mode_connector_list_update(connector); + +	if (maxX && maxY) +		drm_mode_validate_size(dev, &connector->modes, maxX, maxY); + +	if (connector->interlace_allowed) +		mode_flags |= DRM_MODE_FLAG_INTERLACE; +	if (connector->doublescan_allowed) +		mode_flags |= DRM_MODE_FLAG_DBLSCAN; +	if (connector->stereo_allowed) +		mode_flags |= DRM_MODE_FLAG_3D_MASK; +	drm_mode_validate_flag(connector, mode_flags); + +	list_for_each_entry(mode, &connector->modes, head) { +		if (mode->status == MODE_OK) +			mode->status = connector_funcs->mode_valid(connector, +								   mode); +	} + +prune: +	drm_mode_prune_invalid(dev, &connector->modes, verbose_prune); + +	if (list_empty(&connector->modes)) +		return 0; + +	list_for_each_entry(mode, &connector->modes, head) +		mode->vrefresh = drm_mode_vrefresh(mode); + +	drm_mode_sort(&connector->modes); + +	DRM_DEBUG_KMS("[CONNECTOR:%d:%s] probed modes :\n", connector->base.id, +			drm_get_connector_name(connector)); +	list_for_each_entry(mode, &connector->modes, head) { +		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); +		drm_mode_debug_printmodeline(mode); +	} + +	return count; +} +EXPORT_SYMBOL(drm_helper_probe_single_connector_modes); + +/** + * drm_kms_helper_hotplug_event - fire off KMS hotplug events + * @dev: drm_device whose connector state changed + * + * This function fires off the uevent for userspace and also calls the + * output_poll_changed function, which is most commonly used to inform the fbdev + * emulation code and allow it to update the fbcon output configuration. + * + * Drivers should call this from their hotplug handling code when a change is + * detected. Note that this function does not do any output detection of its + * own, like drm_helper_hpd_irq_event() does - this is assumed to be done by the + * driver already. + * + * This function must be called from process context with no mode + * setting locks held. + */ +void drm_kms_helper_hotplug_event(struct drm_device *dev) +{ +	/* send a uevent + call fbdev */ +	drm_sysfs_hotplug_event(dev); +	if (dev->mode_config.funcs->output_poll_changed) +		dev->mode_config.funcs->output_poll_changed(dev); +} +EXPORT_SYMBOL(drm_kms_helper_hotplug_event); + +#define DRM_OUTPUT_POLL_PERIOD (10*HZ) +static void output_poll_execute(struct work_struct *work) +{ +	struct delayed_work *delayed_work = to_delayed_work(work); +	struct drm_device *dev = container_of(delayed_work, struct drm_device, mode_config.output_poll_work); +	struct drm_connector *connector; +	enum drm_connector_status old_status; +	bool repoll = false, changed = false; + +	if (!drm_kms_helper_poll) +		return; + +	mutex_lock(&dev->mode_config.mutex); +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + +		/* Ignore forced connectors. */ +		if (connector->force) +			continue; + +		/* Ignore HPD capable connectors and connectors where we don't +		 * want any hotplug detection at all for polling. */ +		if (!connector->polled || connector->polled == DRM_CONNECTOR_POLL_HPD) +			continue; + +		repoll = true; + +		old_status = connector->status; +		/* if we are connected and don't want to poll for disconnect +		   skip it */ +		if (old_status == connector_status_connected && +		    !(connector->polled & DRM_CONNECTOR_POLL_DISCONNECT)) +			continue; + +		connector->status = connector->funcs->detect(connector, false); +		if (old_status != connector->status) { +			const char *old, *new; + +			old = drm_get_connector_status_name(old_status); +			new = drm_get_connector_status_name(connector->status); + +			DRM_DEBUG_KMS("[CONNECTOR:%d:%s] " +				      "status updated from %s to %s\n", +				      connector->base.id, +				      drm_get_connector_name(connector), +				      old, new); + +			changed = true; +		} +	} + +	mutex_unlock(&dev->mode_config.mutex); + +	if (changed) +		drm_kms_helper_hotplug_event(dev); + +	if (repoll) +		schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); +} + +/** + * drm_kms_helper_poll_disable - disable output polling + * @dev: drm_device + * + * This function disables the output polling work. + * + * Drivers can call this helper from their device suspend implementation. It is + * not an error to call this even when output polling isn't enabled or arlready + * disabled. + */ +void drm_kms_helper_poll_disable(struct drm_device *dev) +{ +	if (!dev->mode_config.poll_enabled) +		return; +	cancel_delayed_work_sync(&dev->mode_config.output_poll_work); +} +EXPORT_SYMBOL(drm_kms_helper_poll_disable); + +/** + * drm_kms_helper_poll_enable - re-enable output polling. + * @dev: drm_device + * + * This function re-enables the output polling work. + * + * Drivers can call this helper from their device resume implementation. It is + * an error to call this when the output polling support has not yet been set + * up. + */ +void drm_kms_helper_poll_enable(struct drm_device *dev) +{ +	bool poll = false; +	struct drm_connector *connector; + +	if (!dev->mode_config.poll_enabled || !drm_kms_helper_poll) +		return; + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { +		if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT | +					 DRM_CONNECTOR_POLL_DISCONNECT)) +			poll = true; +	} + +	if (poll) +		schedule_delayed_work(&dev->mode_config.output_poll_work, DRM_OUTPUT_POLL_PERIOD); +} +EXPORT_SYMBOL(drm_kms_helper_poll_enable); + +/** + * drm_kms_helper_poll_init - initialize and enable output polling + * @dev: drm_device + * + * This function intializes and then also enables output polling support for + * @dev. Drivers which do not have reliable hotplug support in hardware can use + * this helper infrastructure to regularly poll such connectors for changes in + * their connection state. + * + * Drivers can control which connectors are polled by setting the + * DRM_CONNECTOR_POLL_CONNECT and DRM_CONNECTOR_POLL_DISCONNECT flags. On + * connectors where probing live outputs can result in visual distortion drivers + * should not set the DRM_CONNECTOR_POLL_DISCONNECT flag to avoid this. + * Connectors which have no flag or only DRM_CONNECTOR_POLL_HPD set are + * completely ignored by the polling logic. + * + * Note that a connector can be both polled and probed from the hotplug handler, + * in case the hotplug interrupt is known to be unreliable. + */ +void drm_kms_helper_poll_init(struct drm_device *dev) +{ +	INIT_DELAYED_WORK(&dev->mode_config.output_poll_work, output_poll_execute); +	dev->mode_config.poll_enabled = true; + +	drm_kms_helper_poll_enable(dev); +} +EXPORT_SYMBOL(drm_kms_helper_poll_init); + +/** + * drm_kms_helper_poll_fini - disable output polling and clean it up + * @dev: drm_device + */ +void drm_kms_helper_poll_fini(struct drm_device *dev) +{ +	drm_kms_helper_poll_disable(dev); +} +EXPORT_SYMBOL(drm_kms_helper_poll_fini); + +/** + * drm_helper_hpd_irq_event - hotplug processing + * @dev: drm_device + * + * Drivers can use this helper function to run a detect cycle on all connectors + * which have the DRM_CONNECTOR_POLL_HPD flag set in their &polled member. All + * other connectors are ignored, which is useful to avoid reprobing fixed + * panels. + * + * This helper function is useful for drivers which can't or don't track hotplug + * interrupts for each connector. + * + * Drivers which support hotplug interrupts for each connector individually and + * which have a more fine-grained detect logic should bypass this code and + * directly call drm_kms_helper_hotplug_event() in case the connector state + * changed. + * + * This function must be called from process context with no mode + * setting locks held. + * + * Note that a connector can be both polled and probed from the hotplug handler, + * in case the hotplug interrupt is known to be unreliable. + */ +bool drm_helper_hpd_irq_event(struct drm_device *dev) +{ +	struct drm_connector *connector; +	enum drm_connector_status old_status; +	bool changed = false; + +	if (!dev->mode_config.poll_enabled) +		return false; + +	mutex_lock(&dev->mode_config.mutex); +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + +		/* Only handle HPD capable connectors. */ +		if (!(connector->polled & DRM_CONNECTOR_POLL_HPD)) +			continue; + +		old_status = connector->status; + +		connector->status = connector->funcs->detect(connector, false); +		DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", +			      connector->base.id, +			      drm_get_connector_name(connector), +			      drm_get_connector_status_name(old_status), +			      drm_get_connector_status_name(connector->status)); +		if (old_status != connector->status) +			changed = true; +	} + +	mutex_unlock(&dev->mode_config.mutex); + +	if (changed) +		drm_kms_helper_hotplug_event(dev); + +	return changed; +} +EXPORT_SYMBOL(drm_helper_hpd_irq_event); | 

