diff options
Diffstat (limited to 'drivers/gpu/drm/drm_probe_helper.c')
| -rw-r--r-- | drivers/gpu/drm/drm_probe_helper.c | 854 |
1 files changed, 696 insertions, 158 deletions
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index a1bb157bfdfa..09b12c30df69 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -32,14 +32,16 @@ #include <linux/export.h> #include <linux/moduleparam.h> -#include <drm/drmP.h> -#include <drm/drm_client.h> +#include <drm/drm_bridge.h> +#include <drm/drm_client_event.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> +#include <drm/drm_fourcc.h> +#include <drm/drm_managed.h> #include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_sysfs.h> #include "drm_crtc_helper_internal.h" @@ -84,26 +86,28 @@ drm_mode_validate_flag(const struct drm_display_mode *mode, return MODE_OK; } -static enum drm_mode_status +static int drm_mode_validate_pipeline(struct drm_display_mode *mode, - struct drm_connector *connector) + struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + enum drm_mode_status *status) { struct drm_device *dev = connector->dev; - enum drm_mode_status ret = MODE_OK; struct drm_encoder *encoder; - int i; + int ret; /* Step 1: Validate against connector */ - ret = drm_connector_mode_valid(connector, mode); - if (ret != MODE_OK) + ret = drm_connector_mode_valid(connector, mode, ctx, status); + if (ret || *status != MODE_OK) return ret; /* Step 2: Validate against encoders and crtcs */ - drm_connector_for_each_possible_encoder(connector, encoder, i) { + drm_connector_for_each_possible_encoder(connector, encoder) { + struct drm_bridge *bridge; struct drm_crtc *crtc; - ret = drm_encoder_mode_valid(encoder, mode); - if (ret != MODE_OK) { + *status = drm_encoder_mode_valid(encoder, mode); + if (*status != MODE_OK) { /* No point in continuing for crtc check as this encoder * will not accept the mode anyway. If all encoders * reject the mode then, at exit, ret will not be @@ -111,8 +115,12 @@ drm_mode_validate_pipeline(struct drm_display_mode *mode, continue; } - ret = drm_bridge_mode_valid(encoder->bridge, mode); - if (ret != MODE_OK) { + bridge = drm_bridge_chain_get_first_bridge(encoder); + *status = drm_bridge_chain_mode_valid(bridge, + &connector->display_info, + mode); + drm_bridge_put(bridge); + if (*status != MODE_OK) { /* There is also no point in continuing for crtc check * here. */ continue; @@ -122,17 +130,17 @@ drm_mode_validate_pipeline(struct drm_display_mode *mode, if (!drm_encoder_crtc_ok(encoder, crtc)) continue; - ret = drm_crtc_mode_valid(crtc, mode); - if (ret == MODE_OK) { + *status = drm_crtc_mode_valid(crtc, mode); + if (*status == MODE_OK) { /* If we get to this point there is at least * one combination of encoder+crtc that works * for this mode. Lets return now. */ - return ret; + return 0; } } } - return ret; + return 0; } static int drm_helper_probe_add_cmdline_mode(struct drm_connector *connector) @@ -156,6 +164,8 @@ static int drm_helper_probe_add_cmdline_mode(struct drm_connector *connector) continue; } + /* Mark the matching mode as being preferred by the user */ + mode->type |= DRM_MODE_TYPE_USERDEF; return 0; } @@ -191,53 +201,74 @@ enum drm_mode_status drm_encoder_mode_valid(struct drm_encoder *encoder, return encoder_funcs->mode_valid(encoder, mode); } -enum drm_mode_status drm_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) +int +drm_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode, + struct drm_modeset_acquire_ctx *ctx, + enum drm_mode_status *status) { const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private; + int ret = 0; + + if (!connector_funcs) + *status = MODE_OK; + else if (connector_funcs->mode_valid_ctx) + ret = connector_funcs->mode_valid_ctx(connector, mode, ctx, + status); + else if (connector_funcs->mode_valid) + *status = connector_funcs->mode_valid(connector, mode); + else + *status = MODE_OK; - if (!connector_funcs || !connector_funcs->mode_valid) - return MODE_OK; + return ret; +} - return connector_funcs->mode_valid(connector, mode); +static void drm_kms_helper_disable_hpd(struct drm_device *dev) +{ + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + const struct drm_connector_helper_funcs *funcs = + connector->helper_private; + + if (funcs && funcs->disable_hpd) + funcs->disable_hpd(connector); + } + drm_connector_list_iter_end(&conn_iter); } -#define DRM_OUTPUT_POLL_PERIOD (10*HZ) -/** - * drm_kms_helper_poll_enable - re-enable output polling. - * @dev: drm_device - * - * This function re-enables the output polling work, after it has been - * temporarily disabled using drm_kms_helper_poll_disable(), for example over - * suspend/resume. - * - * Drivers can call this helper from their device resume implementation. It is - * not an error to call this even when output polling isn't enabled. - * - * Note that calls to enable and disable polling must be strictly ordered, which - * is automatically the case when they're only call from suspend/resume - * callbacks. - */ -void drm_kms_helper_poll_enable(struct drm_device *dev) +static bool drm_kms_helper_enable_hpd(struct drm_device *dev) { bool poll = false; struct drm_connector *connector; struct drm_connector_list_iter conn_iter; - unsigned long delay = DRM_OUTPUT_POLL_PERIOD; - - if (!dev->mode_config.poll_enabled || !drm_kms_helper_poll) - return; drm_connector_list_iter_begin(dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { + const struct drm_connector_helper_funcs *funcs = + connector->helper_private; + + if (funcs && funcs->enable_hpd) + funcs->enable_hpd(connector); + if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT)) poll = true; } drm_connector_list_iter_end(&conn_iter); - if (dev->mode_config.delayed_event) { + return poll; +} + +#define DRM_OUTPUT_POLL_PERIOD (10*HZ) +static void reschedule_output_poll_work(struct drm_device *dev) +{ + unsigned long delay = DRM_OUTPUT_POLL_PERIOD; + + if (dev->mode_config.delayed_event) /* * FIXME: * @@ -248,19 +279,83 @@ void drm_kms_helper_poll_enable(struct drm_device *dev) * drm_helper_probe_single_connector_modes() in case the poll * was enabled before. */ - poll = true; delay = HZ; - } - if (poll) - schedule_delayed_work(&dev->mode_config.output_poll_work, delay); + schedule_delayed_work(&dev->mode_config.output_poll_work, delay); +} + +/** + * drm_kms_helper_poll_enable - re-enable output polling. + * @dev: drm_device + * + * This function re-enables the output polling work, after it has been + * temporarily disabled using drm_kms_helper_poll_disable(), for example over + * suspend/resume. + * + * Drivers can call this helper from their device resume implementation. It is + * not an error to call this even when output polling isn't enabled. + * + * If device polling was never initialized before, this call will trigger a + * warning and return. + * + * Note that calls to enable and disable polling must be strictly ordered, which + * is automatically the case when they're only call from suspend/resume + * callbacks. + */ +void drm_kms_helper_poll_enable(struct drm_device *dev) +{ + if (drm_WARN_ON_ONCE(dev, !dev->mode_config.poll_enabled) || + !drm_kms_helper_poll || dev->mode_config.poll_running) + return; + + if (drm_kms_helper_enable_hpd(dev) || + dev->mode_config.delayed_event) + reschedule_output_poll_work(dev); + + dev->mode_config.poll_running = true; } EXPORT_SYMBOL(drm_kms_helper_poll_enable); +/** + * drm_kms_helper_poll_reschedule - reschedule the output polling work + * @dev: drm_device + * + * This function reschedules the output polling work, after polling for a + * connector has been enabled. + * + * Drivers must call this helper after enabling polling for a connector by + * setting %DRM_CONNECTOR_POLL_CONNECT / %DRM_CONNECTOR_POLL_DISCONNECT flags + * in drm_connector::polled. Note that after disabling polling by clearing these + * flags for a connector will stop the output polling work automatically if + * the polling is disabled for all other connectors as well. + * + * The function can be called only after polling has been enabled by calling + * drm_kms_helper_poll_init() / drm_kms_helper_poll_enable(). + */ +void drm_kms_helper_poll_reschedule(struct drm_device *dev) +{ + if (dev->mode_config.poll_running) + reschedule_output_poll_work(dev); +} +EXPORT_SYMBOL(drm_kms_helper_poll_reschedule); + +static int detect_connector_status(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + const struct drm_connector_helper_funcs *funcs = connector->helper_private; + + if (funcs->detect_ctx) + return funcs->detect_ctx(connector, ctx, force); + else if (connector->funcs->detect) + return connector->funcs->detect(connector, force); + + return connector_status_connected; +} + static enum drm_connector_status drm_helper_probe_detect_ctx(struct drm_connector *connector, bool force) { - const struct drm_connector_helper_funcs *funcs = connector->helper_private; struct drm_modeset_acquire_ctx ctx; int ret; @@ -268,14 +363,8 @@ drm_helper_probe_detect_ctx(struct drm_connector *connector, bool force) retry: ret = drm_modeset_lock(&connector->dev->mode_config.connection_mutex, &ctx); - if (!ret) { - if (funcs->detect_ctx) - ret = funcs->detect_ctx(connector, &ctx, force); - else if (connector->funcs->detect) - ret = connector->funcs->detect(connector, force); - else - ret = connector_status_connected; - } + if (!ret) + ret = detect_connector_status(connector, &ctx, force); if (ret == -EDEADLK) { drm_modeset_backoff(&ctx); @@ -285,6 +374,9 @@ retry: if (WARN_ON(ret < 0)) ret = connector_status_unknown; + if (ret != connector->status) + connector->epoch_counter += 1; + drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -306,7 +398,6 @@ drm_helper_probe_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force) { - const struct drm_connector_helper_funcs *funcs = connector->helper_private; struct drm_device *dev = connector->dev; int ret; @@ -317,15 +408,95 @@ drm_helper_probe_detect(struct drm_connector *connector, if (ret) return ret; - if (funcs->detect_ctx) - return funcs->detect_ctx(connector, ctx, force); - else if (connector->funcs->detect) - return connector->funcs->detect(connector, force); - else - return connector_status_connected; + ret = detect_connector_status(connector, ctx, force); + + if (ret != connector->status) + connector->epoch_counter += 1; + + return ret; } EXPORT_SYMBOL(drm_helper_probe_detect); +static int drm_helper_probe_get_modes(struct drm_connector *connector) +{ + const struct drm_connector_helper_funcs *connector_funcs = + connector->helper_private; + int count; + + count = connector_funcs->get_modes(connector); + + /* The .get_modes() callback should not return negative values. */ + if (count < 0) { + drm_err(connector->dev, ".get_modes() returned %pe\n", + ERR_PTR(count)); + count = 0; + } + + /* + * Fallback for when DDC probe failed in drm_get_edid() and thus skipped + * override/firmware EDID. + */ + if (count == 0 && connector->status == connector_status_connected) + count = drm_edid_override_connector_update(connector); + + return count; +} + +static int __drm_helper_update_and_validate(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + int mode_flags = 0; + int ret; + + drm_connector_list_update(connector); + + 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; + + list_for_each_entry(mode, &connector->modes, head) { + if (mode->status != MODE_OK) + continue; + + mode->status = drm_mode_validate_driver(dev, mode); + if (mode->status != MODE_OK) + continue; + + mode->status = drm_mode_validate_size(mode, maxX, maxY); + if (mode->status != MODE_OK) + continue; + + mode->status = drm_mode_validate_flag(mode, mode_flags); + if (mode->status != MODE_OK) + continue; + + mode->status = drm_mode_validate_ycbcr420(mode, connector); + if (mode->status != MODE_OK) + continue; + + ret = drm_mode_validate_pipeline(mode, connector, ctx, + &mode->status); + if (ret) { + drm_dbg_kms(dev, + "drm_mode_validate_pipeline failed: %d\n", + ret); + + if (drm_WARN_ON_ONCE(dev, ret != -EDEADLK)) + mode->status = MODE_ERROR; + else + return -EDEADLK; + } + } + + return 0; +} + /** * drm_helper_probe_single_connector_modes - get complete set of display modes * @connector: connector to probe @@ -370,8 +541,9 @@ EXPORT_SYMBOL(drm_helper_probe_detect); * (if specified) * - drm_mode_validate_flag() checks the modes against basic connector * capabilities (interlace_allowed,doublescan_allowed,stereo_allowed) - * - the optional &drm_connector_helper_funcs.mode_valid helper can perform - * driver and/or sink specific checks + * - the optional &drm_connector_helper_funcs.mode_valid or + * &drm_connector_helper_funcs.mode_valid_ctx helpers can perform driver + * and/or sink specific checks * - the optional &drm_crtc_helper_funcs.mode_valid, * &drm_bridge_funcs.mode_valid and &drm_encoder_helper_funcs.mode_valid * helpers can perform driver and/or source specific checks which are also @@ -389,11 +561,7 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector, { struct drm_device *dev = connector->dev; struct drm_display_mode *mode; - const struct drm_connector_helper_funcs *connector_funcs = - connector->helper_private; int count = 0, ret; - int mode_flags = 0; - bool verbose_prune = true; enum drm_connector_status old_status; struct drm_modeset_acquire_ctx ctx; @@ -401,8 +569,8 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector, drm_modeset_acquire_init(&ctx, 0); - DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, - connector->name); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s]\n", connector->base.id, + connector->name); retry: ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); @@ -445,11 +613,10 @@ retry: * check here, and if anything changed start the hotplug code. */ if (old_status != connector->status) { - DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", - connector->base.id, - connector->name, - drm_get_connector_status_name(old_status), - drm_get_connector_status_name(connector->status)); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] status updated from %s to %s\n", + connector->base.id, connector->name, + drm_get_connector_status_name(old_status), + drm_get_connector_status_name(connector->status)); /* * The hotplug event code might call into the fb @@ -459,79 +626,85 @@ retry: */ dev->mode_config.delayed_event = true; if (dev->mode_config.poll_enabled) - schedule_delayed_work(&dev->mode_config.output_poll_work, - 0); + mod_delayed_work(system_wq, + &dev->mode_config.output_poll_work, + 0); } - /* Re-enable polling in case the global poll config changed. */ - if (drm_kms_helper_poll != dev->mode_config.poll_running) + /* + * Re-enable polling in case the global poll config changed but polling + * is still initialized. + */ + if (dev->mode_config.poll_enabled) 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, connector->name); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] disconnected\n", + connector->base.id, connector->name); drm_connector_update_edid_property(connector, NULL); - verbose_prune = false; - goto prune; + drm_mode_prune_invalid(dev, &connector->modes, false); + goto exit; } - count = (*connector_funcs->get_modes)(connector); + count = drm_helper_probe_get_modes(connector); - if (count == 0 && connector->status == connector_status_connected) + if (count == 0 && (connector->status == connector_status_connected || + connector->status == connector_status_unknown)) { count = drm_add_modes_noedid(connector, 1024, 768); - count += drm_helper_probe_add_cmdline_mode(connector); - if (count == 0) - goto prune; - drm_connector_list_update(connector); - - 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; - - list_for_each_entry(mode, &connector->modes, head) { - if (mode->status == MODE_OK) - mode->status = drm_mode_validate_driver(dev, mode); - - if (mode->status == MODE_OK) - mode->status = drm_mode_validate_size(mode, maxX, maxY); - - if (mode->status == MODE_OK) - mode->status = drm_mode_validate_flag(mode, mode_flags); + /* + * Section 4.2.2.6 (EDID Corruption Detection) of the DP 1.4a + * Link CTS specifies that 640x480 (the official "failsafe" + * mode) needs to be the default if there's no EDID. + */ + if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) + drm_set_preferred_mode(connector, 640, 480); + } + count += drm_helper_probe_add_cmdline_mode(connector); + if (count != 0) { + ret = __drm_helper_update_and_validate(connector, maxX, maxY, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry; + } + } - if (mode->status == MODE_OK) - mode->status = drm_mode_validate_pipeline(mode, - connector); + drm_mode_prune_invalid(dev, &connector->modes, true); - if (mode->status == MODE_OK) - mode->status = drm_mode_validate_ycbcr420(mode, - connector); + /* + * Displayport spec section 5.2.1.2 ("Video Timing Format") says that + * all detachable sinks shall support 640x480 @60Hz as a fail safe + * mode. If all modes were pruned, perhaps because they need more + * lanes or a higher pixel clock than available, at least try to add + * in 640x480. + */ + if (list_empty(&connector->modes) && + connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + count = drm_add_modes_noedid(connector, 640, 480); + ret = __drm_helper_update_and_validate(connector, maxX, maxY, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry; + } + drm_mode_prune_invalid(dev, &connector->modes, true); } -prune: - drm_mode_prune_invalid(dev, &connector->modes, verbose_prune); - +exit: drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); 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, - connector->name); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] probed modes:\n", + connector->base.id, connector->name); + list_for_each_entry(mode, &connector->modes, head) { drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); - drm_mode_debug_printmodeline(mode); + drm_dbg_kms(dev, "Probed mode: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(mode)); } return count; @@ -543,7 +716,7 @@ EXPORT_SYMBOL(drm_helper_probe_single_connector_modes); * @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 + * client hotplug 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 @@ -553,18 +726,33 @@ EXPORT_SYMBOL(drm_helper_probe_single_connector_modes); * * This function must be called from process context with no mode * setting locks held. + * + * If only a single connector has changed, consider calling + * drm_kms_helper_connector_hotplug_event() instead. */ 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); - drm_client_dev_hotplug(dev); } EXPORT_SYMBOL(drm_kms_helper_hotplug_event); +/** + * drm_kms_helper_connector_hotplug_event - fire off a KMS connector hotplug event + * @connector: drm_connector which has changed + * + * This is the same as drm_kms_helper_hotplug_event(), except it fires a more + * fine-grained uevent for a single connector. + */ +void drm_kms_helper_connector_hotplug_event(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + + drm_sysfs_connector_hotplug_event(connector); + drm_client_dev_hotplug(dev); +} +EXPORT_SYMBOL(drm_kms_helper_connector_hotplug_event); + static void output_poll_execute(struct work_struct *work) { struct delayed_work *delayed_work = to_delayed_work(work); @@ -573,13 +761,22 @@ static void output_poll_execute(struct work_struct *work) struct drm_connector_list_iter conn_iter; enum drm_connector_status old_status; bool repoll = false, changed; + u64 old_epoch_counter; + + if (!dev->mode_config.poll_enabled) + return; /* Pick up any changes detected by the probe functions. */ changed = dev->mode_config.delayed_event; dev->mode_config.delayed_event = false; - if (!drm_kms_helper_poll) + if (!drm_kms_helper_poll) { + if (dev->mode_config.poll_running) { + drm_kms_helper_disable_hpd(dev); + dev->mode_config.poll_running = false; + } goto out; + } if (!mutex_trylock(&dev->mode_config.mutex)) { repoll = true; @@ -606,8 +803,9 @@ static void output_poll_execute(struct work_struct *work) repoll = true; + old_epoch_counter = connector->epoch_counter; connector->status = drm_helper_probe_detect(connector, NULL, false); - if (old_status != connector->status) { + if (old_epoch_counter != connector->epoch_counter) { const char *old, *new; /* @@ -631,11 +829,12 @@ static void output_poll_execute(struct work_struct *work) 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, - connector->name, - old, new); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] status updated from %s to %s\n", + connector->base.id, connector->name, + old, new); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] epoch counter %llu -> %llu\n", + connector->base.id, connector->name, + old_epoch_counter, connector->epoch_counter); changed = true; } @@ -682,15 +881,24 @@ EXPORT_SYMBOL(drm_kms_helper_is_poll_worker); * not an error to call this even when output polling isn't enabled or already * disabled. Polling is re-enabled by calling drm_kms_helper_poll_enable(). * + * If however, the polling was never initialized, this call will trigger a + * warning and return. + * * Note that calls to enable and disable polling must be strictly ordered, which * is automatically the case when they're only call from suspend/resume * callbacks. */ void drm_kms_helper_poll_disable(struct drm_device *dev) { - if (!dev->mode_config.poll_enabled) + if (drm_WARN_ON(dev, !dev->mode_config.poll_enabled)) return; + + if (dev->mode_config.poll_running) + drm_kms_helper_disable_hpd(dev); + cancel_delayed_work_sync(&dev->mode_config.output_poll_work); + + dev->mode_config.poll_running = false; } EXPORT_SYMBOL(drm_kms_helper_poll_disable); @@ -698,7 +906,7 @@ EXPORT_SYMBOL(drm_kms_helper_poll_disable); * drm_kms_helper_poll_init - initialize and enable output polling * @dev: drm_device * - * This function intializes and then also enables output polling support for + * This function initializes 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. @@ -728,10 +936,122 @@ EXPORT_SYMBOL(drm_kms_helper_poll_init); */ void drm_kms_helper_poll_fini(struct drm_device *dev) { + if (!dev->mode_config.poll_enabled) + return; + drm_kms_helper_poll_disable(dev); + + dev->mode_config.poll_enabled = false; } EXPORT_SYMBOL(drm_kms_helper_poll_fini); +static void drm_kms_helper_poll_init_release(struct drm_device *dev, void *res) +{ + drm_kms_helper_poll_fini(dev); +} + +/** + * drmm_kms_helper_poll_init - initialize and enable output polling + * @dev: drm_device + * + * This function initializes and then also enables output polling support for + * @dev similar to drm_kms_helper_poll_init(). Polling will automatically be + * cleaned up when the DRM device goes away. + * + * See drm_kms_helper_poll_init() for more information. + */ +void drmm_kms_helper_poll_init(struct drm_device *dev) +{ + int ret; + + drm_kms_helper_poll_init(dev); + + ret = drmm_add_action_or_reset(dev, drm_kms_helper_poll_init_release, dev); + if (ret) + drm_warn(dev, "Connector status will not be updated, error %d\n", ret); +} +EXPORT_SYMBOL(drmm_kms_helper_poll_init); + +static bool check_connector_changed(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + enum drm_connector_status old_status; + u64 old_epoch_counter; + + /* Only handle HPD capable connectors. */ + drm_WARN_ON(dev, !(connector->polled & DRM_CONNECTOR_POLL_HPD)); + + drm_WARN_ON(dev, !mutex_is_locked(&dev->mode_config.mutex)); + + old_status = connector->status; + old_epoch_counter = connector->epoch_counter; + connector->status = drm_helper_probe_detect(connector, NULL, false); + + if (old_epoch_counter == connector->epoch_counter) { + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Same epoch counter %llu\n", + connector->base.id, + connector->name, + connector->epoch_counter); + + return false; + } + + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] status updated from %s to %s\n", + connector->base.id, + connector->name, + drm_get_connector_status_name(old_status), + drm_get_connector_status_name(connector->status)); + + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Changed epoch counter %llu => %llu\n", + connector->base.id, + connector->name, + old_epoch_counter, + connector->epoch_counter); + + return true; +} + +/** + * drm_connector_helper_hpd_irq_event - hotplug processing + * @connector: drm_connector + * + * Drivers can use this helper function to run a detect cycle on a connector + * which has the DRM_CONNECTOR_POLL_HPD flag set in its &polled member. + * + * This helper function is useful for drivers which can track hotplug + * interrupts for a single connector. Drivers that want to send a + * hotplug event for all connectors or can't track hotplug interrupts + * per connector need to use drm_helper_hpd_irq_event(). + * + * 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. + * + * Returns: + * A boolean indicating whether the connector status changed or not + */ +bool drm_connector_helper_hpd_irq_event(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + bool changed; + + mutex_lock(&dev->mode_config.mutex); + changed = check_connector_changed(connector); + mutex_unlock(&dev->mode_config.mutex); + + if (changed) { + drm_kms_helper_connector_hotplug_event(connector); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Sent hotplug event\n", + connector->base.id, + connector->name); + } + + return changed; +} +EXPORT_SYMBOL(drm_connector_helper_hpd_irq_event); + /** * drm_helper_hpd_irq_event - hotplug processing * @dev: drm_device @@ -745,22 +1065,25 @@ EXPORT_SYMBOL(drm_kms_helper_poll_fini); * 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. + * which have a more fine-grained detect logic can use + * drm_connector_helper_hpd_irq_event(). Alternatively, they 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. + * + * Returns: + * A boolean indicating whether the connector status changed or not */ bool drm_helper_hpd_irq_event(struct drm_device *dev) { - struct drm_connector *connector; + struct drm_connector *connector, *first_changed_connector = NULL; struct drm_connector_list_iter conn_iter; - enum drm_connector_status old_status; - bool changed = false; + int changed = 0; if (!dev->mode_config.poll_enabled) return false; @@ -772,23 +1095,238 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev) if (!(connector->polled & DRM_CONNECTOR_POLL_HPD)) continue; - old_status = connector->status; + if (check_connector_changed(connector)) { + if (!first_changed_connector) { + drm_connector_get(connector); + first_changed_connector = connector; + } - connector->status = drm_helper_probe_detect(connector, NULL, false); - DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", - connector->base.id, - connector->name, - drm_get_connector_status_name(old_status), - drm_get_connector_status_name(connector->status)); - if (old_status != connector->status) - changed = true; + changed++; + } } drm_connector_list_iter_end(&conn_iter); mutex_unlock(&dev->mode_config.mutex); - if (changed) + if (changed == 1) + drm_kms_helper_connector_hotplug_event(first_changed_connector); + else if (changed > 0) drm_kms_helper_hotplug_event(dev); + if (first_changed_connector) + drm_connector_put(first_changed_connector); + return changed; } EXPORT_SYMBOL(drm_helper_hpd_irq_event); + +/** + * drm_crtc_helper_mode_valid_fixed - Validates a display mode + * @crtc: the crtc + * @mode: the mode to validate + * @fixed_mode: the display hardware's mode + * + * Returns: + * MODE_OK on success, or another mode-status code otherwise. + */ +enum drm_mode_status drm_crtc_helper_mode_valid_fixed(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + const struct drm_display_mode *fixed_mode) +{ + if (mode->hdisplay != fixed_mode->hdisplay && mode->vdisplay != fixed_mode->vdisplay) + return MODE_ONE_SIZE; + else if (mode->hdisplay != fixed_mode->hdisplay) + return MODE_ONE_WIDTH; + else if (mode->vdisplay != fixed_mode->vdisplay) + return MODE_ONE_HEIGHT; + + return MODE_OK; +} +EXPORT_SYMBOL(drm_crtc_helper_mode_valid_fixed); + +/** + * drm_connector_helper_get_modes_fixed - Duplicates a display mode for a connector + * @connector: the connector + * @fixed_mode: the display hardware's mode + * + * This function duplicates a display modes for a connector. Drivers for hardware + * that only supports a single fixed mode can use this function in their connector's + * get_modes helper. + * + * Returns: + * The number of created modes. + */ +int drm_connector_helper_get_modes_fixed(struct drm_connector *connector, + const struct drm_display_mode *fixed_mode) +{ + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(dev, fixed_mode); + if (!mode) { + drm_err(dev, "Failed to duplicate mode " DRM_MODE_FMT "\n", + DRM_MODE_ARG(fixed_mode)); + return 0; + } + + if (mode->name[0] == '\0') + drm_mode_set_name(mode); + + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + if (mode->width_mm) + connector->display_info.width_mm = mode->width_mm; + if (mode->height_mm) + connector->display_info.height_mm = mode->height_mm; + + return 1; +} +EXPORT_SYMBOL(drm_connector_helper_get_modes_fixed); + +/** + * drm_connector_helper_get_modes - Read EDID and update connector. + * @connector: The connector + * + * Read the EDID using drm_edid_read() (which requires that connector->ddc is + * set), and update the connector using the EDID. + * + * This can be used as the "default" connector helper .get_modes() hook if the + * driver does not need any special processing. This is sets the example what + * custom .get_modes() hooks should do regarding EDID read and connector update. + * + * Returns: Number of modes. + */ +int drm_connector_helper_get_modes(struct drm_connector *connector) +{ + const struct drm_edid *drm_edid; + int count; + + drm_edid = drm_edid_read(connector); + + /* + * Unconditionally update the connector. If the EDID was read + * successfully, fill in the connector information derived from the + * EDID. Otherwise, if the EDID is NULL, clear the connector + * information. + */ + drm_edid_connector_update(connector, drm_edid); + + count = drm_edid_connector_add_modes(connector); + + drm_edid_free(drm_edid); + + return count; +} +EXPORT_SYMBOL(drm_connector_helper_get_modes); + +/** + * drm_connector_helper_tv_get_modes - Fills the modes availables to a TV connector + * @connector: The connector + * + * Fills the available modes for a TV connector based on the supported + * TV modes, and the default mode expressed by the kernel command line. + * + * This can be used as the default TV connector helper .get_modes() hook + * if the driver does not need any special processing. + * + * Returns: + * The number of modes added to the connector. + */ +int drm_connector_helper_tv_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *tv_mode_property = + dev->mode_config.tv_mode_property; + struct drm_cmdline_mode *cmdline = &connector->cmdline_mode; + unsigned int ntsc_modes = BIT(DRM_MODE_TV_MODE_NTSC) | + BIT(DRM_MODE_TV_MODE_NTSC_443) | + BIT(DRM_MODE_TV_MODE_NTSC_J) | + BIT(DRM_MODE_TV_MODE_PAL_M); + unsigned int pal_modes = BIT(DRM_MODE_TV_MODE_PAL) | + BIT(DRM_MODE_TV_MODE_PAL_N) | + BIT(DRM_MODE_TV_MODE_SECAM); + unsigned int tv_modes[2] = { UINT_MAX, UINT_MAX }; + unsigned int i, supported_tv_modes = 0; + + if (!tv_mode_property) + return 0; + + for (i = 0; i < tv_mode_property->num_values; i++) + supported_tv_modes |= BIT(tv_mode_property->values[i]); + + if (((supported_tv_modes & ntsc_modes) && + (supported_tv_modes & pal_modes)) || + (supported_tv_modes & BIT(DRM_MODE_TV_MODE_MONOCHROME))) { + uint64_t default_mode; + + if (drm_object_property_get_default_value(&connector->base, + tv_mode_property, + &default_mode)) + return 0; + + if (cmdline->tv_mode_specified) + default_mode = cmdline->tv_mode; + + if (BIT(default_mode) & ntsc_modes) { + tv_modes[0] = DRM_MODE_TV_MODE_NTSC; + tv_modes[1] = DRM_MODE_TV_MODE_PAL; + } else { + tv_modes[0] = DRM_MODE_TV_MODE_PAL; + tv_modes[1] = DRM_MODE_TV_MODE_NTSC; + } + } else if (supported_tv_modes & ntsc_modes) { + tv_modes[0] = DRM_MODE_TV_MODE_NTSC; + } else if (supported_tv_modes & pal_modes) { + tv_modes[0] = DRM_MODE_TV_MODE_PAL; + } else { + return 0; + } + + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + struct drm_display_mode *mode; + + if (tv_modes[i] == DRM_MODE_TV_MODE_NTSC) + mode = drm_mode_analog_ntsc_480i(dev); + else if (tv_modes[i] == DRM_MODE_TV_MODE_PAL) + mode = drm_mode_analog_pal_576i(dev); + else + break; + if (!mode) + return i; + if (!i) + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + } + + return i; +} +EXPORT_SYMBOL(drm_connector_helper_tv_get_modes); + +/** + * drm_connector_helper_detect_from_ddc - Read EDID and detect connector status. + * @connector: The connector + * @ctx: Acquire context + * @force: Perform screen-destructive operations, if necessary + * + * Detects the connector status by reading the EDID using drm_probe_ddc(), + * which requires connector->ddc to be set. Returns connector_status_connected + * on success or connector_status_disconnected on failure. + * + * Returns: + * The connector status as defined by enum drm_connector_status. + */ +int drm_connector_helper_detect_from_ddc(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct i2c_adapter *ddc = connector->ddc; + + if (!ddc) + return connector_status_unknown; + + if (drm_probe_ddc(ddc)) + return connector_status_connected; + + return connector_status_disconnected; +} +EXPORT_SYMBOL(drm_connector_helper_detect_from_ddc); |
