Create a simple fbdev device during SimpleDRM setup so legacy user-space
and fbcon can use it.

Original work by David Herrmann.

Cc: dh.herrmann at gmail.com
Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---

Changes from version 3:
- Remove #ifdef CONFIG_DRM_FBDEV_EMULATION
- Use drm_fb_helper_set_suspend_lock()
- Don't access the native framebuffer directly, but do blitting here as well.
- Use the drm_fb_helper_sys_*() functions instead of the cfb versions.
- Remove FBINFO_CAN_FORCE_OUTPUT flag which doesn't work now.
- Pass struct drm_fb_helper around instead of struct sdrm_fbdev.

Changes from version 2:
- Switch to using drm_fb_helper in preparation for future panic handling
  which needs an enabled pipeline.

Changes from version 1:
  No changes

Changes from previous version:
- Remove the DRM_SIMPLEDRM_FBDEV kconfig option and use DRM_FBDEV_EMULATION
- Suspend fbcon/fbdev when the pipeline is enabled, resume in lastclose
- Add FBINFO_CAN_FORCE_OUTPUT flag so we get oops'es on the console

 drivers/gpu/drm/simpledrm/Kconfig           |   3 +
 drivers/gpu/drm/simpledrm/Makefile          |   2 +-
 drivers/gpu/drm/simpledrm/simpledrm.h       |   5 +
 drivers/gpu/drm/simpledrm/simpledrm_drv.c   |   4 +
 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c | 201 ++++++++++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_kms.c   |  14 ++
 6 files changed, 228 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c

diff --git a/drivers/gpu/drm/simpledrm/Kconfig 
b/drivers/gpu/drm/simpledrm/Kconfig
index f45b25d..3257590 100644
--- a/drivers/gpu/drm/simpledrm/Kconfig
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -13,6 +13,9 @@ config DRM_SIMPLEDRM
          SimpleDRM supports "simple-framebuffer" DeviceTree objects and
          compatible platform framebuffers.

+         If fbdev support is enabled, this driver will also provide an fbdev
+         compatibility layer that supports fbcon, mmap is not supported.
+
          If unsure, say Y.

          To compile this driver as a module, choose M here: the
diff --git a/drivers/gpu/drm/simpledrm/Makefile 
b/drivers/gpu/drm/simpledrm/Makefile
index f6a62dc..5474f7f 100644
--- a/drivers/gpu/drm/simpledrm/Makefile
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -1,4 +1,4 @@
 simpledrm-y := simpledrm_drv.o simpledrm_kms.o simpledrm_gem.o \
-               simpledrm_damage.o
+               simpledrm_damage.o simpledrm_fbdev.o

 obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h 
b/drivers/gpu/drm/simpledrm/simpledrm.h
index 0739581..d4eb52c 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm.h
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -16,6 +16,7 @@
 #include <drm/drm_simple_kms_helper.h>

 struct simplefb_format;
+struct drm_fb_helper;
 struct regulator;
 struct clk;

@@ -23,6 +24,7 @@ struct sdrm_device {
        struct drm_device *ddev;
        struct drm_simple_display_pipe pipe;
        struct drm_connector conn;
+       struct drm_fb_helper *fb_helper;

        /* framebuffer information */
        const struct simplefb_format *fb_sformat;
@@ -42,6 +44,7 @@ struct sdrm_device {
        struct regulator **regulators;
 };

+void sdrm_lastclose(struct drm_device *ddev);
 int sdrm_drm_modeset_init(struct sdrm_device *sdrm);

 int sdrm_dirty(struct drm_framebuffer *fb,
@@ -82,5 +85,7 @@ struct sdrm_framebuffer {
 int sdrm_fb_init(struct drm_device *ddev, struct sdrm_framebuffer *fb,
                 const struct drm_mode_fb_cmd2 *mode_cmd,
                 struct sdrm_gem_object *obj);
+void sdrm_fbdev_init(struct sdrm_device *sdrm);
+void sdrm_fbdev_cleanup(struct sdrm_device *sdrm);

 #endif /* SDRM_DRV_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c 
b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
index 17c1b55..fe752c6 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -47,6 +47,7 @@ static const struct vm_operations_struct sdrm_gem_vm_ops = {
 static struct drm_driver sdrm_drm_driver = {
        .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
        .fops = &sdrm_drm_fops,
+       .lastclose = sdrm_lastclose,

        .gem_free_object = sdrm_gem_free_object,
        .gem_vm_ops = &sdrm_gem_vm_ops,
@@ -451,6 +452,8 @@ static int sdrm_simplefb_probe(struct platform_device *pdev)
        if (ret)
                goto err_regulators;

+       sdrm_fbdev_init(sdrm);
+
        DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name,
                 ddev->primary->index);

@@ -476,6 +479,7 @@ static int sdrm_simplefb_remove(struct platform_device 
*pdev)
        struct drm_device *ddev = platform_get_drvdata(pdev);
        struct sdrm_device *sdrm = ddev->dev_private;

+       sdrm_fbdev_cleanup(sdrm);
        drm_dev_unregister(ddev);
        drm_mode_config_cleanup(ddev);

diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c 
b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
new file mode 100644
index 0000000..c6596ad
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
@@ -0,0 +1,201 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/fb.h>
+#include <linux/platform_device.h>
+
+#include "simpledrm.h"
+
+struct sdrm_fbdev {
+       struct drm_fb_helper fb_helper;
+       struct sdrm_framebuffer fb;
+};
+
+static inline struct sdrm_fbdev *to_sdrm_fbdev(struct drm_fb_helper *helper)
+{
+       return container_of(helper, struct sdrm_fbdev, fb_helper);
+}
+
+/*
+ * simpledrm uses the same gem code as udl and work on that driver has shown
+ * that it doens't work well with fb_deferred_io. So mmap is not supported.
+ *
+ * This is documented in commit 677d23b70bf9:
+ *
+ * drm/udl: disable fb_defio by default
+ * There seems to be a bad interaction between gem/shmem and defio on top,
+ * I get list corruption on the page lru in the shmem code.
+ *
+ * Turn it off for now until we get some more digging done.
+ *
+ */
+static int sdrm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+       return -ENODEV;
+}
+
+static struct fb_ops sdrm_fbdev_ops = {
+       .owner          = THIS_MODULE,
+       .fb_fillrect    = drm_fb_helper_sys_fillrect,
+       .fb_copyarea    = drm_fb_helper_sys_copyarea,
+       .fb_imageblit   = drm_fb_helper_sys_imageblit,
+       .fb_check_var   = drm_fb_helper_check_var,
+       .fb_set_par     = drm_fb_helper_set_par,
+       .fb_setcmap     = drm_fb_helper_setcmap,
+       .fb_mmap        = sdrm_fb_mmap,
+};
+
+static int sdrm_fbdev_create(struct drm_fb_helper *helper,
+                            struct drm_fb_helper_surface_size *sizes)
+{
+       struct sdrm_fbdev *fbdev = to_sdrm_fbdev(helper);
+       struct drm_device *ddev = helper->dev;
+       struct sdrm_device *sdrm = ddev->dev_private;
+       struct drm_mode_fb_cmd2 mode_cmd = {
+               .width = sdrm->fb_width,
+               .height = sdrm->fb_height,
+               .pitches[0] = sdrm->fb_stride,
+               .pixel_format = sdrm->fb_format,
+       };
+       struct sdrm_gem_object *obj;
+       struct drm_framebuffer *fb;
+       struct fb_info *fbi;
+       size_t size;
+       int ret;
+
+       size = PAGE_ALIGN(sdrm->fb_size);
+       obj = sdrm_gem_alloc_object(ddev, size);
+       if (!obj)
+               return -ENOMEM;
+
+       ret = sdrm_gem_vmap(obj);
+       if (ret) {
+               DRM_ERROR("failed to vmap fb\n");
+               goto err_gem_free;
+       }
+
+       fbi = drm_fb_helper_alloc_fbi(helper);
+       if (IS_ERR(fbi)) {
+               ret = PTR_ERR(fbi);
+               goto err_gem_free;
+       }
+
+       ret = sdrm_fb_init(ddev, &fbdev->fb, &mode_cmd, obj);
+       if (ret) {
+               dev_err(ddev->dev, "Failed to init framebuffer: %d\n", ret);
+               goto err_fbi_release;
+       }
+
+       fb = &fbdev->fb.base;
+       helper->fb = fb;
+       fbi->par = helper;
+
+       fbi->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE;
+       fbi->fbops = &sdrm_fbdev_ops;
+
+       drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
+       drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height);
+
+       strncpy(fbi->fix.id, "simpledrmfb", 15);
+       fbi->screen_base = obj->vmapping;
+       fbi->fix.smem_len = sdrm->fb_size;
+
+       return 0;
+
+err_fbi_release:
+       drm_fb_helper_release_fbi(helper);
+
+err_gem_free:
+       drm_gem_object_unreference_unlocked(&obj->base);
+
+       return ret;
+}
+
+static const struct drm_fb_helper_funcs sdrm_fb_helper_funcs = {
+       .fb_probe = sdrm_fbdev_create,
+};
+
+void sdrm_fbdev_init(struct sdrm_device *sdrm)
+{
+       struct drm_device *ddev = sdrm->ddev;
+       struct drm_fb_helper *fb_helper;
+       struct sdrm_fbdev *fbdev;
+       int ret;
+
+       fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
+       if (!fbdev) {
+               dev_err(ddev->dev, "Failed to allocate drm fbdev.\n");
+               return;
+       }
+
+       fb_helper = &fbdev->fb_helper;
+
+       drm_fb_helper_prepare(ddev, fb_helper, &sdrm_fb_helper_funcs);
+
+       ret = drm_fb_helper_init(ddev, fb_helper, 1, 1);
+       if (ret < 0) {
+               dev_err(ddev->dev, "Failed to initialize drm fb helper.\n");
+               goto err_free;
+       }
+
+       ret = drm_fb_helper_single_add_all_connectors(fb_helper);
+       if (ret < 0) {
+               dev_err(ddev->dev, "Failed to add connectors.\n");
+               goto err_drm_fb_helper_fini;
+       }
+
+       ret = drm_fb_helper_initial_config(fb_helper,
+                                          ddev->mode_config.preferred_depth);
+       if (ret < 0) {
+               dev_err(ddev->dev, "Failed to set initial hw configuration.\n");
+               goto err_drm_fb_helper_fini;
+       }
+
+       if (!fb_helper->fbdev) {
+               /* fbdev emulation is disabled */
+               kfree(fbdev);
+               return;
+       }
+
+       sdrm->fb_helper = fb_helper;
+
+       return;
+
+err_drm_fb_helper_fini:
+       drm_fb_helper_fini(fb_helper);
+err_free:
+       kfree(fbdev);
+}
+
+void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
+{
+       struct drm_fb_helper *fb_helper = sdrm->fb_helper;
+       struct sdrm_fbdev *fbdev;
+
+       if (!fb_helper)
+               return;
+
+       sdrm->fb_helper = NULL;
+       fbdev = to_sdrm_fbdev(fb_helper);
+
+       drm_fb_helper_unregister_fbi(fb_helper);
+       cancel_work_sync(&fb_helper->dirty_work);
+       drm_fb_helper_release_fbi(fb_helper);
+
+       drm_framebuffer_unregister_private(fb_helper->fb);
+       drm_framebuffer_cleanup(fb_helper->fb);
+       drm_gem_object_unreference_unlocked(&fbdev->fb.obj->base);
+
+       drm_fb_helper_fini(fb_helper);
+       kfree(fbdev);
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c 
b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
index e6dc3df..8b98a08 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_kms.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
@@ -12,6 +12,7 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_simple_kms_helper.h>
 #include <linux/slab.h>
@@ -24,6 +25,16 @@ static const uint32_t sdrm_formats[] = {
        DRM_FORMAT_XRGB8888,
 };

+void sdrm_lastclose(struct drm_device *ddev)
+{
+       struct sdrm_device *sdrm = ddev->dev_private;
+
+       if (sdrm->fb_helper) {
+               drm_fb_helper_restore_fbdev_mode_unlocked(sdrm->fb_helper);
+               drm_fb_helper_set_suspend_lock(sdrm->fb_helper, 0);
+       }
+}
+
 static int sdrm_conn_get_modes(struct drm_connector *conn)
 {
        struct sdrm_device *sdrm = conn->dev->dev_private;
@@ -92,6 +103,9 @@ void sdrm_display_pipe_update(struct drm_simple_display_pipe 
*pipe,

        sdrm_crtc_send_vblank_event(&pipe->crtc);

+       if (sdrm->fb_helper && fb && fb != sdrm->fb_helper->fb)
+               drm_fb_helper_set_suspend_lock(sdrm->fb_helper, 1);
+
        if (fb) {
                pipe->plane.fb = fb;
                sdrm_dirty_all_locked(sdrm);
--
2.8.2

Reply via email to