.adap_transmit = vc4_hdmi_cec_adap_transmit,
 };
 
+static void vc4_hdmi_cec_release(void *ptr)
+{
+       struct vc4_hdmi *vc4_hdmi = ptr;
+
+       cec_unregister_adapter(vc4_hdmi->cec_adap);
+       vc4_hdmi->cec_adap = NULL;
+}
+
 static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
 {
        struct cec_connector_info conn_info;
        cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info);
 
        if (vc4_hdmi->variant->external_irq_controller) {
-               ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-rx"),
-                                          vc4_cec_irq_handler_rx_bare,
-                                          vc4_cec_irq_handler_rx_thread, 0,
-                                          "vc4 hdmi cec rx", vc4_hdmi);
+               ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-rx"),
+                                               vc4_cec_irq_handler_rx_bare,
+                                               vc4_cec_irq_handler_rx_thread, 0,
+                                               "vc4 hdmi cec rx", vc4_hdmi);
                if (ret)
                        goto err_delete_cec_adap;
 
-               ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-tx"),
-                                          vc4_cec_irq_handler_tx_bare,
-                                          vc4_cec_irq_handler_tx_thread, 0,
-                                          "vc4 hdmi cec tx", vc4_hdmi);
+               ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-tx"),
+                                               vc4_cec_irq_handler_tx_bare,
+                                               vc4_cec_irq_handler_tx_thread, 0,
+                                               "vc4 hdmi cec tx", vc4_hdmi);
                if (ret)
-                       goto err_remove_cec_rx_handler;
+                       goto err_delete_cec_adap;
        } else {
-               ret = request_threaded_irq(platform_get_irq(pdev, 0),
-                                          vc4_cec_irq_handler,
-                                          vc4_cec_irq_handler_thread, 0,
-                                          "vc4 hdmi cec", vc4_hdmi);
+               ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
+                                               vc4_cec_irq_handler,
+                                               vc4_cec_irq_handler_thread, 0,
+                                               "vc4 hdmi cec", vc4_hdmi);
                if (ret)
                        goto err_delete_cec_adap;
        }
 
        ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev);
        if (ret < 0)
-               goto err_remove_handlers;
-
-       return 0;
+               goto err_delete_cec_adap;
 
-err_remove_handlers:
-       if (vc4_hdmi->variant->external_irq_controller)
-               free_irq(platform_get_irq_byname(pdev, "cec-tx"), vc4_hdmi);
-       else
-               free_irq(platform_get_irq(pdev, 0), vc4_hdmi);
+       /*
+        * NOTE: Strictly speaking, we should probably use a DRM-managed
+        * registration there to avoid removing the CEC adapter by the
+        * time the DRM driver doesn't have any user anymore.
+        *
+        * However, the CEC framework already cleans up the CEC adapter
+        * only when the last user has closed its file descriptor, so we
+        * don't need to handle it in DRM.
+        *
+        * By the time the device-managed hook is executed, we will give
+        * up our reference to the CEC adapter and therefore don't
+        * really care when it's actually freed.
+        *
+        * There's still a problematic sequence: if we unregister our
+        * CEC adapter, but the userspace keeps a handle on the CEC
+        * adapter but not the DRM device for some reason. In such a
+        * case, our vc4_hdmi structure will be freed, but the
+        * cec_adapter structure will have a dangling pointer to what
+        * used to be our HDMI controller. If we get a CEC call at that
+        * moment, we could end up with a use-after-free. Fortunately,
+        * the CEC framework already handles this too, by calling
+        * cec_is_registered() in cec_ioctl() and cec_poll().
+        */
+       ret = devm_add_action_or_reset(dev, vc4_hdmi_cec_release, vc4_hdmi);
+       if (ret)
+               return ret;
 
-err_remove_cec_rx_handler:
-       if (vc4_hdmi->variant->external_irq_controller)
-               free_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_hdmi);
+       return 0;
 
 err_delete_cec_adap:
        cec_delete_adapter(vc4_hdmi->cec_adap);
 
        return ret;
 }
-
-static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi)
-{
-       struct platform_device *pdev = vc4_hdmi->pdev;
-
-       if (vc4_hdmi->variant->external_irq_controller) {
-               free_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_hdmi);
-               free_irq(platform_get_irq_byname(pdev, "cec-tx"), vc4_hdmi);
-       } else {
-               free_irq(platform_get_irq(pdev, 0), vc4_hdmi);
-       }
-
-       cec_unregister_adapter(vc4_hdmi->cec_adap);
-}
 #else
 static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
 {
        return 0;
 }
-
-static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {};
 #endif
 
 static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi,
 
        ret = vc4_hdmi_audio_init(vc4_hdmi);
        if (ret)
-               goto err_free_cec;
+               goto err_free_hotplug;
 
        vc4_debugfs_add_file(drm, variant->debugfs_name,
                             vc4_hdmi_debugfs_regs,
 
        return 0;
 
-err_free_cec:
-       vc4_hdmi_cec_exit(vc4_hdmi);
 err_free_hotplug:
        vc4_hdmi_hotplug_exit(vc4_hdmi);
 err_put_runtime_pm:
        kfree(vc4_hdmi->hdmi_regset.regs);
        kfree(vc4_hdmi->hd_regset.regs);
 
-       vc4_hdmi_cec_exit(vc4_hdmi);
        vc4_hdmi_hotplug_exit(vc4_hdmi);
 
        pm_runtime_disable(dev);