+static void rockchip_crtc_wait_for_update(struct drm_crtc *crtc)
+{
+ struct rockchip_drm_private *priv = crtc->dev->dev_private;
+ int pipe = drm_crtc_index(crtc);
+ const struct rockchip_crtc_funcs *crtc_funcs = priv->crtc_funcs[pipe];
+
+ if (crtc_funcs && crtc_funcs->wait_for_update)
+ crtc_funcs->wait_for_update(crtc);
+}
+
+/*
+ * We can't use drm_atomic_helper_wait_for_vblanks() because rk3288 and rk3066
+ * have hardware counters for neither vblanks nor scanlines, which results in
+ * a race where:
+ * | <-- HW vsync irq and reg take effect
+ * plane_commit --> |
+ * get_vblank and wait --> |
+ * | <-- handle_vblank, vblank->count + 1
+ * cleanup_fb --> |
+ * iommu crash --> |
+ * | <-- HW vsync irq and reg take effect
+ *
+ * This function is equivalent but uses rockchip_crtc_wait_for_update() instead
+ * of waiting for vblank_count to change.
+ */
+static void
+rockchip_atomic_wait_for_complete(struct drm_device *dev, struct drm_atomic_state *old_state)
+{
+ struct drm_crtc_state *old_crtc_state;
+ struct drm_crtc *crtc;
+ int i, ret;
+
+ for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) {
+ /* 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->active)
+ continue;
+
+ if (!drm_atomic_helper_framebuffer_changed(dev,
+ old_state, crtc))
+ continue;
+
+ ret = drm_crtc_vblank_get(crtc);
+ if (ret != 0)
+ continue;
+
+ old_crtc_state->enable = true;
+ }
+
+ for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) {
+ if (!old_crtc_state->enable)
+ continue;
+
+ rockchip_crtc_wait_for_update(crtc);
+ drm_crtc_vblank_put(crtc);
+ }
+}
+
+static void
+rockchip_atomic_commit_complete(struct rockchip_atomic_commit *commit)
+{
+ struct drm_atomic_state *state = commit->state;
+ struct drm_device *dev = commit->dev;
+
+ /*
+ * TODO: do fence wait here.
+ */
+
+ /*
+ * Rockchip crtc support runtime PM, can't update display planes
+ * when crtc is disabled.
+ *
+ * drm_atomic_helper_commit comments detail that:
+ * For drivers supporting runtime PM the recommended sequence is
+ *
+ * drm_atomic_helper_commit_modeset_disables(dev, state);
+ *
+ * drm_atomic_helper_commit_modeset_enables(dev, state);
+ *
+ * drm_atomic_helper_commit_planes(dev, state, true);
+ *
+ * See the kerneldoc entries for these three functions for more details.
+ */
+ drm_atomic_helper_commit_modeset_disables(dev, state);
+
+ drm_atomic_helper_commit_modeset_enables(dev, state);
+
+ drm_atomic_helper_commit_planes(dev, state, true);
+
+ rockchip_atomic_wait_for_complete(dev, state);
+
+ drm_atomic_helper_cleanup_planes(dev, state);
+
+ drm_atomic_state_free(state);
+}
+
+void rockchip_drm_atomic_work(struct work_struct *work)
+{
+ struct rockchip_atomic_commit *commit = container_of(work,
+ struct rockchip_atomic_commit, work);
+
+ rockchip_atomic_commit_complete(commit);
+}
+
+int rockchip_drm_atomic_commit(struct drm_device *dev,
+ struct drm_atomic_state *state,
+ bool async)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct rockchip_atomic_commit *commit = &private->commit;
+ int ret;
+
+ ret = drm_atomic_helper_prepare_planes(dev, state);
+ if (ret)
+ return ret;
+
+ /* serialize outstanding asynchronous commits */
+ mutex_lock(&commit->lock);
+ flush_work(&commit->work);
+
+ drm_atomic_helper_swap_state(dev, state);
+
+ commit->dev = dev;
+ commit->state = state;
+
+ if (async)
+ schedule_work(&commit->work);
+ else
+ rockchip_atomic_commit_complete(commit);
+
+ mutex_unlock(&commit->lock);
+
+ return 0;
+}
+