From 88b8ae7642ef21e685d51148e8f57c3dfa1323ac Mon Sep 17 00:00:00 2001
From: Bingwu Zhang <xtex@aosc.io>
Date: Sat, 7 Dec 2024 23:56:43 +0800
Subject: [PATCH 10/10] FROM AOSC: TTM fbdev emulation for Linux 6.13+

Link: https://github.com/torvalds/linux/commit/1000634477d8d178179b1ad45d92e925fabe3deb
Link: https://github.com/NVIDIA/open-gpu-kernel-modules/issues/749
Signed-off-by: xtex <xtexchooser@duck.com>
Signed-off-by: Eric Naim <dnaim@cachyos.org>
---
 kernel-open/nvidia-drm/nvidia-drm-drv.c       | 72 +++++++++++++++++++
 kernel-open/nvidia-drm/nvidia-drm-linux.c     |  4 ++
 .../nvidia-drm/nvidia-drm-os-interface.h      |  5 ++
 3 files changed, 81 insertions(+)

diff --git a/kernel-open/nvidia-drm/nvidia-drm-drv.c b/kernel-open/nvidia-drm/nvidia-drm-drv.c
index 2e4f6404..ab85152f 100644
--- a/kernel-open/nvidia-drm/nvidia-drm-drv.c
+++ b/kernel-open/nvidia-drm/nvidia-drm-drv.c
@@ -1951,7 +1951,60 @@ void nv_drm_update_drm_driver_features(void)
 #endif /* NV_DRM_ATOMIC_MODESET_AVAILABLE */
 }
 
+#if !defined(NV_DRM_FBDEV_TTM_AVAILABLE) &&                                    \
+    !defined(NV_DRM_FBDEV_GENERIC_AVAILABLE)
+// AOSC OS: Workaround for Linux 6.13+
 
+static const struct drm_fb_helper_funcs nv_drm_fbdev_helper_funcs = {
+    .fb_probe = drm_fbdev_ttm_driver_fbdev_probe,
+};
+
+static void nv_drm_fbdev_client_unregister(struct drm_client_dev *client)
+{
+	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
+	if (fb_helper->info) {
+		drm_fb_helper_unregister_info(fb_helper);
+	} else {
+		drm_client_release(&fb_helper->client);
+		drm_fb_helper_unprepare(fb_helper);
+		kfree(fb_helper);
+	}
+}
+static int nv_drm_fbdev_client_restore(struct drm_client_dev *client)
+{
+	drm_fb_helper_lastclose(client->dev);
+	return 0;
+}
+static int nv_drm_fbdev_client_hotplug(struct drm_client_dev *client)
+{
+	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
+	struct drm_device *dev = client->dev;
+	int ret;
+	if (dev->fb_helper)
+		return drm_fb_helper_hotplug_event(dev->fb_helper);
+	ret = drm_fb_helper_init(dev, fb_helper);
+	if (ret)
+		goto err_drm_err;
+	if (!drm_drv_uses_atomic_modeset(dev))
+		drm_helper_disable_unused_functions(dev);
+	ret = drm_fb_helper_initial_config(fb_helper);
+	if (ret)
+		goto err_drm_fb_helper_fini;
+	return 0;
+err_drm_fb_helper_fini:
+	drm_fb_helper_fini(fb_helper);
+err_drm_err:
+	drm_err(dev, "AOSC OS: NV-DRM: fbdev: Failed to setup emulation (ret=%d)\n", ret);
+	return ret;
+}
+
+static const struct drm_client_funcs nv_drm_fbdev_client_funcs = {
+	.owner		= THIS_MODULE,
+	.unregister	= nv_drm_fbdev_client_unregister,
+	.restore	= nv_drm_fbdev_client_restore,
+	.hotplug	= nv_drm_fbdev_client_hotplug,
+};
+#endif
 
 /*
  * Helper function for allocate/register DRM device for given NVIDIA GPU ID.
@@ -1961,6 +2014,7 @@ void nv_drm_register_drm_device(const nv_gpu_info_t *gpu_info)
     struct nv_drm_device *nv_dev = NULL;
     struct drm_device *dev = NULL;
     struct device *device = gpu_info->os_device_ptr;
+    struct drm_fb_helper *fb_helper = NULL;
     bool bus_is_pci;
 
     DRM_DEBUG(
@@ -2039,6 +2093,20 @@ void nv_drm_register_drm_device(const nv_gpu_info_t *gpu_info)
         drm_fbdev_ttm_setup(dev, 32);
         #elif defined(NV_DRM_FBDEV_GENERIC_AVAILABLE)
         drm_fbdev_generic_setup(dev, 32);
+        #else
+        // AOSC OS: Workaround for Linux 6.13+
+        int drm_client_ret;
+        fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL);
+        if (!fb_helper)
+            return;
+        drm_fb_helper_prepare(dev, fb_helper, 32, &nv_drm_fbdev_helper_funcs);
+        drm_client_ret = drm_client_init(dev, &fb_helper->client, "fbdev",
+                                         &nv_drm_fbdev_client_funcs);
+        if (drm_client_ret) {
+            drm_err(dev, "AOSC OS: NV-DRM: Failed to register DRM client: %d\n", drm_client_ret);
+            goto failed_drm_client_init;
+        }
+        drm_client_register(&fb_helper->client);
         #endif
     }
 #endif /* defined(NV_DRM_FBDEV_AVAILABLE) */
@@ -2050,6 +2118,10 @@ void nv_drm_register_drm_device(const nv_gpu_info_t *gpu_info)
 
     return; /* Success */
 
+failed_drm_client_init:
+    drm_fb_helper_unprepare(fb_helper);
+    kfree(fb_helper);
+
 failed_drm_register:
 
     nv_drm_dev_free(dev);
diff --git a/kernel-open/nvidia-drm/nvidia-drm-linux.c b/kernel-open/nvidia-drm/nvidia-drm-linux.c
index 83d40983..ac4fe967 100644
--- a/kernel-open/nvidia-drm/nvidia-drm-linux.c
+++ b/kernel-open/nvidia-drm/nvidia-drm-linux.c
@@ -39,8 +39,12 @@ MODULE_PARM_DESC(
     fbdev,
     "Create a framebuffer device (1 = enable (default), 0 = disable) (EXPERIMENTAL)");
 module_param_named(fbdev, nv_drm_fbdev_module_param, bool, 0400);
+#else
+#error "nvidia-drm fbdev should always be available."
 #endif
 
+#else
+#error "nvidia-drm is not available"
 #endif /* NV_DRM_AVAILABLE */
 
 /*************************************************************************
diff --git a/kernel-open/nvidia-drm/nvidia-drm-os-interface.h b/kernel-open/nvidia-drm/nvidia-drm-os-interface.h
index 71ca5f22..8195af32 100644
--- a/kernel-open/nvidia-drm/nvidia-drm-os-interface.h
+++ b/kernel-open/nvidia-drm/nvidia-drm-os-interface.h
@@ -78,6 +78,11 @@ typedef struct nv_timer nv_drm_timer;
 #define NV_DRM_FBDEV_TTM_AVAILABLE
 #endif
 
+// AOSC OS: Always enable DRM fbdev
+// FIXME: Add config test for drm helper functions.
+// The implementation uses drm_client_register, which is added in v5.2-rc1.
+#define NV_DRM_FBDEV_AVAILABLE
+
 struct page;
 
 /* Set to true when the atomic modeset feature is enabled. */
-- 
2.47.1