summaryrefslogblamecommitdiffstats
path: root/drivers/gpu/drm/drm_atomic_helper.c
blob: c4936bc4318e35eeb73c5e8bf594c772ee77050b (plain) (tree)






























                                                                             
                                  



























                                                                              





































































































































































































































































































































                                                                                            




















                                                                               



                                                          














































                                                                             













































































































































































































































































































































































                                                                                



































































































































































































































                                                                                
/*
 * Copyright (C) 2014 Red Hat
 * Copyright (C) 2014 Intel Corp.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 * Rob Clark <robdclark@gmail.com>
 * Daniel Vetter <daniel.vetter@ffwll.ch>
 */

#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_atomic_helper.h>

static void
drm_atomic_helper_plane_changed(struct drm_atomic_state *state,
				struct drm_plane_state *plane_state,
				struct drm_plane *plane)
{
	struct drm_crtc_state *crtc_state;

	if (plane->state->crtc) {
		crtc_state = state->crtc_states[drm_crtc_index(plane->crtc)];

		if (WARN_ON(!crtc_state))
			return;

		crtc_state->planes_changed = true;
	}

	if (plane_state->crtc) {
		crtc_state =
			state->crtc_states[drm_crtc_index(plane_state->crtc)];

		if (WARN_ON(!crtc_state))
			return;

		crtc_state->planes_changed = true;
	}
}

static struct drm_crtc *
get_current_crtc_for_encoder(struct drm_device *dev,
			     struct drm_encoder *encoder)
{
	struct drm_mode_config *config = &dev->mode_config;
	struct drm_connector *connector;

	WARN_ON(!drm_modeset_is_locked(&config->connection_mutex));

	list_for_each_entry(connector, &config->connector_list, head) {
		if (connector->state->best_encoder != encoder)
			continue;

		return connector->state->crtc;
	}

	return NULL;
}

static int
steal_encoder(struct drm_atomic_state *state,
	      struct drm_encoder *encoder,
	      struct drm_crtc *encoder_crtc)
{
	struct drm_mode_config *config = &state->dev->mode_config;
	struct drm_crtc_state *crtc_state;
	struct drm_connector *connector;
	struct drm_connector_state *connector_state;

	/*
	 * We can only steal an encoder coming from a connector, which means we
	 * must already hold the connection_mutex.
	 */
	WARN_ON(!drm_modeset_is_locked(&config->connection_mutex));

	DRM_DEBUG_KMS("[ENCODER:%d:%s] in use on [CRTC:%d], stealing it\n",
		      encoder->base.id, encoder->name,
		      encoder_crtc->base.id);

	crtc_state = drm_atomic_get_crtc_state(state, encoder_crtc);
	if (IS_ERR(crtc_state))
		return PTR_ERR(crtc_state);

	crtc_state->mode_changed = true;

	list_for_each_entry(connector, &config->connector_list, head) {
		if (connector->state->best_encoder != encoder)
			continue;

		DRM_DEBUG_KMS("Stealing encoder from [CONNECTOR:%d:%s]\n",
			      connector->base.id,
			      connector->name);

		connector_state = drm_atomic_get_connector_state(state,
								 connector);
		if (IS_ERR(connector_state))
			return PTR_ERR(connector_state);

		connector_state->crtc = NULL;
		connector_state->best_encoder = NULL;
	}

	return 0;
}

static int
update_connector_routing(struct drm_atomic_state *state, int conn_idx)
{
	struct drm_connector_helper_funcs *funcs;
	struct drm_encoder *new_encoder;
	struct drm_crtc *encoder_crtc;
	struct drm_connector *connector;
	struct drm_connector_state *connector_state;
	struct drm_crtc_state *crtc_state;
	int idx, ret;

	connector = state->connectors[conn_idx];
	connector_state = state->connector_states[conn_idx];

	if (!connector)
		return 0;

	DRM_DEBUG_KMS("Updating routing for [CONNECTOR:%d:%s]\n",
			connector->base.id,
			connector->name);

	if (connector->state->crtc != connector_state->crtc) {
		if (connector->state->crtc) {
			idx = drm_crtc_index(connector->state->crtc);

			crtc_state = state->crtc_states[idx];
			crtc_state->mode_changed = true;
		}

		if (connector_state->crtc) {
			idx = drm_crtc_index(connector_state->crtc);

			crtc_state = state->crtc_states[idx];
			crtc_state->mode_changed = true;
		}
	}

	if (!connector_state->crtc) {
		DRM_DEBUG_KMS("Disabling [CONNECTOR:%d:%s]\n",
				connector->base.id,
				connector->name);

		connector_state->best_encoder = NULL;

		return 0;
	}

	funcs = connector->helper_private;
	new_encoder = funcs->best_encoder(connector);

	if (!new_encoder) {
		DRM_DEBUG_KMS("No suitable encoder found for [CONNECTOR:%d:%s]\n",
			      connector->base.id,
			      connector->name);
		return -EINVAL;
	}

	if (new_encoder == connector_state->best_encoder) {
		DRM_DEBUG_KMS("[CONNECTOR:%d:%s] keeps [ENCODER:%d:%s], now on [CRTC:%d]\n",
			      connector->base.id,
			      connector->name,
			      new_encoder->base.id,
			      new_encoder->name,
			      connector_state->crtc->base.id);

		return 0;
	}

	encoder_crtc = get_current_crtc_for_encoder(state->dev,
						    new_encoder);

	if (encoder_crtc) {
		ret = steal_encoder(state, new_encoder, encoder_crtc);
		if (ret) {
			DRM_DEBUG_KMS("Encoder stealing failed for [CONNECTOR:%d:%s]\n",
				      connector->base.id,
				      connector->name);
			return ret;
		}
	}

	connector_state->best_encoder = new_encoder;
	idx = drm_crtc_index(connector_state->crtc);

	crtc_state = state->crtc_states[idx];
	crtc_state->mode_changed = true;

	DRM_DEBUG_KMS("[CONNECTOR:%d:%s] using [ENCODER:%d:%s] on [CRTC:%d]\n",
		      connector->base.id,
		      connector->name,
		      new_encoder->base.id,
		      new_encoder->name,
		      connector_state->crtc->base.id);

	return 0;
}

static int
mode_fixup(struct drm_atomic_state *state)
{
	int ncrtcs = state->dev->mode_config.num_crtc;
	int nconnectors = state->dev->mode_config.num_connector;
	struct drm_crtc_state *crtc_state;
	struct drm_connector_state *conn_state;
	int i;
	bool ret;

	for (i = 0; i < ncrtcs; i++) {
		crtc_state = state->crtc_states[i];

		if (!crtc_state || !crtc_state->mode_changed)
			continue;

		drm_mode_copy(&crtc_state->adjusted_mode, &crtc_state->mode);
	}

	for (i = 0; i < nconnectors; i++) {
		struct drm_encoder_helper_funcs *funcs;
		struct drm_encoder *encoder;

		conn_state = state->connector_states[i];

		if (!conn_state)
			continue;

		WARN_ON(!!conn_state->best_encoder != !!conn_state->crtc);

		if (!conn_state->crtc || !conn_state->best_encoder)
			continue;

		crtc_state =
			state->crtc_states[drm_crtc_index(conn_state->crtc)];

		/*
		 * Each encoder has at most one connector (since we always steal
		 * it away), so we won't call ->mode_fixup twice.
		 */
		encoder = conn_state->best_encoder;
		funcs = encoder->helper_private;

		if (encoder->bridge && encoder->bridge->funcs->mode_fixup) {
			ret = encoder->bridge->funcs->mode_fixup(
					encoder->bridge, &crtc_state->mode,
					&crtc_state->adjusted_mode);
			if (!ret) {
				DRM_DEBUG_KMS("Bridge fixup failed\n");
				return -EINVAL;
			}
		}


		ret = funcs->mode_fixup(encoder, &crtc_state->mode,
					&crtc_state->adjusted_mode);
		if (!ret) {
			DRM_DEBUG_KMS("[ENCODER:%d:%s] fixup failed\n",
				      encoder->base.id, encoder->name);
			return -EINVAL;
		}
	}

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc;

		crtc_state = state->crtc_states[i];
		crtc = state->crtcs[i];

		if (!crtc_state || !crtc_state->mode_changed)
			continue;

		funcs = crtc->helper_private;
		ret = funcs->mode_fixup(crtc, &crtc_state->mode,
					&crtc_state->adjusted_mode);
		if (!ret) {
			DRM_DEBUG_KMS("[CRTC:%d] fixup failed\n",
				      crtc->base.id);
			return -EINVAL;
		}
	}

	return 0;
}

static int
drm_atomic_helper_check_prepare(struct drm_device *dev,
				struct drm_atomic_state *state)
{
	int ncrtcs = dev->mode_config.num_crtc;
	int nconnectors = dev->mode_config.num_connector;
	struct drm_crtc *crtc;
	struct drm_crtc_state *crtc_state;
	int i, ret;

	for (i = 0; i < ncrtcs; i++) {
		crtc = state->crtcs[i];
		crtc_state = state->crtc_states[i];

		if (!crtc)
			continue;

		if (!drm_mode_equal(&crtc->state->mode, &crtc_state->mode)) {
			DRM_DEBUG_KMS("[CRTC:%d] mode changed\n",
				      crtc->base.id);
			crtc_state->mode_changed = true;
		}

		if (crtc->state->enable != crtc_state->enable) {
			DRM_DEBUG_KMS("[CRTC:%d] enable changed\n",
				      crtc->base.id);
			crtc_state->mode_changed = true;
		}
	}

	for (i = 0; i < nconnectors; i++) {
		/*
		 * This only sets crtc->mode_changed for routing changes,
		 * drivers must set crtc->mode_changed themselves when connector
		 * properties need to be updated.
		 */
		ret = update_connector_routing(state, i);
		if (ret)
			return ret;
	}

	/*
	 * 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
	 * configuration. This must be done before calling mode_fixup in case a
	 * crtc only changed its mode but has the same set of connectors.
	 */
	for (i = 0; i < ncrtcs; i++) {
		int num_connectors;

		crtc = state->crtcs[i];
		crtc_state = state->crtc_states[i];

		if (!crtc || !crtc_state->mode_changed)
			continue;

		DRM_DEBUG_KMS("[CRTC:%d] needs full modeset, enable: %c\n",
			      crtc->base.id,
			      crtc_state->enable ? 'y' : 'n');

		ret = drm_atomic_add_affected_connectors(state, crtc);
		if (ret != 0)
			return ret;

		num_connectors = drm_atomic_connectors_for_crtc(state,
								crtc);

		if (crtc_state->enable != !!num_connectors) {
			DRM_DEBUG_KMS("[CRTC:%d] enabled/connectors mismatch\n",
				      crtc->base.id);

			return -EINVAL;
		}
	}

	return mode_fixup(state);
}

/**
 * drm_atomic_helper_check - validate state object
 * @dev: DRM device
 * @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)
 * checking that a driver needs it can simply wrap that around this function.
 * Drivers without such needs can directly use this as their ->atomic_check()
 * callback.
 *
 * RETURNS
 * Zero for success or -errno
 */
int drm_atomic_helper_check(struct drm_device *dev,
			    struct drm_atomic_state *state)
{
	int nplanes = dev->mode_config.num_total_plane;
	int ncrtcs = dev->mode_config.num_crtc;
	int i, ret = 0;

	ret = drm_atomic_helper_check_prepare(dev, state);
	if (ret)
		return ret;

	for (i = 0; i < nplanes; i++) {
		struct drm_plane_helper_funcs *funcs;
		struct drm_plane *plane = state->planes[i];
		struct drm_plane_state *plane_state = state->plane_states[i];

		if (!plane)
			continue;

		funcs = plane->helper_private;

		drm_atomic_helper_plane_changed(state, plane_state, plane);

		if (!funcs || !funcs->atomic_check)
			continue;

		ret = funcs->atomic_check(plane, plane_state);
		if (ret) {
			DRM_DEBUG_KMS("[PLANE:%d] atomic check failed\n",
				      plane->base.id);
			return ret;
		}
	}

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc = state->crtcs[i];

		if (!crtc)
			continue;

		funcs = crtc->helper_private;

		if (!funcs || !funcs->atomic_check)
			continue;

		ret = funcs->atomic_check(crtc, state->crtc_states[i]);
		if (ret) {
			DRM_DEBUG_KMS("[CRTC:%d] atomic check failed\n",
				      crtc->base.id);
			return ret;
		}
	}

	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_check);

static void
disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
{
	int ncrtcs = old_state->dev->mode_config.num_crtc;
	int nconnectors = old_state->dev->mode_config.num_connector;
	int i;

	for (i = 0; i < nconnectors; i++) {
		struct drm_connector_state *old_conn_state;
		struct drm_connector *connector;
		struct drm_encoder_helper_funcs *funcs;
		struct drm_encoder *encoder;

		old_conn_state = old_state->connector_states[i];
		connector = old_state->connectors[i];

		/* Shut down everything that's in the changeset and currently
		 * still on. So need to check the old, saved state. */
		if (!old_conn_state || !old_conn_state->crtc)
			continue;

		encoder = connector->state->best_encoder;

		if (!encoder)
			continue;

		funcs = encoder->helper_private;

		/*
		 * Each encoder has at most one connector (since we always steal
		 * it away), so we won't call call disable hooks twice.
		 */
		if (encoder->bridge)
			encoder->bridge->funcs->disable(encoder->bridge);

		/* Right function depends upon target state. */
		if (connector->state->crtc)
			funcs->prepare(encoder);
		else if (funcs->disable)
			funcs->disable(encoder);
		else
			funcs->dpms(encoder, DRM_MODE_DPMS_OFF);

		if (encoder->bridge)
			encoder->bridge->funcs->post_disable(encoder->bridge);
	}

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc;

		crtc = old_state->crtcs[i];

		/* Shut down everything that needs a full modeset. */
		if (!crtc || !crtc->state->mode_changed)
			continue;

		funcs = crtc->helper_private;

		/* Right function depends upon target state. */
		if (crtc->state->enable)
			funcs->prepare(crtc);
		else if (funcs->disable)
			funcs->disable(crtc);
		else
			funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
	}
}

static void
set_routing_links(struct drm_device *dev, struct drm_atomic_state *old_state)
{
	int nconnectors = dev->mode_config.num_connector;
	int ncrtcs = old_state->dev->mode_config.num_crtc;
	int i;

	/* clear out existing links */
	for (i = 0; i < nconnectors; i++) {
		struct drm_connector *connector;

		connector = old_state->connectors[i];

		if (!connector || !connector->encoder)
			continue;

		WARN_ON(!connector->encoder->crtc);

		connector->encoder->crtc = NULL;
		connector->encoder = NULL;
	}

	/* set new links */
	for (i = 0; i < nconnectors; i++) {
		struct drm_connector *connector;

		connector = old_state->connectors[i];

		if (!connector || !connector->state->crtc)
			continue;

		if (WARN_ON(!connector->state->best_encoder))
			continue;

		connector->encoder = connector->state->best_encoder;
		connector->encoder->crtc = connector->state->crtc;
	}

	/* set legacy state in the crtc structure */
	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc *crtc;

		crtc = old_state->crtcs[i];

		if (!crtc)
			continue;

		crtc->mode = crtc->state->mode;
		crtc->enabled = crtc->state->enable;
		crtc->x = crtc->primary->state->src_x >> 16;
		crtc->y = crtc->primary->state->src_y >> 16;
	}
}

static void
crtc_set_mode(struct drm_device *dev, struct drm_atomic_state *old_state)
{
	int ncrtcs = old_state->dev->mode_config.num_crtc;
	int nconnectors = old_state->dev->mode_config.num_connector;
	int i;

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc;

		crtc = old_state->crtcs[i];

		if (!crtc || !crtc->state->mode_changed)
			continue;

		funcs = crtc->helper_private;

		if (crtc->state->enable)
			funcs->mode_set_nofb(crtc);
	}

	for (i = 0; i < nconnectors; i++) {
		struct drm_connector *connector;
		struct drm_crtc_state *new_crtc_state;
		struct drm_encoder_helper_funcs *funcs;
		struct drm_encoder *encoder;
		struct drm_display_mode *mode, *adjusted_mode;

		connector = old_state->connectors[i];

		if (!connector || !connector->state->best_encoder)
			continue;

		encoder = connector->state->best_encoder;
		funcs = encoder->helper_private;
		new_crtc_state = connector->state->crtc->state;
		mode = &new_crtc_state->mode;
		adjusted_mode = &new_crtc_state->adjusted_mode;

		/*
		 * Each encoder has at most one connector (since we always steal
		 * it away), so we won't call call mode_set hooks twice.
		 */
		funcs->mode_set(encoder, mode, adjusted_mode);

		if (encoder->bridge && encoder->bridge->funcs->mode_set)
			encoder->bridge->funcs->mode_set(encoder->bridge,
							 mode, adjusted_mode);
	}
}

/**
 * drm_atomic_helper_commit_pre_planes - modeset commit before plane updates
 * @dev: DRM device
 * @state: atomic state
 *
 * This function commits the modeset changes that need to be committed before
 * updating planes. It shuts down all the outputs that need to be shut down and
 * prepares them (if required) with the new mode.
 */
void drm_atomic_helper_commit_pre_planes(struct drm_device *dev,
					 struct drm_atomic_state *state)
{
	disable_outputs(dev, state);
	set_routing_links(dev, state);
	crtc_set_mode(dev, state);
}
EXPORT_SYMBOL(drm_atomic_helper_commit_pre_planes);

/**
 * drm_atomic_helper_commit_post_planes - modeset commit after plane updates
 * @dev: DRM device
 * @old_state: atomic state object with old state structures
 *
 * This function commits the modeset changes that need to be committed after
 * updating planes: It enables all the outputs with the new configuration which
 * had to be turned off for the update.
 */
void drm_atomic_helper_commit_post_planes(struct drm_device *dev,
					  struct drm_atomic_state *old_state)
{
	int ncrtcs = old_state->dev->mode_config.num_crtc;
	int nconnectors = old_state->dev->mode_config.num_connector;
	int i;

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc;

		crtc = old_state->crtcs[i];

		/* Need to filter out CRTCs where only planes change. */
		if (!crtc || !crtc->state->mode_changed)
			continue;

		funcs = crtc->helper_private;

		if (crtc->state->enable)
			funcs->commit(crtc);
	}

	for (i = 0; i < nconnectors; i++) {
		struct drm_connector *connector;
		struct drm_encoder_helper_funcs *funcs;
		struct drm_encoder *encoder;

		connector = old_state->connectors[i];

		if (!connector || !connector->state->best_encoder)
			continue;

		encoder = connector->state->best_encoder;
		funcs = encoder->helper_private;

		/*
		 * Each encoder has at most one connector (since we always steal
		 * it away), so we won't call call enable hooks twice.
		 */
		if (encoder->bridge)
			encoder->bridge->funcs->pre_enable(encoder->bridge);

		funcs->commit(encoder);

		if (encoder->bridge)
			encoder->bridge->funcs->enable(encoder->bridge);
	}
}
EXPORT_SYMBOL(drm_atomic_helper_commit_post_planes);

static void
wait_for_vblanks(struct drm_device *dev, struct drm_atomic_state *old_state)
{
	struct drm_crtc *crtc;
	struct drm_crtc_state *old_crtc_state;
	int ncrtcs = old_state->dev->mode_config.num_crtc;
	int i, ret;

	for (i = 0; i < ncrtcs; i++) {
		crtc = old_state->crtcs[i];
		old_crtc_state = old_state->crtc_states[i];

		if (!crtc)
			continue;

		/* No one cares about the old state, so abuse it for tracking
		 * and store whether we hold a vblank reference (and should do a
		 * vblank wait) in the ->enable boolean. */
		old_crtc_state->enable = false;

		if (!crtc->state->enable)
			continue;

		ret = drm_crtc_vblank_get(crtc);
		if (ret != 0)
			continue;

		old_crtc_state->enable = true;
		old_crtc_state->last_vblank_count = drm_vblank_count(dev, i);
	}

	for (i = 0; i < ncrtcs; i++) {
		crtc = old_state->crtcs[i];
		old_crtc_state = old_state->crtc_states[i];

		if (!crtc || !old_crtc_state->enable)
			continue;

		ret = wait_event_timeout(dev->vblank[i].queue,
				old_crtc_state->last_vblank_count !=
					drm_vblank_count(dev, i),
				msecs_to_jiffies(50));

		drm_crtc_vblank_put(crtc);
	}
}

/**
 * drm_atomic_helper_commit - commit validated state object
 * @dev: DRM device
 * @state: the driver state object
 * @async: asynchronous commit
 *
 * This function commits a with drm_atomic_helper_check() pre-validated state
 * object. This can still fail when e.g. the framebuffer reservation fails. For
 * now this doesn't implement asynchronous commits.
 *
 * RETURNS
 * Zero for success or -errno.
 */
int drm_atomic_helper_commit(struct drm_device *dev,
			     struct drm_atomic_state *state,
			     bool async)
{
	int ret;

	if (async)
		return -EBUSY;

	ret = drm_atomic_helper_prepare_planes(dev, state);
	if (ret)
		return ret;

	/*
	 * This is the point of no return - everything below never fails except
	 * when the hw goes bonghits. Which means we can commit the new state on
	 * the software side now.
	 */

	drm_atomic_helper_swap_state(dev, state);

	/*
	 * Everything below can be run asynchronously without the need to grab
	 * any modeset locks at all under one conditions: It must be guaranteed
	 * that the asynchronous work has either been cancelled (if the driver
	 * supports it, which at least requires that the framebuffers get
	 * cleaned up with drm_atomic_helper_cleanup_planes()) or completed
	 * before the new state gets committed on the software side with
	 * drm_atomic_helper_swap_state().
	 *
	 * This scheme allows new atomic state updates to be prepared and
	 * checked in parallel to the asynchronous completion of the previous
	 * update. Which is important since compositors need to figure out the
	 * composition of the next frame right after having submitted the
	 * current layout.
	 */

	drm_atomic_helper_commit_pre_planes(dev, state);

	drm_atomic_helper_commit_planes(dev, state);

	drm_atomic_helper_commit_post_planes(dev, state);

	wait_for_vblanks(dev, state);

	drm_atomic_helper_cleanup_planes(dev, state);

	drm_atomic_state_free(state);

	return 0;
}
EXPORT_SYMBOL(drm_atomic_helper_commit);

/**
 * drm_atomic_helper_prepare_planes - prepare plane resources after commit
 * @dev: DRM device
 * @state: atomic state object with old state structures
 *
 * This function prepares plane state, specifically framebuffers, for the new
 * configuration. If any failure is encountered this function will call
 * ->cleanup_fb on any already successfully prepared framebuffer.
 *
 * Returns:
 * 0 on success, negative error code on failure.
 */
int drm_atomic_helper_prepare_planes(struct drm_device *dev,
				     struct drm_atomic_state *state)
{
	int nplanes = dev->mode_config.num_total_plane;
	int ret, i;

	for (i = 0; i < nplanes; i++) {
		struct drm_plane_helper_funcs *funcs;
		struct drm_plane *plane = state->planes[i];
		struct drm_framebuffer *fb;

		if (!plane)
			continue;

		funcs = plane->helper_private;

		fb = state->plane_states[i]->fb;

		if (fb && funcs->prepare_fb) {
			ret = funcs->prepare_fb(plane, fb);
			if (ret)
				goto fail;
		}
	}

	return 0;

fail:
	for (i--; i >= 0; i--) {
		struct drm_plane_helper_funcs *funcs;
		struct drm_plane *plane = state->planes[i];
		struct drm_framebuffer *fb;

		if (!plane)
			continue;

		funcs = plane->helper_private;

		fb = state->plane_states[i]->fb;

		if (fb && funcs->cleanup_fb)
			funcs->cleanup_fb(plane, fb);

	}

	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_prepare_planes);

/**
 * drm_atomic_helper_commit_planes - commit plane state
 * @dev: DRM device
 * @state: atomic 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
 * been pushed into the relevant object state pointers, since this step can no
 * longer fail.
 *
 * It still requires the global state object @state to know which planes and
 * crtcs need to be updated though.
 */
void drm_atomic_helper_commit_planes(struct drm_device *dev,
				     struct drm_atomic_state *state)
{
	int nplanes = dev->mode_config.num_total_plane;
	int ncrtcs = dev->mode_config.num_crtc;
	int i;

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc = state->crtcs[i];

		if (!crtc)
			continue;

		funcs = crtc->helper_private;

		if (!funcs || !funcs->atomic_begin)
			continue;

		funcs->atomic_begin(crtc);
	}

	for (i = 0; i < nplanes; i++) {
		struct drm_plane_helper_funcs *funcs;
		struct drm_plane *plane = state->planes[i];

		if (!plane)
			continue;

		funcs = plane->helper_private;

		if (!funcs || !funcs->atomic_update)
			continue;

		funcs->atomic_update(plane);
	}

	for (i = 0; i < ncrtcs; i++) {
		struct drm_crtc_helper_funcs *funcs;
		struct drm_crtc *crtc = state->crtcs[i];

		if (!crtc)
			continue;

		funcs = crtc->helper_private;

		if (!funcs || !funcs->atomic_flush)
			continue;

		funcs->atomic_flush(crtc);
	}
}
EXPORT_SYMBOL(drm_atomic_helper_commit_planes);

/**
 * drm_atomic_helper_cleanup_planes - cleanup plane resources after commit
 * @dev: DRM device
 * @old_state: atomic state object with old state structures
 *
 * This function cleans up plane state, specifically framebuffers, from the old
 * configuration. Hence the old configuration must be perserved in @old_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().
 */
void drm_atomic_helper_cleanup_planes(struct drm_device *dev,
				      struct drm_atomic_state *old_state)
{
	int nplanes = dev->mode_config.num_total_plane;
	int i;

	for (i = 0; i < nplanes; i++) {
		struct drm_plane_helper_funcs *funcs;
		struct drm_plane *plane = old_state->planes[i];
		struct drm_framebuffer *old_fb;

		if (!plane)
			continue;

		funcs = plane->helper_private;

		old_fb = old_state->plane_states[i]->fb;

		if (old_fb && funcs->cleanup_fb)
			funcs->cleanup_fb(plane, old_fb);
	}
}
EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes);

/**
 * drm_atomic_helper_swap_state - store atomic state into current sw state
 * @dev: DRM device
 * @state: atomic state
 *
 * 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
 * and succeeded, but before the actual hardware state is committed.
 *
 * For cleanup and error recovery the current state for all changed objects will
 * be swaped into @state.
 *
 * With that sequence it fits perfectly into the plane prepare/cleanup sequence:
 *
 * 1. Call drm_atomic_helper_prepare_planes() with the staged atomic state.
 *
 * 2. Do any other steps that might fail.
 *
 * 3. Put the staged state into the current state pointers with this function.
 *
 * 4. Actually commit the hardware state.
 *
 * 5. Call drm_atomic_helper_cleanup_planes with @state, which since step 3
 * contains the old state. Also do any other cleanup required with that state.
 */
void drm_atomic_helper_swap_state(struct drm_device *dev,
				  struct drm_atomic_state *state)
{
	int i;

	for (i = 0; i < dev->mode_config.num_connector; i++) {
		struct drm_connector *connector = state->connectors[i];

		if (!connector)
			continue;

		connector->state->state = state;
		swap(state->connector_states[i], connector->state);
		connector->state->state = NULL;
	}

	for (i = 0; i < dev->mode_config.num_crtc; i++) {
		struct drm_crtc *crtc = state->crtcs[i];

		if (!crtc)
			continue;

		crtc->state->state = state;
		swap(state->crtc_states[i], crtc->state);
		crtc->state->state = NULL;
	}

	for (i = 0; i < dev->mode_config.num_total_plane; i++) {
		struct drm_plane *plane = state->planes[i];

		if (!plane)
			continue;

		plane->state->state = state;
		swap(state->plane_states[i], plane->state);
		plane->state->state = NULL;
	}
}
EXPORT_SYMBOL(drm_atomic_helper_swap_state);