return ret;
  }
  
 -              drm_syncobj_replace_fence(sync_out, 0, sched_done_fence);
+ /**
+  * v3d_submit_tfu_ioctl() - Submits a TFU (texture formatting) job to the V3D.
+  * @dev: DRM device
+  * @data: ioctl argument
+  * @file_priv: DRM file for this fd
+  *
+  * Userspace provides the register setup for the TFU, which we don't
+  * need to validate since the TFU is behind the MMU.
+  */
+ int
+ v3d_submit_tfu_ioctl(struct drm_device *dev, void *data,
+                    struct drm_file *file_priv)
+ {
+       struct v3d_dev *v3d = to_v3d_dev(dev);
+       struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
+       struct drm_v3d_submit_tfu *args = data;
+       struct v3d_tfu_job *job;
+       struct ww_acquire_ctx acquire_ctx;
+       struct drm_syncobj *sync_out;
+       struct dma_fence *sched_done_fence;
+       int ret = 0;
+       int bo_count;
+ 
+       trace_v3d_submit_tfu_ioctl(&v3d->drm, args->iia);
+ 
+       job = kcalloc(1, sizeof(*job), GFP_KERNEL);
+       if (!job)
+               return -ENOMEM;
+ 
+       ret = pm_runtime_get_sync(v3d->dev);
+       if (ret < 0) {
+               kfree(job);
+               return ret;
+       }
+ 
+       kref_init(&job->refcount);
+ 
+       ret = drm_syncobj_find_fence(file_priv, args->in_sync,
+                                    0, 0, &job->in_fence);
+       if (ret == -EINVAL)
+               goto fail;
+ 
+       job->args = *args;
+       job->v3d = v3d;
+ 
+       spin_lock(&file_priv->table_lock);
+       for (bo_count = 0; bo_count < ARRAY_SIZE(job->bo); bo_count++) {
+               struct drm_gem_object *bo;
+ 
+               if (!args->bo_handles[bo_count])
+                       break;
+ 
+               bo = idr_find(&file_priv->object_idr,
+                             args->bo_handles[bo_count]);
+               if (!bo) {
+                       DRM_DEBUG("Failed to look up GEM BO %d: %d\n",
+                                 bo_count, args->bo_handles[bo_count]);
+                       ret = -ENOENT;
+                       spin_unlock(&file_priv->table_lock);
+                       goto fail;
+               }
+               drm_gem_object_get(bo);
+               job->bo[bo_count] = to_v3d_bo(bo);
+       }
+       spin_unlock(&file_priv->table_lock);
+ 
+       ret = v3d_lock_bo_reservations(job->bo, bo_count, &acquire_ctx);
+       if (ret)
+               goto fail;
+ 
+       mutex_lock(&v3d->sched_lock);
+       ret = drm_sched_job_init(&job->base,
+                                &v3d_priv->sched_entity[V3D_TFU],
+                                v3d_priv);
+       if (ret)
+               goto fail_unreserve;
+ 
+       sched_done_fence = dma_fence_get(&job->base.s_fence->finished);
+ 
+       kref_get(&job->refcount); /* put by scheduler job completion */
+       drm_sched_entity_push_job(&job->base, &v3d_priv->sched_entity[V3D_TFU]);
+       mutex_unlock(&v3d->sched_lock);
+ 
+       v3d_attach_object_fences(job->bo, bo_count, sched_done_fence);
+ 
+       v3d_unlock_bo_reservations(job->bo, bo_count, &acquire_ctx);
+ 
+       /* Update the return sync object */
+       sync_out = drm_syncobj_find(file_priv, args->out_sync);
+       if (sync_out) {
++              drm_syncobj_replace_fence(sync_out, sched_done_fence);
+               drm_syncobj_put(sync_out);
+       }
+       dma_fence_put(sched_done_fence);
+ 
+       v3d_tfu_job_put(job);
+ 
+       return 0;
+ 
+ fail_unreserve:
+       mutex_unlock(&v3d->sched_lock);
+       v3d_unlock_bo_reservations(job->bo, bo_count, &acquire_ctx);
+ fail:
+       v3d_tfu_job_put(job);
+ 
+       return ret;
+ }
+ 
  int
  v3d_gem_init(struct drm_device *dev)
  {