diff options
Diffstat (limited to 'drivers/gpu/drm/drm_atomic_helper.c')
| -rw-r--r-- | drivers/gpu/drm/drm_atomic_helper.c | 1891 |
1 files changed, 1169 insertions, 722 deletions
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 54e2ae614dcc..10adac9397cf 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -25,15 +25,25 @@ * Daniel Vetter <daniel.vetter@ffwll.ch> */ -#include <drm/drmP.h> +#include <linux/export.h> +#include <linux/dma-fence.h> +#include <linux/ktime.h> + #include <drm/drm_atomic.h> -#include <drm/drm_atomic_uapi.h> -#include <drm/drm_plane_helper.h> -#include <drm/drm_crtc_helper.h> #include <drm/drm_atomic_helper.h> -#include <drm/drm_writeback.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_blend.h> +#include <drm/drm_bridge.h> #include <drm/drm_damage_helper.h> -#include <linux/dma-fence.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_panic.h> +#include <drm/drm_print.h> +#include <drm/drm_self_refresh_helper.h> +#include <drm/drm_vblank.h> +#include <drm/drm_writeback.h> #include "drm_crtc_helper_internal.h" #include "drm_crtc_internal.h" @@ -55,9 +65,9 @@ * * This library also provides implementations for all the legacy driver * interfaces on top of the atomic interface. See drm_atomic_helper_set_config(), - * drm_atomic_helper_disable_plane(), drm_atomic_helper_disable_plane() and the - * various functions to implement set_property callbacks. New drivers must not - * implement these functions themselves but must use the provided helpers. + * drm_atomic_helper_disable_plane(), and the various functions to implement + * set_property callbacks. New drivers must not implement these functions + * themselves but must use the provided helpers. * * The atomic helper uses the same function table structures as all other * modesetting helpers. See the documentation for &struct drm_crtc_helper_funcs, @@ -93,17 +103,6 @@ drm_atomic_helper_plane_changed(struct drm_atomic_state *state, } } -/* - * For connectors that support multiple encoders, either the - * .atomic_best_encoder() or .best_encoder() operation must be implemented. - */ -static struct drm_encoder * -pick_single_encoder_for_connector(struct drm_connector *connector) -{ - WARN_ON(connector->encoder_ids[1]); - return drm_encoder_find(connector->dev, NULL, connector->encoder_ids[0]); -} - static int handle_conflicting_encoders(struct drm_atomic_state *state, bool disable_conflicting_encoders) { @@ -111,7 +110,7 @@ static int handle_conflicting_encoders(struct drm_atomic_state *state, struct drm_connector *connector; struct drm_connector_list_iter conn_iter; struct drm_encoder *encoder; - unsigned encoder_mask = 0; + unsigned int encoder_mask = 0; int i, ret = 0; /* @@ -127,17 +126,19 @@ static int handle_conflicting_encoders(struct drm_atomic_state *state, continue; if (funcs->atomic_best_encoder) - new_encoder = funcs->atomic_best_encoder(connector, new_conn_state); + new_encoder = funcs->atomic_best_encoder(connector, + state); else if (funcs->best_encoder) new_encoder = funcs->best_encoder(connector); else - new_encoder = pick_single_encoder_for_connector(connector); + new_encoder = drm_connector_get_single_encoder(connector); if (new_encoder) { if (encoder_mask & drm_encoder_mask(new_encoder)) { - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] on [CONNECTOR:%d:%s] already assigned\n", - new_encoder->base.id, new_encoder->name, - connector->base.id, connector->name); + drm_dbg_atomic(connector->dev, + "[ENCODER:%d:%s] on [CONNECTOR:%d:%s] already assigned\n", + new_encoder->base.id, new_encoder->name, + connector->base.id, connector->name); return -EINVAL; } @@ -156,8 +157,8 @@ static int handle_conflicting_encoders(struct drm_atomic_state *state, * is not set, an error is returned. Userspace can provide a solution * through the atomic ioctl. * - * If the flag is set conflicting connectors are removed from the crtc - * and the crtc is disabled if no encoder is left. This preserves + * If the flag is set conflicting connectors are removed from the CRTC + * and the CRTC is disabled if no encoder is left. This preserves * compatibility with the legacy set_config behavior. */ drm_connector_list_iter_begin(state->dev, &conn_iter); @@ -172,11 +173,12 @@ static int handle_conflicting_encoders(struct drm_atomic_state *state, continue; if (!disable_conflicting_encoders) { - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] in use on [CRTC:%d:%s] by [CONNECTOR:%d:%s]\n", - encoder->base.id, encoder->name, - connector->state->crtc->base.id, - connector->state->crtc->name, - connector->base.id, connector->name); + drm_dbg_atomic(connector->dev, + "[ENCODER:%d:%s] in use on [CRTC:%d:%s] by [CONNECTOR:%d:%s]\n", + encoder->base.id, encoder->name, + connector->state->crtc->base.id, + connector->state->crtc->name, + connector->base.id, connector->name); ret = -EINVAL; goto out; } @@ -187,10 +189,11 @@ static int handle_conflicting_encoders(struct drm_atomic_state *state, goto out; } - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] in use on [CRTC:%d:%s], disabling [CONNECTOR:%d:%s]\n", - encoder->base.id, encoder->name, - new_conn_state->crtc->base.id, new_conn_state->crtc->name, - connector->base.id, connector->name); + drm_dbg_atomic(connector->dev, + "[ENCODER:%d:%s] in use on [CRTC:%d:%s], disabling [CONNECTOR:%d:%s]\n", + encoder->base.id, encoder->name, + new_conn_state->crtc->base.id, new_conn_state->crtc->name, + connector->base.id, connector->name); crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); @@ -226,7 +229,7 @@ set_best_encoder(struct drm_atomic_state *state, crtc = conn_state->connector->state->crtc; /* A NULL crtc is an error here because we should have - * duplicated a NULL best_encoder when crtc was NULL. + * duplicated a NULL best_encoder when crtc was NULL. * As an exception restoring duplicated atomic state * during resume is allowed, so don't warn when * best_encoder is equal to encoder we intend to set. @@ -271,9 +274,10 @@ steal_encoder(struct drm_atomic_state *state, encoder_crtc = old_connector_state->crtc; - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] in use on [CRTC:%d:%s], stealing it\n", - encoder->base.id, encoder->name, - encoder_crtc->base.id, encoder_crtc->name); + drm_dbg_atomic(encoder->dev, + "[ENCODER:%d:%s] in use on [CRTC:%d:%s], stealing it\n", + encoder->base.id, encoder->name, + encoder_crtc->base.id, encoder_crtc->name); set_best_encoder(state, new_connector_state, NULL); @@ -288,15 +292,15 @@ static int update_connector_routing(struct drm_atomic_state *state, struct drm_connector *connector, struct drm_connector_state *old_connector_state, - struct drm_connector_state *new_connector_state) + struct drm_connector_state *new_connector_state, + bool added_by_user) { const struct drm_connector_helper_funcs *funcs; struct drm_encoder *new_encoder; struct drm_crtc_state *crtc_state; - DRM_DEBUG_ATOMIC("Updating routing for [CONNECTOR:%d:%s]\n", - connector->base.id, - connector->name); + drm_dbg_atomic(connector->dev, "Updating routing for [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); if (old_connector_state->crtc != new_connector_state->crtc) { if (old_connector_state->crtc) { @@ -311,9 +315,8 @@ update_connector_routing(struct drm_atomic_state *state, } if (!new_connector_state->crtc) { - DRM_DEBUG_ATOMIC("Disabling [CONNECTOR:%d:%s]\n", - connector->base.id, - connector->name); + drm_dbg_atomic(connector->dev, "Disabling [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); set_best_encoder(state, new_connector_state, NULL); @@ -331,51 +334,64 @@ update_connector_routing(struct drm_atomic_state *state, * Since the connector can be unregistered at any point during an * atomic check or commit, this is racy. But that's OK: all we care * about is ensuring that userspace can't do anything but shut off the - * display on a connector that was destroyed after its been notified, + * display on a connector that was destroyed after it's been notified, * not before. + * + * Additionally, we also want to ignore connector registration when + * we're trying to restore an atomic state during system resume since + * there's a chance the connector may have been destroyed during the + * process, but it's better to ignore that then cause + * drm_atomic_helper_resume() to fail. + * + * Last, we want to ignore connector registration when the connector + * was not pulled in the atomic state by user-space (ie, was pulled + * in by the driver, e.g. when updating a DP-MST stream). */ - if (drm_connector_is_unregistered(connector) && crtc_state->active) { - DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] is not registered\n", - connector->base.id, connector->name); + if (!state->duplicated && drm_connector_is_unregistered(connector) && + added_by_user && crtc_state->active) { + drm_dbg_atomic(connector->dev, + "[CONNECTOR:%d:%s] is not registered\n", + connector->base.id, connector->name); return -EINVAL; } funcs = connector->helper_private; if (funcs->atomic_best_encoder) - new_encoder = funcs->atomic_best_encoder(connector, - new_connector_state); + new_encoder = funcs->atomic_best_encoder(connector, state); else if (funcs->best_encoder) new_encoder = funcs->best_encoder(connector); else - new_encoder = pick_single_encoder_for_connector(connector); + new_encoder = drm_connector_get_single_encoder(connector); if (!new_encoder) { - DRM_DEBUG_ATOMIC("No suitable encoder found for [CONNECTOR:%d:%s]\n", - connector->base.id, - connector->name); + drm_dbg_atomic(connector->dev, + "No suitable encoder found for [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); return -EINVAL; } if (!drm_encoder_crtc_ok(new_encoder, new_connector_state->crtc)) { - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] incompatible with [CRTC:%d:%s]\n", - new_encoder->base.id, - new_encoder->name, - new_connector_state->crtc->base.id, - new_connector_state->crtc->name); + drm_dbg_atomic(connector->dev, + "[ENCODER:%d:%s] incompatible with [CRTC:%d:%s]\n", + new_encoder->base.id, + new_encoder->name, + new_connector_state->crtc->base.id, + new_connector_state->crtc->name); return -EINVAL; } if (new_encoder == new_connector_state->best_encoder) { set_best_encoder(state, new_connector_state, new_encoder); - DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] keeps [ENCODER:%d:%s], now on [CRTC:%d:%s]\n", - connector->base.id, - connector->name, - new_encoder->base.id, - new_encoder->name, - new_connector_state->crtc->base.id, - new_connector_state->crtc->name); + drm_dbg_atomic(connector->dev, + "[CONNECTOR:%d:%s] keeps [ENCODER:%d:%s], now on [CRTC:%d:%s]\n", + connector->base.id, + connector->name, + new_encoder->base.id, + new_encoder->name, + new_connector_state->crtc->base.id, + new_connector_state->crtc->name); return 0; } @@ -386,13 +402,14 @@ update_connector_routing(struct drm_atomic_state *state, crtc_state->connectors_changed = true; - DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] using [ENCODER:%d:%s] on [CRTC:%d:%s]\n", - connector->base.id, - connector->name, - new_encoder->base.id, - new_encoder->name, - new_connector_state->crtc->base.id, - new_connector_state->crtc->name); + drm_dbg_atomic(connector->dev, + "[CONNECTOR:%d:%s] using [ENCODER:%d:%s] on [CRTC:%d:%s]\n", + connector->base.id, + connector->name, + new_encoder->base.id, + new_encoder->name, + new_connector_state->crtc->base.id, + new_connector_state->crtc->name); return 0; } @@ -418,6 +435,7 @@ mode_fixup(struct drm_atomic_state *state) for_each_new_connector_in_state(state, connector, new_conn_state, i) { const struct drm_encoder_helper_funcs *funcs; struct drm_encoder *encoder; + struct drm_bridge *bridge; WARN_ON(!!new_conn_state->best_encoder != !!new_conn_state->crtc); @@ -434,27 +452,32 @@ mode_fixup(struct drm_atomic_state *state) encoder = new_conn_state->best_encoder; funcs = encoder->helper_private; - ret = drm_bridge_mode_fixup(encoder->bridge, &new_crtc_state->mode, - &new_crtc_state->adjusted_mode); - if (!ret) { - DRM_DEBUG_ATOMIC("Bridge fixup failed\n"); - return -EINVAL; + bridge = drm_bridge_chain_get_first_bridge(encoder); + ret = drm_atomic_bridge_chain_check(bridge, + new_crtc_state, + new_conn_state); + drm_bridge_put(bridge); + if (ret) { + drm_dbg_atomic(encoder->dev, "Bridge atomic check failed\n"); + return ret; } if (funcs && funcs->atomic_check) { ret = funcs->atomic_check(encoder, new_crtc_state, new_conn_state); if (ret) { - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] check failed\n", - encoder->base.id, encoder->name); + drm_dbg_atomic(encoder->dev, + "[ENCODER:%d:%s] check failed\n", + encoder->base.id, encoder->name); return ret; } } else if (funcs && funcs->mode_fixup) { ret = funcs->mode_fixup(encoder, &new_crtc_state->mode, &new_crtc_state->adjusted_mode); if (!ret) { - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] fixup failed\n", - encoder->base.id, encoder->name); + drm_dbg_atomic(encoder->dev, + "[ENCODER:%d:%s] fixup failed\n", + encoder->base.id, encoder->name); return -EINVAL; } } @@ -471,14 +494,14 @@ mode_fixup(struct drm_atomic_state *state) continue; funcs = crtc->helper_private; - if (!funcs->mode_fixup) + if (!funcs || !funcs->mode_fixup) continue; ret = funcs->mode_fixup(crtc, &new_crtc_state->mode, &new_crtc_state->adjusted_mode); if (!ret) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] fixup failed\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(crtc->dev, "[CRTC:%d:%s] fixup failed\n", + crtc->base.id, crtc->name); return -EINVAL; } } @@ -489,27 +512,32 @@ mode_fixup(struct drm_atomic_state *state) static enum drm_mode_status mode_valid_path(struct drm_connector *connector, struct drm_encoder *encoder, struct drm_crtc *crtc, - struct drm_display_mode *mode) + const struct drm_display_mode *mode) { + struct drm_bridge *bridge; enum drm_mode_status ret; ret = drm_encoder_mode_valid(encoder, mode); if (ret != MODE_OK) { - DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] mode_valid() failed\n", - encoder->base.id, encoder->name); + drm_dbg_atomic(encoder->dev, + "[ENCODER:%d:%s] mode_valid() failed\n", + encoder->base.id, encoder->name); return ret; } - ret = drm_bridge_mode_valid(encoder->bridge, mode); + bridge = drm_bridge_chain_get_first_bridge(encoder); + ret = drm_bridge_chain_mode_valid(bridge, &connector->display_info, + mode); + drm_bridge_put(bridge); if (ret != MODE_OK) { - DRM_DEBUG_ATOMIC("[BRIDGE] mode_valid() failed\n"); + drm_dbg_atomic(encoder->dev, "[BRIDGE] mode_valid() failed\n"); return ret; } ret = drm_crtc_mode_valid(crtc, mode); if (ret != MODE_OK) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] mode_valid() failed\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(encoder->dev, "[CRTC:%d:%s] mode_valid() failed\n", + crtc->base.id, crtc->name); return ret; } @@ -528,7 +556,7 @@ mode_valid(struct drm_atomic_state *state) struct drm_crtc *crtc = conn_state->crtc; struct drm_crtc_state *crtc_state; enum drm_mode_status mode_status; - struct drm_display_mode *mode; + const struct drm_display_mode *mode; if (!crtc || !encoder) continue; @@ -549,45 +577,69 @@ mode_valid(struct drm_atomic_state *state) return 0; } +static int drm_atomic_check_valid_clones(struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + struct drm_encoder *drm_enc; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + + drm_for_each_encoder_mask(drm_enc, crtc->dev, crtc_state->encoder_mask) { + if (!drm_enc->possible_clones) { + DRM_DEBUG("enc%d possible_clones is 0\n", drm_enc->base.id); + continue; + } + + if ((crtc_state->encoder_mask & drm_enc->possible_clones) != + crtc_state->encoder_mask) { + DRM_DEBUG("crtc%d failed valid clone check for mask 0x%x\n", + crtc->base.id, crtc_state->encoder_mask); + return -EINVAL; + } + } + + return 0; +} + /** * drm_atomic_helper_check_modeset - validate state object for modeset changes * @dev: DRM device * @state: the driver state object * * Check the state object to see if the requested state is physically possible. - * This does all the crtc and connector related computations for an atomic + * This does all the CRTC and connector related computations for an atomic * update and adds any additional connectors needed for full modesets. It calls * the various per-object callbacks in the follow order: * * 1. &drm_connector_helper_funcs.atomic_best_encoder for determining the new encoder. * 2. &drm_connector_helper_funcs.atomic_check to validate the connector state. - * 3. If it's determined a modeset is needed then all connectors on the affected crtc - * crtc are added and &drm_connector_helper_funcs.atomic_check is run on them. + * 3. If it's determined a modeset is needed then all connectors on the affected + * CRTC are added and &drm_connector_helper_funcs.atomic_check is run on them. * 4. &drm_encoder_helper_funcs.mode_valid, &drm_bridge_funcs.mode_valid and * &drm_crtc_helper_funcs.mode_valid are called on the affected components. * 5. &drm_bridge_funcs.mode_fixup is called on all encoder bridges. * 6. &drm_encoder_helper_funcs.atomic_check is called to validate any encoder state. - * This function is only called when the encoder will be part of a configured crtc, + * This function is only called when the encoder will be part of a configured CRTC, * it must not be used for implementing connector property validation. * If this function is NULL, &drm_atomic_encoder_helper_funcs.mode_fixup is called * instead. - * 7. &drm_crtc_helper_funcs.mode_fixup is called last, to fix up the mode with crtc constraints. + * 7. &drm_crtc_helper_funcs.mode_fixup is called last, to fix up the mode with CRTC constraints. * * &drm_crtc_state.mode_changed is set when the input mode is changed. * &drm_crtc_state.connectors_changed is set when a connector is added or - * removed from the crtc. &drm_crtc_state.active_changed is set when + * removed from the CRTC. &drm_crtc_state.active_changed is set when * &drm_crtc_state.active changes, which is used for DPMS. + * &drm_crtc_state.no_vblank is set from the result of drm_dev_has_vblank(). * See also: drm_atomic_crtc_needs_modeset() * * IMPORTANT: * * Drivers which set &drm_crtc_state.mode_changed (e.g. in their * &drm_plane_helper_funcs.atomic_check hooks if a plane update can't be done - * without a full modeset) _must_ call this function afterwards after that - * change. It is permitted to call this function multiple times for the same - * update, e.g. when the &drm_crtc_helper_funcs.atomic_check functions depend - * upon the adjusted dotclock for fifo space allocation and watermark - * computation. + * without a full modeset) _must_ call this function after that change. It is + * permitted to call this function multiple times for the same update, e.g. + * when the &drm_crtc_helper_funcs.atomic_check functions depend upon the + * adjusted dotclock for fifo space allocation and watermark computation. * * RETURNS: * Zero for success or -errno @@ -601,7 +653,10 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, struct drm_connector *connector; struct drm_connector_state *old_connector_state, *new_connector_state; int i, ret; - unsigned connectors_mask = 0; + unsigned int connectors_mask = 0, user_connectors_mask = 0; + + for_each_oldnew_connector_in_state(state, connector, old_connector_state, new_connector_state, i) + user_connectors_mask |= BIT(i); for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { bool has_connectors = @@ -610,14 +665,14 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, WARN_ON(!drm_modeset_is_locked(&crtc->mutex)); if (!drm_mode_equal(&old_crtc_state->mode, &new_crtc_state->mode)) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] mode changed\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(dev, "[CRTC:%d:%s] mode changed\n", + crtc->base.id, crtc->name); new_crtc_state->mode_changed = true; } if (old_crtc_state->enable != new_crtc_state->enable) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] enable changed\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(dev, "[CRTC:%d:%s] enable changed\n", + crtc->base.id, crtc->name); /* * For clarity this assignment is done here, but @@ -625,24 +680,30 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, * connectors and a NULL mode. * * The other way around is true as well. enable != 0 - * iff connectors are attached and a mode is set. + * implies that connectors are attached and a mode is set. */ new_crtc_state->mode_changed = true; new_crtc_state->connectors_changed = true; } if (old_crtc_state->active != new_crtc_state->active) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] active changed\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(dev, "[CRTC:%d:%s] active changed\n", + crtc->base.id, crtc->name); new_crtc_state->active_changed = true; } if (new_crtc_state->enable != has_connectors) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] enabled/connectors mismatch\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(dev, "[CRTC:%d:%s] enabled/connectors mismatch (%d/%d)\n", + crtc->base.id, crtc->name, + new_crtc_state->enable, has_connectors); return -EINVAL; } + + if (drm_dev_has_vblank(dev)) + new_crtc_state->no_vblank = false; + else + new_crtc_state->no_vblank = true; } ret = handle_conflicting_encoders(state, false); @@ -661,7 +722,8 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, */ ret = update_connector_routing(state, connector, old_connector_state, - new_connector_state); + new_connector_state, + BIT(i) & user_connectors_mask); if (ret) return ret; if (old_connector_state->crtc) { @@ -677,16 +739,20 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, } if (funcs->atomic_check) - ret = funcs->atomic_check(connector, new_connector_state); - if (ret) + ret = funcs->atomic_check(connector, state); + if (ret) { + drm_dbg_atomic(dev, + "[CONNECTOR:%d:%s] driver check failed\n", + connector->base.id, connector->name); return ret; + } connectors_mask |= BIT(i); } /* * After all the routing has been prepared we need to add in any - * connector which is itself unchanged, but who's crtc changes it's + * connector which is itself unchanged, but whose CRTC changes its * configuration. This must be done before calling mode_fixup in case a * crtc only changed its mode but has the same set of connectors. */ @@ -694,10 +760,11 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) continue; - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] needs all connectors, enable: %c, active: %c\n", - crtc->base.id, crtc->name, - new_crtc_state->enable ? 'y' : 'n', - new_crtc_state->active ? 'y' : 'n'); + drm_dbg_atomic(dev, + "[CRTC:%d:%s] needs all connectors, enable: %c, active: %c\n", + crtc->base.id, crtc->name, + new_crtc_state->enable ? 'y' : 'n', + new_crtc_state->active ? 'y' : 'n'); ret = drm_atomic_add_affected_connectors(state, crtc); if (ret != 0) @@ -706,6 +773,10 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, ret = drm_atomic_add_affected_planes(state, crtc); if (ret != 0) return ret; + + ret = drm_atomic_check_valid_clones(state, crtc); + if (ret != 0) + return ret; } /* @@ -719,7 +790,31 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, continue; if (funcs->atomic_check) - ret = funcs->atomic_check(connector, new_connector_state); + ret = funcs->atomic_check(connector, state); + if (ret) { + drm_dbg_atomic(dev, + "[CONNECTOR:%d:%s] driver check failed\n", + connector->base.id, connector->name); + return ret; + } + } + + /* + * Iterate over all connectors again, and add all affected bridges to + * the state. + */ + for_each_oldnew_connector_in_state(state, connector, + old_connector_state, + new_connector_state, i) { + struct drm_encoder *encoder; + + encoder = old_connector_state->best_encoder; + ret = drm_atomic_add_encoder_bridges(state, encoder); + if (ret) + return ret; + + encoder = new_connector_state->best_encoder; + ret = drm_atomic_add_encoder_bridges(state, encoder); if (ret) return ret; } @@ -733,15 +828,56 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, EXPORT_SYMBOL(drm_atomic_helper_check_modeset); /** + * drm_atomic_helper_check_wb_connector_state() - Check writeback connector state + * @connector: corresponding connector + * @state: the driver state object + * + * Checks if the writeback connector state is valid, and returns an error if it + * isn't. + * + * RETURNS: + * Zero for success or -errno + */ +int +drm_atomic_helper_check_wb_connector_state(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *conn_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_writeback_job *wb_job = conn_state->writeback_job; + struct drm_property_blob *pixel_format_blob; + struct drm_framebuffer *fb; + size_t i, nformats; + u32 *formats; + + if (!wb_job || !wb_job->fb) + return 0; + + pixel_format_blob = wb_job->connector->pixel_formats_blob_ptr; + nformats = pixel_format_blob->length / sizeof(u32); + formats = pixel_format_blob->data; + fb = wb_job->fb; + + for (i = 0; i < nformats; i++) + if (fb->format->format == formats[i]) + return 0; + + drm_dbg_kms(connector->dev, "Invalid pixel format %p4cc\n", &fb->format->format); + + return -EINVAL; +} +EXPORT_SYMBOL(drm_atomic_helper_check_wb_connector_state); + +/** * drm_atomic_helper_check_plane_state() - Check plane state for validity * @plane_state: plane state to check - * @crtc_state: crtc state to check + * @crtc_state: CRTC state to check * @min_scale: minimum @src:@dest scaling factor in 16.16 fixed point * @max_scale: maximum @src:@dest scaling factor in 16.16 fixed point * @can_position: is it legal to position the plane such that it - * doesn't cover the entire crtc? This will generally + * doesn't cover the entire CRTC? This will generally * only be false for primary planes. - * @can_update_disabled: can the plane be updated while the crtc + * @can_update_disabled: can the plane be updated while the CRTC * is disabled? * * Checks that a desired plane update is valid, and updates various @@ -784,7 +920,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, } if (!crtc_state->enable && !can_update_disabled) { - DRM_DEBUG_KMS("Cannot update plane of a disabled CRTC.\n"); + drm_dbg_kms(plane_state->plane->dev, + "Cannot update plane of a disabled CRTC.\n"); return -EINVAL; } @@ -794,7 +931,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, hscale = drm_rect_calc_hscale(src, dst, min_scale, max_scale); vscale = drm_rect_calc_vscale(src, dst, min_scale, max_scale); if (hscale < 0 || vscale < 0) { - DRM_DEBUG_KMS("Invalid scaling of plane\n"); + drm_dbg_kms(plane_state->plane->dev, + "Invalid scaling of plane\n"); drm_rect_debug_print("src: ", &plane_state->src, true); drm_rect_debug_print("dst: ", &plane_state->dst, false); return -ERANGE; @@ -818,7 +956,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, return 0; if (!can_position && !drm_rect_equals(dst, &clip)) { - DRM_DEBUG_KMS("Plane must cover entire CRTC\n"); + drm_dbg_kms(plane_state->plane->dev, + "Plane must cover entire CRTC\n"); drm_rect_debug_print("dst: ", dst, false); drm_rect_debug_print("clip: ", &clip, false); return -EINVAL; @@ -829,6 +968,37 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, EXPORT_SYMBOL(drm_atomic_helper_check_plane_state); /** + * drm_atomic_helper_check_crtc_primary_plane() - Check CRTC state for primary plane + * @crtc_state: CRTC state to check + * + * Checks that a CRTC has at least one primary plane attached to it, which is + * a requirement on some hardware. Note that this only involves the CRTC side + * of the test. To test if the primary plane is visible or if it can be updated + * without the CRTC being enabled, use drm_atomic_helper_check_plane_state() in + * the plane's atomic check. + * + * RETURNS: + * 0 if a primary plane is attached to the CRTC, or an error code otherwise + */ +int drm_atomic_helper_check_crtc_primary_plane(struct drm_crtc_state *crtc_state) +{ + struct drm_crtc *crtc = crtc_state->crtc; + struct drm_device *dev = crtc->dev; + struct drm_plane *plane; + + /* needs at least one primary plane to be enabled */ + drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + return 0; + } + + drm_dbg_atomic(dev, "[CRTC:%d:%s] primary plane missing\n", crtc->base.id, crtc->name); + + return -EINVAL; +} +EXPORT_SYMBOL(drm_atomic_helper_check_crtc_primary_plane); + +/** * drm_atomic_helper_check_planes - validate state object for planes changes * @dev: DRM device * @state: the driver state object @@ -838,7 +1008,7 @@ EXPORT_SYMBOL(drm_atomic_helper_check_plane_state); * &drm_crtc_helper_funcs.atomic_check and &drm_plane_helper_funcs.atomic_check * hooks provided by the driver. * - * It also sets &drm_crtc_state.planes_changed to indicate that a crtc has + * It also sets &drm_crtc_state.planes_changed to indicate that a CRTC has * updated planes. * * RETURNS: @@ -868,10 +1038,11 @@ drm_atomic_helper_check_planes(struct drm_device *dev, if (!funcs || !funcs->atomic_check) continue; - ret = funcs->atomic_check(plane, new_plane_state); + ret = funcs->atomic_check(plane, state); if (ret) { - DRM_DEBUG_ATOMIC("[PLANE:%d:%s] atomic driver check failed\n", - plane->base.id, plane->name); + drm_dbg_atomic(plane->dev, + "[PLANE:%d:%s] atomic driver check failed\n", + plane->base.id, plane->name); return ret; } } @@ -884,10 +1055,11 @@ drm_atomic_helper_check_planes(struct drm_device *dev, if (!funcs || !funcs->atomic_check) continue; - ret = funcs->atomic_check(crtc, new_crtc_state); + ret = funcs->atomic_check(crtc, state); if (ret) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] atomic driver check failed\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(crtc->dev, + "[CRTC:%d:%s] atomic driver check failed\n", + crtc->base.id, crtc->name); return ret; } } @@ -902,7 +1074,7 @@ EXPORT_SYMBOL(drm_atomic_helper_check_planes); * @state: the driver state object * * Check the state object to see if the requested state is physically possible. - * Only crtcs and planes have check callbacks, so for any additional (global) + * Only CRTCs and planes have check callbacks, so for any additional (global) * checking that a driver needs it can simply wrap that around this function. * Drivers without such needs can directly use this as their * &drm_mode_config_funcs.atomic_check callback. @@ -919,6 +1091,15 @@ EXPORT_SYMBOL(drm_atomic_helper_check_planes); * For example enable/disable of a cursor plane which have fixed zpos value * would trigger all other enabled planes to be forced to the state change. * + * IMPORTANT: + * + * As this function calls drm_atomic_helper_check_modeset() internally, its + * restrictions also apply: + * Drivers which set &drm_crtc_state.mode_changed (e.g. in their + * &drm_plane_helper_funcs.atomic_check hooks if a plane update can't be done + * without a full modeset) _must_ call drm_atomic_helper_check_modeset() + * function again after that change. + * * RETURNS: * Zero for success or -errno */ @@ -944,31 +1125,73 @@ int drm_atomic_helper_check(struct drm_device *dev, if (state->legacy_cursor_update) state->async_update = !drm_atomic_helper_async_check(dev, state); + drm_self_refresh_helper_alter_state(state); + return ret; } EXPORT_SYMBOL(drm_atomic_helper_check); +static bool +crtc_needs_disable(struct drm_crtc_state *old_state, + struct drm_crtc_state *new_state) +{ + /* + * No new_state means the CRTC is off, so the only criteria is whether + * it's currently active or in self refresh mode. + */ + if (!new_state) + return drm_atomic_crtc_effectively_active(old_state); + + /* + * We need to disable bridge(s) and CRTC if we're transitioning out of + * self-refresh and changing CRTCs at the same time, because the + * bridge tracks self-refresh status via CRTC state. + */ + if (old_state->self_refresh_active && + old_state->crtc != new_state->crtc) + return true; + + /* + * We also need to run through the crtc_funcs->disable() function if + * the CRTC is currently on, if it's transitioning to self refresh + * mode, or if it's in self refresh mode and needs to be fully + * disabled. + */ + return old_state->active || + (old_state->self_refresh_active && !new_state->active) || + new_state->self_refresh_active; +} + static void -disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state) +encoder_bridge_disable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *old_conn_state, *new_conn_state; - struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; int i; - for_each_oldnew_connector_in_state(old_state, connector, old_conn_state, new_conn_state, i) { + for_each_oldnew_connector_in_state(state, connector, old_conn_state, new_conn_state, i) { const struct drm_encoder_helper_funcs *funcs; struct drm_encoder *encoder; + struct drm_bridge *bridge; - /* Shut down everything that's in the changeset and currently - * still on. So need to check the old, saved state. */ + /* + * Shut down everything that's in the changeset and currently + * still on. So need to check the old, saved state. + */ if (!old_conn_state->crtc) continue; - old_crtc_state = drm_atomic_get_old_crtc_state(old_state, old_conn_state->crtc); + old_crtc_state = drm_atomic_get_old_crtc_state(state, old_conn_state->crtc); + + if (new_conn_state->crtc) + new_crtc_state = drm_atomic_get_new_crtc_state( + state, + new_conn_state->crtc); + else + new_crtc_state = NULL; - if (!old_crtc_state->active || + if (!crtc_needs_disable(old_crtc_state, new_crtc_state) || !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state)) continue; @@ -982,29 +1205,39 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state) funcs = encoder->helper_private; - DRM_DEBUG_ATOMIC("disabling [ENCODER:%d:%s]\n", - encoder->base.id, encoder->name); + drm_dbg_atomic(dev, "disabling [ENCODER:%d:%s]\n", + encoder->base.id, encoder->name); /* * Each encoder has at most one connector (since we always steal * it away), so we won't call disable hooks twice. */ - drm_bridge_disable(encoder->bridge); + bridge = drm_bridge_chain_get_first_bridge(encoder); + drm_atomic_bridge_chain_disable(bridge, state); + drm_bridge_put(bridge); /* Right function depends upon target state. */ if (funcs) { - if (new_conn_state->crtc && funcs->prepare) + if (funcs->atomic_disable) + funcs->atomic_disable(encoder, state); + else if (new_conn_state->crtc && funcs->prepare) funcs->prepare(encoder); else if (funcs->disable) funcs->disable(encoder); else if (funcs->dpms) funcs->dpms(encoder, DRM_MODE_DPMS_OFF); } - - drm_bridge_post_disable(encoder->bridge); } +} - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { +static void +crtc_disable(struct drm_device *dev, struct drm_atomic_state *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + int i; + + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; int ret; @@ -1012,44 +1245,114 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state) if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) continue; - if (!old_crtc_state->active) + if (!crtc_needs_disable(old_crtc_state, new_crtc_state)) continue; funcs = crtc->helper_private; - DRM_DEBUG_ATOMIC("disabling [CRTC:%d:%s]\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(dev, "disabling [CRTC:%d:%s]\n", + crtc->base.id, crtc->name); /* Right function depends upon target state. */ if (new_crtc_state->enable && funcs->prepare) funcs->prepare(crtc); else if (funcs->atomic_disable) - funcs->atomic_disable(crtc, old_crtc_state); + funcs->atomic_disable(crtc, state); else if (funcs->disable) funcs->disable(crtc); - else + else if (funcs->dpms) funcs->dpms(crtc, DRM_MODE_DPMS_OFF); - if (!(dev->irq_enabled && dev->num_crtcs)) + if (!drm_dev_has_vblank(dev)) continue; ret = drm_crtc_vblank_get(crtc); - WARN_ONCE(ret != -EINVAL, "driver forgot to call drm_crtc_vblank_off()\n"); + /* + * Self-refresh is not a true "disable"; ensure vblank remains + * enabled. + */ + if (new_crtc_state->self_refresh_active) + WARN_ONCE(ret != 0, + "driver disabled vblank in self-refresh\n"); + else + WARN_ONCE(ret != -EINVAL, + "driver forgot to call drm_crtc_vblank_off()\n"); if (ret == 0) drm_crtc_vblank_put(crtc); } } +static void +encoder_bridge_post_disable(struct drm_device *dev, struct drm_atomic_state *state) +{ + struct drm_connector *connector; + struct drm_connector_state *old_conn_state, *new_conn_state; + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + int i; + + for_each_oldnew_connector_in_state(state, connector, old_conn_state, new_conn_state, i) { + struct drm_encoder *encoder; + struct drm_bridge *bridge; + + /* + * Shut down everything that's in the changeset and currently + * still on. So need to check the old, saved state. + */ + if (!old_conn_state->crtc) + continue; + + old_crtc_state = drm_atomic_get_old_crtc_state(state, old_conn_state->crtc); + + if (new_conn_state->crtc) + new_crtc_state = drm_atomic_get_new_crtc_state(state, + new_conn_state->crtc); + else + new_crtc_state = NULL; + + if (!crtc_needs_disable(old_crtc_state, new_crtc_state) || + !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state)) + continue; + + encoder = old_conn_state->best_encoder; + + /* + * We shouldn't get this far if we didn't previously have + * an encoder.. but WARN_ON() rather than explode. + */ + if (WARN_ON(!encoder)) + continue; + + drm_dbg_atomic(dev, "post-disabling bridges [ENCODER:%d:%s]\n", + encoder->base.id, encoder->name); + + /* + * Each encoder has at most one connector (since we always steal + * it away), so we won't call disable hooks twice. + */ + bridge = drm_bridge_chain_get_first_bridge(encoder); + drm_atomic_bridge_chain_post_disable(bridge, state); + drm_bridge_put(bridge); + } +} + +static void +disable_outputs(struct drm_device *dev, struct drm_atomic_state *state) +{ + encoder_bridge_disable(dev, state); + + crtc_disable(dev, state); + + encoder_bridge_post_disable(dev, state); +} + /** * drm_atomic_helper_update_legacy_modeset_state - update legacy modeset state * @dev: DRM device - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * * This function updates all the various legacy modeset state pointers in - * connectors, encoders and crtcs. It also updates the timestamping constants - * used for precise vblank timestamps by calling - * drm_calc_timestamping_constants(). + * connectors, encoders and CRTCs. * * Drivers can use this for building their own atomic commit if they don't have * a pure helper-based modeset implementation. @@ -1062,7 +1365,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state) */ void drm_atomic_helper_update_legacy_modeset_state(struct drm_device *dev, - struct drm_atomic_state *old_state) + struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *old_conn_state, *new_conn_state; @@ -1071,7 +1374,7 @@ drm_atomic_helper_update_legacy_modeset_state(struct drm_device *dev, int i; /* clear out existing links and update dpms */ - for_each_oldnew_connector_in_state(old_state, connector, old_conn_state, new_conn_state, i) { + for_each_oldnew_connector_in_state(state, connector, old_conn_state, new_conn_state, i) { if (connector->encoder) { WARN_ON(!connector->encoder->crtc); @@ -1092,7 +1395,7 @@ drm_atomic_helper_update_legacy_modeset_state(struct drm_device *dev, } /* set new links */ - for_each_new_connector_in_state(old_state, connector, new_conn_state, i) { + for_each_new_connector_in_state(state, connector, new_conn_state, i) { if (!new_conn_state->crtc) continue; @@ -1104,7 +1407,7 @@ drm_atomic_helper_update_legacy_modeset_state(struct drm_device *dev, } /* set legacy state in the crtc structure */ - for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) { + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { struct drm_plane *primary = crtc->primary; struct drm_plane_state *new_plane_state; @@ -1112,22 +1415,39 @@ drm_atomic_helper_update_legacy_modeset_state(struct drm_device *dev, crtc->enabled = new_crtc_state->enable; new_plane_state = - drm_atomic_get_new_plane_state(old_state, primary); + drm_atomic_get_new_plane_state(state, primary); if (new_plane_state && new_plane_state->crtc == crtc) { crtc->x = new_plane_state->src_x >> 16; crtc->y = new_plane_state->src_y >> 16; } + } +} +EXPORT_SYMBOL(drm_atomic_helper_update_legacy_modeset_state); + +/** + * drm_atomic_helper_calc_timestamping_constants - update vblank timestamping constants + * @state: atomic state object + * + * Updates the timestamping constants used for precise vblank timestamps + * by calling drm_calc_timestamping_constants() for all enabled crtcs in @state. + */ +void drm_atomic_helper_calc_timestamping_constants(struct drm_atomic_state *state) +{ + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; + int i; + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { if (new_crtc_state->enable) drm_calc_timestamping_constants(crtc, &new_crtc_state->adjusted_mode); } } -EXPORT_SYMBOL(drm_atomic_helper_update_legacy_modeset_state); +EXPORT_SYMBOL(drm_atomic_helper_calc_timestamping_constants); static void -crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state) +crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; @@ -1135,7 +1455,7 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state) struct drm_connector_state *new_conn_state; int i; - for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) { + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; if (!new_crtc_state->mode_changed) @@ -1144,17 +1464,18 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state) funcs = crtc->helper_private; if (new_crtc_state->enable && funcs->mode_set_nofb) { - DRM_DEBUG_ATOMIC("modeset on [CRTC:%d:%s]\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(dev, "modeset on [CRTC:%d:%s]\n", + crtc->base.id, crtc->name); funcs->mode_set_nofb(crtc); } } - for_each_new_connector_in_state(old_state, connector, new_conn_state, i) { + for_each_new_connector_in_state(state, connector, new_conn_state, i) { const struct drm_encoder_helper_funcs *funcs; struct drm_encoder *encoder; struct drm_display_mode *mode, *adjusted_mode; + struct drm_bridge *bridge; if (!new_conn_state->best_encoder) continue; @@ -1165,11 +1486,11 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state) mode = &new_crtc_state->mode; adjusted_mode = &new_crtc_state->adjusted_mode; - if (!new_crtc_state->mode_changed) + if (!new_crtc_state->mode_changed && !new_crtc_state->connectors_changed) continue; - DRM_DEBUG_ATOMIC("modeset on [ENCODER:%d:%s]\n", - encoder->base.id, encoder->name); + drm_dbg_atomic(dev, "modeset on [ENCODER:%d:%s]\n", + encoder->base.id, encoder->name); /* * Each encoder has at most one connector (since we always steal @@ -1182,43 +1503,46 @@ crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state) funcs->mode_set(encoder, mode, adjusted_mode); } - drm_bridge_mode_set(encoder->bridge, mode, adjusted_mode); + bridge = drm_bridge_chain_get_first_bridge(encoder); + drm_bridge_chain_mode_set(bridge, mode, adjusted_mode); + drm_bridge_put(bridge); } } /** * drm_atomic_helper_commit_modeset_disables - modeset commit to disable outputs * @dev: DRM device - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * * This function shuts down all the outputs that need to be shut down and * prepares them (if required) with the new mode. * - * For compatibility with legacy crtc helpers this should be called before + * For compatibility with legacy CRTC helpers this should be called before * drm_atomic_helper_commit_planes(), which is what the default commit function * does. But drivers with different needs can group the modeset commits together * and do the plane commits at the end. This is useful for drivers doing runtime * PM since planes updates then only happen when the CRTC is actually enabled. */ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev, - struct drm_atomic_state *old_state) + struct drm_atomic_state *state) { - disable_outputs(dev, old_state); + disable_outputs(dev, state); - drm_atomic_helper_update_legacy_modeset_state(dev, old_state); + drm_atomic_helper_update_legacy_modeset_state(dev, state); + drm_atomic_helper_calc_timestamping_constants(state); - crtc_set_mode(dev, old_state); + crtc_set_mode(dev, state); } EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables); static void drm_atomic_helper_commit_writebacks(struct drm_device *dev, - struct drm_atomic_state *old_state) + struct drm_atomic_state *state) { struct drm_connector *connector; struct drm_connector_state *new_conn_state; int i; - for_each_new_connector_in_state(old_state, connector, new_conn_state, i) { + for_each_new_connector_in_state(state, connector, new_conn_state, i) { const struct drm_connector_helper_funcs *funcs; funcs = connector->helper_private; @@ -1227,36 +1551,53 @@ static void drm_atomic_helper_commit_writebacks(struct drm_device *dev, if (new_conn_state->writeback_job && new_conn_state->writeback_job->fb) { WARN_ON(connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK); - funcs->atomic_commit(connector, new_conn_state); + funcs->atomic_commit(connector, state); } } } -/** - * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs - * @dev: DRM device - * @old_state: atomic state object with old state structures - * - * This function enables all the outputs with the new configuration which had to - * be turned off for the update. - * - * For compatibility with legacy crtc helpers this should be called after - * drm_atomic_helper_commit_planes(), which is what the default commit function - * does. But drivers with different needs can group the modeset commits together - * and do the plane commits at the end. This is useful for drivers doing runtime - * PM since planes updates then only happen when the CRTC is actually enabled. - */ -void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, - struct drm_atomic_state *old_state) +static void +encoder_bridge_pre_enable(struct drm_device *dev, struct drm_atomic_state *state) +{ + struct drm_connector *connector; + struct drm_connector_state *new_conn_state; + int i; + + for_each_new_connector_in_state(state, connector, new_conn_state, i) { + struct drm_encoder *encoder; + struct drm_bridge *bridge; + + if (!new_conn_state->best_encoder) + continue; + + if (!new_conn_state->crtc->state->active || + !drm_atomic_crtc_needs_modeset(new_conn_state->crtc->state)) + continue; + + encoder = new_conn_state->best_encoder; + + drm_dbg_atomic(dev, "pre-enabling bridges [ENCODER:%d:%s]\n", + encoder->base.id, encoder->name); + + /* + * Each encoder has at most one connector (since we always steal + * it away), so we won't call enable hooks twice. + */ + bridge = drm_bridge_chain_get_first_bridge(encoder); + drm_atomic_bridge_chain_pre_enable(bridge, state); + drm_bridge_put(bridge); + } +} + +static void +crtc_enable(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; struct drm_crtc_state *new_crtc_state; - struct drm_connector *connector; - struct drm_connector_state *new_conn_state; int i; - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; /* Need to filter out CRTCs where only planes change. */ @@ -1269,19 +1610,27 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, funcs = crtc->helper_private; if (new_crtc_state->enable) { - DRM_DEBUG_ATOMIC("enabling [CRTC:%d:%s]\n", - crtc->base.id, crtc->name); - + drm_dbg_atomic(dev, "enabling [CRTC:%d:%s]\n", + crtc->base.id, crtc->name); if (funcs->atomic_enable) - funcs->atomic_enable(crtc, old_crtc_state); - else + funcs->atomic_enable(crtc, state); + else if (funcs->commit) funcs->commit(crtc); } } +} - for_each_new_connector_in_state(old_state, connector, new_conn_state, i) { +static void +encoder_bridge_enable(struct drm_device *dev, struct drm_atomic_state *state) +{ + struct drm_connector *connector; + struct drm_connector_state *new_conn_state; + int i; + + for_each_new_connector_in_state(state, connector, new_conn_state, i) { const struct drm_encoder_helper_funcs *funcs; struct drm_encoder *encoder; + struct drm_bridge *bridge; if (!new_conn_state->best_encoder) continue; @@ -1293,35 +1642,103 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, encoder = new_conn_state->best_encoder; funcs = encoder->helper_private; - DRM_DEBUG_ATOMIC("enabling [ENCODER:%d:%s]\n", - encoder->base.id, encoder->name); + drm_dbg_atomic(dev, "enabling [ENCODER:%d:%s]\n", + encoder->base.id, encoder->name); /* * Each encoder has at most one connector (since we always steal * it away), so we won't call enable hooks twice. */ - drm_bridge_pre_enable(encoder->bridge); + bridge = drm_bridge_chain_get_first_bridge(encoder); if (funcs) { - if (funcs->enable) + if (funcs->atomic_enable) + funcs->atomic_enable(encoder, state); + else if (funcs->enable) funcs->enable(encoder); else if (funcs->commit) funcs->commit(encoder); } - drm_bridge_enable(encoder->bridge); + drm_atomic_bridge_chain_enable(bridge, state); + drm_bridge_put(bridge); } +} + +/** + * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs + * @dev: DRM device + * @state: atomic state object being committed + * + * This function enables all the outputs with the new configuration which had to + * be turned off for the update. + * + * For compatibility with legacy CRTC helpers this should be called after + * drm_atomic_helper_commit_planes(), which is what the default commit function + * does. But drivers with different needs can group the modeset commits together + * and do the plane commits at the end. This is useful for drivers doing runtime + * PM since planes updates then only happen when the CRTC is actually enabled. + */ +void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, + struct drm_atomic_state *state) +{ + encoder_bridge_pre_enable(dev, state); + + crtc_enable(dev, state); + + encoder_bridge_enable(dev, state); - drm_atomic_helper_commit_writebacks(dev, old_state); + drm_atomic_helper_commit_writebacks(dev, state); } EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables); +/* + * For atomic updates which touch just a single CRTC, calculate the time of the + * next vblank, and inform all the fences of the deadline. + */ +static void set_fence_deadline(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_plane *plane; + struct drm_plane_state *new_plane_state; + ktime_t vbltime = 0; + int i; + + for_each_new_crtc_in_state (state, crtc, new_crtc_state, i) { + ktime_t v; + + if (drm_atomic_crtc_needs_modeset(new_crtc_state)) + continue; + + if (!new_crtc_state->active) + continue; + + if (drm_crtc_next_vblank_start(crtc, &v)) + continue; + + if (!vbltime || ktime_before(v, vbltime)) + vbltime = v; + } + + /* If no CRTCs updated, then nothing to do: */ + if (!vbltime) + return; + + for_each_new_plane_in_state (state, plane, new_plane_state, i) { + if (!new_plane_state->fence) + continue; + dma_fence_set_deadline(new_plane_state->fence, vbltime); + } +} + /** * drm_atomic_helper_wait_for_fences - wait for fences stashed in plane state * @dev: DRM device * @state: atomic state object with old state structures * @pre_swap: If true, do an interruptible wait, and @state is the new state. - * Otherwise @state is the old state. + * Otherwise @state is the old state. * * For implicit sync, driver should fish the exclusive fence out from the * incoming fb's and stash it in the drm_plane_state. This is called after @@ -1345,6 +1762,8 @@ int drm_atomic_helper_wait_for_fences(struct drm_device *dev, struct drm_plane_state *new_plane_state; int i, ret; + set_fence_deadline(dev, state); + for_each_new_plane_in_state(state, plane, new_plane_state, i) { if (!new_plane_state->fence) continue; @@ -1369,12 +1788,12 @@ int drm_atomic_helper_wait_for_fences(struct drm_device *dev, EXPORT_SYMBOL(drm_atomic_helper_wait_for_fences); /** - * drm_atomic_helper_wait_for_vblanks - wait for vblank on crtcs + * drm_atomic_helper_wait_for_vblanks - wait for vblank on CRTCs * @dev: DRM device - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * - * Helper to, after atomic commit, wait for vblanks on all effected - * crtcs (ie. before cleaning up old framebuffers using + * Helper to, after atomic commit, wait for vblanks on all affected + * CRTCs (ie. before cleaning up old framebuffers using * drm_atomic_helper_cleanup_planes()). It will only wait on CRTCs where the * framebuffers have actually changed to optimize for the legacy cursor and * plane update use-case. @@ -1385,21 +1804,21 @@ EXPORT_SYMBOL(drm_atomic_helper_wait_for_fences); */ void drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, - struct drm_atomic_state *old_state) + struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; int i, ret; - unsigned crtc_mask = 0; + unsigned int crtc_mask = 0; /* * Legacy cursor ioctls are completely unsynced, and userspace * relies on that (by doing tons of cursor updates). */ - if (old_state->legacy_cursor_update) + if (state->legacy_cursor_update) return; - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { if (!new_crtc_state->active) continue; @@ -1408,17 +1827,19 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, continue; crtc_mask |= drm_crtc_mask(crtc); - old_state->crtcs[i].last_vblank_count = drm_crtc_vblank_count(crtc); + state->crtcs[i].last_vblank_count = drm_crtc_vblank_count(crtc); } - for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) { + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { + wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc); + if (!(crtc_mask & drm_crtc_mask(crtc))) continue; - ret = wait_event_timeout(dev->vblank[i].queue, - old_state->crtcs[i].last_vblank_count != - drm_crtc_vblank_count(crtc), - msecs_to_jiffies(50)); + ret = wait_event_timeout(*queue, + state->crtcs[i].last_vblank_count != + drm_crtc_vblank_count(crtc), + msecs_to_jiffies(100)); WARN(!ret, "[CRTC:%d:%s] vblank wait timed out\n", crtc->base.id, crtc->name); @@ -1431,12 +1852,12 @@ EXPORT_SYMBOL(drm_atomic_helper_wait_for_vblanks); /** * drm_atomic_helper_wait_for_flip_done - wait for all page flips to be done * @dev: DRM device - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * - * Helper to, after atomic commit, wait for page flips on all effected + * Helper to, after atomic commit, wait for page flips on all affected * crtcs (ie. before cleaning up old framebuffers using * drm_atomic_helper_cleanup_planes()). Compared to - * drm_atomic_helper_wait_for_vblanks() this waits for the completion of on all + * drm_atomic_helper_wait_for_vblanks() this waits for the completion on all * CRTCs, assuming that cursors-only updates are signalling their completion * immediately (or using a different path). * @@ -1444,34 +1865,34 @@ EXPORT_SYMBOL(drm_atomic_helper_wait_for_vblanks); * initialized using drm_atomic_helper_setup_commit(). */ void drm_atomic_helper_wait_for_flip_done(struct drm_device *dev, - struct drm_atomic_state *old_state) + struct drm_atomic_state *state) { struct drm_crtc *crtc; int i; for (i = 0; i < dev->mode_config.num_crtc; i++) { - struct drm_crtc_commit *commit = old_state->crtcs[i].commit; + struct drm_crtc_commit *commit = state->crtcs[i].commit; int ret; - crtc = old_state->crtcs[i].ptr; + crtc = state->crtcs[i].ptr; if (!crtc || !commit) continue; ret = wait_for_completion_timeout(&commit->flip_done, 10 * HZ); if (ret == 0) - DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n", - crtc->base.id, crtc->name); + drm_err(dev, "[CRTC:%d:%s] flip_done timed out\n", + crtc->base.id, crtc->name); } - if (old_state->fake_commit) - complete_all(&old_state->fake_commit->flip_done); + if (state->fake_commit) + complete_all(&state->fake_commit->flip_done); } EXPORT_SYMBOL(drm_atomic_helper_wait_for_flip_done); /** * drm_atomic_helper_commit_tail - commit atomic update to hardware - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * * This is the default implementation for the * &drm_mode_config_helper_funcs.atomic_commit_tail hook, for drivers @@ -1482,29 +1903,29 @@ EXPORT_SYMBOL(drm_atomic_helper_wait_for_flip_done); * Note that the default ordering of how the various stages are called is to * match the legacy modeset helper library closest. */ -void drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state) +void drm_atomic_helper_commit_tail(struct drm_atomic_state *state) { - struct drm_device *dev = old_state->dev; + struct drm_device *dev = state->dev; - drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_modeset_disables(dev, state); - drm_atomic_helper_commit_planes(dev, old_state, 0); + drm_atomic_helper_commit_planes(dev, state, 0); - drm_atomic_helper_commit_modeset_enables(dev, old_state); + drm_atomic_helper_commit_modeset_enables(dev, state); - drm_atomic_helper_fake_vblank(old_state); + drm_atomic_helper_fake_vblank(state); - drm_atomic_helper_commit_hw_done(old_state); + drm_atomic_helper_commit_hw_done(state); - drm_atomic_helper_wait_for_vblanks(dev, old_state); + drm_atomic_helper_wait_for_vblanks(dev, state); - drm_atomic_helper_cleanup_planes(dev, old_state); + drm_atomic_helper_cleanup_planes(dev, state); } EXPORT_SYMBOL(drm_atomic_helper_commit_tail); /** * drm_atomic_helper_commit_tail_rpm - commit atomic update to hardware - * @old_state: new modeset state to be committed + * @state: new modeset state to be committed * * This is an alternative implementation for the * &drm_mode_config_helper_funcs.atomic_commit_tail hook, for drivers @@ -1512,46 +1933,78 @@ EXPORT_SYMBOL(drm_atomic_helper_commit_tail); * commit. Otherwise, one should use the default implementation * drm_atomic_helper_commit_tail(). */ -void drm_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state) +void drm_atomic_helper_commit_tail_rpm(struct drm_atomic_state *state) { - struct drm_device *dev = old_state->dev; + struct drm_device *dev = state->dev; - drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_modeset_disables(dev, state); - drm_atomic_helper_commit_modeset_enables(dev, old_state); + drm_atomic_helper_commit_modeset_enables(dev, state); - drm_atomic_helper_commit_planes(dev, old_state, + drm_atomic_helper_commit_planes(dev, state, DRM_PLANE_COMMIT_ACTIVE_ONLY); - drm_atomic_helper_fake_vblank(old_state); + drm_atomic_helper_fake_vblank(state); - drm_atomic_helper_commit_hw_done(old_state); + drm_atomic_helper_commit_hw_done(state); - drm_atomic_helper_wait_for_vblanks(dev, old_state); + drm_atomic_helper_wait_for_vblanks(dev, state); - drm_atomic_helper_cleanup_planes(dev, old_state); + drm_atomic_helper_cleanup_planes(dev, state); } EXPORT_SYMBOL(drm_atomic_helper_commit_tail_rpm); -static void commit_tail(struct drm_atomic_state *old_state) +static void commit_tail(struct drm_atomic_state *state) { - struct drm_device *dev = old_state->dev; + struct drm_device *dev = state->dev; const struct drm_mode_config_helper_funcs *funcs; + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; + ktime_t start; + s64 commit_time_ms; + unsigned int i, new_self_refresh_mask = 0; funcs = dev->mode_config.helper_private; - drm_atomic_helper_wait_for_fences(dev, old_state, false); + /* + * We're measuring the _entire_ commit, so the time will vary depending + * on how many fences and objects are involved. For the purposes of self + * refresh, this is desirable since it'll give us an idea of how + * congested things are. This will inform our decision on how often we + * should enter self refresh after idle. + * + * These times will be averaged out in the self refresh helpers to avoid + * overreacting over one outlier frame + */ + start = ktime_get(); + + drm_atomic_helper_wait_for_fences(dev, state, false); + + drm_atomic_helper_wait_for_dependencies(state); - drm_atomic_helper_wait_for_dependencies(old_state); + /* + * We cannot safely access new_crtc_state after + * drm_atomic_helper_commit_hw_done() so figure out which crtc's have + * self-refresh active beforehand: + */ + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) + if (new_crtc_state->self_refresh_active) + new_self_refresh_mask |= BIT(i); if (funcs && funcs->atomic_commit_tail) - funcs->atomic_commit_tail(old_state); + funcs->atomic_commit_tail(state); else - drm_atomic_helper_commit_tail(old_state); + drm_atomic_helper_commit_tail(state); + + commit_time_ms = ktime_ms_delta(ktime_get(), start); + if (commit_time_ms > 0) + drm_self_refresh_helper_update_avg_times(state, + (unsigned long)commit_time_ms, + new_self_refresh_mask); - drm_atomic_helper_commit_cleanup_done(old_state); + drm_atomic_helper_commit_cleanup_done(state); - drm_atomic_state_put(old_state); + drm_atomic_state_put(state); } static void commit_work(struct work_struct *work) @@ -1563,7 +2016,7 @@ static void commit_work(struct work_struct *work) } /** - * drm_atomic_helper_async_check - check if state can be commited asynchronously + * drm_atomic_helper_async_check - check if state can be committed asynchronously * @dev: DRM device * @state: the driver state object * @@ -1572,7 +2025,7 @@ static void commit_work(struct work_struct *work) * but just do in-place changes on the current state. * * It will return 0 if the commit can happen in an asynchronous fashion or error - * if not. Note that error just mean it can't be commited asynchronously, if it + * if not. Note that error just mean it can't be committed asynchronously, if it * fails the commit should be treated like a normal synchronous commit. */ int drm_atomic_helper_async_check(struct drm_device *dev, @@ -1584,7 +2037,7 @@ int drm_atomic_helper_async_check(struct drm_device *dev, struct drm_plane_state *old_plane_state = NULL; struct drm_plane_state *new_plane_state = NULL; const struct drm_plane_helper_funcs *funcs; - int i, n_planes = 0; + int i, ret, n_planes = 0; for_each_new_crtc_in_state(state, crtc, crtc_state, i) { if (drm_atomic_crtc_needs_modeset(crtc_state)) @@ -1595,19 +2048,34 @@ int drm_atomic_helper_async_check(struct drm_device *dev, n_planes++; /* FIXME: we support only single plane updates for now */ - if (n_planes != 1) + if (n_planes != 1) { + drm_dbg_atomic(dev, + "only single plane async updates are supported\n"); return -EINVAL; + } if (!new_plane_state->crtc || - old_plane_state->crtc != new_plane_state->crtc) + old_plane_state->crtc != new_plane_state->crtc) { + drm_dbg_atomic(dev, + "[PLANE:%d:%s] async update cannot change CRTC\n", + plane->base.id, plane->name); return -EINVAL; + } funcs = plane->helper_private; - if (!funcs->atomic_async_update) + if (!funcs->atomic_async_update) { + drm_dbg_atomic(dev, + "[PLANE:%d:%s] driver does not support async updates\n", + plane->base.id, plane->name); return -EINVAL; + } - if (new_plane_state->fence) + if (new_plane_state->fence) { + drm_dbg_atomic(dev, + "[PLANE:%d:%s] missing fence for async update\n", + plane->base.id, plane->name); return -EINVAL; + } /* * Don't do an async update if there is an outstanding commit modifying @@ -1615,10 +2083,19 @@ int drm_atomic_helper_async_check(struct drm_device *dev, * overridden by a previous synchronous update's state. */ if (old_plane_state->commit && - !try_wait_for_completion(&old_plane_state->commit->hw_done)) + !try_wait_for_completion(&old_plane_state->commit->hw_done)) { + drm_dbg_atomic(dev, + "[PLANE:%d:%s] inflight previous commit preventing async commit\n", + plane->base.id, plane->name); return -EBUSY; + } - return funcs->atomic_async_check(plane, new_plane_state); + ret = funcs->atomic_async_check(plane, state, false); + if (ret != 0) + drm_dbg_atomic(dev, + "[PLANE:%d:%s] driver async check failed\n", + plane->base.id, plane->name); + return ret; } EXPORT_SYMBOL(drm_atomic_helper_async_check); @@ -1632,6 +2109,8 @@ EXPORT_SYMBOL(drm_atomic_helper_async_check); * drm_atomic_async_check() succeeds. Async commits are not supposed to swap * the states like normal sync commits, but just do in-place changes on the * current state. + * + * TODO: Implement full swap instead of doing in-place changes. */ void drm_atomic_helper_async_commit(struct drm_device *dev, struct drm_atomic_state *state) @@ -1642,19 +2121,28 @@ void drm_atomic_helper_async_commit(struct drm_device *dev, int i; for_each_new_plane_in_state(state, plane, plane_state, i) { + struct drm_framebuffer *new_fb = plane_state->fb; + struct drm_framebuffer *old_fb = plane->state->fb; + funcs = plane->helper_private; - funcs->atomic_async_update(plane, plane_state); + funcs->atomic_async_update(plane, state); /* * ->atomic_async_update() is supposed to update the * plane->state in-place, make sure at least common * properties have been properly updated. */ - WARN_ON_ONCE(plane->state->fb != plane_state->fb); + WARN_ON_ONCE(plane->state->fb != new_fb); WARN_ON_ONCE(plane->state->crtc_x != plane_state->crtc_x); WARN_ON_ONCE(plane->state->crtc_y != plane_state->crtc_y); WARN_ON_ONCE(plane->state->src_x != plane_state->src_x); WARN_ON_ONCE(plane->state->src_y != plane_state->src_y); + + /* + * Make sure the FBs have been swapped so that cleanups in the + * new_state performs a cleanup in the old FB. + */ + WARN_ON_ONCE(plane_state->fb != old_fb); } } EXPORT_SYMBOL(drm_atomic_helper_async_commit); @@ -1671,7 +2159,7 @@ EXPORT_SYMBOL(drm_atomic_helper_async_commit); * drm_atomic_helper_setup_commit() and related functions. * * Committing the actual hardware state is done through the - * &drm_mode_config_helper_funcs.atomic_commit_tail callback, or it's default + * &drm_mode_config_helper_funcs.atomic_commit_tail callback, or its default * implementation drm_atomic_helper_commit_tail(). * * RETURNS: @@ -1689,7 +2177,7 @@ int drm_atomic_helper_commit(struct drm_device *dev, return ret; drm_atomic_helper_async_commit(dev, state); - drm_atomic_helper_cleanup_planes(dev, state); + drm_atomic_helper_unprepare_planes(dev, state); return 0; } @@ -1737,7 +2225,7 @@ int drm_atomic_helper_commit(struct drm_device *dev, * * NOTE: Commit work has multiple phases, first hardware commit, then * cleanup. We want them to overlap, hence need system_unbound_wq to - * make sure work items don't artifically stall on each another. + * make sure work items don't artificially stall on each another. */ drm_atomic_state_get(state); @@ -1749,7 +2237,7 @@ int drm_atomic_helper_commit(struct drm_device *dev, return 0; err: - drm_atomic_helper_cleanup_planes(dev, state); + drm_atomic_helper_unprepare_planes(dev, state); return ret; } EXPORT_SYMBOL(drm_atomic_helper_commit); @@ -1757,42 +2245,54 @@ EXPORT_SYMBOL(drm_atomic_helper_commit); /** * DOC: implementing nonblocking commit * - * Nonblocking atomic commits have to be implemented in the following sequence: + * Nonblocking atomic commits should use struct &drm_crtc_commit to sequence + * different operations against each another. Locks, especially struct + * &drm_modeset_lock, should not be held in worker threads or any other + * asynchronous context used to commit the hardware state. + * + * drm_atomic_helper_commit() implements the recommended sequence for + * nonblocking commits, using drm_atomic_helper_setup_commit() internally: * - * 1. Run drm_atomic_helper_prepare_planes() first. This is the only function - * which commit needs to call which can fail, so we want to run it first and + * 1. Run drm_atomic_helper_prepare_planes(). Since this can fail and we + * need to propagate out of memory/VRAM errors to userspace, it must be called * synchronously. * * 2. Synchronize with any outstanding nonblocking commit worker threads which - * might be affected the new state update. This can be done by either cancelling - * or flushing the work items, depending upon whether the driver can deal with - * cancelled updates. Note that it is important to ensure that the framebuffer - * cleanup is still done when cancelling. + * might be affected by the new state update. This is handled by + * drm_atomic_helper_setup_commit(). * * Asynchronous workers need to have sufficient parallelism to be able to run * different atomic commits on different CRTCs in parallel. The simplest way to - * achive this is by running them on the &system_unbound_wq work queue. Note + * achieve this is by running them on the &system_unbound_wq work queue. Note * that drivers are not required to split up atomic commits and run an * individual commit in parallel - userspace is supposed to do that if it cares. * But it might be beneficial to do that for modesets, since those necessarily * must be done as one global operation, and enabling or disabling a CRTC can * take a long time. But even that is not required. * + * IMPORTANT: A &drm_atomic_state update for multiple CRTCs is sequenced + * against all CRTCs therein. Therefore for atomic state updates which only flip + * planes the driver must not get the struct &drm_crtc_state of unrelated CRTCs + * in its atomic check code: This would prevent committing of atomic updates to + * multiple CRTCs in parallel. In general, adding additional state structures + * should be avoided as much as possible, because this reduces parallelism in + * (nonblocking) commits, both due to locking and due to commit sequencing + * requirements. + * * 3. The software state is updated synchronously with * drm_atomic_helper_swap_state(). Doing this under the protection of all modeset - * locks means concurrent callers never see inconsistent state. And doing this - * while it's guaranteed that no relevant nonblocking worker runs means that - * nonblocking workers do not need grab any locks. Actually they must not grab - * locks, for otherwise the work flushing will deadlock. + * locks means concurrent callers never see inconsistent state. Note that commit + * workers do not hold any locks; their access is only coordinated through + * ordering. If workers would access state only through the pointers in the + * free-standing state objects (currently not the case for any driver) then even + * multiple pending commits could be in-flight at the same time. * * 4. Schedule a work item to do all subsequent steps, using the split-out * commit helpers: a) pre-plane commit b) plane commit c) post-plane commit and * then cleaning up the framebuffers after the old framebuffer is no longer - * being displayed. - * - * The above scheme is implemented in the atomic helper libraries in - * drm_atomic_helper_commit() using a bunch of helper functions. See - * drm_atomic_helper_setup_commit() for a starting point. + * being displayed. The scheduled work should synchronize against other workers + * using the &drm_crtc_commit infrastructure as needed. See + * drm_atomic_helper_setup_commit() for more details. */ static int stall_checks(struct drm_crtc *crtc, bool nonblock) @@ -1807,10 +2307,16 @@ static int stall_checks(struct drm_crtc *crtc, bool nonblock) list_for_each_entry(commit, &crtc->commit_list, commit_entry) { if (i == 0) { completed = try_wait_for_completion(&commit->flip_done); - /* Userspace is not allowed to get ahead of the previous - * commit with nonblocking ones. */ + /* + * Userspace is not allowed to get ahead of the previous + * commit with nonblocking ones. + */ if (!completed && nonblock) { spin_unlock(&crtc->commit_lock); + drm_dbg_atomic(crtc->dev, + "[CRTC:%d:%s] busy with a previous commit\n", + crtc->base.id, crtc->name); + return -EBUSY; } } else if (i == 1) { @@ -1831,8 +2337,8 @@ static int stall_checks(struct drm_crtc *crtc, bool nonblock) ret = wait_for_completion_interruptible_timeout(&stall_commit->cleanup_done, 10*HZ); if (ret == 0) - DRM_ERROR("[CRTC:%d:%s] cleanup_done timed out\n", - crtc->base.id, crtc->name); + drm_err(crtc->dev, "[CRTC:%d:%s] cleanup_done timed out\n", + crtc->base.id, crtc->name); drm_crtc_commit_put(stall_commit); @@ -1890,11 +2396,14 @@ crtc_or_fake_commit(struct drm_atomic_state *state, struct drm_crtc *crtc) * should always call this function from their * &drm_mode_config_funcs.atomic_commit hook. * + * Drivers that need to extend the commit setup to private objects can use the + * &drm_mode_config_helper_funcs.atomic_commit_setup hook. + * * To be able to use this support drivers need to use a few more helper * functions. drm_atomic_helper_wait_for_dependencies() must be called before * actually committing the hardware state, and for nonblocking commits this call * must be placed in the async worker. See also drm_atomic_helper_swap_state() - * and it's stall parameter, for when a driver's commit hooks look at the + * and its stall parameter, for when a driver's commit hooks look at the * &drm_crtc.state, &drm_plane.state or &drm_connector.state pointer directly. * * Completion of the hardware commit step must be signalled using @@ -1919,7 +2428,6 @@ crtc_or_fake_commit(struct drm_atomic_state *state, struct drm_crtc *crtc) * automatically. * * Returns: - * * 0 on success. -EBUSY when userspace schedules nonblocking commits too fast, * -ENOMEM on allocation failures and -EINTR when a signal is pending. */ @@ -1933,8 +2441,11 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, struct drm_plane *plane; struct drm_plane_state *old_plane_state, *new_plane_state; struct drm_crtc_commit *commit; + const struct drm_mode_config_helper_funcs *funcs; int i, ret; + funcs = state->dev->mode_config.helper_private; + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { commit = kzalloc(sizeof(*commit), GFP_KERNEL); if (!commit) @@ -1948,9 +2459,11 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, if (ret) return ret; - /* Drivers only send out events when at least either current or + /* + * Drivers only send out events when at least either current or * new CRTC state is active. Complete right away if everything - * stays off. */ + * stays off. + */ if (!old_crtc_state->active && !new_crtc_state->active) { complete_all(&commit->flip_done); continue; @@ -1982,11 +2495,18 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, } for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) { - /* Userspace is not allowed to get ahead of the previous - * commit with nonblocking ones. */ + /* + * Userspace is not allowed to get ahead of the previous + * commit with nonblocking ones. + */ if (nonblock && old_conn_state->commit && - !try_wait_for_completion(&old_conn_state->commit->flip_done)) + !try_wait_for_completion(&old_conn_state->commit->flip_done)) { + drm_dbg_atomic(conn->dev, + "[CONNECTOR:%d:%s] busy with a previous commit\n", + conn->base.id, conn->name); + return -EBUSY; + } /* Always track connectors explicitly for e.g. link retraining. */ commit = crtc_or_fake_commit(state, new_conn_state->crtc ?: old_conn_state->crtc); @@ -1997,11 +2517,18 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, } for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { - /* Userspace is not allowed to get ahead of the previous - * commit with nonblocking ones. */ + /* + * Userspace is not allowed to get ahead of the previous + * commit with nonblocking ones. + */ if (nonblock && old_plane_state->commit && - !try_wait_for_completion(&old_plane_state->commit->flip_done)) + !try_wait_for_completion(&old_plane_state->commit->flip_done)) { + drm_dbg_atomic(plane->dev, + "[PLANE:%d:%s] busy with a previous commit\n", + plane->base.id, plane->name); + return -EBUSY; + } /* Always track planes explicitly for async pageflip support. */ commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc); @@ -2011,23 +2538,26 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, new_plane_state->commit = drm_crtc_commit_get(commit); } + if (funcs && funcs->atomic_commit_setup) + return funcs->atomic_commit_setup(state); + return 0; } EXPORT_SYMBOL(drm_atomic_helper_setup_commit); /** - * drm_atomic_helper_wait_for_dependencies - wait for required preceeding commits - * @old_state: atomic state object with old state structures + * drm_atomic_helper_wait_for_dependencies - wait for required preceding commits + * @state: atomic state object being committed * - * This function waits for all preceeding commits that touch the same CRTC as - * @old_state to both be committed to the hardware (as signalled by - * drm_atomic_helper_commit_hw_done) and executed by the hardware (as signalled + * This function waits for all preceding commits that touch the same CRTC as + * @state to both be committed to the hardware (as signalled by + * drm_atomic_helper_commit_hw_done()) and executed by the hardware (as signalled * by calling drm_crtc_send_vblank_event() on the &drm_crtc_state.event). * * This is part of the atomic helper support for nonblocking commits, see * drm_atomic_helper_setup_commit() for an overview. */ -void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *old_state) +void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; @@ -2035,117 +2565,79 @@ void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *old_state) struct drm_plane_state *old_plane_state; struct drm_connector *conn; struct drm_connector_state *old_conn_state; - struct drm_crtc_commit *commit; int i; long ret; - for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) { - commit = old_crtc_state->commit; - - if (!commit) - continue; - - ret = wait_for_completion_timeout(&commit->hw_done, - 10*HZ); - if (ret == 0) - DRM_ERROR("[CRTC:%d:%s] hw_done timed out\n", - crtc->base.id, crtc->name); - - /* Currently no support for overwriting flips, hence - * stall for previous one to execute completely. */ - ret = wait_for_completion_timeout(&commit->flip_done, - 10*HZ); - if (ret == 0) - DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n", - crtc->base.id, crtc->name); + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { + ret = drm_crtc_commit_wait(old_crtc_state->commit); + if (ret) + drm_err(crtc->dev, + "[CRTC:%d:%s] commit wait timed out\n", + crtc->base.id, crtc->name); } - for_each_old_connector_in_state(old_state, conn, old_conn_state, i) { - commit = old_conn_state->commit; - - if (!commit) - continue; - - ret = wait_for_completion_timeout(&commit->hw_done, - 10*HZ); - if (ret == 0) - DRM_ERROR("[CONNECTOR:%d:%s] hw_done timed out\n", - conn->base.id, conn->name); - - /* Currently no support for overwriting flips, hence - * stall for previous one to execute completely. */ - ret = wait_for_completion_timeout(&commit->flip_done, - 10*HZ); - if (ret == 0) - DRM_ERROR("[CONNECTOR:%d:%s] flip_done timed out\n", - conn->base.id, conn->name); + for_each_old_connector_in_state(state, conn, old_conn_state, i) { + ret = drm_crtc_commit_wait(old_conn_state->commit); + if (ret) + drm_err(conn->dev, + "[CONNECTOR:%d:%s] commit wait timed out\n", + conn->base.id, conn->name); } - for_each_old_plane_in_state(old_state, plane, old_plane_state, i) { - commit = old_plane_state->commit; - - if (!commit) - continue; - - ret = wait_for_completion_timeout(&commit->hw_done, - 10*HZ); - if (ret == 0) - DRM_ERROR("[PLANE:%d:%s] hw_done timed out\n", - plane->base.id, plane->name); - - /* Currently no support for overwriting flips, hence - * stall for previous one to execute completely. */ - ret = wait_for_completion_timeout(&commit->flip_done, - 10*HZ); - if (ret == 0) - DRM_ERROR("[PLANE:%d:%s] flip_done timed out\n", - plane->base.id, plane->name); + for_each_old_plane_in_state(state, plane, old_plane_state, i) { + ret = drm_crtc_commit_wait(old_plane_state->commit); + if (ret) + drm_err(plane->dev, + "[PLANE:%d:%s] commit wait timed out\n", + plane->base.id, plane->name); } } EXPORT_SYMBOL(drm_atomic_helper_wait_for_dependencies); /** * drm_atomic_helper_fake_vblank - fake VBLANK events if needed - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * - * This function walks all CRTCs and fake VBLANK events on those with + * This function walks all CRTCs and fakes VBLANK events on those with * &drm_crtc_state.no_vblank set to true and &drm_crtc_state.event != NULL. * The primary use of this function is writeback connectors working in oneshot * mode and faking VBLANK events. In this case they only fake the VBLANK event * when a job is queued, and any change to the pipeline that does not touch the * connector is leading to timeouts when calling * drm_atomic_helper_wait_for_vblanks() or - * drm_atomic_helper_wait_for_flip_done(). + * drm_atomic_helper_wait_for_flip_done(). In addition to writeback + * connectors, this function can also fake VBLANK events for CRTCs without + * VBLANK interrupt. * * This is part of the atomic helper support for nonblocking commits, see * drm_atomic_helper_setup_commit() for an overview. */ -void drm_atomic_helper_fake_vblank(struct drm_atomic_state *old_state) +void drm_atomic_helper_fake_vblank(struct drm_atomic_state *state) { struct drm_crtc_state *new_crtc_state; struct drm_crtc *crtc; int i; - for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) { + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { unsigned long flags; if (!new_crtc_state->no_vblank) continue; - spin_lock_irqsave(&old_state->dev->event_lock, flags); + spin_lock_irqsave(&state->dev->event_lock, flags); if (new_crtc_state->event) { drm_crtc_send_vblank_event(crtc, new_crtc_state->event); new_crtc_state->event = NULL; } - spin_unlock_irqrestore(&old_state->dev->event_lock, flags); + spin_unlock_irqrestore(&state->dev->event_lock, flags); } } EXPORT_SYMBOL(drm_atomic_helper_fake_vblank); /** * drm_atomic_helper_commit_hw_done - setup possible nonblocking commit - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * * This function is used to signal completion of the hardware commit step. After * this step the driver is not allowed to read or change any permanent software @@ -2158,14 +2650,14 @@ EXPORT_SYMBOL(drm_atomic_helper_fake_vblank); * This is part of the atomic helper support for nonblocking commits, see * drm_atomic_helper_setup_commit() for an overview. */ -void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *old_state) +void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct drm_crtc_commit *commit; int i; - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { commit = new_crtc_state->commit; if (!commit) continue; @@ -2185,32 +2677,32 @@ void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *old_state) complete_all(&commit->hw_done); } - if (old_state->fake_commit) { - complete_all(&old_state->fake_commit->hw_done); - complete_all(&old_state->fake_commit->flip_done); + if (state->fake_commit) { + complete_all(&state->fake_commit->hw_done); + complete_all(&state->fake_commit->flip_done); } } EXPORT_SYMBOL(drm_atomic_helper_commit_hw_done); /** * drm_atomic_helper_commit_cleanup_done - signal completion of commit - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * - * This signals completion of the atomic update @old_state, including any + * This signals completion of the atomic update @state, including any * cleanup work. If used, it must be called right before calling * drm_atomic_state_put(). * * This is part of the atomic helper support for nonblocking commits, see * drm_atomic_helper_setup_commit() for an overview. */ -void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *old_state) +void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; struct drm_crtc_commit *commit; int i; - for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) { + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { commit = old_crtc_state->commit; if (WARN_ON(!commit)) continue; @@ -2223,9 +2715,9 @@ void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *old_state) spin_unlock(&crtc->commit_lock); } - if (old_state->fake_commit) { - complete_all(&old_state->fake_commit->cleanup_done); - WARN_ON(!try_wait_for_completion(&old_state->fake_commit->hw_done)); + if (state->fake_commit) { + complete_all(&state->fake_commit->cleanup_done); + WARN_ON(!try_wait_for_completion(&state->fake_commit->hw_done)); } } EXPORT_SYMBOL(drm_atomic_helper_commit_cleanup_done); @@ -2246,10 +2738,21 @@ EXPORT_SYMBOL(drm_atomic_helper_commit_cleanup_done); int drm_atomic_helper_prepare_planes(struct drm_device *dev, struct drm_atomic_state *state) { + struct drm_connector *connector; + struct drm_connector_state *new_conn_state; struct drm_plane *plane; struct drm_plane_state *new_plane_state; int ret, i, j; + for_each_new_connector_in_state(state, connector, new_conn_state, i) { + if (!new_conn_state->writeback_job) + continue; + + ret = drm_writeback_prepare_job(new_conn_state->writeback_job); + if (ret < 0) + return ret; + } + for_each_new_plane_in_state(state, plane, new_plane_state, i) { const struct drm_plane_helper_funcs *funcs; @@ -2258,13 +2761,43 @@ int drm_atomic_helper_prepare_planes(struct drm_device *dev, if (funcs->prepare_fb) { ret = funcs->prepare_fb(plane, new_plane_state); if (ret) - goto fail; + goto fail_prepare_fb; + } else { + WARN_ON_ONCE(funcs->cleanup_fb); + + if (!drm_core_check_feature(dev, DRIVER_GEM)) + continue; + + ret = drm_gem_plane_helper_prepare_fb(plane, new_plane_state); + if (ret) + goto fail_prepare_fb; + } + } + + for_each_new_plane_in_state(state, plane, new_plane_state, i) { + const struct drm_plane_helper_funcs *funcs = plane->helper_private; + + if (funcs->begin_fb_access) { + ret = funcs->begin_fb_access(plane, new_plane_state); + if (ret) + goto fail_begin_fb_access; } } return 0; -fail: +fail_begin_fb_access: + for_each_new_plane_in_state(state, plane, new_plane_state, j) { + const struct drm_plane_helper_funcs *funcs = plane->helper_private; + + if (j >= i) + continue; + + if (funcs->end_fb_access) + funcs->end_fb_access(plane, new_plane_state); + } + i = j; /* set i to upper limit to cleanup all planes */ +fail_prepare_fb: for_each_new_plane_in_state(state, plane, new_plane_state, j) { const struct drm_plane_helper_funcs *funcs; @@ -2281,6 +2814,39 @@ fail: } EXPORT_SYMBOL(drm_atomic_helper_prepare_planes); +/** + * drm_atomic_helper_unprepare_planes - release plane resources on aborts + * @dev: DRM device + * @state: atomic state object with old state structures + * + * This function cleans up plane state, specifically framebuffers, from the + * atomic state. It undoes the effects of drm_atomic_helper_prepare_planes() + * when aborting an atomic commit. For cleaning up after a successful commit + * use drm_atomic_helper_cleanup_planes(). + */ +void drm_atomic_helper_unprepare_planes(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct drm_plane *plane; + struct drm_plane_state *new_plane_state; + int i; + + for_each_new_plane_in_state(state, plane, new_plane_state, i) { + const struct drm_plane_helper_funcs *funcs = plane->helper_private; + + if (funcs->end_fb_access) + funcs->end_fb_access(plane, new_plane_state); + } + + for_each_new_plane_in_state(state, plane, new_plane_state, i) { + const struct drm_plane_helper_funcs *funcs = plane->helper_private; + + if (funcs->cleanup_fb) + funcs->cleanup_fb(plane, new_plane_state); + } +} +EXPORT_SYMBOL(drm_atomic_helper_unprepare_planes); + static bool plane_crtc_active(const struct drm_plane_state *state) { return state->crtc && state->crtc->state->active; @@ -2289,15 +2855,15 @@ static bool plane_crtc_active(const struct drm_plane_state *state) /** * drm_atomic_helper_commit_planes - commit plane state * @dev: DRM device - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * @flags: flags for committing plane state * * This function commits the new plane state using the plane and atomic helper - * functions for planes and crtcs. It assumes that the atomic state has already + * functions for planes and CRTCs. It assumes that the atomic state has already * been pushed into the relevant object state pointers, since this step can no * longer fail. * - * It still requires the global state object @old_state to know which planes and + * It still requires the global state object @state to know which planes and * crtcs need to be updated though. * * Note that this function does all plane updates across all CRTCs in one step. @@ -2328,7 +2894,7 @@ static bool plane_crtc_active(const struct drm_plane_state *state) * This should not be copied blindly by drivers. */ void drm_atomic_helper_commit_planes(struct drm_device *dev, - struct drm_atomic_state *old_state, + struct drm_atomic_state *state, uint32_t flags) { struct drm_crtc *crtc; @@ -2339,7 +2905,7 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, bool active_only = flags & DRM_PLANE_COMMIT_ACTIVE_ONLY; bool no_disable = flags & DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET; - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; funcs = crtc->helper_private; @@ -2350,10 +2916,10 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, if (active_only && !new_crtc_state->active) continue; - funcs->atomic_begin(crtc, old_crtc_state); + funcs->atomic_begin(crtc, state); } - for_each_oldnew_plane_in_state(old_state, plane, old_plane_state, new_plane_state, i) { + for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { const struct drm_plane_helper_funcs *funcs; bool disabling; @@ -2391,13 +2957,18 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, no_disable) continue; - funcs->atomic_disable(plane, old_plane_state); + funcs->atomic_disable(plane, state); } else if (new_plane_state->crtc || disabling) { - funcs->atomic_update(plane, old_plane_state); + funcs->atomic_update(plane, state); + + if (!disabling && funcs->atomic_enable) { + if (drm_atomic_plane_enabling(old_plane_state, new_plane_state)) + funcs->atomic_enable(plane, state); + } } } - for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; funcs = crtc->helper_private; @@ -2408,26 +2979,37 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, if (active_only && !new_crtc_state->active) continue; - funcs->atomic_flush(crtc, old_crtc_state); + funcs->atomic_flush(crtc, state); + } + + /* + * Signal end of framebuffer access here before hw_done. After hw_done, + * a later commit might have already released the plane state. + */ + for_each_old_plane_in_state(state, plane, old_plane_state, i) { + const struct drm_plane_helper_funcs *funcs = plane->helper_private; + + if (funcs->end_fb_access) + funcs->end_fb_access(plane, old_plane_state); } } EXPORT_SYMBOL(drm_atomic_helper_commit_planes); /** - * drm_atomic_helper_commit_planes_on_crtc - commit plane state for a crtc - * @old_crtc_state: atomic state object with the old crtc state + * drm_atomic_helper_commit_planes_on_crtc - commit plane state for a CRTC + * @old_crtc_state: atomic state object with the old CRTC state * * This function commits the new plane state using the plane and atomic helper - * functions for planes on the specific crtc. It assumes that the atomic state + * functions for planes on the specific CRTC. It assumes that the atomic state * has already been pushed into the relevant object state pointers, since this * step can no longer fail. * - * This function is useful when plane updates should be done crtc-by-crtc + * This function is useful when plane updates should be done CRTC-by-CRTC * instead of one global step like drm_atomic_helper_commit_planes() does. * * This function can only be savely used when planes are not allowed to move * between different CRTCs because this function doesn't handle inter-CRTC - * depencies. Callers need to ensure that either no such depencies exist, + * dependencies. Callers need to ensure that either no such dependencies exist, * resolve them through ordering of commit calls or through some other means. */ void @@ -2439,14 +3021,14 @@ drm_atomic_helper_commit_planes_on_crtc(struct drm_crtc_state *old_crtc_state) struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); struct drm_plane *plane; - unsigned plane_mask; + unsigned int plane_mask; plane_mask = old_crtc_state->plane_mask; plane_mask |= new_crtc_state->plane_mask; crtc_funcs = crtc->helper_private; if (crtc_funcs && crtc_funcs->atomic_begin) - crtc_funcs->atomic_begin(crtc, old_crtc_state); + crtc_funcs->atomic_begin(crtc, old_state); drm_for_each_plane_mask(plane, crtc->dev, plane_mask) { struct drm_plane_state *old_plane_state = @@ -2454,6 +3036,7 @@ drm_atomic_helper_commit_planes_on_crtc(struct drm_crtc_state *old_crtc_state) struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(old_state, plane); const struct drm_plane_helper_funcs *plane_funcs; + bool disabling; plane_funcs = plane->helper_private; @@ -2463,16 +3046,22 @@ drm_atomic_helper_commit_planes_on_crtc(struct drm_crtc_state *old_crtc_state) WARN_ON(new_plane_state->crtc && new_plane_state->crtc != crtc); - if (drm_atomic_plane_disabling(old_plane_state, new_plane_state) && - plane_funcs->atomic_disable) - plane_funcs->atomic_disable(plane, old_plane_state); - else if (new_plane_state->crtc || - drm_atomic_plane_disabling(old_plane_state, new_plane_state)) - plane_funcs->atomic_update(plane, old_plane_state); + disabling = drm_atomic_plane_disabling(old_plane_state, new_plane_state); + + if (disabling && plane_funcs->atomic_disable) { + plane_funcs->atomic_disable(plane, old_state); + } else if (new_plane_state->crtc || disabling) { + plane_funcs->atomic_update(plane, old_state); + + if (!disabling && plane_funcs->atomic_enable) { + if (drm_atomic_plane_enabling(old_plane_state, new_plane_state)) + plane_funcs->atomic_enable(plane, old_state); + } + } } if (crtc_funcs && crtc_funcs->atomic_flush) - crtc_funcs->atomic_flush(crtc, old_crtc_state); + crtc_funcs->atomic_flush(crtc, old_state); } EXPORT_SYMBOL(drm_atomic_helper_commit_planes_on_crtc); @@ -2524,39 +3113,28 @@ EXPORT_SYMBOL(drm_atomic_helper_disable_planes_on_crtc); /** * drm_atomic_helper_cleanup_planes - cleanup plane resources after commit * @dev: DRM device - * @old_state: atomic state object with old state structures + * @state: atomic state object being committed * * This function cleans up plane state, specifically framebuffers, from the old - * configuration. Hence the old configuration must be perserved in @old_state to + * configuration. Hence the old configuration must be perserved in @state to * be able to call this function. * - * This function must also be called on the new state when the atomic update - * fails at any point after calling drm_atomic_helper_prepare_planes(). + * This function may not be called on the new state when the atomic update + * fails at any point after calling drm_atomic_helper_prepare_planes(). Use + * drm_atomic_helper_unprepare_planes() in this case. */ void drm_atomic_helper_cleanup_planes(struct drm_device *dev, - struct drm_atomic_state *old_state) + struct drm_atomic_state *state) { struct drm_plane *plane; - struct drm_plane_state *old_plane_state, *new_plane_state; + struct drm_plane_state *old_plane_state; int i; - for_each_oldnew_plane_in_state(old_state, plane, old_plane_state, new_plane_state, i) { - const struct drm_plane_helper_funcs *funcs; - struct drm_plane_state *plane_state; - - /* - * This might be called before swapping when commit is aborted, - * in which case we have to cleanup the new state. - */ - if (old_plane_state == plane->state) - plane_state = new_plane_state; - else - plane_state = old_plane_state; - - funcs = plane->helper_private; + for_each_old_plane_in_state(state, plane, old_plane_state, i) { + const struct drm_plane_helper_funcs *funcs = plane->helper_private; if (funcs->cleanup_fb) - funcs->cleanup_fb(plane, plane_state); + funcs->cleanup_fb(plane, old_plane_state); } } EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes); @@ -2564,7 +3142,7 @@ EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes); /** * drm_atomic_helper_swap_state - store atomic state into current sw state * @state: atomic state - * @stall: stall for preceeding commits + * @stall: stall for preceding commits * * This function stores the atomic state into the current state pointers in all * driver objects. It should be called after all failing steps have been done @@ -2592,7 +3170,6 @@ EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes); * don't pass the right state structures to the callbacks. * * Returns: - * * Returns 0 on success. Can return -ERESTARTSYS when @stall is true and the * waiting for the previous commits has been interrupted. */ @@ -2600,12 +3177,15 @@ int drm_atomic_helper_swap_state(struct drm_atomic_state *state, bool stall) { int i, ret; + unsigned long flags = 0; struct drm_connector *connector; struct drm_connector_state *old_conn_state, *new_conn_state; struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct drm_plane *plane; struct drm_plane_state *old_plane_state, *new_plane_state; + struct drm_colorop *colorop; + struct drm_colorop_state *old_colorop_state, *new_colorop_state; struct drm_crtc_commit *commit; struct drm_private_obj *obj; struct drm_private_state *old_obj_state, *new_obj_state; @@ -2660,7 +3240,7 @@ int drm_atomic_helper_swap_state(struct drm_atomic_state *state, old_conn_state->state = state; new_conn_state->state = NULL; - state->connectors[i].state = old_conn_state; + state->connectors[i].state_to_destroy = old_conn_state; connector->state = new_conn_state; } @@ -2670,7 +3250,7 @@ int drm_atomic_helper_swap_state(struct drm_atomic_state *state, old_crtc_state->state = state; new_crtc_state->state = NULL; - state->crtcs[i].state = old_crtc_state; + state->crtcs[i].state_to_destroy = old_crtc_state; crtc->state = new_crtc_state; if (new_crtc_state->commit) { @@ -2683,15 +3263,27 @@ int drm_atomic_helper_swap_state(struct drm_atomic_state *state, } } + for_each_oldnew_colorop_in_state(state, colorop, old_colorop_state, new_colorop_state, i) { + WARN_ON(colorop->state != old_colorop_state); + + old_colorop_state->state = state; + new_colorop_state->state = NULL; + + state->colorops[i].state = old_colorop_state; + colorop->state = new_colorop_state; + } + + drm_panic_lock(state->dev, flags); for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { WARN_ON(plane->state != old_plane_state); old_plane_state->state = state; new_plane_state->state = NULL; - state->planes[i].state = old_plane_state; + state->planes[i].state_to_destroy = old_plane_state; plane->state = new_plane_state; } + drm_panic_unlock(state->dev, flags); for_each_oldnew_private_obj_in_state(state, obj, old_obj_state, new_obj_state, i) { WARN_ON(obj->state != old_obj_state); @@ -2699,7 +3291,7 @@ int drm_atomic_helper_swap_state(struct drm_atomic_state *state, old_obj_state->state = state; new_obj_state->state = NULL; - state->private_objs[i].state = old_obj_state; + state->private_objs[i].state_to_destroy = old_obj_state; obj->state = new_obj_state; } @@ -2712,10 +3304,10 @@ EXPORT_SYMBOL(drm_atomic_helper_swap_state); * @plane: plane object to update * @crtc: owning CRTC of owning plane * @fb: framebuffer to flip onto plane - * @crtc_x: x offset of primary plane on crtc - * @crtc_y: y offset of primary plane on crtc - * @crtc_w: width of primary plane rectangle on crtc - * @crtc_h: height of primary plane rectangle on crtc + * @crtc_x: x offset of primary plane on @crtc + * @crtc_y: y offset of primary plane on @crtc + * @crtc_w: width of primary plane rectangle on @crtc + * @crtc_h: height of primary plane rectangle on @crtc * @src_x: x offset of @fb for panning * @src_y: y offset of @fb for panning * @src_w: width of source rectangle in @fb @@ -2775,7 +3367,7 @@ fail: EXPORT_SYMBOL(drm_atomic_helper_update_plane); /** - * drm_atomic_helper_disable_plane - Helper for primary plane disable using * atomic + * drm_atomic_helper_disable_plane - Helper for primary plane disable using atomic * @plane: plane to disable * @ctx: lock acquire context * @@ -2816,101 +3408,12 @@ fail: } EXPORT_SYMBOL(drm_atomic_helper_disable_plane); -/* just used from fb-helper and atomic-helper: */ -int __drm_atomic_helper_disable_plane(struct drm_plane *plane, - struct drm_plane_state *plane_state) -{ - int ret; - - ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); - if (ret != 0) - return ret; - - drm_atomic_set_fb_for_plane(plane_state, NULL); - plane_state->crtc_x = 0; - plane_state->crtc_y = 0; - plane_state->crtc_w = 0; - plane_state->crtc_h = 0; - plane_state->src_x = 0; - plane_state->src_y = 0; - plane_state->src_w = 0; - plane_state->src_h = 0; - - return 0; -} - -static int update_output_state(struct drm_atomic_state *state, - struct drm_mode_set *set) -{ - struct drm_device *dev = set->crtc->dev; - struct drm_crtc *crtc; - struct drm_crtc_state *new_crtc_state; - struct drm_connector *connector; - struct drm_connector_state *new_conn_state; - int ret, i; - - ret = drm_modeset_lock(&dev->mode_config.connection_mutex, - state->acquire_ctx); - if (ret) - return ret; - - /* First disable all connectors on the target crtc. */ - ret = drm_atomic_add_affected_connectors(state, set->crtc); - if (ret) - return ret; - - for_each_new_connector_in_state(state, connector, new_conn_state, i) { - if (new_conn_state->crtc == set->crtc) { - ret = drm_atomic_set_crtc_for_connector(new_conn_state, - NULL); - if (ret) - return ret; - - /* Make sure legacy setCrtc always re-trains */ - new_conn_state->link_status = DRM_LINK_STATUS_GOOD; - } - } - - /* Then set all connectors from set->connectors on the target crtc */ - for (i = 0; i < set->num_connectors; i++) { - new_conn_state = drm_atomic_get_connector_state(state, - set->connectors[i]); - if (IS_ERR(new_conn_state)) - return PTR_ERR(new_conn_state); - - ret = drm_atomic_set_crtc_for_connector(new_conn_state, - set->crtc); - if (ret) - return ret; - } - - for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { - /* Don't update ->enable for the CRTC in the set_config request, - * since a mismatch would indicate a bug in the upper layers. - * The actual modeset code later on will catch any - * inconsistencies here. */ - if (crtc == set->crtc) - continue; - - if (!new_crtc_state->connector_mask) { - ret = drm_atomic_set_mode_prop_for_crtc(new_crtc_state, - NULL); - if (ret < 0) - return ret; - - new_crtc_state->active = false; - } - } - - return 0; -} - /** * drm_atomic_helper_set_config - set a new config from userspace * @set: mode set configuration * @ctx: lock acquisition context * - * Provides a default crtc set_config handler using the atomic driver interface. + * Provides a default CRTC set_config handler using the atomic driver interface. * * NOTE: For backwards compatibility with old userspace this automatically * resets the "link-status" property to GOOD, to force any link @@ -2939,7 +3442,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set, ret = handle_conflicting_encoders(state, true); if (ret) - return ret; + goto fail; ret = drm_atomic_commit(state); @@ -2949,84 +3452,31 @@ fail: } EXPORT_SYMBOL(drm_atomic_helper_set_config); -/* just used from fb-helper and atomic-helper: */ -int __drm_atomic_helper_set_config(struct drm_mode_set *set, - struct drm_atomic_state *state) -{ - struct drm_crtc_state *crtc_state; - struct drm_plane_state *primary_state; - struct drm_crtc *crtc = set->crtc; - int hdisplay, vdisplay; - int ret; - - crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(crtc_state)) - return PTR_ERR(crtc_state); - - primary_state = drm_atomic_get_plane_state(state, crtc->primary); - if (IS_ERR(primary_state)) - return PTR_ERR(primary_state); - - if (!set->mode) { - WARN_ON(set->fb); - WARN_ON(set->num_connectors); - - ret = drm_atomic_set_mode_for_crtc(crtc_state, NULL); - if (ret != 0) - return ret; - - crtc_state->active = false; - - ret = drm_atomic_set_crtc_for_plane(primary_state, NULL); - if (ret != 0) - return ret; - - drm_atomic_set_fb_for_plane(primary_state, NULL); - - goto commit; - } - - WARN_ON(!set->fb); - WARN_ON(!set->num_connectors); - - ret = drm_atomic_set_mode_for_crtc(crtc_state, set->mode); - if (ret != 0) - return ret; - - crtc_state->active = true; - - ret = drm_atomic_set_crtc_for_plane(primary_state, crtc); - if (ret != 0) - return ret; - - drm_mode_get_hv_timing(set->mode, &hdisplay, &vdisplay); - - drm_atomic_set_fb_for_plane(primary_state, set->fb); - primary_state->crtc_x = 0; - primary_state->crtc_y = 0; - primary_state->crtc_w = hdisplay; - primary_state->crtc_h = vdisplay; - primary_state->src_x = set->x << 16; - primary_state->src_y = set->y << 16; - if (drm_rotation_90_or_270(primary_state->rotation)) { - primary_state->src_w = vdisplay << 16; - primary_state->src_h = hdisplay << 16; - } else { - primary_state->src_w = hdisplay << 16; - primary_state->src_h = vdisplay << 16; - } - -commit: - ret = update_output_state(state, set); - if (ret) - return ret; - - return 0; -} - -static int __drm_atomic_helper_disable_all(struct drm_device *dev, - struct drm_modeset_acquire_ctx *ctx, - bool clean_old_fbs) +/** + * drm_atomic_helper_disable_all - disable all currently active outputs + * @dev: DRM device + * @ctx: lock acquisition context + * + * Loops through all connectors, finding those that aren't turned off and then + * turns them off by setting their DPMS mode to OFF and deactivating the CRTC + * that they are connected to. + * + * This is used for example in suspend/resume to disable all currently active + * functions when suspending. If you just want to shut down everything at e.g. + * driver unload, look at drm_atomic_helper_shutdown(). + * + * Note that if callers haven't already acquired all modeset locks this might + * return -EDEADLK, which must be handled by calling drm_modeset_backoff(). + * + * Returns: + * 0 on success or a negative error code on failure. + * + * See also: + * drm_atomic_helper_suspend(), drm_atomic_helper_resume() and + * drm_atomic_helper_shutdown(). + */ +int drm_atomic_helper_disable_all(struct drm_device *dev, + struct drm_modeset_acquire_ctx *ctx) { struct drm_atomic_state *state; struct drm_connector_state *conn_state; @@ -3084,36 +3534,55 @@ free: drm_atomic_state_put(state); return ret; } +EXPORT_SYMBOL(drm_atomic_helper_disable_all); /** - * drm_atomic_helper_disable_all - disable all currently active outputs - * @dev: DRM device + * drm_atomic_helper_reset_crtc - reset the active outputs of a CRTC + * @crtc: DRM CRTC * @ctx: lock acquisition context * - * Loops through all connectors, finding those that aren't turned off and then - * turns them off by setting their DPMS mode to OFF and deactivating the CRTC - * that they are connected to. + * Reset the active outputs by indicating that connectors have changed. + * This implies a reset of all active components available between the CRTC and + * connectors. * - * This is used for example in suspend/resume to disable all currently active - * functions when suspending. If you just want to shut down everything at e.g. - * driver unload, look at drm_atomic_helper_shutdown(). + * A variant of this function exists with + * drm_bridge_helper_reset_crtc(), dedicated to bridges. * - * Note that if callers haven't already acquired all modeset locks this might - * return -EDEADLK, which must be handled by calling drm_modeset_backoff(). + * NOTE: This relies on resetting &drm_crtc_state.connectors_changed. + * For drivers which optimize out unnecessary modesets this will result in + * a no-op commit, achieving nothing. * * Returns: * 0 on success or a negative error code on failure. - * - * See also: - * drm_atomic_helper_suspend(), drm_atomic_helper_resume() and - * drm_atomic_helper_shutdown(). */ -int drm_atomic_helper_disable_all(struct drm_device *dev, - struct drm_modeset_acquire_ctx *ctx) +int drm_atomic_helper_reset_crtc(struct drm_crtc *crtc, + struct drm_modeset_acquire_ctx *ctx) { - return __drm_atomic_helper_disable_all(dev, ctx, false); + struct drm_atomic_state *state; + struct drm_crtc_state *crtc_state; + int ret; + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = ctx; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto out; + } + + crtc_state->connectors_changed = true; + + ret = drm_atomic_commit(state); +out: + drm_atomic_state_put(state); + + return ret; } -EXPORT_SYMBOL(drm_atomic_helper_disable_all); +EXPORT_SYMBOL(drm_atomic_helper_reset_crtc); /** * drm_atomic_helper_shutdown - shutdown all CRTC @@ -3124,20 +3593,25 @@ EXPORT_SYMBOL(drm_atomic_helper_disable_all); * that also takes a snapshot of the modeset state to be restored on resume. * * This is just a convenience wrapper around drm_atomic_helper_disable_all(), - * and it is the atomic version of drm_crtc_force_disable_all(). + * and it is the atomic version of drm_helper_force_disable_all(). */ void drm_atomic_helper_shutdown(struct drm_device *dev) { struct drm_modeset_acquire_ctx ctx; int ret; + if (dev == NULL) + return; + DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); - ret = __drm_atomic_helper_disable_all(dev, &ctx, true); + ret = drm_atomic_helper_disable_all(dev, &ctx); if (ret) - DRM_ERROR("Disabling all crtc's during unload failed with %i\n", ret); + drm_err(dev, + "Disabling all crtc's during unload failed with %i\n", + ret); - DRM_MODESET_LOCK_ALL_END(ctx, ret); + DRM_MODESET_LOCK_ALL_END(dev, ctx, ret); } EXPORT_SYMBOL(drm_atomic_helper_shutdown); @@ -3181,6 +3655,7 @@ drm_atomic_helper_duplicate_state(struct drm_device *dev, return ERR_PTR(-ENOMEM); state->acquire_ctx = ctx; + state->duplicated = true; drm_for_each_crtc(crtc, dev) { struct drm_crtc_state *crtc_state; @@ -3276,7 +3751,7 @@ struct drm_atomic_state *drm_atomic_helper_suspend(struct drm_device *dev) } unlock: - DRM_MODESET_LOCK_ALL_END(ctx, err); + DRM_MODESET_LOCK_ALL_END(dev, ctx, err); if (err) return ERR_PTR(err); @@ -3357,7 +3832,7 @@ int drm_atomic_helper_resume(struct drm_device *dev, err = drm_atomic_helper_commit_duplicated_state(state, &ctx); - DRM_MODESET_LOCK_ALL_END(ctx, err); + DRM_MODESET_LOCK_ALL_END(dev, ctx, err); drm_atomic_state_put(state); return err; @@ -3380,7 +3855,7 @@ static int page_flip_common(struct drm_atomic_state *state, return PTR_ERR(crtc_state); crtc_state->event = event; - crtc_state->pageflip_flags = flags; + crtc_state->async_flip = flags & DRM_MODE_PAGE_FLIP_ASYNC; plane_state = drm_atomic_get_plane_state(state, plane); if (IS_ERR(plane_state)) @@ -3394,8 +3869,9 @@ static int page_flip_common(struct drm_atomic_state *state, /* Make sure we don't accidentally do a full modeset. */ state->allow_modeset = false; if (!crtc_state->active) { - DRM_DEBUG_ATOMIC("[CRTC:%d:%s] disabled, rejecting legacy flip\n", - crtc->base.id, crtc->name); + drm_dbg_atomic(crtc->dev, + "[CRTC:%d:%s] disabled, rejecting legacy flip\n", + crtc->base.id, crtc->name); return -EINVAL; } @@ -3404,7 +3880,7 @@ static int page_flip_common(struct drm_atomic_state *state, /** * drm_atomic_helper_page_flip - execute a legacy page flip - * @crtc: DRM crtc + * @crtc: DRM CRTC * @fb: DRM framebuffer * @event: optional DRM event to signal upon completion * @flags: flip flags for non-vblank sync'ed updates @@ -3448,7 +3924,7 @@ EXPORT_SYMBOL(drm_atomic_helper_page_flip); /** * drm_atomic_helper_page_flip_target - do page flip on target vblank period. - * @crtc: DRM crtc + * @crtc: DRM CRTC * @fb: DRM framebuffer * @event: optional DRM event to signal upon completion * @flags: flip flags for non-vblank sync'ed updates @@ -3499,71 +3975,42 @@ fail: EXPORT_SYMBOL(drm_atomic_helper_page_flip_target); /** - * drm_atomic_helper_legacy_gamma_set - set the legacy gamma correction table - * @crtc: CRTC object - * @red: red correction table - * @green: green correction table - * @blue: green correction table - * @size: size of the tables - * @ctx: lock acquire context + * drm_atomic_helper_bridge_propagate_bus_fmt() - Propagate output format to + * the input end of a bridge + * @bridge: bridge control structure + * @bridge_state: new bridge state + * @crtc_state: new CRTC state + * @conn_state: new connector state + * @output_fmt: tested output bus format + * @num_input_fmts: will contain the size of the returned array + * + * This helper is a pluggable implementation of the + * &drm_bridge_funcs.atomic_get_input_bus_fmts operation for bridges that don't + * modify the bus configuration between their input and their output. It + * returns an array of input formats with a single element set to @output_fmt. * - * Implements support for legacy gamma correction table for drivers - * that support color management through the DEGAMMA_LUT/GAMMA_LUT - * properties. See drm_crtc_enable_color_mgmt() and the containing chapter for - * how the atomic color management and gamma tables work. + * RETURNS: + * a valid format array of size @num_input_fmts, or NULL if the allocation + * failed */ -int drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc, - u16 *red, u16 *green, u16 *blue, - uint32_t size, - struct drm_modeset_acquire_ctx *ctx) +u32 * +drm_atomic_helper_bridge_propagate_bus_fmt(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) { - struct drm_device *dev = crtc->dev; - struct drm_atomic_state *state; - struct drm_crtc_state *crtc_state; - struct drm_property_blob *blob = NULL; - struct drm_color_lut *blob_data; - int i, ret = 0; - bool replaced; + u32 *input_fmts; - state = drm_atomic_state_alloc(crtc->dev); - if (!state) - return -ENOMEM; - - blob = drm_property_create_blob(dev, - sizeof(struct drm_color_lut) * size, - NULL); - if (IS_ERR(blob)) { - ret = PTR_ERR(blob); - blob = NULL; - goto fail; - } - - /* Prepare GAMMA_LUT with the legacy values. */ - blob_data = blob->data; - for (i = 0; i < size; i++) { - blob_data[i].red = red[i]; - blob_data[i].green = green[i]; - blob_data[i].blue = blue[i]; + input_fmts = kzalloc(sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) { + *num_input_fmts = 0; + return NULL; } - state->acquire_ctx = ctx; - crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(crtc_state)) { - ret = PTR_ERR(crtc_state); - goto fail; - } - - /* Reset DEGAMMA_LUT and CTM properties. */ - replaced = drm_property_replace_blob(&crtc_state->degamma_lut, NULL); - replaced |= drm_property_replace_blob(&crtc_state->ctm, NULL); - replaced |= drm_property_replace_blob(&crtc_state->gamma_lut, blob); - crtc_state->color_mgmt_changed |= replaced; - - ret = drm_atomic_commit(state); - -fail: - drm_atomic_state_put(state); - drm_property_blob_put(blob); - return ret; + *num_input_fmts = 1; + input_fmts[0] = output_fmt; + return input_fmts; } -EXPORT_SYMBOL(drm_atomic_helper_legacy_gamma_set); +EXPORT_SYMBOL(drm_atomic_helper_bridge_propagate_bus_fmt); |
