[PATCH v4 4/5] drm: simpledrm: add fbdev fallback support

From: Noralf TrÃnnes
Date: Mon Aug 22 2016 - 16:27:04 EST


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@xxxxxxxxx
Signed-off-by: Noralf TrÃnnes <noralf@xxxxxxxxxxx>
---

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@xxxxxxxxx>
+ *
+ * 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