693 lines
19 KiB
C
693 lines
19 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/component.h>
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_atomic_uapi.h>
|
|
#include <drm/drm_flip_work.h>
|
|
#include <drm/drm_self_refresh_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_plane_helper.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_rect.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_gem_dma_helper.h>
|
|
#include <drm/drm_fb_dma_helper.h>
|
|
|
|
#include "meson_drv.h"
|
|
#include "meson_async_atomic.h"
|
|
#include "meson_crtc.h"
|
|
#include "meson_plane.h"
|
|
#include "meson_writeback.h"
|
|
|
|
struct meson_commit_work_item {
|
|
struct kthread_work kthread_work;
|
|
struct work_struct *work;
|
|
int crtc_id;
|
|
int commit_flag;
|
|
};
|
|
|
|
static bool check_parallel_commit(struct drm_atomic_state *state,
|
|
struct drm_crtc **dest_crtc)
|
|
{
|
|
struct drm_connector_state *connector_state;
|
|
struct drm_connector *connector;
|
|
struct drm_crtc_state *crtc_state;
|
|
struct drm_crtc *crtc;
|
|
int i, num_crtcs = 0;
|
|
|
|
/* any connector change, return false */
|
|
for_each_new_connector_in_state(state, connector, connector_state, i)
|
|
return false;
|
|
|
|
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
|
|
if (drm_atomic_crtc_needs_modeset(crtc_state))
|
|
return false;
|
|
if (++num_crtcs > 1)
|
|
return false;
|
|
*dest_crtc = crtc;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int find_first_new_crtc(struct drm_atomic_state *state)
|
|
{
|
|
struct drm_crtc_state *crtc_state;
|
|
struct drm_crtc *crtc;
|
|
int i;
|
|
|
|
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
|
|
if (crtc)
|
|
return crtc->index;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* flag: 0:block 1:nonblock 2:async*/
|
|
void meson_commit_reenter_inc(struct meson_drm *priv,
|
|
int crtc_index, int flag)
|
|
{
|
|
struct am_meson_crtc *amcrtc;
|
|
|
|
amcrtc = priv->crtcs[crtc_index];
|
|
mutex_lock(&amcrtc->commit_mutex);
|
|
atomic_inc(&amcrtc->commit_num);
|
|
/* WARN_ON(atomic_read(&amcrtc->commit_num) > 1); */
|
|
if (atomic_read(&amcrtc->commit_num) > 1)
|
|
DRM_ERROR("commit re-enter\n");
|
|
|
|
DRM_DEBUG_ATOMIC("crtc_index%d, commit_num=%d, flag=%d\n",
|
|
crtc_index, atomic_read(&amcrtc->commit_num), flag);
|
|
}
|
|
|
|
void meson_commit_reenter_dec(struct meson_drm *priv,
|
|
int crtc_index, int flag)
|
|
{
|
|
struct am_meson_crtc *amcrtc;
|
|
|
|
amcrtc = priv->crtcs[crtc_index];
|
|
WARN_ON(!mutex_is_locked(&amcrtc->commit_mutex));
|
|
atomic_dec(&amcrtc->commit_num);
|
|
mutex_unlock(&amcrtc->commit_mutex);
|
|
DRM_DEBUG_ATOMIC("crtc_index%d, commit_num=%d, flag=%d\n",
|
|
crtc_index, atomic_read(&amcrtc->commit_num), flag);
|
|
}
|
|
|
|
/**
|
|
* drm_crtc_commit_wait - Waits for a commit to complete
|
|
* @commit: &drm_crtc_commit to wait for
|
|
*
|
|
* Waits for a given &drm_crtc_commit to be programmed into the
|
|
* hardware and flipped to.
|
|
*
|
|
* Returns:
|
|
*
|
|
* 0 on success, a negative error code otherwise.
|
|
*/
|
|
static int meson_drm_crtc_commit_wait(struct drm_crtc_commit *commit)
|
|
{
|
|
/*
|
|
* Amlogic modify.
|
|
* timeout is 10s (10 * HZ) in drm framework, and it is too long in HPD case,
|
|
* so change it to be 100ms (HZ / 10).
|
|
*/
|
|
unsigned long timeout = HZ / 10;
|
|
int ret;
|
|
|
|
if (!commit)
|
|
return 0;
|
|
|
|
ret = wait_for_completion_timeout(&commit->hw_done, timeout);
|
|
if (!ret) {
|
|
DRM_ERROR("hw_done timed out\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* Currently no support for overwriting flips, hence
|
|
* stall for previous one to execute completely.
|
|
*/
|
|
ret = wait_for_completion_timeout(&commit->flip_done, timeout);
|
|
if (!ret) {
|
|
DRM_ERROR("flip_done timed out\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void meson_drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *old_state)
|
|
{
|
|
struct drm_crtc *crtc;
|
|
struct drm_crtc_state *old_crtc_state;
|
|
struct drm_plane *plane;
|
|
struct drm_plane_state *old_plane_state;
|
|
struct drm_connector *conn;
|
|
struct drm_connector_state *old_conn_state;
|
|
int i;
|
|
long ret;
|
|
|
|
for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) {
|
|
ret = meson_drm_crtc_commit_wait(old_crtc_state->commit);
|
|
if (ret)
|
|
DRM_ERROR("[CRTC:%d:%s] commit wait timed out\n",
|
|
crtc->base.id, crtc->name);
|
|
}
|
|
|
|
for_each_old_connector_in_state(old_state, conn, old_conn_state, i) {
|
|
ret = meson_drm_crtc_commit_wait(old_conn_state->commit);
|
|
if (ret)
|
|
DRM_ERROR("[CONNECTOR:%d:%s] commit wait timed out\n",
|
|
conn->base.id, conn->name);
|
|
}
|
|
|
|
for_each_old_plane_in_state(old_state, plane, old_plane_state, i) {
|
|
ret = meson_drm_crtc_commit_wait(old_plane_state->commit);
|
|
if (ret)
|
|
DRM_ERROR("[PLANE:%d:%s] commit wait timed out\n",
|
|
plane->base.id, plane->name);
|
|
}
|
|
}
|
|
|
|
static int get_nonblock_by_vblank_flag(struct drm_atomic_state *old_state)
|
|
{
|
|
struct am_meson_crtc_state *meson_crtc_state;
|
|
struct drm_crtc_state *crtc_state;
|
|
struct drm_crtc *crtc;
|
|
int i, ret = 0;
|
|
|
|
for_each_new_crtc_in_state(old_state, crtc, crtc_state, i) {
|
|
meson_crtc_state = to_am_meson_crtc_state(crtc_state);
|
|
|
|
ret |= meson_crtc_state->nonblock_by_vblank;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void meson_commit_tail(struct drm_atomic_state *old_state)
|
|
{
|
|
struct drm_device *dev = old_state->dev;
|
|
const struct drm_mode_config_helper_funcs *funcs;
|
|
struct drm_crtc_state *new_crtc_state;
|
|
struct drm_crtc *crtc;
|
|
ktime_t start;
|
|
s64 commit_time_ms;
|
|
unsigned int i, new_self_refresh_mask = 0;
|
|
|
|
funcs = dev->mode_config.helper_private;
|
|
|
|
/*
|
|
* We're measuring the _entire_ commit, so the time will vary depending
|
|
* on how many fences and objects are involved. For the purposes of self
|
|
* refresh, this is desirable since it'll give us an idea of how
|
|
* congested things are. This will inform our decision on how often we
|
|
* should enter self refresh after idle.
|
|
*
|
|
* These times will be averaged out in the self refresh helpers to avoid
|
|
* overreacting over one outlier frame
|
|
*/
|
|
start = ktime_get();
|
|
|
|
drm_atomic_helper_wait_for_fences(dev, old_state, false);
|
|
|
|
if (!get_nonblock_by_vblank_flag(old_state))
|
|
meson_drm_atomic_helper_wait_for_dependencies(old_state);
|
|
/*
|
|
* We cannot safely access new_crtc_state after
|
|
* drm_atomic_helper_commit_hw_done() so figure out which crtc's have
|
|
* self-refresh active beforehand:
|
|
*/
|
|
for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i)
|
|
if (new_crtc_state->self_refresh_active)
|
|
new_self_refresh_mask |= BIT(i);
|
|
|
|
if (funcs && funcs->atomic_commit_tail)
|
|
funcs->atomic_commit_tail(old_state);
|
|
else
|
|
drm_atomic_helper_commit_tail(old_state);
|
|
|
|
commit_time_ms = ktime_ms_delta(ktime_get(), start);
|
|
if (commit_time_ms > 0)
|
|
drm_self_refresh_helper_update_avg_times(old_state,
|
|
(unsigned long)commit_time_ms,
|
|
new_self_refresh_mask);
|
|
|
|
drm_atomic_helper_commit_cleanup_done(old_state);
|
|
|
|
drm_atomic_state_put(old_state);
|
|
}
|
|
|
|
static void meson_commit_work(struct kthread_work *work)
|
|
{
|
|
struct meson_commit_work_item *work_item = container_of(work,
|
|
struct meson_commit_work_item,
|
|
kthread_work);
|
|
struct drm_atomic_state *state = container_of(work_item->work,
|
|
struct drm_atomic_state,
|
|
commit_work);
|
|
meson_commit_tail(state);
|
|
kfree(work_item);
|
|
work_item = NULL;
|
|
}
|
|
|
|
static int stall_checks(struct drm_crtc *crtc, bool nonblock)
|
|
{
|
|
struct drm_crtc_commit *commit, *stall_commit = NULL;
|
|
bool completed = true;
|
|
int i;
|
|
long ret = 0;
|
|
|
|
spin_lock(&crtc->commit_lock);
|
|
i = 0;
|
|
list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
|
|
if (i == 0) {
|
|
completed = try_wait_for_completion(&commit->flip_done);
|
|
/* Userspace is not allowed to get ahead of the previous
|
|
* commit with nonblocking ones.
|
|
*/
|
|
if (!completed && nonblock) {
|
|
spin_unlock(&crtc->commit_lock);
|
|
return -EBUSY;
|
|
}
|
|
} else if (i == 1) {
|
|
stall_commit = drm_crtc_commit_get(commit);
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
spin_unlock(&crtc->commit_lock);
|
|
|
|
if (!stall_commit)
|
|
return 0;
|
|
|
|
/* We don't want to let commits get ahead of cleanup work too much,
|
|
* stalling on 2nd previous commit means triple-buffer won't ever stall.
|
|
*/
|
|
ret = wait_for_completion_interruptible_timeout(&stall_commit->cleanup_done,
|
|
10 * HZ);
|
|
if (ret == 0)
|
|
DRM_ERROR("[CRTC:%d:%s] cleanup_done timed out\n",
|
|
crtc->base.id, crtc->name);
|
|
|
|
drm_crtc_commit_put(stall_commit);
|
|
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
static void release_crtc_commit(struct completion *completion)
|
|
{
|
|
struct drm_crtc_commit *commit = container_of(completion,
|
|
typeof(*commit),
|
|
flip_done);
|
|
|
|
drm_crtc_commit_put(commit);
|
|
}
|
|
|
|
static void init_commit(struct drm_crtc_commit *commit, struct drm_crtc *crtc)
|
|
{
|
|
init_completion(&commit->flip_done);
|
|
init_completion(&commit->hw_done);
|
|
init_completion(&commit->cleanup_done);
|
|
INIT_LIST_HEAD(&commit->commit_entry);
|
|
kref_init(&commit->ref);
|
|
commit->crtc = crtc;
|
|
}
|
|
|
|
static struct drm_crtc_commit *
|
|
crtc_or_fake_commit(struct drm_atomic_state *state, struct drm_crtc *crtc)
|
|
{
|
|
if (crtc) {
|
|
struct drm_crtc_state *new_crtc_state;
|
|
|
|
new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
|
|
|
return new_crtc_state->commit;
|
|
}
|
|
|
|
if (!state->fake_commit) {
|
|
state->fake_commit = kzalloc(sizeof(*state->fake_commit), GFP_KERNEL);
|
|
if (!state->fake_commit)
|
|
return NULL;
|
|
|
|
init_commit(state->fake_commit, NULL);
|
|
}
|
|
|
|
return state->fake_commit;
|
|
}
|
|
|
|
static int meson_drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
|
|
bool nonblock)
|
|
{
|
|
struct drm_crtc *crtc;
|
|
struct drm_crtc_state *old_crtc_state, *new_crtc_state;
|
|
struct drm_connector *conn;
|
|
struct drm_connector_state *old_conn_state, *new_conn_state;
|
|
struct drm_plane *plane;
|
|
struct drm_plane_state *old_plane_state, *new_plane_state;
|
|
struct drm_crtc_commit *commit;
|
|
int i, ret;
|
|
|
|
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
|
|
commit = kzalloc(sizeof(*commit), GFP_KERNEL);
|
|
if (!commit)
|
|
return -ENOMEM;
|
|
|
|
init_commit(commit, crtc);
|
|
|
|
new_crtc_state->commit = commit;
|
|
|
|
ret = stall_checks(crtc, nonblock);
|
|
if (ret)
|
|
DRM_DEBUG_ATOMIC("meson stall checks %d\n", ret);
|
|
|
|
/* Drivers only send out events when at least either current or
|
|
* new CRTC state is active. Complete right away if everything
|
|
* stays off.
|
|
*/
|
|
if (!old_crtc_state->active && !new_crtc_state->active) {
|
|
complete_all(&commit->flip_done);
|
|
continue;
|
|
}
|
|
|
|
/* Legacy cursor updates are fully unsynchronized. */
|
|
if (state->legacy_cursor_update) {
|
|
complete_all(&commit->flip_done);
|
|
continue;
|
|
}
|
|
|
|
if (!new_crtc_state->event) {
|
|
commit->event = kzalloc(sizeof(*commit->event),
|
|
GFP_KERNEL);
|
|
if (!commit->event)
|
|
return -ENOMEM;
|
|
|
|
new_crtc_state->event = commit->event;
|
|
}
|
|
|
|
new_crtc_state->event->base.completion = &commit->flip_done;
|
|
new_crtc_state->event->base.completion_release = release_crtc_commit;
|
|
drm_crtc_commit_get(commit);
|
|
|
|
commit->abort_completion = true;
|
|
|
|
state->crtcs[i].commit = commit;
|
|
drm_crtc_commit_get(commit);
|
|
}
|
|
|
|
for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) {
|
|
/* Userspace is not allowed to get ahead of the previous
|
|
* commit with nonblocking ones.
|
|
*/
|
|
if (nonblock && old_conn_state->commit &&
|
|
!try_wait_for_completion(&old_conn_state->commit->flip_done))
|
|
DRM_DEBUG_ATOMIC("connector commit not done\n");
|
|
|
|
/* Always track connectors explicitly for e.g. link retraining. */
|
|
commit = crtc_or_fake_commit(state, new_conn_state->crtc ?: old_conn_state->crtc);
|
|
if (!commit)
|
|
return -ENOMEM;
|
|
|
|
new_conn_state->commit = drm_crtc_commit_get(commit);
|
|
}
|
|
|
|
for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
|
|
/* Userspace is not allowed to get ahead of the previous
|
|
* commit with nonblocking ones.
|
|
*/
|
|
if (nonblock && old_plane_state->commit &&
|
|
!try_wait_for_completion(&old_plane_state->commit->flip_done))
|
|
DRM_DEBUG_ATOMIC("plane commit not done\n");
|
|
|
|
/* Always track planes explicitly for async pageflip support. */
|
|
commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc);
|
|
if (!commit)
|
|
return -ENOMEM;
|
|
|
|
new_plane_state->commit = drm_crtc_commit_get(commit);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int meson_atomic_commit(struct drm_device *dev,
|
|
struct drm_atomic_state *state,
|
|
bool nonblock)
|
|
{
|
|
int ret, crtc_index = 0;
|
|
struct meson_commit_work_item *work_item = NULL;
|
|
struct kthread_worker *worker;
|
|
struct drm_crtc *dest_crtc = NULL;
|
|
struct meson_drm *priv = dev->dev_private;
|
|
bool is_parallel = check_parallel_commit(state, &dest_crtc);
|
|
|
|
DRM_DEBUG("wait for no shutdown start!\n");
|
|
wait_event_interruptible(priv->wq_shut_ctrl,
|
|
!READ_ONCE(priv->shutdown_on));
|
|
DRM_DEBUG("wait for no shutdown end!\n");
|
|
|
|
if (is_parallel && dest_crtc)
|
|
crtc_index = dest_crtc->index;
|
|
else
|
|
crtc_index = find_first_new_crtc(state);
|
|
|
|
if (state->async_update) {
|
|
|
|
priv->pan_async_commit_ran = true;
|
|
ret = drm_atomic_helper_prepare_planes(dev, state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drm_atomic_helper_async_commit(dev, state);
|
|
drm_atomic_helper_cleanup_planes(dev, state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (state->legacy_cursor_update) {
|
|
ret = drm_atomic_helper_setup_commit(state, nonblock);
|
|
/*drm_atomic_helper_wait_for_vblanks reference it,
|
|
*so it should be flase for non-async update commit.
|
|
*/
|
|
state->legacy_cursor_update = false;
|
|
DRM_DEBUG_ATOMIC("legacy_cursor_update force to false!\n");
|
|
} else {
|
|
ret = meson_drm_atomic_helper_setup_commit(state, nonblock);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = drm_atomic_helper_prepare_planes(dev, state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!nonblock) {
|
|
ret = drm_atomic_helper_wait_for_fences(dev, state, true);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (nonblock) {
|
|
work_item = kzalloc(sizeof(*work_item), GFP_KERNEL);
|
|
if (!work_item)
|
|
goto err;
|
|
}
|
|
|
|
ret = drm_atomic_helper_swap_state(state, true);
|
|
if (ret)
|
|
goto err;
|
|
|
|
/*
|
|
* Everything below can be run asynchronously without the need to grab
|
|
* any modeset locks at all under one condition: 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.
|
|
*
|
|
* NOTE: Commit work has multiple phases, first hardware commit, then
|
|
* cleanup. We want them to overlap, hence need system_unbound_wq to
|
|
* make sure work items don't artificially stall on each another.
|
|
*/
|
|
|
|
drm_atomic_state_get(state);
|
|
if (nonblock) {
|
|
work_item->work = &state->commit_work;
|
|
work_item->crtc_id = crtc_index;
|
|
work_item->commit_flag = nonblock;
|
|
kthread_init_work(&work_item->kthread_work, meson_commit_work);
|
|
worker = &priv->commit_thread[crtc_index].worker;
|
|
if (!kthread_queue_work(worker, &work_item->kthread_work))
|
|
DRM_DEBUG_ATOMIC("atomic state:%p line:%d queuework fail!\n",
|
|
state, __LINE__);
|
|
} else {
|
|
meson_commit_tail(state);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (nonblock && work_item)
|
|
kfree(work_item);
|
|
drm_atomic_helper_cleanup_planes(dev, state);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* drm_atomic_helper_wait_for_vblanks - wait for vblank on CRTCs
|
|
* @dev: DRM device
|
|
* @old_state: atomic state object with old state structures
|
|
*
|
|
* Helper to, after atomic commit, wait for vblanks on all affected
|
|
* CRTCs (ie. before cleaning up old framebuffers using
|
|
* drm_atomic_helper_cleanup_planes()). It will only wait on CRTCs where the
|
|
* framebuffers have actually changed to optimize for the legacy cursor and
|
|
* plane update use-case.
|
|
*
|
|
* Drivers using the nonblocking commit tracking support initialized by calling
|
|
* drm_atomic_helper_setup_commit() should look at
|
|
* drm_atomic_helper_wait_for_flip_done() as an alternative.
|
|
*/
|
|
static void
|
|
meson_drm_atomic_helper_wait_for_vblanks(struct drm_device *dev,
|
|
struct drm_atomic_state *old_state)
|
|
{
|
|
struct drm_crtc *crtc;
|
|
struct drm_crtc_state *old_crtc_state, *new_crtc_state;
|
|
int i, ret;
|
|
unsigned int crtc_mask = 0;
|
|
|
|
/*
|
|
* Legacy cursor ioctls are completely unsynced, and userspace
|
|
* relies on that (by doing tons of cursor updates).
|
|
*/
|
|
if (old_state->legacy_cursor_update)
|
|
return;
|
|
|
|
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
|
|
if (!new_crtc_state->active)
|
|
continue;
|
|
|
|
ret = drm_crtc_vblank_get(crtc);
|
|
if (ret != 0)
|
|
continue;
|
|
|
|
crtc_mask |= drm_crtc_mask(crtc);
|
|
old_state->crtcs[i].last_vblank_count = drm_crtc_vblank_count(crtc);
|
|
}
|
|
|
|
for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) {
|
|
if (!(crtc_mask & drm_crtc_mask(crtc)))
|
|
continue;
|
|
|
|
ret = wait_event_timeout(dev->vblank[i].queue,
|
|
old_state->crtcs[i].last_vblank_count !=
|
|
drm_crtc_vblank_count(crtc),
|
|
msecs_to_jiffies(100));
|
|
|
|
/*
|
|
* amlogic modify, WARN api print so many messages.
|
|
* it will lead to system high latency occasionally.
|
|
* just print the timed out message is enough
|
|
*/
|
|
if (!ret)
|
|
DRM_WARN("[CRTC:%d:%s] vblank wait timed out\n", crtc->base.id,
|
|
crtc->name);
|
|
|
|
drm_crtc_vblank_put(crtc);
|
|
}
|
|
}
|
|
|
|
void __meson_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
|
|
{
|
|
struct drm_device *dev = old_state->dev;
|
|
struct meson_drm *priv = dev->dev_private;
|
|
|
|
drm_atomic_helper_commit_modeset_disables(dev, old_state);
|
|
|
|
drm_atomic_helper_commit_planes(dev, old_state, 0);
|
|
|
|
drm_atomic_helper_commit_modeset_enables(dev, old_state);
|
|
|
|
drm_atomic_helper_fake_vblank(old_state);
|
|
|
|
drm_atomic_helper_commit_hw_done(old_state);
|
|
|
|
if (!priv->disable_video_plane && !get_nonblock_by_vblank_flag(old_state))
|
|
meson_drm_atomic_helper_wait_for_vblanks(dev, old_state);
|
|
priv->disable_video_plane = 0;
|
|
|
|
drm_atomic_helper_cleanup_planes(dev, old_state);
|
|
}
|
|
|
|
void meson_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
|
|
{
|
|
struct drm_connector *conn;
|
|
struct meson_connector *meson_conn;
|
|
struct am_drm_writeback *drm_writeback;
|
|
struct drm_connector_state *new_conn_state, *old_conn_state;
|
|
int i;
|
|
|
|
/*modified by meson to do update which don't need pipe change.*/
|
|
for_each_oldnew_connector_in_state(old_state, conn, old_conn_state, new_conn_state, i) {
|
|
if (conn->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) {
|
|
drm_writeback = connector_to_am_writeback(conn);
|
|
meson_conn = &drm_writeback->base;
|
|
} else {
|
|
meson_conn = connector_to_meson_connector(conn);
|
|
}
|
|
|
|
if (meson_conn->update)
|
|
meson_conn->update(new_conn_state, old_conn_state);
|
|
}
|
|
|
|
__meson_atomic_helper_commit_tail(old_state);
|
|
}
|
|
|
|
void meson_atomic_state_free(struct drm_atomic_state *state)
|
|
{
|
|
struct drm_device *dev = state->dev;
|
|
struct meson_drm *priv = dev->dev_private;
|
|
|
|
if (!priv) {
|
|
DRM_ERROR("Failed to get meson drm!\n");
|
|
return;
|
|
}
|
|
|
|
if (priv->logo && priv->logo->is_cma &&
|
|
priv->logo->is_std &&
|
|
priv->logo->plane_has_fb)
|
|
am_meson_free_logo_memory();
|
|
|
|
drm_atomic_state_default_release(state);
|
|
kfree(state);
|
|
}
|
|
|