diff options
author | Dave Airlie <airlied@redhat.com> | 2014-11-10 09:59:16 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2014-11-10 09:59:16 +1000 |
commit | 122387a53eeac62e6546fd707259b63feca2d839 (patch) | |
tree | d186390dad553d7879db3d497c280e8a1b115b35 /drivers | |
parent | 1f9e14baa9139fce2265206746fe5491be7726e9 (diff) | |
parent | 321ebf04dc7ab5c54d658f93db0ffe35277664ab (diff) |
Merge tag 'topic/atomic-helpers-2014-11-09' of git://anongit.freedesktop.org/drm-intel into drm-next
So here's my atomic series, finally all debugged&reviewed. Sean Paul has
done a full detailed pass over it all, and a lot of other people have
commented and provided feedback on some parts. Rob Clark also converted
msm over the w/e and seems happy. The only small thing is that Rob wants
to export the wait_for_vblank, which imo makes sense. Since there's other
stuff still to do I think we should apply Rob's patch (once it has grown
appropriate kerneldoc) later on top of this.
This is just the core<->driver interface plus a big pile of helpers. Short
recap of the main ideas:
- There are essentially three helper libraries in this patch set:
* Transitional helpers to use the new plane callbacks for legacy plane
updates and in the crtc helper's ->mode_set callback. These helpers are
only temporarily used to convert drivers to atomic, but they allow a
nice separation between changing the driver backend and switching to
the atomic commit logic.
* Legacy helpers to implement all the legacy driver entry points
(page_flip, set_config, plane vfuncs) on top of the new atomic driver
interface. These are completely driver agnostic. The reason for having
the legacy support as helpers is that drivers can switch step-by-step.
And they could e.g. even keep the legacy page_flip code around for some
old platforms where converting to full-blown atomic isn't worth it.
* Atomic helpers which implement the various new ->atomic_* driver
interfaces in terms of the revised crtc helper and new plane helper
hooks.
- The revised crtc helper implemenation essentially implements all the
lessons learned in the i915 modeset rework (when using the atomic helpers
only):
* Enable/disable sequence for a given config are always the same and
callbacks are always called in the same order. This contrast starkly
with the crtc helpers, where the sequence of operations is heavily
dependent on the previous config.
One corollary of this is that if the configuration of a crtc only
partially changes (e.g. a connector moves in a cloned config) the
helper code will still disable/enable the full display pipeline. This
is the only way to ensure that the enable/disable sequence is always
the same.
* It won't call disable or enable hooks more than once any more because
it lost track of state, thanks to the atomic state tracking. And if
drivers implement the ->reset hook properly (by either resetting the hw
or reading out the hw state into the atomic structures) this even
extends to the hardware state. So no more disable-me-harder kind of
nonsense.
* The only thing missing is the hw state readout/cross-check support, but
if drivers have hw state readout support in their ->reset handlers it's
simple to extend that to cross-check the hw state.
* The crtc->mode_set callback is gone and its replacement only sets crtc
timings and no longer updates the primary plane state. This way we can
finally implement primary planes properly.
- The new plane helpers should be suitable enough for pretty much
everything, and a perfect fit for hardware with GO bits. Even if they
don't fit the atomic helper library is rather flexible and exports all
the functions for the individual steps to drivers. So drivers can pick
what matches and implement their own magic for everything else.
- A big difference compared to all previous atomic series is that this one
doesn't implement async commit in a generic way. Imo driver requirements
for that are too diverse to create anything reasonable sane which would
actually work on a reasonable amount of different drivers. Also, we've
never had a helper library for page_flips even, so it's really hard to
know what might work and what's stupid without a bit of experience in the form
of a few driver implementations.
I think with the current flexibility for drivers to pick individual
stages and existing helpers like drm_flip_queue it's rather easy though
to implement proper async commit.
- There's a few other differences of minor importance to earlier atomic
series:
* Common/generic properties are parsed in the callers/core and not in
drivers, and passed to drivers by directly setting the right members in
atomic state structures. That greatly simplifies all the transitional
and legacy helpers an removes a lot of boilerplate code.
* There's no crazy trylock mode used for the async commit since these
helpers don't do async commit. A simple ordered flip queue of atomic
state updates should be sufficient for preventing concurrent hw access
anyway, as long as synchronous updates stall correctly with e.g.
flush_work_queue or similar function. Abusing locks to enforce ordering
isn't a good idea imo anyway.
* These helpers reuse the existing ->mode_fixup hooks in the atomic_check
callback. Which means that drivers need to adapat and move a lot less code
into their atomic_check callbacks.
Now this isn't everything needed in the drm core and helpers for full
atomic support. But it's enough to start with converting drivers, and
except for actually testing multiplane and multicrtc updates also enough to
implement full atomic updates. Still missing are:
- Per-plane locking. Since these helpers here encapsulate the locking
completely this should be fairly easy to implement.
- fbdev support for atomic_check/commit, so that multi-pipe finally works
sanely in fbcon.
- Adding and decoding shared/core properties. That just needs to be rebased
from Rob's latest patch series, with minor adjustments so that the
decoding happens in the core instead of in drivers.
- Actually adding the atomic ioctl. Again just rebasing Rob's latest patch
should be all that's needed.
- Resolving how to deal with DPMS in atomic. Atomic is a good excuse to fix up
the crazy semantics dpms currently has. I'm floating an RFC about this topic
already.
- Finally I couldn't test connector/encoder stealing properly since my test
vehicle here doesn't allow a connector on different crtcs. So drivers
which support this might see some surprises in that area. There is no semantic
change though in how encoder stealing and assignment works (or at least no
intended one), so I think the risk is minimal.
As just mentioned I've done a fake conversion of an existing driver using
crtc helpers to debug the helper code and validate the smooth transition
approach. And that smooth transition was the really big motivation for
this. It seems to actually work and consists of 3 phases:
Phase 1: Rework driver backend for crtc/plane helpers
The requirement here is that universal plane support is already implement. If
universal plane support isn't implement yet it might be better though to just do
it as part of this phase, directly using the new plane helpers. There are two
big things to do:
- Split up the existing ->update/disable_plane hooks into check/commit
hooks and extract the crtc-wide prep/flush parts (like setting/clearing
GO bits).
- The other big change is to split the crtc->mode_set hook into the plane
update (done using the plane helpers) and the crtc setup in a new
->mode_set_nofb hook.
When phase 1 is complete the driver implements all the new callbacks which
push the software state into hardware, but still using all the legacy entry
points and crtc helpers. The transitional helpers serve as impendance
mismatch here.
Phase 2: Rework state handling
This consists of rolling out the state handling helpers for planes, crtcs
and connectors and reviewing all ->mode_fixup and similar hooks to make
sure they don't depend upon implicit global state which might change in the
atomic world. Any such code must be moved into ->atomic_check functions which
just rely on the free-standing atomic state update structures.
This phase also adds a few small pieces of fixup code to make sure the
atomic state doesn't get out of sync in the legacy driver callbacks.
Phase 3: Roll out atomic support
Now it's just about replacing vfuncs with the ones provided by the helper
and filling out the small missing pieces (like atomic_check logic or async
commit support needed for page_flips). Due to the prep work in phase 1 no
changes to the driver backend functions should be required, and because of
the prep work in phase 2 atomic implementations can be rolled out
step-by-step. So if async commit ins't implemented yet page_flip can be
implemented with the legacy functions without wreaking havoc in the other
operations.
* tag 'topic/atomic-helpers-2014-11-09' of git://anongit.freedesktop.org/drm-intel:
drm/atomic: Refcounting for plane_state->fb
drm: Docbook integration and over sections for all the new helpers
drm/atomic-helpers: functions for state duplicate/destroy/reset
drm/atomic-helper: implement ->page_flip
drm/atomic-helpers: document how to implement async commit
drm/atomic: Integrate fence support
drm/atomic-helper: implementatations for legacy interfaces
drm: Atomic crtc/connector updates using crtc/plane helper interfaces
drm/crtc-helper: Transitional functions using atomic plane helpers
drm/plane-helper: transitional atomic plane helpers
drm: Add atomic/plane helpers
drm: Global atomic state handling
drm: Add atomic driver interface definitions for objects
drm/modeset_lock: document trylock_only in kerneldoc
drm: fixup kerneldoc in drm_crtc.h
drm: Pull drm_crtc.h into the kerneldoc template
drm: Move drm_crtc_init from drm_crtc.h to drm_plane_helper.h
Diffstat (limited to 'drivers')
25 files changed, 2886 insertions, 3 deletions
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 9292a761ea6d..c3cf64ce2891 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -14,7 +14,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm_info.o drm_debugfs.o drm_encoder_slave.o \ drm_trace_points.o drm_global.o drm_prime.o \ drm_rect.o drm_vma_manager.o drm_flip_work.o \ - drm_modeset_lock.o + drm_modeset_lock.o drm_atomic.o drm-$(CONFIG_COMPAT) += drm_ioc32.o drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o @@ -23,7 +23,7 @@ drm-$(CONFIG_DRM_PANEL) += drm_panel.o drm-$(CONFIG_OF) += drm_of.o drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_probe_helper.o \ - drm_plane_helper.o drm_dp_mst_topology.o + drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o drm_kms_helper-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o drm_kms_helper-$(CONFIG_DRM_KMS_FB_HELPER) += drm_fb_helper.o drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 9a0cc09e6653..0b164fb1c107 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -12,6 +12,7 @@ #include <linux/platform_device.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include "armada_crtc.h" #include "armada_drm.h" #include "armada_fb.h" diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index 9dc0fd5c1ea4..b7ee2634e47c 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -31,6 +31,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include "ast_drv.h" #include "ast_tables.h" diff --git a/drivers/gpu/drm/bochs/bochs_kms.c b/drivers/gpu/drm/bochs/bochs_kms.c index 6b7efcf363d6..5ffd4895d040 100644 --- a/drivers/gpu/drm/bochs/bochs_kms.c +++ b/drivers/gpu/drm/bochs/bochs_kms.c @@ -6,6 +6,7 @@ */ #include "bochs.h" +#include <drm/drm_plane_helper.h> static int defx = 1024; static int defy = 768; diff --git a/drivers/gpu/drm/cirrus/cirrus_mode.c b/drivers/gpu/drm/cirrus/cirrus_mode.c index c7c5a9d91fa0..99d4a74ffeaf 100644 --- a/drivers/gpu/drm/cirrus/cirrus_mode.c +++ b/drivers/gpu/drm/cirrus/cirrus_mode.c @@ -16,6 +16,7 @@ */ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include <video/cirrus.h> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c new file mode 100644 index 000000000000..ed991ba66e21 --- /dev/null +++ b/drivers/gpu/drm/drm_atomic.c @@ -0,0 +1,628 @@ +/* + * 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> + +static void kfree_state(struct drm_atomic_state *state) +{ + kfree(state->connectors); + kfree(state->connector_states); + kfree(state->crtcs); + kfree(state->crtc_states); + kfree(state->planes); + kfree(state->plane_states); + kfree(state); +} + +/** + * drm_atomic_state_alloc - allocate atomic state + * @dev: DRM device + * + * This allocates an empty atomic state to track updates. + */ +struct drm_atomic_state * +drm_atomic_state_alloc(struct drm_device *dev) +{ + struct drm_atomic_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + state->crtcs = kcalloc(dev->mode_config.num_crtc, + sizeof(*state->crtcs), GFP_KERNEL); + if (!state->crtcs) + goto fail; + state->crtc_states = kcalloc(dev->mode_config.num_crtc, + sizeof(*state->crtc_states), GFP_KERNEL); + if (!state->crtc_states) + goto fail; + state->planes = kcalloc(dev->mode_config.num_total_plane, + sizeof(*state->planes), GFP_KERNEL); + if (!state->planes) + goto fail; + state->plane_states = kcalloc(dev->mode_config.num_total_plane, + sizeof(*state->plane_states), GFP_KERNEL); + if (!state->plane_states) + goto fail; + state->connectors = kcalloc(dev->mode_config.num_connector, + sizeof(*state->connectors), + GFP_KERNEL); + if (!state->connectors) + goto fail; + state->connector_states = kcalloc(dev->mode_config.num_connector, + sizeof(*state->connector_states), + GFP_KERNEL); + if (!state->connector_states) + goto fail; + + state->dev = dev; + + DRM_DEBUG_KMS("Allocate atomic state %p\n", state); + + return state; +fail: + kfree_state(state); + + return NULL; +} +EXPORT_SYMBOL(drm_atomic_state_alloc); + +/** + * drm_atomic_state_clear - clear state object + * @state: atomic state + * + * When the w/w mutex algorithm detects a deadlock we need to back off and drop + * all locks. So someone else could sneak in and change the current modeset + * configuration. Which means that all the state assembled in @state is no + * longer an atomic update to the current state, but to some arbitrary earlier + * state. Which could break assumptions the driver's ->atomic_check likely + * relies on. + * + * Hence we must clear all cached state and completely start over, using this + * function. + */ +void drm_atomic_state_clear(struct drm_atomic_state *state) +{ + struct drm_device *dev = state->dev; + int i; + + DRM_DEBUG_KMS("Clearing atomic state %p\n", state); + + for (i = 0; i < dev->mode_config.num_connector; i++) { + struct drm_connector *connector = state->connectors[i]; + + if (!connector) + continue; + + connector->funcs->atomic_destroy_state(connector, + state->connector_states[i]); + } + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct drm_crtc *crtc = state->crtcs[i]; + + if (!crtc) + continue; + + crtc->funcs->atomic_destroy_state(crtc, + state->crtc_states[i]); + } + + for (i = 0; i < dev->mode_config.num_total_plane; i++) { + struct drm_plane *plane = state->planes[i]; + + if (!plane) + continue; + + plane->funcs->atomic_destroy_state(plane, + state->plane_states[i]); + } +} +EXPORT_SYMBOL(drm_atomic_state_clear); + +/** + * drm_atomic_state_free - free all memory for an atomic state + * @state: atomic state to deallocate + * + * This frees all memory associated with an atomic state, including all the + * per-object state for planes, crtcs and connectors. + */ +void drm_atomic_state_free(struct drm_atomic_state *state) +{ + drm_atomic_state_clear(state); + + DRM_DEBUG_KMS("Freeing atomic state %p\n", state); + + kfree_state(state); +} +EXPORT_SYMBOL(drm_atomic_state_free); + +/** + * drm_atomic_get_crtc_state - get crtc state + * @state: global atomic state object + * @crtc: crtc to get state object for + * + * This function returns the crtc state for the given crtc, allocating it if + * needed. It will also grab the relevant crtc lock to make sure that the state + * is consistent. + * + * Returns: + * + * Either the allocated state or the error code encoded into the pointer. When + * the error is EDEADLK then the w/w mutex code has detected a deadlock and the + * entire atomic sequence must be restarted. All other errors are fatal. + */ +struct drm_crtc_state * +drm_atomic_get_crtc_state(struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + int ret, index; + struct drm_crtc_state *crtc_state; + + index = drm_crtc_index(crtc); + + if (state->crtc_states[index]) + return state->crtc_states[index]; + + ret = drm_modeset_lock(&crtc->mutex, state->acquire_ctx); + if (ret) + return ERR_PTR(ret); + + crtc_state = crtc->funcs->atomic_duplicate_state(crtc); + if (!crtc_state) + return ERR_PTR(-ENOMEM); + + state->crtc_states[index] = crtc_state; + state->crtcs[index] = crtc; + crtc_state->state = state; + + DRM_DEBUG_KMS("Added [CRTC:%d] %p state to %p\n", + crtc->base.id, crtc_state, state); + + return crtc_state; +} +EXPORT_SYMBOL(drm_atomic_get_crtc_state); + +/** + * drm_atomic_get_plane_state - get plane state + * @state: global atomic state object + * @plane: plane to get state object for + * + * This function returns the plane state for the given plane, allocating it if + * needed. It will also grab the relevant plane lock to make sure that the state + * is consistent. + * + * Returns: + * + * Either the allocated state or the error code encoded into the pointer. When + * the error is EDEADLK then the w/w mutex code has detected a deadlock and the + * entire atomic sequence must be restarted. All other errors are fatal. + */ +struct drm_plane_state * +drm_atomic_get_plane_state(struct drm_atomic_state *state, + struct drm_plane *plane) +{ + int ret, index; + struct drm_plane_state *plane_state; + + index = drm_plane_index(plane); + + if (state->plane_states[index]) + return state->plane_states[index]; + + /* + * TODO: We currently don't have per-plane mutexes. So instead of trying + * crazy tricks with deferring plane->crtc and hoping for the best just + * grab all crtc locks. Once we have per-plane locks we must update this + * to only take the plane mutex. + */ + ret = drm_modeset_lock_all_crtcs(state->dev, state->acquire_ctx); + if (ret) + return ERR_PTR(ret); + + plane_state = plane->funcs->atomic_duplicate_state(plane); + if (!plane_state) + return ERR_PTR(-ENOMEM); + + state->plane_states[index] = plane_state; + state->planes[index] = plane; + plane_state->state = state; + + DRM_DEBUG_KMS("Added [PLANE:%d] %p state to %p\n", + plane->base.id, plane_state, state); + + if (plane_state->crtc) { + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_crtc_state(state, + plane_state->crtc); + if (IS_ERR(crtc_state)) + return ERR_CAST(crtc_state); + } + + return plane_state; +} +EXPORT_SYMBOL(drm_atomic_get_plane_state); + +/** + * drm_atomic_get_connector_state - get connector state + * @state: global atomic state object + * @connector: connector to get state object for + * + * This function returns the connector state for the given connector, + * allocating it if needed. It will also grab the relevant connector lock to + * make sure that the state is consistent. + * + * Returns: + * + * Either the allocated state or the error code encoded into the pointer. When + * the error is EDEADLK then the w/w mutex code has detected a deadlock and the + * entire atomic sequence must be restarted. All other errors are fatal. + */ +struct drm_connector_state * +drm_atomic_get_connector_state(struct drm_atomic_state *state, + struct drm_connector *connector) +{ + int ret, index; + struct drm_mode_config *config = &connector->dev->mode_config; + struct drm_connector_state *connector_state; + + index = drm_connector_index(connector); + + if (state->connector_states[index]) + return state->connector_states[index]; + + ret = drm_modeset_lock(&config->connection_mutex, state->acquire_ctx); + if (ret) + return ERR_PTR(ret); + + connector_state = connector->funcs->atomic_duplicate_state(connector); + if (!connector_state) + return ERR_PTR(-ENOMEM); + + state->connector_states[index] = connector_state; + state->connectors[index] = connector; + connector_state->state = state; + + DRM_DEBUG_KMS("Added [CONNECTOR:%d] %p state to %p\n", + connector->base.id, connector_state, state); + + if (connector_state->crtc) { + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_crtc_state(state, + connector_state->crtc); + if (IS_ERR(crtc_state)) + return ERR_CAST(crtc_state); + } + + return connector_state; +} +EXPORT_SYMBOL(drm_atomic_get_connector_state); + +/** + * drm_atomic_set_crtc_for_plane - set crtc for plane + * @plane_state: atomic state object for the plane + * @crtc: crtc to use for the plane + * + * Changing the assigned crtc for a plane requires us to grab the lock and state + * for the new crtc, as needed. This function takes care of all these details + * besides updating the pointer in the state object itself. + * + * Returns: + * 0 on success or can fail with -EDEADLK or -ENOMEM. When the error is EDEADLK + * then the w/w mutex code has detected a deadlock and the entire atomic + * sequence must be restarted. All other errors are fatal. + */ +int +drm_atomic_set_crtc_for_plane(struct drm_plane_state *plane_state, + struct drm_crtc *crtc) +{ + struct drm_crtc_state *crtc_state; + + if (crtc) { + crtc_state = drm_atomic_get_crtc_state(plane_state->state, + crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + plane_state->crtc = crtc; + + if (crtc) + DRM_DEBUG_KMS("Link plane state %p to [CRTC:%d]\n", + plane_state, crtc->base.id); + else + DRM_DEBUG_KMS("Link plane state %p to [NOCRTC]\n", plane_state); + + return 0; +} +EXPORT_SYMBOL(drm_atomic_set_crtc_for_plane); + +/** + * drm_atomic_set_fb_for_plane - set crtc for plane + * @plane_state: atomic state object for the plane + * @fb: fb to use for the plane + * + * Changing the assigned framebuffer for a plane requires us to grab a reference + * to the new fb and drop the reference to the old fb, if there is one. This + * function takes care of all these details besides updating the pointer in the + * state object itself. + */ +void +drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state, + struct drm_framebuffer *fb) +{ + if (plane_state->fb) + drm_framebuffer_unreference(plane_state->fb); + if (fb) + drm_framebuffer_reference(fb); + plane_state->fb = fb; + + if (fb) + DRM_DEBUG_KMS("Set [FB:%d] for plane state %p\n", + fb->base.id, plane_state); + else + DRM_DEBUG_KMS("Set [NOFB] for plane state %p\n", plane_state); +} +EXPORT_SYMBOL(drm_atomic_set_fb_for_plane); + +/** + * drm_atomic_set_crtc_for_connector - set crtc for connector + * @conn_state: atomic state object for the connector + * @crtc: crtc to use for the connector + * + * Changing the assigned crtc for a connector requires us to grab the lock and + * state for the new crtc, as needed. This function takes care of all these + * details besides updating the pointer in the state object itself. + * + * Returns: + * 0 on success or can fail with -EDEADLK or -ENOMEM. When the error is EDEADLK + * then the w/w mutex code has detected a deadlock and the entire atomic + * sequence must be restarted. All other errors are fatal. + */ +int +drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state, + struct drm_crtc *crtc) +{ + struct drm_crtc_state *crtc_state; + + if (crtc) { + crtc_state = drm_atomic_get_crtc_state(conn_state->state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + conn_state->crtc = crtc; + + if (crtc) + DRM_DEBUG_KMS("Link connector state %p to [CRTC:%d]\n", + conn_state, crtc->base.id); + else + DRM_DEBUG_KMS("Link connector state %p to [NOCRTC]\n", + conn_state); + + return 0; +} +EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector); + +/** + * drm_atomic_add_affected_connectors - add connectors for crtc + * @state: atomic state + * @crtc: DRM crtc + * + * This function walks the current configuration and adds all connectors + * currently using @crtc to the atomic configuration @state. Note that this + * function must acquire the connection mutex. This can potentially cause + * unneeded seralization if the update is just for the planes on one crtc. Hence + * drivers and helpers should only call this when really needed (e.g. when a + * full modeset needs to happen due to some change). + * + * Returns: + * 0 on success or can fail with -EDEADLK or -ENOMEM. When the error is EDEADLK + * then the w/w mutex code has detected a deadlock and the entire atomic + * sequence must be restarted. All other errors are fatal. + */ +int +drm_atomic_add_affected_connectors(struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + struct drm_mode_config *config = &state->dev->mode_config; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + int ret; + + ret = drm_modeset_lock(&config->connection_mutex, state->acquire_ctx); + if (ret) + return ret; + + DRM_DEBUG_KMS("Adding all current connectors for [CRTC:%d] to %p\n", + crtc->base.id, state); + + /* + * Changed connectors are already in @state, so only need to look at the + * current configuration. + */ + list_for_each_entry(connector, &config->connector_list, head) { + if (connector->state->crtc != crtc) + continue; + + conn_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + } + + return 0; +} +EXPORT_SYMBOL(drm_atomic_add_affected_connectors); + +/** + * drm_atomic_connectors_for_crtc - count number of connected outputs + * @state: atomic state + * @crtc: DRM crtc + * + * This function counts all connectors which will be connected to @crtc + * according to @state. Useful to recompute the enable state for @crtc. + */ +int +drm_atomic_connectors_for_crtc(struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + int nconnectors = state->dev->mode_config.num_connector; + int i, num_connected_connectors = 0; + + for (i = 0; i < nconnectors; i++) { + struct drm_connector_state *conn_state; + + conn_state = state->connector_states[i]; + + if (conn_state && conn_state->crtc == crtc) + num_connected_connectors++; + } + + DRM_DEBUG_KMS("State %p has %i connectors for [CRTC:%d]\n", + state, num_connected_connectors, crtc->base.id); + + return num_connected_connectors; +} +EXPORT_SYMBOL(drm_atomic_connectors_for_crtc); + +/** + * drm_atomic_legacy_backoff - locking backoff for legacy ioctls + * @state: atomic state + * + * This function should be used by legacy entry points which don't understand + * -EDEADLK semantics. For simplicity this one will grab all modeset locks after + * the slowpath completed. + */ +void drm_atomic_legacy_backoff(struct drm_atomic_state *state) +{ + int ret; + +retry: + drm_modeset_backoff(state->acquire_ctx); + + ret = drm_modeset_lock(&state->dev->mode_config.connection_mutex, + state->acquire_ctx); + if (ret) + goto retry; + ret = drm_modeset_lock_all_crtcs(state->dev, + state->acquire_ctx); + if (ret) + goto retry; +} +EXPORT_SYMBOL(drm_atomic_legacy_backoff); + +/** + * drm_atomic_check_only - check whether a given config would work + * @state: atomic configuration to check + * + * Note that this function can return -EDEADLK if the driver needed to acquire + * more locks but encountered a deadlock. The caller must then do the usual w/w + * backoff dance and restart. All other errors are fatal. + * + * Returns: + * 0 on success, negative error code on failure. + */ +int drm_atomic_check_only(struct drm_atomic_state *state) +{ + struct drm_mode_config *config = &state->dev->mode_config; + + DRM_DEBUG_KMS("checking %p\n", state); + + if (config->funcs->atomic_check) + return config->funcs->atomic_check(state->dev, state); + else + return 0; +} +EXPORT_SYMBOL(drm_atomic_check_only); + +/** + * drm_atomic_commit - commit configuration atomically + * @state: atomic configuration to check + * + * Note that this function can return -EDEADLK if the driver needed to acquire + * more locks but encountered a deadlock. The caller must then do the usual w/w + * backoff dance and restart. All other errors are fatal. + * + * Also note that on successful execution ownership of @state is transferred + * from the caller of this function to the function itself. The caller must not + * free or in any other way access @state. If the function fails then the caller + * must clean up @state itself. + * + * Returns: + * 0 on success, negative error code on failure. + */ +int drm_atomic_commit(struct drm_atomic_state *state) +{ + struct drm_mode_config *config = &state->dev->mode_config; + int ret; + + ret = drm_atomic_check_only(state); + if (ret) + return ret; + + DRM_DEBUG_KMS("commiting %p\n", state); + + return config->funcs->atomic_commit(state->dev, state, false); +} +EXPORT_SYMBOL(drm_atomic_commit); + +/** + * drm_atomic_async_commit - atomic&async configuration commit + * @state: atomic configuration to check + * + * Note that this function can return -EDEADLK if the driver needed to acquire + * more locks but encountered a deadlock. The caller must then do the usual w/w + * backoff dance and restart. All other errors are fatal. + * + * Also note that on successful execution ownership of @state is transferred + * from the caller of this function to the function itself. The caller must not + * free or in any other way access @state. If the function fails then the caller + * must clean up @state itself. + * + * Returns: + * 0 on success, negative error code on failure. + */ +int drm_atomic_async_commit(struct drm_atomic_state *state) +{ + struct drm_mode_config *config = &state->dev->mode_config; + int ret; + + ret = drm_atomic_check_only(state); + if (ret) + return ret; + + DRM_DEBUG_KMS("commiting %p asynchronously\n", state); + + return config->funcs->atomic_commit(state->dev, state, true); +} +EXPORT_SYMBOL(drm_atomic_async_commit); diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c new file mode 100644 index 000000000000..ca839bd9bb0d --- /dev/null +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -0,0 +1,1906 @@ +/* + * 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> +#include <linux/fence.h> + +/** + * DOC: overview + * + * This helper library provides implementations of check and commit functions on + * top of the CRTC modeset helper callbacks and the plane helper callbacks. It + * also provides convenience implementations for the atomic state handling + * callbacks for drivers which don't need to subclass the drm core structures to + * add their own additional internal state. + * + * This library also provides default implementations for the check callback in + * drm_atomic_helper_check and for the commit callback with + * drm_atomic_helper_commit. But the individual stages and callbacks are expose + * to allow drivers to mix and match and e.g. use the plane helpers only + * together with a driver private modeset implementation. + * + * 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. + */ +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; + int ret; + + /* + * 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); + + ret = drm_atomic_set_crtc_for_connector(connector_state, NULL); + if (ret) + return ret; + 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_fences(struct drm_device *dev, + struct drm_atomic_state *state) +{ + int nplanes = dev->mode_config.num_total_plane; + int i; + + for (i = 0; i < nplanes; i++) { + struct drm_plane *plane = state->planes[i]; + + if (!plane || !plane->state->fence) + continue; + + WARN_ON(!plane->state->fb); + + fence_wait(plane->state->fence, false); + fence_put(plane->state->fence); + plane->state->fence = NULL; + } +} + +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. + */ + + wait_for_fences(dev, state); + + 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); + +/** + * DOC: implementing async commit + * + * For now the atomic helpers don't support async commit directly. If there is + * real need it could be added though, using the dma-buf fence infrastructure + * for generic synchronization with outstanding rendering. + * + * For now drivers have to implement async commit themselves, with the following + * sequence being the recommended one: + * + * 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 + * synchronously. + * + * 2. Synchronize with any outstanding asynchronous 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. + * + * For sufficient parallelism it is recommended to have a work item per crtc + * (for updates which don't touch global state) and a global one. Then we only + * need to synchronize with the crtc work items for changed crtcs and the global + * work item, which allows nice concurrent updates on disjoint sets of crtcs. + * + * 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 async worker runs means that async + * workers do not need grab any locks. Actually they must not grab locks, for + * otherwise the work flushing will deadlock. + * + * 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. + */ + +/** + * 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); + +/** + * drm_atomic_helper_update_plane - Helper for primary plane update using atomic + * @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 + * @src_x: x offset of @fb for panning + * @src_y: y offset of @fb for panning + * @src_w: width of source rectangle in @fb + * @src_h: height of source rectangle in @fb + * + * Provides a default plane update handler using the atomic driver interface. + * + * RETURNS: + * Zero on success, error code on failure + */ +int drm_atomic_helper_update_plane(struct drm_plane *plane, + struct drm_crtc *crtc, + struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct drm_atomic_state *state; + struct drm_plane_state *plane_state; + int ret = 0; + + state = drm_atomic_state_alloc(plane->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = drm_modeset_legacy_acquire_ctx(crtc); +retry: + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto fail; + } + + ret = drm_atomic_set_crtc_for_plane(plane_state, crtc); + if (ret != 0) + goto fail; + drm_atomic_set_fb_for_plane(plane_state, fb); + plane_state->crtc_x = crtc_x; + plane_state->crtc_y = crtc_y; + plane_state->crtc_h = crtc_h; + plane_state->crtc_w = crtc_w; + plane_state->src_x = src_x; + plane_state->src_y = src_y; + plane_state->src_h = src_h; + plane_state->src_w = src_w; + + ret = drm_atomic_commit(state); + if (ret != 0) + goto fail; + + /* Driver takes ownership of state on successful commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + /* + * Someone might have exchanged the framebuffer while we dropped locks + * in the backoff code. We need to fix up the fb refcount tracking the + * core does for us. + */ + plane->old_fb = plane->fb; + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_update_plane); + +/** + * drm_atomic_helper_disable_plane - Helper for primary plane disable using * atomic + * @plane: plane to disable + * + * Provides a default plane disable handler using the atomic driver interface. + * + * RETURNS: + * Zero on success, error code on failure + */ +int drm_atomic_helper_disable_plane(struct drm_plane *plane) +{ + struct drm_atomic_state *state; + struct drm_plane_state *plane_state; + int ret = 0; + + state = drm_atomic_state_alloc(plane->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = drm_modeset_legacy_acquire_ctx(plane->crtc); +retry: + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto fail; + } + + ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); + if (ret != 0) + goto fail; + drm_atomic_set_fb_for_plane(plane_state, NULL); + plane_state->crtc_x = 0; + plane_state->crtc_y = 0; + plane_state->crtc_h = 0; + plane_state->crtc_w = 0; + plane_state->src_x = 0; + plane_state->src_y = 0; + plane_state->src_h = 0; + plane_state->src_w = 0; + + ret = drm_atomic_commit(state); + if (ret != 0) + goto fail; + + /* Driver takes ownership of state on successful commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + /* + * Someone might have exchanged the framebuffer while we dropped locks + * in the backoff code. We need to fix up the fb refcount tracking the + * core does for us. + */ + plane->old_fb = plane->fb; + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_disable_plane); + +static int update_output_state(struct drm_atomic_state *state, + struct drm_mode_set *set) +{ + struct drm_device *dev = set->crtc->dev; + struct drm_connector_state *conn_state; + int nconnectors = state->dev->mode_config.num_connector; + int ncrtcs = state->dev->mode_config.num_crtc; + int ret, i, j; + + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, + state->acquire_ctx); + if (ret) + return ret; + + /* First grab all affected connector/crtc states. */ + for (i = 0; i < set->num_connectors; i++) { + conn_state = drm_atomic_get_connector_state(state, + set->connectors[i]); + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + } + + for (i = 0; i < ncrtcs; i++) { + struct drm_crtc *crtc = state->crtcs[i]; + + if (!crtc) + continue; + + ret = drm_atomic_add_affected_connectors(state, crtc); + if (ret) + return ret; + } + + /* Then recompute connector->crtc links and crtc enabling state. */ + for (i = 0; i < nconnectors; i++) { + struct drm_connector *connector; + + connector = state->connectors[i]; + conn_state = state->connector_states[i]; + + if (!connector) + continue; + + if (conn_state->crtc == set->crtc) { + ret = drm_atomic_set_crtc_for_connector(conn_state, + NULL); + if (ret) + return ret; + } + + for (j = 0; j < set->num_connectors; j++) { + if (set->connectors[j] == connector) { + ret = drm_atomic_set_crtc_for_connector(conn_state, + set->crtc); + if (ret) + return ret; + break; + } + } + } + + for (i = 0; i < ncrtcs; i++) { + struct drm_crtc *crtc = state->crtcs[i]; + struct drm_crtc_state *crtc_state = state->crtc_states[i]; + + if (!crtc) + continue; + + /* 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; + + crtc_state->enable = + drm_atomic_connectors_for_crtc(state, crtc); + } + + return 0; +} + +/** + * drm_atomic_helper_set_config - set a new config from userspace + * @set: mode set configuration + * + * Provides a default crtc set_config handler using the atomic driver interface. + * + * Returns: + * Returns 0 on success, negative errno numbers on failure. + */ +int drm_atomic_helper_set_config(struct drm_mode_set *set) +{ + struct drm_atomic_state *state; + struct drm_crtc *crtc = set->crtc; + struct drm_crtc_state *crtc_state; + struct drm_plane_state *primary_state; + int ret = 0; + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = drm_modeset_legacy_acquire_ctx(crtc); +retry: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto fail; + } + + if (!set->mode) { + WARN_ON(set->fb); + WARN_ON(set->num_connectors); + + crtc_state->enable = false; + goto commit; + } + + WARN_ON(!set->fb); + WARN_ON(!set->num_connectors); + + crtc_state->enable = true; + drm_mode_copy(&crtc_state->mode, set->mode); + + primary_state = drm_atomic_get_plane_state(state, crtc->primary); + if (IS_ERR(primary_state)) { + ret = PTR_ERR(primary_state); + goto fail; + } + + ret = drm_atomic_set_crtc_for_plane(primary_state, crtc); + if (ret != 0) + goto fail; + drm_atomic_set_fb_for_plane(primary_state, set->fb); + primary_state->crtc_x = 0; + primary_state->crtc_y = 0; + primary_state->crtc_h = set->mode->vdisplay; + primary_state->crtc_w = set->mode->hdisplay; + primary_state->src_x = set->x << 16; + primary_state->src_y = set->y << 16; + primary_state->src_h = set->mode->vdisplay << 16; + primary_state->src_w = set->mode->hdisplay << 16; + +commit: + ret = update_output_state(state, set); + if (ret) + goto fail; + + ret = drm_atomic_commit(state); + if (ret != 0) + goto fail; + + /* Driver takes ownership of state on successful commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + /* + * Someone might have exchanged the framebuffer while we dropped locks + * in the backoff code. We need to fix up the fb refcount tracking the + * core does for us. + */ + crtc->primary->old_fb = crtc->primary->fb; + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_set_config); + +/** + * drm_atomic_helper_crtc_set_property - helper for crtc prorties + * @crtc: DRM crtc + * @property: DRM property + * @val: value of property + * + * Provides a default plane disablle handler using the atomic driver interface. + * + * RETURNS: + * Zero on success, error code on failure + */ +int +drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, + struct drm_property *property, + uint64_t val) +{ + struct drm_atomic_state *state; + struct drm_crtc_state *crtc_state; + int ret = 0; + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) + return -ENOMEM; + + /* ->set_property is always called with all locks held. */ + state->acquire_ctx = crtc->dev->mode_config.acquire_ctx; +retry: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto fail; + } + + ret = crtc->funcs->atomic_set_property(crtc, crtc_state, + property, val); + if (ret) + goto fail; + + ret = drm_atomic_commit(state); + if (ret != 0) + goto fail; + + /* Driver takes ownership of state on successful commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_crtc_set_property); + +/** + * drm_atomic_helper_plane_set_property - helper for plane prorties + * @plane: DRM plane + * @property: DRM property + * @val: value of property + * + * Provides a default plane disable handler using the atomic driver interface. + * + * RETURNS: + * Zero on success, error code on failure + */ +int +drm_atomic_helper_plane_set_property(struct drm_plane *plane, + struct drm_property *property, + uint64_t val) +{ + struct drm_atomic_state *state; + struct drm_plane_state *plane_state; + int ret = 0; + + state = drm_atomic_state_alloc(plane->dev); + if (!state) + return -ENOMEM; + + /* ->set_property is always called with all locks held. */ + state->acquire_ctx = plane->dev->mode_config.acquire_ctx; +retry: + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto fail; + } + + ret = plane->funcs->atomic_set_property(plane, plane_state, + property, val); + if (ret) + goto fail; + + ret = drm_atomic_commit(state); + if (ret != 0) + goto fail; + + /* Driver takes ownership of state on successful commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_plane_set_property); + +/** + * drm_atomic_helper_connector_set_property - helper for connector prorties + * @connector: DRM connector + * @property: DRM property + * @val: value of property + * + * Provides a default plane disablle handler using the atomic driver interface. + * + * RETURNS: + * Zero on success, error code on failure + */ +int +drm_atomic_helper_connector_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + struct drm_atomic_state *state; + struct drm_connector_state *connector_state; + int ret = 0; + + state = drm_atomic_state_alloc(connector->dev); + if (!state) + return -ENOMEM; + + /* ->set_property is always called with all locks held. */ + state->acquire_ctx = connector->dev->mode_config.acquire_ctx; +retry: + connector_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(connector_state)) { + ret = PTR_ERR(connector_state); + goto fail; + } + + ret = connector->funcs->atomic_set_property(connector, connector_state, + property, val); + if (ret) + goto fail; + + ret = drm_atomic_commit(state); + if (ret != 0) + goto fail; + + /* Driver takes ownership of state on successful commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_connector_set_property); + +/** + * drm_atomic_helper_page_flip - execute a legacy page flip + * @crtc: DRM crtc + * @fb: DRM framebuffer + * @event: optional DRM event to signal upon completion + * @flags: flip flags for non-vblank sync'ed updates + * + * Provides a default page flip implementation using the atomic driver interface. + * + * Note that for now so called async page flips (i.e. updates which are not + * synchronized to vblank) are not supported, since the atomic interfaces have + * no provisions for this yet. + * + * Returns: + * Returns 0 on success, negative errno numbers on failure. + */ +int drm_atomic_helper_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t flags) +{ + struct drm_plane *plane = crtc->primary; + struct drm_atomic_state *state; + struct drm_plane_state *plane_state; + struct drm_crtc_state *crtc_state; + int ret = 0; + + if (flags & DRM_MODE_PAGE_FLIP_ASYNC) + return -EINVAL; + + state = drm_atomic_state_alloc(plane->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = drm_modeset_legacy_acquire_ctx(crtc); +retry: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto fail; + } + crtc_state->event = event; + + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto fail; + } + + ret = drm_atomic_set_crtc_for_plane(plane_state, crtc); + if (ret != 0) + goto fail; + drm_atomic_set_fb_for_plane(plane_state, fb); + + ret = drm_atomic_async_commit(state); + if (ret != 0) + goto fail; + + /* TODO: ->page_flip is the only driver callback where the core + * doesn't update plane->fb. For now patch it up here. */ + plane->fb = plane->state->fb; + + /* Driver takes ownership of state on successful async commit. */ + return 0; +fail: + if (ret == -EDEADLK) + goto backoff; + + drm_atomic_state_free(state); + + return ret; +backoff: + drm_atomic_legacy_backoff(state); + drm_atomic_state_clear(state); + + /* + * Someone might have exchanged the framebuffer while we dropped locks + * in the backoff code. We need to fix up the fb refcount tracking the + * core does for us. + */ + plane->old_fb = plane->fb; + + goto retry; +} +EXPORT_SYMBOL(drm_atomic_helper_page_flip); + +/** + * DOC: atomic state reset and initialization + * + * Both the drm core and the atomic helpers assume that there is always the full + * and correct atomic software state for all connectors, CRTCs and planes + * available. Which is a bit a problem on driver load and also after system + * suspend. One way to solve this is to have a hardware state read-out + * infrastructure which reconstructs the full software state (e.g. the i915 + * driver). + * + * The simpler solution is to just reset the software state to everything off, + * which is easiest to do by calling drm_mode_config_reset(). To facilitate this + * the atomic helpers provide default reset implementations for all hooks. + */ + +/** + * drm_atomic_helper_crtc_reset - default ->reset hook for CRTCs + * @crtc: drm CRTC + * + * Resets the atomic state for @crtc by freeing the state pointer (which might + * be NULL, e.g. at driver load time) and allocating a new empty state object. + */ +void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc) +{ + kfree(crtc->state); + crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL); +} +EXPORT_SYMBOL(drm_atomic_helper_crtc_reset); + +/** + * drm_atomic_helper_crtc_duplicate_state - default state duplicate hook + * @crtc: drm CRTC + * + * Default CRTC state duplicate hook for drivers which don't have their own + * subclassed CRTC state structure. + */ +struct drm_crtc_state * +drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct drm_crtc_state *state; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = kmemdup(crtc->state, sizeof(*crtc->state), GFP_KERNEL); + + if (state) { + state->mode_changed = false; + state->planes_changed = false; + state->event = NULL; + } + + return state; +} +EXPORT_SYMBOL(drm_atomic_helper_crtc_duplicate_state); + +/** + * drm_atomic_helper_crtc_destroy_state - default state destroy hook + * @crtc: drm CRTC + * @state: CRTC state object to release + * + * Default CRTC state destroy hook for drivers which don't have their own + * subclassed CRTC state structure. + */ +void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + kfree(state); +} +EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state); + +/** + * drm_atomic_helper_plane_reset - default ->reset hook for planes + * @plane: drm plane + * + * Resets the atomic state for @plane by freeing the state pointer (which might + * be NULL, e.g. at driver load time) and allocating a new empty state object. + */ +void drm_atomic_helper_plane_reset(struct drm_plane *plane) +{ + if (plane->state && plane->state->fb) + drm_framebuffer_unreference(plane->state->fb); + + kfree(plane->state); + plane->state = kzalloc(sizeof(*plane->state), GFP_KERNEL); +} +EXPORT_SYMBOL(drm_atomic_helper_plane_reset); + +/** + * drm_atomic_helper_plane_duplicate_state - default state duplicate hook + * @plane: drm plane + * + * Default plane state duplicate hook for drivers which don't have their own + * subclassed plane state structure. + */ +struct drm_plane_state * +drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane) +{ + struct drm_plane_state *state; + + if (WARN_ON(!plane->state)) + return NULL; + + state = kmemdup(plane->state, sizeof(*plane->state), GFP_KERNEL); + + if (state && state->fb) + drm_framebuffer_reference(state->fb); + + return state; +} +EXPORT_SYMBOL(drm_atomic_helper_plane_duplicate_state); + +/** + * drm_atomic_helper_plane_destroy_state - default state destroy hook + * @plane: drm plane + * @state: plane state object to release + * + * Default plane state destroy hook for drivers which don't have their own + * subclassed plane state structure. + */ +void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + if (state->fb) + drm_framebuffer_unreference(state->fb); + + kfree(state); +} +EXPORT_SYMBOL(drm_atomic_helper_plane_destroy_state); + +/** + * drm_atomic_helper_connector_reset - default ->reset hook for connectors + * @connector: drm connector + * + * Resets the atomic state for @connector by freeing the state pointer (which + * might be NULL, e.g. at driver load time) and allocating a new empty state + * object. + */ +void drm_atomic_helper_connector_reset(struct drm_connector *connector) +{ + kfree(connector->state); + connector->state = kzalloc(sizeof(*connector->state), GFP_KERNEL); +} +EXPORT_SYMBOL(drm_atomic_helper_connector_reset); + +/** + * drm_atomic_helper_connector_duplicate_state - default state duplicate hook + * @connector: drm connector + * + * Default connector state duplicate hook for drivers which don't have their own + * subclassed connector state structure. + */ +struct drm_connector_state * +drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector) +{ + if (WARN_ON(!connector->state)) + return NULL; + + return kmemdup(connector->state, sizeof(*connector->state), GFP_KERNEL); +} +EXPORT_SYMBOL(drm_atomic_helper_connector_duplicate_state); + +/** + * drm_atomic_helper_connector_destroy_state - default state destroy hook + * @connector: drm connector + * @state: connector state object to release + * + * Default connector state destroy hook for drivers which don't have their own + * subclassed connector state structure. + */ +void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector, + struct drm_connector_state *state) +{ + kfree(state); +} +EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state); diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index 6c65a0a28fbd..d552708409de 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -34,12 +34,35 @@ #include <linux/moduleparam.h> #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_crtc.h> #include <drm/drm_fourcc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_edid.h> +/** + * DOC: overview + * + * The CRTC modeset helper library provides a default set_config implementation + * in drm_crtc_helper_set_config(). Plus a few other convenience functions using + * the same callbacks which drivers can use to e.g. restore the modeset + * configuration on resume with drm_helper_resume_force_mode(). + * + * The driver callbacks are mostly compatible with the atomic modeset helpers, + * except for the handling of the primary plane: Atomic helpers require that the + * primary plane is implemented as a real standalone plane and not directly tied + * to the CRTC state. For easier transition this library provides functions to + * implement the old semantics required by the CRTC helpers using the new plane + * and atomic helper callbacks. + * + * Drivers are strongly urged to convert to the atomic helpers (by way of first + * converting to the plane helpers). New drivers must not use these functions + * but need to implement the atomic interface instead, potentially using the + * atomic helpers for that. + */ MODULE_AUTHOR("David Airlie, Jesse Barnes"); MODULE_DESCRIPTION("DRM KMS helper"); MODULE_LICENSE("GPL and additional rights"); @@ -888,3 +911,112 @@ void drm_helper_resume_force_mode(struct drm_device *dev) drm_modeset_unlock_all(dev); } EXPORT_SYMBOL(drm_helper_resume_force_mode); + +/** + * drm_helper_crtc_mode_set - mode_set implementation for atomic plane helpers + * @crtc: DRM CRTC + * @mode: DRM display mode which userspace requested + * @adjusted_mode: DRM display mode adjusted by ->mode_fixup callbacks + * @x: x offset of the CRTC scanout area on the underlying framebuffer + * @y: y offset of the CRTC scanout area on the underlying framebuffer + * @old_fb: previous framebuffer + * + * This function implements a callback useable as the ->mode_set callback + * required by the crtc helpers. Besides the atomic plane helper functions for + * the primary plane the driver must also provide the ->mode_set_nofb callback + * to set up the crtc. + * + * This is a transitional helper useful for converting drivers to the atomic + * interfaces. + */ +int drm_helper_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct drm_crtc_state *crtc_state; + struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; + int ret; + + if (crtc->funcs->atomic_duplicate_state) + crtc_state = crtc->funcs->atomic_duplicate_state(crtc); + else if (crtc->state) + crtc_state = kmemdup(crtc->state, sizeof(*crtc_state), + GFP_KERNEL); + else + crtc_state = kzalloc(sizeof(*crtc_state), GFP_KERNEL); + if (!crtc_state) + return -ENOMEM; + + crtc_state->enable = true; + crtc_state->planes_changed = true; + crtc_state->mode_changed = true; + drm_mode_copy(&crtc_state->mode, mode); + drm_mode_copy(&crtc_state->adjusted_mode, adjusted_mode); + + if (crtc_funcs->atomic_check) { + ret = crtc_funcs->atomic_check(crtc, crtc_state); + if (ret) { + kfree(crtc_state); + + return ret; + } + } + + swap(crtc->state, crtc_state); + + crtc_funcs->mode_set_nofb(crtc); + + if (crtc_state) { + if (crtc->funcs->atomic_destroy_state) + crtc->funcs->atomic_destroy_state(crtc, crtc_state); + else + kfree(crtc_state); + } + + return drm_helper_crtc_mode_set_base(crtc, x, y, old_fb); +} +EXPORT_SYMBOL(drm_helper_crtc_mode_set); + +/** + * drm_helper_crtc_mode_set_base - mode_set_base implementation for atomic plane helpers + * @crtc: DRM CRTC + * @x: x offset of the CRTC scanout area on the underlying framebuffer + * @y: y offset of the CRTC scanout area on the underlying framebuffer + * @old_fb: previous framebuffer + * + * This function implements a callback useable as the ->mode_set_base used + * required by the crtc helpers. The driver must provide the atomic plane helper + * functions for the primary plane. + * + * This is a transitional helper useful for converting drivers to the atomic + * interfaces. + */ +int drm_helper_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct drm_plane_state *plane_state; + struct drm_plane *plane = crtc->primary; + + if (plane->funcs->atomic_duplicate_state) + plane_state = plane->funcs->atomic_duplicate_state(plane); + else if (plane->state) + plane_state = drm_atomic_helper_plane_duplicate_state(plane); + else + plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL); + if (!plane_state) + return -ENOMEM; + + plane_state->crtc = crtc; + drm_atomic_set_fb_for_plane(plane_state, crtc->primary->fb); + plane_state->crtc_x = 0; + plane_state->crtc_y = 0; + plane_state->crtc_h = crtc->mode.vdisplay; + plane_state->crtc_w = crtc->mode.hdisplay; + plane_state->src_x = x << 16; + plane_state->src_y = y << 16; + plane_state->src_h = crtc->mode.vdisplay << 16; + plane_state->src_w = crtc->mode.hdisplay << 16; + + return drm_plane_helper_commit(plane, plane_state, old_fb); +} +EXPORT_SYMBOL(drm_helper_crtc_mode_set_base); diff --git a/drivers/gpu/drm/drm_plane_helper.c b/drivers/gpu/drm/drm_plane_helper.c index 827ec1a3040b..d99c452b0563 100644 --- a/drivers/gpu/drm/drm_plane_helper.c +++ b/drivers/gpu/drm/drm_plane_helper.c @@ -27,10 +27,38 @@ #include <drm/drmP.h> #include <drm/drm_plane_helper.h> #include <drm/drm_rect.h> -#include <drm/drm_plane_helper.h> +#include <drm/drm_atomic.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_atomic_helper.h> #define SUBPIXEL_MASK 0xffff +/** + * DOC: overview + * + * This helper library has two parts. The first part has support to implement + * primary plane support on top of the normal CRTC configuration interface. + * Since the legacy ->set_config interface ties the primary plane together with + * the CRTC state this does not allow userspace to disable the primary plane + * itself. To avoid too much duplicated code use + * drm_plane_helper_check_update() which can be used to enforce the same + * restrictions as primary planes had thus. The default primary plane only + * expose XRBG8888 and ARGB8888 as valid pixel formats for the attached + * framebuffer. + * + * Drivers are highly recommended to implement proper support for primary + * planes, and newly merged drivers must not rely upon these transitional + * helpers. + * + * The second part also implements transitional helpers which allow drivers to + * gradually switch to the atomic helper infrastructure for plane updates. Once + * that switch is complete drivers shouldn't use these any longer, instead using + * the proper legacy implementations for update and disable plane hooks provided + * by the atomic helpers. + * + * Again drivers are strongly urged to switch to the new interfaces. + */ + /* * This is the minimal list of formats that seem to be safe for modeset use * with all current DRM drivers. Most hardware can actually support more @@ -369,3 +397,171 @@ int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, return drm_crtc_init_with_planes(dev, crtc, primary, NULL, funcs); } EXPORT_SYMBOL(drm_crtc_init); + +int drm_plane_helper_commit(struct drm_plane *plane, + struct drm_plane_state *plane_state, + struct drm_framebuffer *old_fb) +{ + struct drm_plane_helper_funcs *plane_funcs; + struct drm_crtc *crtc[2]; + struct drm_crtc_helper_funcs *crtc_funcs[2]; + int i, ret = 0; + + plane_funcs = plane->helper_private; + + /* Since this is a transitional helper we can't assume that plane->state + * is always valid. Hence we need to use plane->crtc instead of + * plane->state->crtc as the old crtc. */ + crtc[0] = plane->crtc; + crtc[1] = crtc[0] != plane_state->crtc ? plane_state->crtc : NULL; + + for (i = 0; i < 2; i++) + crtc_funcs[i] = crtc[i] ? crtc[i]->helper_private : NULL; + + if (plane_funcs->atomic_check) { + ret = plane_funcs->atomic_check(plane, plane_state); + if (ret) + goto out; + } + + if (plane_funcs->prepare_fb && plane_state->fb) { + ret = plane_funcs->prepare_fb(plane, plane_state->fb); + if (ret) + goto out; + } + + /* Point of no return, commit sw state. */ + swap(plane->state, plane_state); + + for (i = 0; i < 2; i++) { + if (crtc_funcs[i] && crtc_funcs[i]->atomic_begin) + crtc_funcs[i]->atomic_begin(crtc[i]); + } + + plane_funcs->atomic_update(plane); + + for (i = 0; i < 2; i++) { + if (crtc_funcs[i] && crtc_funcs[i]->atomic_flush) + crtc_funcs[i]->atomic_flush(crtc[i]); + } + + for (i = 0; i < 2; i++) { + if (!crtc[i]) + continue; + + /* There's no other way to figure out whether the crtc is running. */ + ret = drm_crtc_vblank_get(crtc[i]); + if (ret == 0) { + drm_crtc_wait_one_vblank(crtc[i]); + drm_crtc_vblank_put(crtc[i]); + } + + ret = 0; + } + + if (plane_funcs->cleanup_fb && old_fb) + plane_funcs->cleanup_fb(plane, old_fb); +out: + if (plane_state) { + if (plane->funcs->atomic_destroy_state) + plane->funcs->atomic_destroy_state(plane, plane_state); + else + drm_atomic_helper_plane_destroy_state(plane, plane_state); + } + + return ret; +} + +/** + * drm_plane_helper_update() - Helper for primary plane update + * @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 + * @src_x: x offset of @fb for panning + * @src_y: y offset of @fb for panning + * @src_w: width of source rectangle in @fb + * @src_h: height of source rectangle in @fb + * + * Provides a default plane update handler using the atomic plane update + * functions. It is fully left to the driver to check plane constraints and + * handle corner-cases like a fully occluded or otherwise invisible plane. + * + * This is useful for piecewise transitioning of a driver to the atomic helpers. + * + * RETURNS: + * Zero on success, error code on failure + */ +int drm_plane_helper_update(struct drm_plane *plane, struct drm_crtc *crtc, + struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct drm_plane_state *plane_state; + + if (plane->funcs->atomic_duplicate_state) + plane_state = plane->funcs->atomic_duplicate_state(plane); + else if (plane->state) + plane_state = drm_atomic_helper_plane_duplicate_state(plane); + else + plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL); + if (!plane_state) + return -ENOMEM; + + plane_state->crtc = crtc; + drm_atomic_set_fb_for_plane(plane_state, fb); + plane_state->crtc_x = crtc_x; + plane_state->crtc_y = crtc_y; + plane_state->crtc_h = crtc_h; + plane_state->crtc_w = crtc_w; + plane_state->src_x = src_x; + plane_state->src_y = src_y; + plane_state->src_h = src_h; + plane_state->src_w = src_w; + + return drm_plane_helper_commit(plane, plane_state, plane->fb); +} +EXPORT_SYMBOL(drm_plane_helper_update); + +/** + * drm_plane_helper_disable() - Helper for primary plane disable + * @plane: plane to disable + * + * Provides a default plane disable handler using the atomic plane update + * functions. It is fully left to the driver to check plane constraints and + * handle corner-cases like a fully occluded or otherwise invisible plane. + * + * This is useful for piecewise transitioning of a driver to the atomic helpers. + * + * RETURNS: + * Zero on success, error code on failure + */ +int drm_plane_helper_disable(struct drm_plane *plane) +{ + struct drm_plane_state *plane_state; + + /* crtc helpers love to call disable functions for already disabled hw + * functions. So cope with that. */ + if (!plane->crtc) + return 0; + + if (plane->funcs->atomic_duplicate_state) + plane_state = plane->funcs->atomic_duplicate_state(plane); + else if (plane->state) + plane_state = drm_atomic_helper_plane_duplicate_state(plane); + else + plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL); + if (!plane_state) + return -ENOMEM; + + plane_state->crtc = NULL; + drm_atomic_set_fb_for_plane(plane_state, NULL); + + return drm_plane_helper_commit(plane, plane_state, plane->fb); +} +EXPORT_SYMBOL(drm_plane_helper_disable); diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c index 87b50ba64ed4..b21a09451d1d 100644 --- a/drivers/gpu/drm/gma500/psb_intel_display.c +++ b/drivers/gpu/drm/gma500/psb_intel_display.c @@ -21,6 +21,7 @@ #include <linux/i2c.h> #include <drm/drmP.h> +#include <drm/drm_plane_helper.h> #include "framebuffer.h" #include "psb_drv.h" #include "psb_intel_drv.h" diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c index 83485ab81ce8..9872ba9abf1a 100644 --- a/drivers/gpu/drm/mgag200/mgag200_mode.c +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c @@ -15,6 +15,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include "mgag200_drv.h" diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c index fca6a1f9c20c..2a03e77abef4 100644 --- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c +++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c @@ -26,6 +26,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include "nouveau_drm.h" #include "nouveau_reg.h" diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c index ae873d1a8d46..76b8c4f980ea 100644 --- a/drivers/gpu/drm/nouveau/nv50_display.c +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -26,6 +26,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include <drm/drm_dp_helper.h> #include <nvif/class.h> diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c index 2d28dc337cfb..b0566a1ca28f 100644 --- a/drivers/gpu/drm/omapdrm/omap_crtc.c +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c @@ -20,6 +20,7 @@ #include "omap_drv.h" #include <drm/drm_mode.h> +#include <drm/drm_plane_helper.h> #include "drm_crtc.h" #include "drm_crtc_helper.h" diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c index 0d1396266857..8b7892880ad2 100644 --- a/drivers/gpu/drm/qxl/qxl_display.c +++ b/drivers/gpu/drm/qxl/qxl_display.c @@ -29,6 +29,7 @@ #include "qxl_drv.h" #include "qxl_object.h" #include "drm_crtc_helper.h" +#include <drm/drm_plane_helper.h> static bool qxl_head_enabled(struct qxl_head *head) { diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c index 00ead8c2758a..f1b0fa1285bb 100644 --- a/drivers/gpu/drm/radeon/radeon_display.c +++ b/drivers/gpu/drm/radeon/radeon_display.c @@ -32,6 +32,7 @@ #include <linux/pm_runtime.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include <drm/drm_edid.h> #include <linux/gcd.h> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index 148b50589181..088bfd875d29 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -19,6 +19,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> #include "rcar_du_crtc.h" #include "rcar_du_drv.h" diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c index 0ddce4d046d9..859ccb658601 100644 --- a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c +++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c @@ -19,6 +19,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> #include <video/sh_mobile_meram.h> diff --git a/drivers/gpu/drm/sti/sti_drm_crtc.c b/drivers/gpu/drm/sti/sti_drm_crtc.c index d2ae0c0e13be..36a1ad3c4823 100644 --- a/drivers/gpu/drm/sti/sti_drm_crtc.c +++ b/drivers/gpu/drm/sti/sti_drm_crtc.c @@ -10,6 +10,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include "sti_compositor.h" #include "sti_drm_drv.h" diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 6553fd238685..cdfa126a4725 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -15,6 +15,8 @@ #include "drm.h" #include "gem.h" +#include <drm/drm_plane_helper.h> + struct tegra_dc_soc_info { bool supports_interlacing; bool supports_cursor; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index d642d4a02134..29ec98baffd1 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -16,6 +16,7 @@ */ #include "drm_flip_work.h" +#include <drm/drm_plane_helper.h> #include "tilcdc_drv.h" #include "tilcdc_regs.h" diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c index dc145d320b25..1701f1dfb23f 100644 --- a/drivers/gpu/drm/udl/udl_modeset.c +++ b/drivers/gpu/drm/udl/udl_modeset.c @@ -14,6 +14,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> #include "udl_drv.h" /* diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c index 15e185ae4c99..5c289f748ab4 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c @@ -26,6 +26,7 @@ **************************************************************************/ #include "vmwgfx_kms.h" +#include <drm/drm_plane_helper.h> #define vmw_crtc_to_ldu(x) \ diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c index b295463a60b3..7dc591d04d9a 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c @@ -26,6 +26,7 @@ **************************************************************************/ #include "vmwgfx_kms.h" +#include <drm/drm_plane_helper.h> #define vmw_crtc_to_sou(x) \ diff --git a/drivers/staging/imx-drm/imx-drm-core.c b/drivers/staging/imx-drm/imx-drm-core.c index 9cb222e2996f..2f8007241734 100644 --- a/drivers/staging/imx-drm/imx-drm-core.c +++ b/drivers/staging/imx-drm/imx-drm-core.c @@ -24,6 +24,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_gem_cma_helper.h> #include <drm/drm_fb_cma_helper.h> +#include <drm/drm_plane_helper.h> #include "imx-drm.h" |