// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }