diff options
Diffstat (limited to 'drivers/gpu/drm/drm_crtc_helper.c')
-rw-r--r-- | drivers/gpu/drm/drm_crtc_helper.c | 443 |
1 files changed, 95 insertions, 348 deletions
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index ea92b827e787..df281b54db01 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -72,165 +72,23 @@ void drm_helper_move_panel_connectors_to_head(struct drm_device *dev) } EXPORT_SYMBOL(drm_helper_move_panel_connectors_to_head); -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 - * - * LOCKING: - * Caller must hold mode config lock. - * - * 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: - * 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; - - 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, 0); - - 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_helper_encoder_in_use - check if a given encoder is in use * @encoder: encoder to check * - * LOCKING: - * Caller must hold mode config lock. + * Checks whether @encoder is with the current mode setting output configuration + * in use by any connector. This doesn't mean that it is actually enabled since + * the DPMS state is tracked separately. * - * Walk @encoders's DRM device's mode_config and see if it's in use. - * - * RETURNS: - * True if @encoder is part of the mode_config, false otherwise. + * Returns: + * True if @encoder is used, false otherwise. */ bool drm_helper_encoder_in_use(struct drm_encoder *encoder) { struct drm_connector *connector; struct drm_device *dev = encoder->dev; + + WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); list_for_each_entry(connector, &dev->mode_config.connector_list, head) if (connector->encoder == encoder) return true; @@ -242,19 +100,19 @@ EXPORT_SYMBOL(drm_helper_encoder_in_use); * drm_helper_crtc_in_use - check if a given CRTC is in a mode_config * @crtc: CRTC to check * - * LOCKING: - * Caller must hold mode config lock. - * - * Walk @crtc's DRM device's mode_config and see if it's in use. + * Checks whether @crtc is with the current mode setting output configuration + * in use by any connector. This doesn't mean that it is actually enabled since + * the DPMS state is tracked separately. * - * RETURNS: - * True if @crtc is part of the mode_config, false otherwise. + * Returns: + * True if @crtc is used, false otherwise. */ bool drm_helper_crtc_in_use(struct drm_crtc *crtc) { struct drm_encoder *encoder; struct drm_device *dev = crtc->dev; - /* FIXME: Locking around list access? */ + + WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) if (encoder->crtc == crtc && drm_helper_encoder_in_use(encoder)) return true; @@ -279,27 +137,17 @@ drm_encoder_disable(struct drm_encoder *encoder) encoder->bridge->funcs->post_disable(encoder->bridge); } -/** - * drm_helper_disable_unused_functions - disable unused objects - * @dev: DRM device - * - * LOCKING: - * Caller must hold mode config lock. - * - * If an connector or CRTC isn't part of @dev's mode_config, it can be disabled - * by calling its dpms function, which should power it off. - */ -void drm_helper_disable_unused_functions(struct drm_device *dev) +static void __drm_helper_disable_unused_functions(struct drm_device *dev) { struct drm_encoder *encoder; struct drm_connector *connector; struct drm_crtc *crtc; + drm_warn_on_modeset_not_all_locked(dev); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { if (!connector->encoder) continue; - if (connector->status == connector_status_disconnected) - connector->encoder = NULL; } list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { @@ -318,10 +166,27 @@ void drm_helper_disable_unused_functions(struct drm_device *dev) (*crtc_funcs->disable)(crtc); else (*crtc_funcs->dpms)(crtc, DRM_MODE_DPMS_OFF); - crtc->fb = NULL; + crtc->primary->fb = NULL; } } } + +/** + * drm_helper_disable_unused_functions - disable unused objects + * @dev: DRM device + * + * This function walks through the entire mode setting configuration of @dev. It + * will remove any crtc links of unused encoders and encoder links of + * disconnected connectors. Then it will disable all unused encoders and crtcs + * either by calling their disable callback if available or by calling their + * dpms callback with DRM_MODE_DPMS_OFF. + */ +void drm_helper_disable_unused_functions(struct drm_device *dev) +{ + drm_modeset_lock_all(dev); + __drm_helper_disable_unused_functions(dev); + drm_modeset_unlock_all(dev); +} EXPORT_SYMBOL(drm_helper_disable_unused_functions); /* @@ -355,9 +220,6 @@ drm_crtc_prepare_encoders(struct drm_device *dev) * @y: vertical offset into the surface * @old_fb: old framebuffer, for cleanup * - * LOCKING: - * Caller must hold mode config lock. - * * Try to set @mode on @crtc. Give @crtc and its associated connectors a chance * to fixup or reject the mode prior to trying to set it. This is an internal * helper that drivers could e.g. use to update properties that require the @@ -367,8 +229,8 @@ drm_crtc_prepare_encoders(struct drm_device *dev) * drm_crtc_helper_set_config() helper function to drive the mode setting * sequence. * - * RETURNS: - * True if the mode was set successfully, or false otherwise. + * Returns: + * True if the mode was set successfully, false otherwise. */ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, struct drm_display_mode *mode, @@ -384,6 +246,8 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, struct drm_encoder *encoder; bool ret = true; + drm_warn_on_modeset_not_all_locked(dev); + saved_enabled = crtc->enabled; crtc->enabled = drm_helper_crtc_in_use(crtc); if (!crtc->enabled) @@ -552,7 +416,7 @@ drm_crtc_helper_disable(struct drm_crtc *crtc) } } - drm_helper_disable_unused_functions(dev); + __drm_helper_disable_unused_functions(dev); return 0; } @@ -560,17 +424,14 @@ drm_crtc_helper_disable(struct drm_crtc *crtc) * drm_crtc_helper_set_config - set a new config from userspace * @set: mode set configuration * - * LOCKING: - * Caller must hold mode config lock. - * * Setup a new configuration, provided by the upper layers (either an ioctl call - * from userspace or internally e.g. from the fbdev suppport code) in @set, and + * from userspace or internally e.g. from the fbdev support code) in @set, and * enable it. This is the main helper functions for drivers that implement * kernel mode setting with the crtc helper functions and the assorted * ->prepare(), ->modeset() and ->commit() helper callbacks. * - * RETURNS: - * Returns 0 on success, -ERRNO on failure. + * Returns: + * Returns 0 on success, negative errno numbers on failure. */ int drm_crtc_helper_set_config(struct drm_mode_set *set) { @@ -612,6 +473,8 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) dev = set->crtc->dev; + drm_warn_on_modeset_not_all_locked(dev); + /* * Allocate space for the backup of all (non-pointer) encoder and * connector data. @@ -647,19 +510,19 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) save_set.mode = &set->crtc->mode; save_set.x = set->crtc->x; save_set.y = set->crtc->y; - save_set.fb = set->crtc->fb; + save_set.fb = set->crtc->primary->fb; /* We should be able to check here if the fb has the same properties * and then just flip_or_move it */ - if (set->crtc->fb != set->fb) { + if (set->crtc->primary->fb != set->fb) { /* If we have no fb then treat it as a full mode set */ - if (set->crtc->fb == NULL) { + if (set->crtc->primary->fb == NULL) { DRM_DEBUG_KMS("crtc has no fb, full mode set\n"); mode_changed = true; } else if (set->fb == NULL) { mode_changed = true; } else if (set->fb->pixel_format != - set->crtc->fb->pixel_format) { + set->crtc->primary->fb->pixel_format) { mode_changed = true; } else fb_changed = true; @@ -689,12 +552,13 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) if (new_encoder == NULL) /* don't break so fail path works correct */ fail = 1; - break; if (connector->dpms != DRM_MODE_DPMS_ON) { DRM_DEBUG_KMS("connector dpms not on, full mode switch\n"); mode_changed = true; } + + break; } } @@ -760,13 +624,13 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) DRM_DEBUG_KMS("attempting to set mode from" " userspace\n"); drm_mode_debug_printmodeline(set->mode); - set->crtc->fb = set->fb; + set->crtc->primary->fb = set->fb; if (!drm_crtc_helper_set_mode(set->crtc, set->mode, set->x, set->y, save_set.fb)) { DRM_ERROR("failed to set mode on [CRTC:%d]\n", set->crtc->base.id); - set->crtc->fb = save_set.fb; + set->crtc->primary->fb = save_set.fb; ret = -EINVAL; goto fail; } @@ -777,17 +641,17 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) set->connectors[i]->funcs->dpms(set->connectors[i], DRM_MODE_DPMS_ON); } } - drm_helper_disable_unused_functions(dev); + __drm_helper_disable_unused_functions(dev); } else if (fb_changed) { set->crtc->x = set->x; set->crtc->y = set->y; - set->crtc->fb = set->fb; + set->crtc->primary->fb = set->fb; ret = crtc_funcs->mode_set_base(set->crtc, set->x, set->y, save_set.fb); if (ret != 0) { set->crtc->x = save_set.x; set->crtc->y = save_set.y; - set->crtc->fb = save_set.fb; + set->crtc->primary->fb = save_set.fb; goto fail; } } @@ -924,8 +788,16 @@ void drm_helper_connector_dpms(struct drm_connector *connector, int mode) } EXPORT_SYMBOL(drm_helper_connector_dpms); -int drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb, - struct drm_mode_fb_cmd2 *mode_cmd) +/** + * drm_helper_mode_fill_fb_struct - fill out framebuffer metadata + * @fb: drm_framebuffer object to fill out + * @mode_cmd: metadata from the userspace fb creation request + * + * This helper can be used in a drivers fb_create callback to pre-fill the fb's + * metadata fields. + */ +void drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb, + struct drm_mode_fb_cmd2 *mode_cmd) { int i; @@ -938,26 +810,47 @@ int drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb, drm_fb_get_bpp_depth(mode_cmd->pixel_format, &fb->depth, &fb->bits_per_pixel); fb->pixel_format = mode_cmd->pixel_format; - - return 0; } EXPORT_SYMBOL(drm_helper_mode_fill_fb_struct); -int drm_helper_resume_force_mode(struct drm_device *dev) +/** + * drm_helper_resume_force_mode - force-restore mode setting configuration + * @dev: drm_device which should be restored + * + * Drivers which use the mode setting helpers can use this function to + * force-restore the mode setting configuration e.g. on resume or when something + * else might have trampled over the hw state (like some overzealous old BIOSen + * tended to do). + * + * This helper doesn't provide a error return value since restoring the old + * config should never fail due to resource allocation issues since the driver + * has successfully set the restored configuration already. Hence this should + * boil down to the equivalent of a few dpms on calls, which also don't provide + * an error code. + * + * Drivers where simply restoring an old configuration again might fail (e.g. + * due to slight differences in allocating shared resources when the + * configuration is restored in a different order than when userspace set it up) + * need to use their own restore logic. + */ +void drm_helper_resume_force_mode(struct drm_device *dev) { struct drm_crtc *crtc; struct drm_encoder *encoder; struct drm_crtc_helper_funcs *crtc_funcs; - int ret, encoder_dpms; + int encoder_dpms; + bool ret; + drm_modeset_lock_all(dev); list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { if (!crtc->enabled) continue; ret = drm_crtc_helper_set_mode(crtc, &crtc->mode, - crtc->x, crtc->y, crtc->fb); + crtc->x, crtc->y, crtc->primary->fb); + /* Restoring the old config should never fail! */ if (ret == false) DRM_ERROR("failed to set mode on crtc %p\n", crtc); @@ -980,155 +873,9 @@ int drm_helper_resume_force_mode(struct drm_device *dev) drm_helper_choose_crtc_dpms(crtc)); } } + /* disable the unused connectors while restoring the modesetting */ - drm_helper_disable_unused_functions(dev); - return 0; + __drm_helper_disable_unused_functions(dev); + drm_modeset_unlock_all(dev); } EXPORT_SYMBOL(drm_helper_resume_force_mode); - -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); -} - -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); - -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); - -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); - -void drm_kms_helper_poll_fini(struct drm_device *dev) -{ - drm_kms_helper_poll_disable(dev); -} -EXPORT_SYMBOL(drm_kms_helper_poll_fini); - -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); |