diff --git a/MAINTAINERS b/MAINTAINERS
index 0bbe4b1..408863d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4140,6 +4140,12 @@ S:	Orphan / Obsolete
 F:	drivers/gpu/drm/savage/
 F:	include/uapi/drm/savage_drm.h
 
+DRM DRIVER FOR SIMPLE FRAMEBUFFER DEVICES
+M:	David Herrmann <dh.herrmann@gmail.com>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+F:	drivers/gpu/drm/simpledrm/
+
 DRM DRIVER FOR SIS VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/sis/
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index f27f9b5..61cbcd1 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -291,3 +291,5 @@ source "drivers/gpu/drm/arc/Kconfig"
 source "drivers/gpu/drm/hisilicon/Kconfig"
 
 source "drivers/gpu/drm/mediatek/Kconfig"
+
+source "drivers/gpu/drm/simpledrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0238bf8..3e6fe99 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -83,3 +83,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
 obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
+obj-y			+= simpledrm/
diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
new file mode 100644
index 0000000..f45b25d
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -0,0 +1,19 @@
+config DRM_SIMPLEDRM
+	tristate "Simple firmware framebuffer DRM driver"
+	depends on DRM
+	select DRM_KMS_HELPER
+	help
+	  SimpleDRM can run on all systems with pre-initialized graphics
+	  hardware. It uses a framebuffer that was initialized during
+	  firmware boot. No page-flipping, modesetting or other advanced
+	  features are available. However, other DRM drivers can be loaded
+	  later and take over from SimpleDRM if they provide real hardware
+	  support.
+
+	  SimpleDRM supports "simple-framebuffer" DeviceTree objects and
+	  compatible platform framebuffers.
+
+	  If unsure, say Y.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called simpledrm.
diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
new file mode 100644
index 0000000..d7b179d
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -0,0 +1,8 @@
+simpledrm-y := \
+	simpledrm_damage.o \
+	simpledrm_drv.o \
+	simpledrm_gem.o \
+	simpledrm_kms.o \
+	simpledrm_of.o
+
+obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
new file mode 100644
index 0000000..ed6d725
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -0,0 +1,83 @@
+#ifndef __SDRM_SIMPLEDRM_H
+#define __SDRM_SIMPLEDRM_H
+
+/*
+ * Copyright (C) 2012-2016 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+
+struct clk;
+struct regulator;
+struct simplefb_format;
+
+struct sdrm_hw {
+	struct mutex lock;
+	u32 width;
+	u32 height;
+	u32 stride;
+	u32 bpp;
+	u32 format;
+	unsigned long base;
+	unsigned long size;
+	void *map;
+};
+
+struct sdrm_bo {
+	struct drm_gem_object base;
+	struct page **pages;
+	void *vmapping;
+};
+
+struct sdrm_fb {
+	struct drm_framebuffer base;
+	struct sdrm_bo *bo;
+};
+
+struct sdrm_device {
+	atomic_t n_used;
+	struct drm_device *ddev;
+	struct sdrm_hw *hw;
+
+	size_t n_clks;
+	size_t n_regulators;
+	struct clk **clks;
+	struct regulator **regulators;
+
+	struct drm_simple_display_pipe pipe;
+	struct drm_connector conn;
+};
+
+void sdrm_of_bootstrap(void);
+int sdrm_of_bind(struct sdrm_device *sdrm);
+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_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);
+void sdrm_bo_free(struct drm_gem_object *obj);
+int sdrm_bo_vmap(struct sdrm_bo *bo);
+
+int sdrm_dumb_create(struct drm_file *dfile,
+		     struct drm_device *ddev,
+		     struct drm_mode_create_dumb *arg);
+int sdrm_dumb_map_offset(struct drm_file *dfile,
+			 struct drm_device *ddev,
+			 uint32_t handle,
+			 uint64_t *offset);
+
+#endif /* __SDRM_SIMPLEDRM_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_damage.c b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
new file mode 100644
index 0000000..4f7af5d
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2012-2016 Red Hat, Inc.
+ *
+ * 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 <asm/unaligned.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <linux/kernel.h>
+#include "simpledrm.h"
+
+static inline void sdrm_put(u8 *dst, u32 four_cc, u16 r, u16 g, u16 b)
+{
+	switch (four_cc) {
+	case DRM_FORMAT_RGB565:
+		r >>= 11;
+		g >>= 10;
+		b >>= 11;
+		put_unaligned((u16)((r << 11) | (g << 5) | b), (u16 *)dst);
+		break;
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		r >>= 11;
+		g >>= 11;
+		b >>= 11;
+		put_unaligned((u16)((r << 10) | (g << 5) | b), (u16 *)dst);
+		break;
+	case DRM_FORMAT_RGB888:
+		r >>= 8;
+		g >>= 8;
+		b >>= 8;
+#ifdef __LITTLE_ENDIAN
+		dst[2] = r;
+		dst[1] = g;
+		dst[0] = b;
+#elif defined(__BIG_ENDIAN)
+		dst[0] = r;
+		dst[1] = g;
+		dst[2] = b;
+#endif
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		r >>= 8;
+		g >>= 8;
+		b >>= 8;
+		put_unaligned((u32)((r << 16) | (g << 8) | b), (u32 *)dst);
+		break;
+	case DRM_FORMAT_ABGR8888:
+		r >>= 8;
+		g >>= 8;
+		b >>= 8;
+		put_unaligned((u32)((b << 16) | (g << 8) | r), (u32 *)dst);
+		break;
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		r >>= 4;
+		g >>= 4;
+		b >>= 4;
+		put_unaligned((u32)((r << 20) | (g << 10) | b), (u32 *)dst);
+		break;
+	}
+}
+
+static void sdrm_blit_from_xrgb8888(const u8 *src,
+				    u32 src_stride,
+				    u32 src_bpp,
+				    u8 *dst,
+				    u32 dst_stride,
+				    u32 dst_bpp,
+				    u32 dst_four_cc,
+				    u32 width,
+				    u32 height)
+{
+	u32 val, i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i) {
+			val = get_unaligned((const u32 *)&src[i * src_bpp]);
+			sdrm_put(&dst[i * dst_bpp], dst_four_cc,
+				 (val & 0x00ff0000U) >> 8,
+				 (val & 0x0000ff00U),
+				 (val & 0x000000ffU) << 8);
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit_from_rgb565(const u8 *src,
+				  u32 src_stride,
+				  u32 src_bpp,
+				  u8 *dst,
+				  u32 dst_stride,
+				  u32 dst_bpp,
+				  u32 dst_four_cc,
+				  u32 width,
+				  u32 height)
+{
+	u32 val, i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i) {
+			val = get_unaligned((const u16 *)&src[i * src_bpp]);
+			sdrm_put(&dst[i * dst_bpp], dst_four_cc,
+				 (val & 0xf800),
+				 (val & 0x07e0) << 5,
+				 (val & 0x001f) << 11);
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit_lines(const u8 *src,
+			    u32 src_stride,
+			    u8 *dst,
+			    u32 dst_stride,
+			    u32 bpp,
+			    u32 width,
+			    u32 height)
+{
+	u32 len;
+
+	len = width * bpp;
+
+	while (height--) {
+		memcpy(dst, src, len);
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit(struct sdrm_hw *hw,
+		      struct sdrm_fb *fb,
+		      u32 x,
+		      u32 y,
+		      u32 width,
+		      u32 height)
+{
+	u32 src_bpp, dst_bpp;
+	u8 *src, *dst;
+
+	src = fb->bo->vmapping;
+	src_bpp = DIV_ROUND_UP(fb->base.bits_per_pixel, 8);
+	src += fb->base.offsets[0] + y * fb->base.pitches[0] + x * src_bpp;
+
+	dst = hw->map;
+	dst_bpp = DIV_ROUND_UP(hw->bpp, 8);
+	dst += y * hw->stride + x * dst_bpp;
+
+	if (fb->base.pixel_format == hw->format) {
+		/* if formats are identical, do a line-by-line copy.. */
+		sdrm_blit_lines(src, fb->base.pitches[0],
+				dst, hw->stride, src_bpp, width, height);
+	} else {
+		/* ..otherwise call slow blit-function */
+		switch (fb->base.pixel_format) {
+		case DRM_FORMAT_ARGB8888:
+		case DRM_FORMAT_XRGB8888:
+			sdrm_blit_from_xrgb8888(src, fb->base.pitches[0],
+						src_bpp, dst, hw->stride,
+						dst_bpp, hw->format,
+						width, height);
+			break;
+		case DRM_FORMAT_RGB565:
+			sdrm_blit_from_rgb565(src, fb->base.pitches[0],
+					      src_bpp, dst, hw->stride,
+					      dst_bpp, hw->format,
+					      width, height);
+			break;
+		}
+	}
+}
+
+void sdrm_dirty(struct sdrm_fb *fb, u32 x, u32 y, u32 width, u32 height)
+{
+	struct sdrm_device *sdrm = fb->base.dev->dev_private;
+
+	if (WARN_ON(!fb->bo->vmapping))
+		return;
+
+	mutex_lock(&sdrm->hw->lock);
+	if (sdrm->hw->map)
+		sdrm_blit(sdrm->hw, fb, x, y, width, height);
+	mutex_unlock(&sdrm->hw->lock);
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
new file mode 100644
index 0000000..d569120
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2012-2016 Red Hat, Inc.
+ *
+ * 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 <linux/atomic.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/string.h>
+#include "simpledrm.h"
+
+static struct drm_driver sdrm_drm_driver;
+static DEFINE_MUTEX(sdrm_lock);
+
+static int sdrm_hw_identify(struct platform_device *pdev,
+			    struct simplefb_platform_data *modep,
+			    struct simplefb_format *formatp,
+			    struct resource **memp,
+			    u32 *bppp)
+{
+	static const struct simplefb_format valid_formats[] = SIMPLEFB_FORMATS;
+	struct simplefb_platform_data pm = {}, *mode = pdev->dev.platform_data;
+	struct device_node *np = pdev->dev.of_node;
+	const struct simplefb_format *format = NULL;
+	struct resource *mem;
+	unsigned int depth;
+	int r, bpp;
+	size_t i;
+
+	if (!mode) {
+		if (!np)
+			return -ENODEV;
+
+		mode = &pm;
+
+		r = of_property_read_u32(np, "width", &mode->width);
+		if (r >= 0)
+			r = of_property_read_u32(np, "height", &mode->height);
+		if (r >= 0)
+			r = of_property_read_u32(np, "stride", &mode->stride);
+		if (r >= 0)
+			r = of_property_read_string(np, "format",
+						    &mode->format);
+		if (r < 0)
+			return r;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem)
+		return -ENODEV;
+
+	for (i = 0; i < ARRAY_SIZE(valid_formats); ++i) {
+		if (!strcmp(mode->format, valid_formats[i].name)) {
+			format = &valid_formats[i];
+			break;
+		}
+	}
+
+	if (!format)
+		return -ENODEV;
+
+	switch (format->fourcc) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		/*
+		 * You must adjust sdrm_put() whenever you add a new format
+		 * here, otherwise, blitting operations will not work.
+		 * Furthermore, include/linux/platform_data/simplefb.h needs
+		 * to be adjusted so the platform-device actually allows this
+		 * format.
+		 */
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	drm_fb_get_bpp_depth(format->fourcc, &depth, &bpp);
+	if (!bpp)
+		return -ENODEV;
+	if (resource_size(mem) < mode->stride * mode->height)
+		return -ENODEV;
+	if ((bpp + 7) / 8 * mode->width > mode->stride)
+		return -ENODEV;
+
+	*modep = *mode;
+	*formatp = *format;
+	*memp = mem;
+	*bppp = bpp;
+	return 0;
+}
+
+static struct sdrm_hw *sdrm_hw_new(const struct simplefb_platform_data *mode,
+				   const struct simplefb_format *format,
+				   const struct resource *mem,
+				   u32 bpp)
+{
+	struct sdrm_hw *hw;
+
+	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+	if (!hw)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&hw->lock);
+	hw->width = mode->width;
+	hw->height = mode->height;
+	hw->stride = mode->stride;
+	hw->bpp = bpp;
+	hw->format = format->fourcc;
+	hw->base = mem->start;
+	hw->size = resource_size(mem);
+
+	return hw;
+}
+
+static struct sdrm_hw *sdrm_hw_free(struct sdrm_hw *hw)
+{
+	if (!hw)
+		return NULL;
+
+	WARN_ON(hw->map);
+	mutex_destroy(&hw->lock);
+	kfree(hw);
+
+	return NULL;
+}
+
+static int sdrm_hw_bind(struct sdrm_hw *hw)
+{
+	mutex_lock(&hw->lock);
+	if (!hw->map)
+		hw->map = ioremap_wc(hw->base, hw->size);
+	mutex_unlock(&hw->lock);
+
+	return hw->map ? 0 : -EIO;
+}
+
+static void sdrm_hw_unbind(struct sdrm_hw *hw)
+{
+	if (!hw)
+		return;
+
+	mutex_lock(&hw->lock);
+	if (hw->map) {
+		iounmap(hw->map);
+		hw->map = NULL;
+	}
+	mutex_unlock(&hw->lock);
+}
+
+static struct sdrm_device *sdrm_device_free(struct sdrm_device *sdrm)
+{
+	if (!sdrm)
+		return NULL;
+
+	WARN_ON(atomic_read(&sdrm->n_used) != INT_MIN);
+	sdrm->hw = sdrm_hw_free(sdrm->hw);
+	drm_dev_unref(sdrm->ddev);
+	kfree(sdrm);
+
+	return NULL;
+}
+
+static struct sdrm_device *sdrm_device_new(struct platform_device *pdev,
+					   struct sdrm_hw *hw)
+{
+	struct sdrm_device *sdrm;
+	int r;
+
+	sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
+	if (!sdrm)
+		return ERR_PTR(-ENOMEM);
+
+	atomic_set(&sdrm->n_used, INT_MIN);
+
+	sdrm->ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev);
+	if (!sdrm->ddev) {
+		r = -ENOMEM;
+		goto error;
+	}
+
+	sdrm->ddev->dev_private = sdrm;
+	sdrm->hw = hw;
+
+	return sdrm;
+
+error:
+	sdrm_device_free(sdrm);
+	return ERR_PTR(r);
+}
+
+static void sdrm_device_unbind(struct sdrm_device *sdrm)
+{
+	if (sdrm) {
+		sdrm_kms_unbind(sdrm);
+		sdrm_hw_unbind(sdrm->hw);
+		sdrm_of_unbind(sdrm);
+	}
+}
+
+static int sdrm_device_bind(struct sdrm_device *sdrm)
+{
+	int r;
+
+	r = sdrm_of_bind(sdrm);
+	if (r < 0)
+		goto error;
+
+	r = sdrm_hw_bind(sdrm->hw);
+	if (r < 0)
+		goto error;
+
+	r = sdrm_kms_bind(sdrm);
+	if (r < 0)
+		goto error;
+
+	return 0;
+
+error:
+	sdrm_device_unbind(sdrm);
+	return r;
+}
+
+static int sdrm_device_acquire(struct sdrm_device *sdrm)
+{
+	return (sdrm && atomic_inc_unless_negative(&sdrm->n_used))
+		? 0 : -ENODEV;
+}
+
+static void sdrm_device_release(struct sdrm_device *sdrm)
+{
+	if (sdrm && atomic_dec_return(&sdrm->n_used) == INT_MIN) {
+		sdrm_device_unbind(sdrm);
+		sdrm_device_free(sdrm);
+	}
+}
+
+static int sdrm_fop_open(struct inode *inode, struct file *file)
+{
+	struct drm_device *ddev;
+	int r;
+
+	mutex_lock(&sdrm_lock);
+	r = drm_open(inode, file);
+	if (r >= 0) {
+		ddev = file->private_data;
+		r = sdrm_device_acquire(ddev->dev_private);
+		if (r < 0)
+			drm_release(inode, file);
+	}
+	mutex_unlock(&sdrm_lock);
+
+	return r;
+}
+
+static int sdrm_fop_release(struct inode *inode, struct file *file)
+{
+	struct drm_file *dfile = file->private_data;
+	struct drm_device *ddev = dfile->minor->dev;
+	struct sdrm_device *sdrm = ddev->dev_private;
+	int res;
+
+	res = drm_release(inode, file);
+	sdrm_device_release(sdrm);
+	return res;
+}
+
+static int sdrm_fop_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct drm_file *dfile = file->private_data;
+	struct drm_device *dev = dfile->minor->dev;
+	struct drm_gem_object *obj = NULL;
+	struct drm_vma_offset_node *node;
+	int r;
+
+	drm_vma_offset_lock_lookup(dev->vma_offset_manager);
+	node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
+						  vma->vm_pgoff,
+						  vma_pages(vma));
+	if (likely(node)) {
+		obj = container_of(node, struct drm_gem_object, vma_node);
+		if (!kref_get_unless_zero(&obj->refcount))
+			obj = NULL;
+	}
+	drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
+
+	if (!obj)
+		return -EINVAL;
+
+	if (!drm_vma_node_is_allowed(node, dfile)) {
+		drm_gem_object_unreference_unlocked(obj);
+		return -EACCES;
+	}
+
+	if (vma->vm_file)
+		fput(vma->vm_file);
+	vma->vm_file = get_file(obj->filp);
+	vma->vm_pgoff = 0;
+
+	r = obj->filp->f_op->mmap(obj->filp, vma);
+	drm_gem_object_unreference_unlocked(obj);
+	return r;
+}
+
+static int sdrm_simplefb_probe(struct platform_device *pdev)
+{
+	struct simplefb_platform_data hw_mode;
+	struct simplefb_format hw_format;
+	struct sdrm_device *sdrm = NULL;
+	struct sdrm_hw *hw = NULL;
+	struct resource *hw_mem;
+	u32 hw_bpp;
+	int r;
+
+	r = sdrm_hw_identify(pdev, &hw_mode, &hw_format, &hw_mem, &hw_bpp);
+	if (r < 0)
+		goto error;
+
+	hw = sdrm_hw_new(&hw_mode, &hw_format, hw_mem, hw_bpp);
+	if (IS_ERR(hw)) {
+		r = PTR_ERR(hw);
+		hw = NULL;
+		goto error;
+	}
+
+	sdrm = sdrm_device_new(pdev, hw);
+	if (IS_ERR(sdrm)) {
+		r = PTR_ERR(sdrm);
+		sdrm = NULL;
+		goto error;
+	}
+	hw = NULL;
+	platform_set_drvdata(pdev, sdrm);
+
+	r = sdrm_device_bind(sdrm);
+	if (r < 0)
+		goto error;
+
+	/* mark device as enabled and acquire bus ref */
+	WARN_ON(atomic_read(&sdrm->n_used) != INT_MIN);
+	atomic_set(&sdrm->n_used, 1);
+
+	r = drm_dev_register(sdrm->ddev, 0);
+	if (r < 0) {
+		/* mark device as disabled and drop bus ref */
+		WARN_ON(atomic_add_return(INT_MIN, &sdrm->n_used) != INT_MIN);
+		sdrm_device_release(sdrm);
+		return r;
+	}
+
+	dev_info(sdrm->ddev->dev, "initialized %s on minor %d\n",
+		 sdrm->ddev->driver->name, sdrm->ddev->primary->index);
+
+	return 0;
+
+error:
+	sdrm_device_unbind(sdrm);
+	sdrm_device_free(sdrm);
+	sdrm_hw_free(hw);
+	return r;
+}
+
+static int sdrm_simplefb_remove(struct platform_device *pdev)
+{
+	struct sdrm_device *sdrm = platform_get_drvdata(pdev);
+
+	/* mark device as disabled */
+	atomic_add(INT_MIN, &sdrm->n_used);
+	sdrm_hw_unbind(sdrm->hw);
+
+	mutex_lock(&sdrm_lock);
+	drm_dev_unregister(sdrm->ddev);
+	sdrm_device_release(sdrm);
+	mutex_unlock(&sdrm_lock);
+
+	return 0;
+}
+
+static const struct file_operations sdrm_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = sdrm_fop_open,
+	.release = sdrm_fop_release,
+	.mmap = sdrm_fop_mmap,
+	.poll = drm_poll,
+	.read = drm_read,
+	.unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+	.llseek = noop_llseek,
+};
+
+static struct drm_driver sdrm_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops = &sdrm_drm_fops,
+
+	.gem_free_object = sdrm_bo_free,
+
+	.dumb_create = sdrm_dumb_create,
+	.dumb_map_offset = sdrm_dumb_map_offset,
+	.dumb_destroy = drm_gem_dumb_destroy,
+
+	.name = "simpledrm",
+	.desc = "Simple firmware framebuffer DRM driver",
+	.date = "20160901",
+};
+
+static const struct of_device_id sdrm_simplefb_of_match[] = {
+	{ .compatible = "simple-framebuffer", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sdrm_simplefb_of_match);
+
+static struct platform_driver sdrm_simplefb_driver = {
+	.probe = sdrm_simplefb_probe,
+	.remove = sdrm_simplefb_remove,
+	.driver = {
+		.name = "simple-framebuffer",
+		.mod_name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+		.of_match_table = sdrm_simplefb_of_match,
+	},
+};
+
+static int __init sdrm_init(void)
+{
+	int r;
+
+	r = platform_driver_register(&sdrm_simplefb_driver);
+	if (r < 0)
+		return r;
+
+	sdrm_of_bootstrap();
+	return 0;
+}
+
+static void __exit sdrm_exit(void)
+{
+	platform_driver_unregister(&sdrm_simplefb_driver);
+}
+
+module_init(sdrm_init);
+module_exit(sdrm_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
+MODULE_ALIAS("platform:simple-framebuffer");
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
new file mode 100644
index 0000000..4aaae6e
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012-2016 Red Hat, Inc.
+ *
+ * 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_gem.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include "simpledrm.h"
+
+struct sdrm_bo *sdrm_bo_new(struct drm_device *ddev, size_t size)
+{
+	struct sdrm_bo *bo;
+
+	WARN_ON(!size || (size & ~PAGE_MASK) != 0);
+
+	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+	if (!bo)
+		return NULL;
+
+	if (drm_gem_object_init(ddev, &bo->base, size)) {
+		kfree(bo);
+		return NULL;
+	}
+
+	return bo;
+}
+
+void sdrm_bo_free(struct drm_gem_object *dobj)
+{
+	struct sdrm_bo *bo = container_of(dobj, struct sdrm_bo, base);
+
+	if (bo->vmapping)
+		vunmap(bo->vmapping);
+	if (bo->pages)
+		drm_gem_put_pages(dobj, bo->pages, false, false);
+	drm_gem_object_release(dobj);
+	kfree(bo);
+}
+
+int sdrm_bo_vmap(struct sdrm_bo *bo)
+{
+	int r;
+
+	if (!bo->pages) {
+		bo->pages = drm_gem_get_pages(&bo->base);
+		if (IS_ERR(bo->pages)) {
+			r = PTR_ERR(bo->pages);
+			bo->pages = NULL;
+			return r;
+		}
+	}
+
+	if (!bo->vmapping) {
+		bo->vmapping = vmap(bo->pages, bo->base.size / PAGE_SIZE, 0,
+				    PAGE_KERNEL);
+		if (!bo->vmapping)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+int sdrm_dumb_create(struct drm_file *dfile,
+		     struct drm_device *ddev,
+		     struct drm_mode_create_dumb *args)
+{
+	struct sdrm_bo *bo;
+	int r;
+
+	/* overflow checks are done by DRM core */
+	args->pitch = (args->bpp + 7) / 8 * args->width;
+	args->size = PAGE_ALIGN(args->pitch * args->height);
+
+	bo = sdrm_bo_new(ddev, args->size);
+	if (!bo)
+		return -ENOMEM;
+
+	r = drm_gem_handle_create(dfile, &bo->base, &args->handle);
+	drm_gem_object_unreference_unlocked(&bo->base);
+	return r;
+}
+
+int sdrm_dumb_map_offset(struct drm_file *dfile,
+			 struct drm_device *ddev,
+			 uint32_t handle,
+			 uint64_t *offset)
+{
+	struct drm_gem_object *dobj;
+	int r;
+
+	dobj = drm_gem_object_lookup(dfile, handle);
+	if (!dobj)
+		return -ENOENT;
+
+	r = drm_gem_create_mmap_offset(dobj);
+	if (r >= 0)
+		*offset = drm_vma_node_offset_addr(&dobj->vma_node);
+	drm_gem_object_unreference_unlocked(dobj);
+	return r;
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
new file mode 100644
index 0000000..00101c9
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
@@ -0,0 +1,263 @@
+/*
+ * 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_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "simpledrm.h"
+
+static const uint32_t sdrm_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+};
+
+static int sdrm_conn_get_modes(struct drm_connector *conn)
+{
+	struct sdrm_device *sdrm = conn->dev->dev_private;
+	struct drm_display_mode *mode;
+
+	mode = drm_cvt_mode(sdrm->ddev, sdrm->hw->width, sdrm->hw->height,
+			    60, false, false, false);
+	if (!mode)
+		return 0;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(conn, mode);
+
+	return 1;
+}
+
+static const struct drm_connector_helper_funcs sdrm_conn_hfuncs = {
+	.get_modes	= sdrm_conn_get_modes,
+	.best_encoder	= drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn,
+						  bool force)
+{
+	/*
+	 * We simulate an always connected monitor. simple-fb doesn't
+	 * provide any way to detect whether the connector is active. Hence,
+	 * signal DRM core that it is always connected.
+	 */
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sdrm_conn_ops = {
+	.dpms			= drm_atomic_helper_connector_dpms,
+	.reset			= drm_atomic_helper_connector_reset,
+	.detect			= sdrm_conn_detect,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= drm_connector_cleanup,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+};
+
+static void sdrm_crtc_send_vblank_event(struct drm_crtc *crtc)
+{
+	if (crtc->state && crtc->state->event) {
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		crtc->state->event = NULL;
+	}
+}
+
+void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+			      struct drm_plane_state *plane_state)
+{
+	struct drm_framebuffer *dfb = pipe->plane.state->fb;
+	struct sdrm_fb *fb;
+
+	sdrm_crtc_send_vblank_event(&pipe->crtc);
+
+	if (dfb) {
+		fb = container_of(dfb, struct sdrm_fb, base);
+		pipe->plane.fb = dfb;
+		sdrm_dirty(fb, 0, 0, dfb->width, dfb->height);
+	}
+}
+
+static void sdrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+				     struct drm_crtc_state *crtc_state)
+{
+	sdrm_crtc_send_vblank_event(&pipe->crtc);
+}
+
+static void sdrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	sdrm_crtc_send_vblank_event(&pipe->crtc);
+}
+
+static const struct drm_simple_display_pipe_funcs sdrm_pipe_funcs = {
+	.update		= sdrm_display_pipe_update,
+	.enable		= sdrm_display_pipe_enable,
+	.disable	= sdrm_display_pipe_disable,
+};
+
+static int sdrm_fb_create_handle(struct drm_framebuffer *dfb,
+				 struct drm_file *dfile,
+				 unsigned int *handle)
+{
+	struct sdrm_fb *fb = container_of(dfb, struct sdrm_fb, base);
+
+	return drm_gem_handle_create(dfile, &fb->bo->base, handle);
+}
+
+static int sdrm_fb_dirty(struct drm_framebuffer *dfb,
+			 struct drm_file *dfile,
+			 unsigned int flags,
+			 unsigned int color,
+			 struct drm_clip_rect *clips,
+			 unsigned int n_clips)
+{
+	struct sdrm_fb *fb = container_of(dfb, struct sdrm_fb, base);
+	struct sdrm_device *sdrm = dfb->dev->dev_private;
+	unsigned int i;
+
+	drm_modeset_lock_all(sdrm->ddev);
+	if (dfb == sdrm->pipe.plane.fb) {
+		if (!clips || !n_clips) {
+			sdrm_dirty(fb, 0, 0, dfb->width, dfb->height);
+		} else {
+			for (i = 0; i < n_clips; i++) {
+				if (clips[i].x1 > clips[i].x2 ||
+				    clips[i].x2 > dfb->width ||
+				    clips[i].y1 > clips[i].y2 ||
+				    clips[i].y2 > dfb->height)
+					continue;
+
+				sdrm_dirty(fb, clips[i].x1, clips[i].y1,
+					   clips[i].x2 - clips[i].x1,
+					   clips[i].y2 - clips[i].y1);
+			}
+		}
+	}
+	drm_modeset_unlock_all(sdrm->ddev);
+
+	return 0;
+}
+
+static void sdrm_fb_destroy(struct drm_framebuffer *dfb)
+{
+	struct sdrm_fb *fb = container_of(dfb, struct sdrm_fb, base);
+
+	drm_framebuffer_cleanup(dfb);
+	drm_gem_object_unreference_unlocked(&fb->bo->base);
+	kfree(fb);
+}
+
+static const struct drm_framebuffer_funcs sdrm_fb_ops = {
+	.create_handle		= sdrm_fb_create_handle,
+	.dirty			= sdrm_fb_dirty,
+	.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 drm_gem_object *dobj;
+	struct sdrm_fb *fb = NULL;
+	struct sdrm_bo *bo;
+	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;
+
+	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+	if (!fb) {
+		r = -ENOMEM;
+		goto error;
+	}
+
+	fb->bo = bo;
+	drm_helper_mode_fill_fb_struct(&fb->base, cmd);
+
+	r = drm_framebuffer_init(ddev, &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;
+
+error:
+	kfree(fb);
+	drm_gem_object_unreference_unlocked(dobj);
+	return ERR_PTR(r);
+}
+
+static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
+	.fb_create		= sdrm_fb_create,
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+};
+
+int sdrm_kms_bind(struct sdrm_device *sdrm)
+{
+	struct drm_connector *conn = &sdrm->conn;
+	struct drm_device *ddev = sdrm->ddev;
+	int r;
+
+	drm_mode_config_init(ddev);
+	ddev->mode_config.min_width = sdrm->hw->width;
+	ddev->mode_config.max_width = sdrm->hw->width;
+	ddev->mode_config.min_height = sdrm->hw->height;
+	ddev->mode_config.max_height = sdrm->hw->height;
+	ddev->mode_config.preferred_depth = sdrm->hw->bpp;
+	ddev->mode_config.funcs = &sdrm_mode_config_ops;
+	drm_connector_helper_add(conn, &sdrm_conn_hfuncs);
+
+	r = drm_connector_init(ddev, conn, &sdrm_conn_ops,
+			       DRM_MODE_CONNECTOR_VIRTUAL);
+	if (r < 0)
+		goto error;
+
+	r = drm_simple_display_pipe_init(ddev, &sdrm->pipe, &sdrm_pipe_funcs,
+					 sdrm_formats,
+					 ARRAY_SIZE(sdrm_formats), conn);
+	if (r < 0)
+		goto error;
+
+	drm_mode_config_reset(ddev);
+	return 0;
+
+error:
+	drm_mode_config_cleanup(ddev);
+	return r;
+}
+
+void sdrm_kms_unbind(struct sdrm_device *sdrm)
+{
+	if (sdrm->ddev->mode_config.funcs)
+		drm_mode_config_cleanup(sdrm->ddev);
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_of.c b/drivers/gpu/drm/simpledrm/simpledrm_of.c
new file mode 100644
index 0000000..5620000
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_of.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2012-2016 Red Hat, Inc.
+ *
+ * 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 <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include "simpledrm.h"
+
+#ifdef CONFIG_COMMON_CLK
+
+static int sdrm_of_bind_clocks(struct sdrm_device *sdrm,
+			       struct device_node *np)
+{
+	struct clk *clock;
+	size_t i, n;
+	int r;
+
+	n = of_clk_get_parent_count(np);
+	if (n < 1)
+		return 0;
+
+	sdrm->clks = kcalloc(n, sizeof(*sdrm->clks), GFP_KERNEL);
+	if (!sdrm->clks)
+		return -ENOMEM;
+
+	for (i = 0; i < n; ++i) {
+		clock = of_clk_get(np, i);
+		if (!IS_ERR(clock)) {
+			sdrm->clks[sdrm->n_clks++] = clock;
+		} else if (PTR_ERR(clock) == -EPROBE_DEFER) {
+			r = -EPROBE_DEFER;
+			goto error;
+		} else {
+			dev_err(sdrm->ddev->dev, "cannot find clock %zu: %ld\n",
+				i, PTR_ERR(clock));
+		}
+	}
+
+	for (i = 0; i < sdrm->n_clks; ++i) {
+		if (!sdrm->clks[i])
+			continue;
+
+		r = clk_prepare_enable(sdrm->clks[i]);
+		if (r < 0) {
+			dev_err(sdrm->ddev->dev,
+				"cannot find clock %zu: %d\n", i, r);
+			clk_put(sdrm->clks[i]);
+			sdrm->clks[i] = NULL;
+		}
+	}
+
+	return 0;
+
+error:
+	for (i = 0; i < sdrm->n_clks; ++i)
+		clk_put(sdrm->clks[i]);
+	kfree(sdrm->clks);
+	sdrm->clks = NULL;
+	sdrm->n_clks = 0;
+	return r;
+}
+
+static void sdrm_of_unbind_clocks(struct sdrm_device *sdrm)
+{
+	size_t i;
+
+	for (i = 0; i < sdrm->n_clks; ++i) {
+		clk_disable_unprepare(sdrm->clks[i]);
+		clk_put(sdrm->clks[i]);
+	}
+
+	kfree(sdrm->clks);
+	sdrm->clks = NULL;
+	sdrm->n_clks = 0;
+}
+
+#else /* CONFIG_COMMON_CLK */
+
+static int sdrm_of_bind_clocks(struct sdrm_device *sdrm,
+			       struct device_node *np)
+{
+	return 0;
+}
+
+static void sdrm_of_unbind_clocks(struct sdrm_device *sdrm)
+{
+}
+
+#endif /* CONFIG_COMMON_CLK */
+
+#ifdef CONFIG_REGULATOR
+
+static int sdrm_of_bind_regulators(struct sdrm_device *sdrm,
+				   struct device_node *np)
+{
+	struct regulator *regulator;
+	struct property *prop;
+	char *p, *name;
+	size_t i, n;
+	int r;
+
+	n = 0;
+	for_each_property_of_node(np, prop) {
+		p = strstr(prop->name, "-supply");
+		if (p && p != prop->name)
+			++n;
+	}
+
+	if (n < 1)
+		return 0;
+
+	sdrm->regulators = kcalloc(n, sizeof(*sdrm->regulators), GFP_KERNEL);
+	if (!sdrm->regulators)
+		return -ENOMEM;
+
+	for_each_property_of_node(np, prop) {
+		p = strstr(prop->name, "-supply");
+		if (!p || p == prop->name)
+			continue;
+
+		name = kstrndup(prop->name, p - prop->name, GFP_TEMPORARY);
+		if (!name)
+			continue;
+
+		regulator = regulator_get_optional(sdrm->ddev->dev, name);
+		kfree(name);
+
+		if (!IS_ERR(regulator)) {
+			sdrm->regulators[sdrm->n_regulators++] = regulator;
+		} else if (PTR_ERR(regulator) == -EPROBE_DEFER) {
+			r = -EPROBE_DEFER;
+			goto error;
+		} else {
+			dev_warn(sdrm->ddev->dev,
+				 "cannot find regulator %s: %ld\n",
+				 prop->name, PTR_ERR(regulator));
+		}
+	}
+
+	for (i = 0; i < sdrm->n_regulators; ++i) {
+		if (!sdrm->regulators[i])
+			continue;
+
+		r = regulator_enable(sdrm->regulators[i]);
+		if (r < 0) {
+			dev_warn(sdrm->ddev->dev,
+				 "cannot enable regulator %zu: %d\n", i, r);
+			regulator_put(sdrm->regulators[i]);
+			sdrm->regulators[i] = NULL;
+		}
+	}
+
+	return 0;
+
+error:
+	for (i = 0; i < sdrm->n_regulators; ++i)
+		if (sdrm->regulators[i])
+			regulator_put(sdrm->regulators[i]);
+	kfree(sdrm->regulators);
+	sdrm->regulators = NULL;
+	sdrm->n_regulators = 0;
+	return r;
+}
+
+static void sdrm_of_unbind_regulators(struct sdrm_device *sdrm)
+{
+	size_t i;
+
+	for (i = 0; i < sdrm->n_regulators; ++i) {
+		if (sdrm->regulators[i]) {
+			regulator_disable(sdrm->regulators[i]);
+			regulator_put(sdrm->regulators[i]);
+		}
+	}
+
+	kfree(sdrm->regulators);
+	sdrm->regulators = NULL;
+	sdrm->n_regulators = 0;
+}
+
+#else /* CONFIG_REGULATORS */
+
+static int sdrm_of_bind_regulators(struct sdrm_device *sdrm,
+				   struct device_node *np)
+{
+	return 0;
+}
+
+static void sdrm_of_unbind_regulators(struct sdrm_device *sdrm)
+{
+}
+
+#endif /* CONFIG_REGULATORS */
+
+#ifdef CONFIG_OF
+
+void sdrm_of_bootstrap(void)
+{
+#ifdef CONFIG_OF_ADDRESS
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "simple-framebuffer")
+		of_platform_device_create(np, NULL, NULL);
+#endif
+}
+
+int sdrm_of_bind(struct sdrm_device *sdrm)
+{
+	int r;
+
+	if (WARN_ON(sdrm->n_clks > 0 || sdrm->n_regulators > 0))
+		return 0;
+	if (!sdrm->ddev->dev->of_node)
+		return 0;
+
+	r = sdrm_of_bind_clocks(sdrm, sdrm->ddev->dev->of_node);
+	if (r < 0)
+		goto error;
+
+	r = sdrm_of_bind_regulators(sdrm, sdrm->ddev->dev->of_node);
+	if (r < 0)
+		goto error;
+
+	return 0;
+
+error:
+	sdrm_of_unbind(sdrm);
+	return r;
+}
+
+void sdrm_of_unbind(struct sdrm_device *sdrm)
+{
+	sdrm_of_unbind_regulators(sdrm);
+	sdrm_of_unbind_clocks(sdrm);
+}
+
+#else /* CONFIG_OF */
+
+void sdrm_of_bootstrap(void)
+{
+}
+
+int sdrm_of_bind(struct sdrm_device *sdrm)
+{
+	return 0;
+}
+
+void sdrm_of_unbind(struct sdrm_device *sdrm)
+{
+}
+
+#endif /* CONFIG_OF */
