drm/fb-helper: Finish the generic fbdev emulation
authorNoralf Trønnes <noralf@tronnes.org>
Tue, 3 Jul 2018 16:03:52 +0000 (18:03 +0200)
committerNoralf Trønnes <noralf@tronnes.org>
Tue, 10 Jul 2018 12:54:09 +0000 (14:54 +0200)
This adds a drm_fbdev_generic_setup() function that sets up generic
fbdev emulation with client callbacks for restore, hotplug and
unregister.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/20180703160354.59955-7-noralf@tronnes.org
drivers/gpu/drm/drm_fb_helper.c
include/drm/drm_fb_helper.h

index 7e29d5340a170ce220cb5444f8cff8a7f3bcf366..4b0dd20bccb8f3cc2b7da96eb4c4203dabf2815f 100644 (file)
@@ -67,6 +67,9 @@ static DEFINE_MUTEX(kernel_fb_helper_lock);
  * helper functions used by many drivers to implement the kernel mode setting
  * interfaces.
  *
+ * Drivers that support a dumb buffer with a virtual address and mmap support,
+ * should try out the generic fbdev emulation using drm_fbdev_generic_setup().
+ *
  * Setup fbdev emulation by calling drm_fb_helper_fbdev_setup() and tear it
  * down by calling drm_fb_helper_fbdev_teardown().
  *
@@ -3118,6 +3121,120 @@ err_free_buffer:
 }
 EXPORT_SYMBOL(drm_fb_helper_generic_probe);
 
+static const struct drm_fb_helper_funcs drm_fb_helper_generic_funcs = {
+       .fb_probe = drm_fb_helper_generic_probe,
+};
+
+static void drm_fbdev_client_unregister(struct drm_client_dev *client)
+{
+       struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
+
+       if (fb_helper->fbdev) {
+               drm_fb_helper_unregister_fbi(fb_helper);
+               /* drm_fbdev_fb_destroy() takes care of cleanup */
+               return;
+       }
+
+       /* Did drm_fb_helper_fbdev_setup() run? */
+       if (fb_helper->dev)
+               drm_fb_helper_fini(fb_helper);
+
+       drm_client_release(client);
+       kfree(fb_helper);
+}
+
+static int drm_fbdev_client_restore(struct drm_client_dev *client)
+{
+       struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
+
+       drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
+
+       return 0;
+}
+
+static int 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 drm_fb_helper_fbdev_setup() failed, we only try once */
+       if (!fb_helper->dev && fb_helper->funcs)
+               return 0;
+
+       if (dev->fb_helper)
+               return drm_fb_helper_hotplug_event(dev->fb_helper);
+
+       if (!dev->mode_config.num_connector)
+               return 0;
+
+       ret = drm_fb_helper_fbdev_setup(dev, fb_helper, &drm_fb_helper_generic_funcs,
+                                       fb_helper->preferred_bpp, 0);
+       if (ret) {
+               fb_helper->dev = NULL;
+               fb_helper->fbdev = NULL;
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct drm_client_funcs drm_fbdev_client_funcs = {
+       .owner          = THIS_MODULE,
+       .unregister     = drm_fbdev_client_unregister,
+       .restore        = drm_fbdev_client_restore,
+       .hotplug        = drm_fbdev_client_hotplug,
+};
+
+/**
+ * drm_fb_helper_generic_fbdev_setup() - Setup generic fbdev emulation
+ * @dev: DRM device
+ * @preferred_bpp: Preferred bits per pixel for the device.
+ *                 @dev->mode_config.preferred_depth is used if this is zero.
+ *
+ * This function sets up generic fbdev emulation for drivers that supports
+ * dumb buffers with a virtual address and that can be mmap'ed.
+ *
+ * Restore, hotplug events and teardown are all taken care of. Drivers that do
+ * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves.
+ * Simple drivers might use drm_mode_config_helper_suspend().
+ *
+ * Drivers that set the dirty callback on their framebuffer will get a shadow
+ * fbdev buffer that is blitted onto the real buffer. This is done in order to
+ * make deferred I/O work with all kinds of buffers.
+ *
+ * This function is safe to call even when there are no connectors present.
+ * Setup will be retried on the next hotplug event.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
+{
+       struct drm_fb_helper *fb_helper;
+       int ret;
+
+       if (!drm_fbdev_emulation)
+               return 0;
+
+       fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL);
+       if (!fb_helper)
+               return -ENOMEM;
+
+       ret = drm_client_new(dev, &fb_helper->client, "fbdev", &drm_fbdev_client_funcs);
+       if (ret) {
+               kfree(fb_helper);
+               return ret;
+       }
+
+       fb_helper->preferred_bpp = preferred_bpp;
+
+       drm_fbdev_client_hotplug(&fb_helper->client);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_fbdev_generic_setup);
+
 /* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT)
  * but the module doesn't depend on any fb console symbols.  At least
  * attempt to load fbcon to avoid leaving the system without a usable console.
index c134bbcfd2e96f6909a93ff7ecd6322515a3cbb8..5db08c8f1d258a6e4ef6336831f15001e8780519 100644 (file)
@@ -354,6 +354,7 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev);
 
 int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
                                struct drm_fb_helper_surface_size *sizes);
+int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp);
 #else
 static inline void drm_fb_helper_prepare(struct drm_device *dev,
                                        struct drm_fb_helper *helper,
@@ -595,6 +596,12 @@ drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
        return 0;
 }
 
+static inline int
+drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
+{
+       return 0;
+}
+
 #endif
 
 static inline int