diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
index d7b179d..e368d14 100644
--- a/drivers/gpu/drm/simpledrm/Makefile
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -1,6 +1,7 @@
 simpledrm-y := \
 	simpledrm_damage.o \
 	simpledrm_drv.o \
+	simpledrm_fbdev.o \
 	simpledrm_gem.o \
 	simpledrm_kms.o \
 	simpledrm_of.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
index ed6d725..d7a2045 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm.h
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -19,6 +19,7 @@
 #include <linux/mutex.h>
 
 struct clk;
+struct drm_fb_helper;
 struct regulator;
 struct simplefb_format;
 
@@ -49,6 +50,7 @@ struct sdrm_device {
 	atomic_t n_used;
 	struct drm_device *ddev;
 	struct sdrm_hw *hw;
+	struct drm_fb_helper *fbdev;
 
 	size_t n_clks;
 	size_t n_regulators;
@@ -66,6 +68,9 @@ void sdrm_of_unbind(struct sdrm_device *sdrm);
 int sdrm_kms_bind(struct sdrm_device *sdrm);
 void sdrm_kms_unbind(struct sdrm_device *sdrm);
 
+void sdrm_fbdev_bind(struct sdrm_device *sdrm);
+void sdrm_fbdev_unbind(struct sdrm_device *sdrm);
+
 void sdrm_dirty(struct sdrm_fb *fb, u32 x, u32 y, u32 width, u32 height);
 
 struct sdrm_bo *sdrm_bo_new(struct drm_device *ddev, size_t size);
@@ -80,4 +85,7 @@ int sdrm_dumb_map_offset(struct drm_file *dfile,
 			 uint32_t handle,
 			 uint64_t *offset);
 
+struct sdrm_fb *sdrm_fb_new(struct sdrm_bo *bo,
+			    const struct drm_mode_fb_cmd2 *cmd);
+
 #endif /* __SDRM_SIMPLEDRM_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
index d569120..6aefe49 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -9,6 +9,7 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 #include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
 #include <linux/atomic.h>
 #include <linux/err.h>
 #include <linux/io.h>
@@ -210,6 +211,7 @@ error:
 static void sdrm_device_unbind(struct sdrm_device *sdrm)
 {
 	if (sdrm) {
+		sdrm_fbdev_unbind(sdrm);
 		sdrm_kms_unbind(sdrm);
 		sdrm_hw_unbind(sdrm->hw);
 		sdrm_of_unbind(sdrm);
@@ -232,6 +234,8 @@ static int sdrm_device_bind(struct sdrm_device *sdrm)
 	if (r < 0)
 		goto error;
 
+	sdrm_fbdev_bind(sdrm);
+
 	return 0;
 
 error:
@@ -253,6 +257,14 @@ static void sdrm_device_release(struct sdrm_device *sdrm)
 	}
 }
 
+static void sdrm_device_lastclose(struct drm_device *ddev)
+{
+	struct sdrm_device *sdrm = ddev->dev_private;
+
+	if (sdrm->fbdev)
+		drm_fb_helper_restore_fbdev_mode_unlocked(sdrm->fbdev);
+}
+
 static int sdrm_fop_open(struct inode *inode, struct file *file)
 {
 	struct drm_device *ddev;
@@ -411,6 +423,7 @@ static const struct file_operations sdrm_drm_fops = {
 static struct drm_driver sdrm_drm_driver = {
 	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
 	.fops = &sdrm_drm_fops,
+	.lastclose = sdrm_device_lastclose,
 
 	.gem_free_object = sdrm_bo_free,
 
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
new file mode 100644
index 0000000..17e4e83
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012-2016 Red Hat, Inc.
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "simpledrm.h"
+
+static int sdrm_fbdev_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_fbdev_mmap,
+};
+
+static int sdrm_fbdev_probe(struct drm_fb_helper *fbdev,
+			    struct drm_fb_helper_surface_size *sizes)
+{
+	struct sdrm_device *sdrm = fbdev->dev->dev_private;
+	struct drm_mode_fb_cmd2 cmd = {
+		.width = sdrm->hw->width,
+		.height = sdrm->hw->height,
+		.pitches[0] = sdrm->hw->stride,
+		.pixel_format = sdrm->hw->format,
+	};
+	struct fb_info *fbi;
+	struct sdrm_bo *bo;
+	struct sdrm_fb *fb;
+	int r;
+
+	fbi = drm_fb_helper_alloc_fbi(fbdev);
+	if (IS_ERR(fbi))
+		return PTR_ERR(fbi);
+
+	bo = sdrm_bo_new(sdrm->ddev,
+			 PAGE_ALIGN(sdrm->hw->height * sdrm->hw->stride));
+	if (!bo) {
+		r = -ENOMEM;
+		goto error;
+	}
+
+	fb = sdrm_fb_new(bo, &cmd);
+	drm_gem_object_unreference_unlocked(&bo->base);
+	if (IS_ERR(fb)) {
+		r = PTR_ERR(fb);
+		goto error;
+	}
+
+	fbdev->fb = &fb->base;
+	fbi->par = fbdev;
+	fbi->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE;
+	fbi->fbops = &sdrm_fbdev_ops;
+
+	drm_fb_helper_fill_fix(fbi, fb->base.pitches[0], fb->base.depth);
+	drm_fb_helper_fill_var(fbi, fbdev, fb->base.width, fb->base.height);
+
+	strncpy(fbi->fix.id, "simpledrmfb", sizeof(fbi->fix.id) - 1);
+	fbi->screen_base = bo->vmapping;
+	fbi->fix.smem_len = bo->base.size;
+
+	return 0;
+
+error:
+	drm_fb_helper_release_fbi(fbdev);
+	return r;
+}
+
+static const struct drm_fb_helper_funcs sdrm_fbdev_funcs = {
+	.fb_probe = sdrm_fbdev_probe,
+};
+
+void sdrm_fbdev_bind(struct sdrm_device *sdrm)
+{
+	struct drm_fb_helper *fbdev;
+	int r;
+
+	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
+	if (!fbdev)
+		return;
+
+	drm_fb_helper_prepare(sdrm->ddev, fbdev, &sdrm_fbdev_funcs);
+
+	r = drm_fb_helper_init(sdrm->ddev, fbdev, 1, 1);
+	if (r < 0)
+		goto error;
+
+	r = drm_fb_helper_single_add_all_connectors(fbdev);
+	if (r < 0)
+		goto error;
+
+	r = drm_fb_helper_initial_config(fbdev,
+				sdrm->ddev->mode_config.preferred_depth);
+	if (r < 0)
+		goto error;
+
+	if (!fbdev->fbdev)
+		goto error;
+
+	sdrm->fbdev = fbdev;
+	return;
+
+error:
+	drm_fb_helper_fini(fbdev);
+	kfree(fbdev);
+}
+
+void sdrm_fbdev_unbind(struct sdrm_device *sdrm)
+{
+	struct drm_fb_helper *fbdev = sdrm->fbdev;
+
+	if (!fbdev)
+		return;
+
+	sdrm->fbdev = NULL;
+	drm_fb_helper_unregister_fbi(fbdev);
+	cancel_work_sync(&fbdev->dirty_work);
+	drm_fb_helper_release_fbi(fbdev);
+	drm_framebuffer_unreference(fbdev->fb);
+	fbdev->fb = NULL;
+	drm_fb_helper_fini(fbdev);
+	kfree(fbdev);
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
index 00101c9..8f67fe5 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_kms.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
@@ -169,53 +169,60 @@ static const struct drm_framebuffer_funcs sdrm_fb_ops = {
 	.destroy		= sdrm_fb_destroy,
 };
 
-static struct drm_framebuffer *
-sdrm_fb_create(struct drm_device *ddev,
-	       struct drm_file *dfile,
-	       const struct drm_mode_fb_cmd2 *cmd)
+struct sdrm_fb *sdrm_fb_new(struct sdrm_bo *bo,
+			    const struct drm_mode_fb_cmd2 *cmd)
 {
-	struct drm_gem_object *dobj;
-	struct sdrm_fb *fb = NULL;
-	struct sdrm_bo *bo;
+	struct sdrm_fb *fb;
 	int r;
 
 	if (cmd->flags)
 		return ERR_PTR(-EINVAL);
 
-	dobj = drm_gem_object_lookup(dfile, cmd->handles[0]);
-	if (!dobj)
-		return ERR_PTR(-EINVAL);
-
-	bo = container_of(dobj, struct sdrm_bo, base);
-
 	r = sdrm_bo_vmap(bo);
 	if (r < 0)
-		goto error;
+		return ERR_PTR(r);
 
 	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
-	if (!fb) {
-		r = -ENOMEM;
-		goto error;
-	}
+	if (!fb)
+		return ERR_PTR(r);
 
+	drm_gem_object_reference(&bo->base);
 	fb->bo = bo;
 	drm_helper_mode_fill_fb_struct(&fb->base, cmd);
 
-	r = drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
+	r = drm_framebuffer_init(bo->base.dev, &fb->base, &sdrm_fb_ops);
 	if (r < 0)
 		goto error;
 
-	DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.base.id,
-		      drm_get_format_name(fb->base.pixel_format));
-
-	return &fb->base;
+	return fb;
 
 error:
+	drm_gem_object_unreference_unlocked(&bo->base);
 	kfree(fb);
-	drm_gem_object_unreference_unlocked(dobj);
 	return ERR_PTR(r);
 }
 
+static struct drm_framebuffer *
+sdrm_fb_create(struct drm_device *ddev,
+	       struct drm_file *dfile,
+	       const struct drm_mode_fb_cmd2 *cmd)
+{
+	struct drm_gem_object *dobj;
+	struct sdrm_fb *fb;
+
+	dobj = drm_gem_object_lookup(dfile, cmd->handles[0]);
+	if (!dobj)
+		return ERR_PTR(-EINVAL);
+
+	fb = sdrm_fb_new(container_of(dobj, struct sdrm_bo, base), cmd);
+	drm_gem_object_unreference_unlocked(dobj);
+
+	if (IS_ERR(fb))
+		return ERR_CAST(fb);
+
+	return &fb->base;
+}
+
 static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
 	.fb_create		= sdrm_fb_create,
 	.atomic_check		= drm_atomic_helper_check,
