if (conn_state->crtc != crtc)
                        continue;
 
-               /* The writeback connector is implemented using the transposer
-                * block which is directly taking its data from the HVS FIFO.
-                */
-               if (conn->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) {
-                       state->no_vblank = true;
-                       vc4_state->feed_txp = true;
-               } else {
-                       state->no_vblank = false;
-                       vc4_state->feed_txp = false;
-               }
-
                vc4_state->margins.left = conn_state->tv.margins.left;
                vc4_state->margins.right = conn_state->tv.margins.right;
                vc4_state->margins.top = conn_state->tv.margins.top;
                                        struct drm_crtc *crtc)
 {
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
-       const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc);
        const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
        const enum vc4_encoder_type *encoder_types = pv_data->encoder_types;
        struct drm_encoder *encoder;
                struct vc4_encoder *vc4_encoder;
                int i;
 
-               /* HVS FIFO2 can feed the TXP IP. */
-               if (crtc_data->hvs_channel == 2 &&
-                   encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL) {
-                       encoder->possible_crtcs |= drm_crtc_mask(crtc);
-                       continue;
-               }
-
                vc4_encoder = to_vc4_encoder(encoder);
                for (i = 0; i < ARRAY_SIZE(pv_data->encoder_types); i++) {
                        if (vc4_encoder->type == encoder_types[i]) {
 
 #include <drm/drm_fourcc.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 #include <drm/drm_writeback.h>
 
 #include "vc4_drv.h"
 #define TXP_WRITE(offset, val) writel(val, txp->regs + (offset))
 
 struct vc4_txp {
+       struct vc4_crtc base;
+
        struct platform_device *pdev;
 
        struct drm_writeback_connector connector;
        .disable = vc4_txp_encoder_disable,
 };
 
+static int vc4_txp_enable_vblank(struct drm_crtc *crtc)
+{
+       return 0;
+}
+
+static void vc4_txp_disable_vblank(struct drm_crtc *crtc) {}
+
+static const struct drm_crtc_funcs vc4_txp_crtc_funcs = {
+       .set_config             = drm_atomic_helper_set_config,
+       .destroy                = vc4_crtc_destroy,
+       .page_flip              = vc4_page_flip,
+       .reset                  = vc4_crtc_reset,
+       .atomic_duplicate_state = vc4_crtc_duplicate_state,
+       .atomic_destroy_state   = vc4_crtc_destroy_state,
+       .gamma_set              = drm_atomic_helper_legacy_gamma_set,
+       .enable_vblank          = vc4_txp_enable_vblank,
+       .disable_vblank         = vc4_txp_disable_vblank,
+};
+
+static int vc4_txp_atomic_check(struct drm_crtc *crtc,
+                               struct drm_crtc_state *state)
+{
+       struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
+       int ret;
+
+       ret = vc4_hvs_atomic_check(crtc, state);
+       if (ret)
+               return ret;
+
+       state->no_vblank = true;
+       vc4_state->feed_txp = true;
+
+       return 0;
+}
+
+static void vc4_txp_atomic_enable(struct drm_crtc *crtc,
+                                 struct drm_crtc_state *old_state)
+{
+       drm_crtc_vblank_on(crtc);
+       vc4_hvs_atomic_enable(crtc, old_state);
+}
+
+static void vc4_txp_atomic_disable(struct drm_crtc *crtc,
+                                  struct drm_crtc_state *old_state)
+{
+       struct drm_device *dev = crtc->dev;
+
+       /* Disable vblank irq handling before crtc is disabled. */
+       drm_crtc_vblank_off(crtc);
+
+       vc4_hvs_atomic_disable(crtc, old_state);
+
+       /*
+        * Make sure we issue a vblank event after disabling the CRTC if
+        * someone was waiting it.
+        */
+       if (crtc->state->event) {
+               unsigned long flags;
+
+               spin_lock_irqsave(&dev->event_lock, flags);
+               drm_crtc_send_vblank_event(crtc, crtc->state->event);
+               crtc->state->event = NULL;
+               spin_unlock_irqrestore(&dev->event_lock, flags);
+       }
+}
+
+static const struct drm_crtc_helper_funcs vc4_txp_crtc_helper_funcs = {
+       .atomic_check   = vc4_txp_atomic_check,
+       .atomic_flush   = vc4_hvs_atomic_flush,
+       .atomic_enable  = vc4_txp_atomic_enable,
+       .atomic_disable = vc4_txp_atomic_disable,
+       .mode_set_nofb  = vc4_hvs_mode_set_nofb,
+};
+
 static irqreturn_t vc4_txp_interrupt(int irq, void *data)
 {
        struct vc4_txp *txp = data;
+       struct vc4_crtc *vc4_crtc = &txp->base;
 
        TXP_WRITE(TXP_DST_CTRL, TXP_READ(TXP_DST_CTRL) & ~TXP_EI);
-       vc4_crtc_handle_vblank(to_vc4_crtc(txp->connector.base.state->crtc));
+       vc4_crtc_handle_vblank(vc4_crtc);
        drm_writeback_signal_completion(&txp->connector, 0);
 
        return IRQ_HANDLED;
 }
 
+static const struct vc4_crtc_data vc4_txp_crtc_data = {
+       .hvs_channel = 2,
+};
+
 static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
 {
        struct platform_device *pdev = to_platform_device(dev);
        struct drm_device *drm = dev_get_drvdata(master);
        struct vc4_dev *vc4 = to_vc4_dev(drm);
+       struct vc4_crtc *vc4_crtc;
        struct vc4_txp *txp;
+       struct drm_crtc *crtc;
+       struct drm_encoder *encoder;
        int ret, irq;
 
        irq = platform_get_irq(pdev, 0);
        txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL);
        if (!txp)
                return -ENOMEM;
+       vc4_crtc = &txp->base;
+       crtc = &vc4_crtc->base;
+
+       vc4_crtc->pdev = pdev;
+       vc4_crtc->data = &vc4_txp_crtc_data;
 
        txp->pdev = pdev;
 
        if (ret)
                return ret;
 
+       ret = vc4_crtc_init(drm, vc4_crtc,
+                           &vc4_txp_crtc_funcs, &vc4_txp_crtc_helper_funcs);
+       if (ret)
+               return ret;
+
+       encoder = &txp->connector.encoder;
+       encoder->possible_crtcs |= drm_crtc_mask(crtc);
+
        ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0,
                               dev_name(dev), txp);
        if (ret)