diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 983201b..e116998 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -212,3 +212,5 @@ source "drivers/gpu/drm/cirrus/Kconfig"
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
+
+source "drivers/gpu/drm/dvbe/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6f58c81..daf3114 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -50,4 +50,5 @@ obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
+obj-$(CONFIG_DRM_DVBE) += dvbe/
 obj-y			+= i2c/
diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig
new file mode 100644
index 0000000..a5277c6
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/Kconfig
@@ -0,0 +1,14 @@
+config DRM_DVBE
+	tristate "VESA BIOS Extension DRM Driver"
+	depends on DRM
+	select DRM_KMS_HELPER
+	help
+	  This is a DRM/KMS driver for VESA BIOS Extension (VBE) compatible
+	  cards. It does not allow mode-switching during runtime but requires
+	  the kernel to setup the mode with the vga= kernel command line
+	  option.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called dvbe.
diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile
new file mode 100644
index 0000000..f6fb888
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/Makefile
@@ -0,0 +1,4 @@
+ccflags-y := -Iinclude/drm
+
+dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o dvbe_vesa.o
+obj-$(CONFIG_DRM_DVBE) := dvbe.o
diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h
new file mode 100644
index 0000000..68fd452
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe.h
@@ -0,0 +1,98 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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.
+ */
+
+#ifndef DVBE_DRV_H
+#define DVBE_DRV_H
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+
+/* dvbe devices */
+
+struct dvbe_device {
+	struct drm_device *ddev;
+
+	/* vbe information */
+	unsigned long vbe_addr;
+	unsigned long vbe_vsize;
+	unsigned long vbe_size;
+	unsigned int vbe_depth;
+	unsigned int vbe_bpp;
+	unsigned int vbe_width;
+	unsigned int vbe_height;
+	unsigned int vbe_stride;
+	uint8_t vbe_red_size;
+	uint8_t vbe_red_pos;
+	uint8_t vbe_green_size;
+	uint8_t vbe_green_pos;
+	uint8_t vbe_blue_size;
+	uint8_t vbe_blue_pos;
+	uint8_t *vbe_map;
+
+	/* mode-setting objects */
+	struct drm_crtc crtc;
+	struct drm_encoder enc;
+	struct drm_connector conn;
+	struct drm_display_mode *mode;
+};
+
+int dvbe_drm_load(struct drm_device *ddev, unsigned long flags);
+int dvbe_drm_unload(struct drm_device *ddev);
+int dvbe_drm_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* dvbe gem objects */
+
+struct dvbe_gem_object {
+	struct drm_gem_object base;
+	struct page **pages;
+	uint8_t *vmapping;
+};
+
+#define to_dvbe_bo(x) container_of(x, struct dvbe_gem_object, base)
+
+int dvbe_gem_init_object(struct drm_gem_object *obj);
+void dvbe_gem_free_object(struct drm_gem_object *obj);
+int dvbe_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
+int dvbe_gem_vmap(struct dvbe_gem_object *obj);
+void dvbe_gem_vunmap(struct dvbe_gem_object *obj);
+
+/* dumb buffers */
+
+int dvbe_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
+		     struct drm_mode_create_dumb *arg);
+int dvbe_dumb_destroy(struct drm_file *file_priv, struct drm_device *ddev,
+		      uint32_t handle);
+int dvbe_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
+			 uint32_t handle, uint64_t *offset);
+
+/* dvbe framebuffers */
+
+struct dvbe_framebuffer {
+	struct drm_framebuffer base;
+	struct dvbe_gem_object *obj;
+};
+
+#define to_dvbe_fb(x) container_of(x, struct dvbe_framebuffer, base)
+
+/* vesa helpers */
+
+int dvbe_vesa_init(struct dvbe_device *dvbe);
+void dvbe_vesa_cleanup(struct dvbe_device *dvbe);
+int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb,
+		     unsigned int flags, unsigned int color,
+		     struct drm_clip_rect *clips, unsigned int num);
+
+#endif /* DVBE_DRV_H */
diff --git a/drivers/gpu/drm/dvbe/dvbe_drv.c b/drivers/gpu/drm/dvbe/dvbe_drv.c
new file mode 100644
index 0000000..daeab16
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_drv.c
@@ -0,0 +1,133 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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 <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include "dvbe.h"
+
+static const struct vm_operations_struct dvbe_gem_vm_ops = {
+	.fault = dvbe_gem_fault,
+	.open = drm_gem_vm_open,
+	.close = drm_gem_vm_close,
+};
+
+static const struct file_operations dvbe_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.mmap = dvbe_drm_mmap,
+	.poll = drm_poll,
+	.read = drm_read,
+	.unlocked_ioctl = drm_ioctl,
+	.release = drm_release,
+	.fasync = drm_fasync,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+	.llseek = noop_llseek,
+};
+
+static struct drm_driver dvbe_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM,
+	.load = dvbe_drm_load,
+	.unload = dvbe_drm_unload,
+	.fops = &dvbe_drm_fops,
+
+	.gem_init_object = dvbe_gem_init_object,
+	.gem_free_object = dvbe_gem_free_object,
+	.gem_vm_ops = &dvbe_gem_vm_ops,
+
+	.dumb_create = dvbe_dumb_create,
+	.dumb_map_offset = dvbe_dumb_map_offset,
+	.dumb_destroy = dvbe_dumb_destroy,
+
+	.name = "dvbe",
+	.desc = "DRM VESA BIOS Extension Driver",
+	.date = "20130127",
+	.major = 0,
+	.minor = 0,
+	.patchlevel = 1,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int dvbe_platform_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static int dvbe_platform_resume(struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops dvbe_platform_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(dvbe_platform_suspend, dvbe_platform_resume)
+};
+
+static int dvbe_platform_probe(struct platform_device *pdev)
+{
+	return drm_platform_init(&dvbe_drm_driver, pdev);
+}
+
+static int dvbe_platform_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&dvbe_drm_driver, pdev);
+
+	return 0;
+}
+
+static struct platform_driver dvbe_platform_driver = {
+	.probe = dvbe_platform_probe,
+	.remove = dvbe_platform_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "drm-dvbe",
+		.pm = &dvbe_platform_pm,
+	},
+};
+
+static struct platform_device *dvbe_platform_dev;
+
+static int __init dvbe_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&dvbe_platform_driver);
+	if (ret)
+		return ret;
+
+	dvbe_platform_dev = platform_device_register_simple("drm-dvbe", -1,
+							    NULL, 0);
+	if (IS_ERR(dvbe_platform_dev)) {
+		platform_driver_unregister(&dvbe_platform_driver);
+		return PTR_ERR(dvbe_platform_dev);
+	}
+
+	return 0;
+}
+
+static void __exit dvbe_exit(void)
+{
+	platform_device_unregister(dvbe_platform_dev);
+	platform_driver_unregister(&dvbe_platform_driver);
+}
+
+module_init(dvbe_init);
+module_exit(dvbe_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("DRM VESA BIOS Extension Driver");
diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c
new file mode 100644
index 0000000..5950d21
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_main.c
@@ -0,0 +1,432 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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.
+ */
+
+/*
+ * Main Modesetting and Control
+ * We register exactly one CRTC, encoder and connector on initialization. This
+ * simplifies the logic a lot. The actual device control is not implemented
+ * here. This file only provides the interaction with DRM core.
+ *
+ * Userspace has only one valid CRTC+encoder+connector combination which
+ * corresponds to the VESA framebuffer that we detected. The initial mode is
+ * deduced from the initial VESA configuration and should be used by user-space.
+ * Real mode-setting (i.e., changing display resolution) requires the processor
+ * to be in RealMode, which we cannot do in the kernel so it is not provided.
+ * Driver-specific ioctls may be used to allow userspace to perform mode-setting
+ * themself.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "dvbe.h"
+
+/* crtcs */
+
+static void dvbe_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+	/* There is no way to tell DRM core that we do not support DPMS.
+	 * Therefore, simply make this a no-op and always be online. */
+}
+
+static bool dvbe_crtc_mode_fixup(struct drm_crtc *crtc,
+				 const struct drm_display_mode *mode,
+				 struct drm_display_mode *adjusted_mode)
+{
+	struct dvbe_device *dvbe = crtc->dev->dev_private;
+
+	if (mode->hdisplay != dvbe->vbe_width ||
+	    mode->vdisplay != dvbe->vbe_height) {
+		dev_dbg(dvbe->ddev->dev, "invalid mode %ux%u\n",
+			mode->hdisplay, mode->vdisplay);
+		return false;
+	}
+
+	drm_mode_copy(adjusted_mode, mode);
+	return true;
+}
+
+static void dvbe_crtc_prepare(struct drm_crtc *crtc)
+{
+	/* nothing to prepare */
+}
+
+static void dvbe_crtc_commit(struct drm_crtc *crtc)
+{
+	/* all work already done by immediate mode-set */
+}
+
+static int dvbe_crtc_mode_set(struct drm_crtc *crtc,
+			      struct drm_display_mode *mode,
+			      struct drm_display_mode *adjusted_mode,
+			      int x, int y,
+			      struct drm_framebuffer *old_fb)
+{
+	struct dvbe_device *dvbe = crtc->dev->dev_private;
+	struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
+
+	/* We can scan out any framebuffer that is given. The framebuffer
+	 * allocation guarantees that it is a valid framebuffer. If the x/y
+	 * corrdinates are out of bounds, we fail. We don't care whether the
+	 * framebuffer is bigger/smaller than the real screen. The
+	 * blit-functions clip it correctly.
+	 * The mode was already checked by mode_fixup above so we can savely let
+	 * it pass. */
+
+	if (x >= dfb->base.width || y >= dfb->base.height)
+		return -EINVAL;
+
+	return dvbe_vesa_damage(dvbe, dfb, 0, 0, NULL, 0);
+}
+
+/*
+ * Shortcut if no full mode-set is needed but only the offsets or framebuffer
+ * parameters changed. See @mode_set for details.
+ */
+static int dvbe_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+				   struct drm_framebuffer *fb)
+{
+	struct dvbe_device *dvbe = crtc->dev->dev_private;
+	struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
+
+	if (x >= dfb->base.width || y >= dfb->base.height)
+		return -EINVAL;
+
+	return dvbe_vesa_damage(dvbe, dfb, 0, 0, NULL, 0);
+}
+
+static const struct drm_crtc_helper_funcs dvbe_crtc_helper_ops = {
+	.dpms = dvbe_crtc_dpms,
+	.mode_fixup = dvbe_crtc_mode_fixup,
+	.prepare = dvbe_crtc_prepare,
+	.commit = dvbe_crtc_commit,
+	.mode_set = dvbe_crtc_mode_set,
+	.mode_set_base = dvbe_crtc_mode_set_base,
+};
+
+static const struct drm_crtc_funcs dvbe_crtc_ops = {
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_crtc_helper_set_config,
+};
+
+/* encoders */
+
+static void dvbe_enc_dpms(struct drm_encoder *enc, int mode)
+{
+	/* DPMS is done by the CRTC */
+}
+
+static bool dvbe_enc_mode_fixup(struct drm_encoder *enc,
+				const struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	/* mode-fixup is done by the CRTC */
+	return true;
+}
+
+static void dvbe_enc_prepare(struct drm_encoder *enc)
+{
+	/* no preparation to do */
+}
+
+static void dvbe_enc_mode_set(struct drm_encoder *enc,
+			      struct drm_display_mode *mode,
+			      struct drm_display_mode *adjusted_mode)
+{
+	/* mode-setting is done by the CRTC */
+}
+
+static void dvbe_enc_commit(struct drm_encoder *enc)
+{
+	/* committing mode changes is done by the CRTC */
+}
+
+static const struct drm_encoder_helper_funcs dvbe_enc_helper_ops = {
+	.dpms = dvbe_enc_dpms,
+	.mode_fixup = dvbe_enc_mode_fixup,
+	.prepare = dvbe_enc_prepare,
+	.mode_set = dvbe_enc_mode_set,
+	.commit = dvbe_enc_commit,
+};
+
+static const struct drm_encoder_funcs dvbe_enc_ops = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/* connectors */
+
+static int dvbe_conn_get_modes(struct drm_connector *conn)
+{
+	struct dvbe_device *dvbe = conn->dev->dev_private;
+	struct drm_display_mode *mode;
+
+	mode = drm_gtf_mode(dvbe->ddev, dvbe->vbe_width, dvbe->vbe_height,
+			    60, 0, 0);
+	if (!mode)
+		return 0;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(conn, mode);
+	dvbe->mode = mode;
+
+	return 1;
+}
+
+static int dvbe_conn_mode_valid(struct drm_connector *conn,
+				struct drm_display_mode *mode)
+{
+	/* every mode of our own is valid */
+	return MODE_OK;
+}
+
+static struct drm_encoder *dvbe_conn_best_encoder(struct drm_connector *conn)
+{
+	struct dvbe_device *dvbe = conn->dev->dev_private;
+
+	/* We only have a single encoder for the VBE device. Hence, return it
+	 * as best-encoder so it is always used. There is no real encoder that
+	 * we control as VBE doesn't provide such information. */
+	return &dvbe->enc;
+}
+
+static const struct drm_connector_helper_funcs dvbe_conn_helper_ops = {
+	.get_modes = dvbe_conn_get_modes,
+	.mode_valid = dvbe_conn_mode_valid,
+	.best_encoder = dvbe_conn_best_encoder,
+};
+
+static enum drm_connector_status dvbe_conn_detect(struct drm_connector *conn,
+						  bool force)
+{
+	/* We simulate an always connected monitor. The VESA ABI 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 void dvbe_conn_destroy(struct drm_connector *conn)
+{
+	/* Remove the fake-connector from sysfs and then let the DRM core
+	 * clean up all associated resources. */
+	if (device_is_registered(&conn->kdev))
+		drm_sysfs_connector_remove(conn);
+	drm_connector_cleanup(conn);
+}
+
+static const struct drm_connector_funcs dvbe_conn_ops = {
+	.dpms = drm_helper_connector_dpms,
+	.detect = dvbe_conn_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = dvbe_conn_destroy,
+};
+
+/* framebuffers */
+
+static int dvbe_fb_create_handle(struct drm_framebuffer *fb,
+				 struct drm_file *dfile,
+				 unsigned int *handle)
+{
+	struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
+
+	return drm_gem_handle_create(dfile, &dfb->obj->base, handle);
+}
+
+static int dvbe_fb_dirty(struct drm_framebuffer *fb, struct drm_file *file,
+			 unsigned int flags, unsigned int color,
+			 struct drm_clip_rect *clips, unsigned int num)
+{
+	struct dvbe_device *dvbe = fb->dev->dev_private;
+	struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
+
+	if (dvbe->crtc.fb != fb)
+		return 0;
+
+	return dvbe_vesa_damage(dvbe, dfb, flags, color, clips, num);
+}
+
+static void dvbe_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
+
+	drm_framebuffer_cleanup(fb);
+	drm_gem_object_unreference_unlocked(&dfb->obj->base);
+	kfree(dfb);
+}
+
+static const struct drm_framebuffer_funcs dvbe_fb_ops = {
+	.create_handle = dvbe_fb_create_handle,
+	.dirty = dvbe_fb_dirty,
+	.destroy = dvbe_fb_destroy,
+};
+
+static struct drm_framebuffer *dvbe_fb_create(struct drm_device *ddev,
+					      struct drm_file *dfile,
+					      struct drm_mode_fb_cmd2 *cmd)
+{
+	struct dvbe_framebuffer *dfb;
+	int ret;
+	struct drm_gem_object *dobj;
+	unsigned int Bpp;
+	size_t siz;
+	void *err;
+
+	if (cmd->flags)
+		return ERR_PTR(-EINVAL);
+
+	switch (cmd->pixel_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+		Bpp = 2;
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		Bpp = 4;
+		break;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+
+	siz = cmd->pitches[0] * (cmd->height - 1) +
+	      Bpp * cmd->width + cmd->offsets[0];
+
+	dobj = drm_gem_object_lookup(ddev, dfile, cmd->handles[0]);
+	if (!dobj)
+		return ERR_PTR(-EINVAL);
+	if (dobj->size < siz) {
+		err = ERR_PTR(-ENOMEM);
+		goto err_unref;
+	}
+
+	dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
+	if (!dfb) {
+		err = ERR_PTR(-ENOMEM);
+		goto err_unref;
+	}
+	dfb->obj = to_dvbe_bo(dobj);
+
+	ret = drm_helper_mode_fill_fb_struct(&dfb->base, cmd);
+	if (ret < 0) {
+		err = ERR_PTR(ret);
+		goto err_free;
+	}
+
+	ret = drm_framebuffer_init(ddev, &dfb->base, &dvbe_fb_ops);
+	if (ret < 0) {
+		err = ERR_PTR(ret);
+		goto err_free;
+	}
+
+	return &dfb->base;
+
+err_free:
+	kfree(dfb);
+err_unref:
+	drm_gem_object_unreference_unlocked(dobj);
+	return err;
+}
+
+static const struct drm_mode_config_funcs dvbe_mode_config_ops = {
+	.fb_create = dvbe_fb_create,
+};
+
+/* initialization */
+
+int dvbe_drm_load(struct drm_device *ddev, unsigned long flags)
+{
+	struct dvbe_device *dvbe;
+	struct platform_device *pdev = ddev->platformdev;
+	int ret;
+
+	dvbe = kzalloc(sizeof(*dvbe), GFP_KERNEL);
+	if (!dvbe)
+		return -ENOMEM;
+
+	dvbe->ddev = ddev;
+	ddev->dev_private = dvbe;
+
+	ret = dvbe_vesa_init(dvbe);
+	if (ret)
+		goto err_free;
+
+	drm_mode_config_init(ddev);
+	ddev->mode_config.min_width = 0;
+	ddev->mode_config.min_height = 0;
+	ddev->mode_config.max_width = 4095;
+	ddev->mode_config.max_height = 4095;
+	ddev->mode_config.funcs = &dvbe_mode_config_ops;
+
+	ret = drm_mode_create_dirty_info_property(ddev);
+	if (ret)
+		goto err_cleanup;
+
+	ret = drm_crtc_init(ddev, &dvbe->crtc, &dvbe_crtc_ops);
+	if (ret)
+		goto err_cleanup;
+	drm_crtc_helper_add(&dvbe->crtc, &dvbe_crtc_helper_ops);
+
+	dvbe->enc.possible_crtcs = 1;
+	dvbe->enc.possible_clones = 0;
+	ret = drm_encoder_init(ddev, &dvbe->enc, &dvbe_enc_ops,
+			       DRM_MODE_ENCODER_VIRTUAL);
+	if (ret)
+		goto err_cleanup;
+	drm_encoder_helper_add(&dvbe->enc, &dvbe_enc_helper_ops);
+
+	dvbe->conn.display_info.width_mm = 0;
+	dvbe->conn.display_info.height_mm = 0;
+	dvbe->conn.interlace_allowed = false;
+	dvbe->conn.doublescan_allowed = false;
+	dvbe->conn.polled = 0;
+	ret = drm_connector_init(ddev, &dvbe->conn, &dvbe_conn_ops,
+				 DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret)
+		goto err_cleanup;
+	drm_connector_helper_add(&dvbe->conn, &dvbe_conn_helper_ops);
+
+	drm_object_attach_property(&dvbe->conn.base,
+				   ddev->mode_config.dirty_info_property, 1);
+
+	ret = drm_mode_connector_attach_encoder(&dvbe->conn, &dvbe->enc);
+	if (ret)
+		goto err_cleanup;
+
+	ret = drm_sysfs_connector_add(&dvbe->conn);
+	if (ret)
+		goto err_cleanup;
+
+	platform_set_drvdata(pdev, dvbe);
+	return 0;
+
+err_cleanup:
+	drm_mode_config_cleanup(ddev);
+	dvbe_vesa_cleanup(dvbe);
+err_free:
+	kfree(dvbe);
+	return ret;
+}
+
+int dvbe_drm_unload(struct drm_device *ddev)
+{
+	struct dvbe_device *dvbe = ddev->dev_private;
+
+	drm_mode_config_cleanup(ddev);
+	dvbe_vesa_cleanup(dvbe);
+	kfree(dvbe);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/dvbe/dvbe_mem.c b/drivers/gpu/drm/dvbe/dvbe_mem.c
new file mode 100644
index 0000000..211aebf
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_mem.c
@@ -0,0 +1,269 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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.
+ */
+
+/*
+ * DVBA Memory Objects
+ * Due to the limited functionality of the VESA ABI, we support only one memory
+ * object type which is the framebuffer object. To allow file-descriptors for
+ * frambuffers we use shmem storage for all gem objects.
+ *
+ * Furthermore, we use lazy storage allocation. As long as the storage isn't
+ * mapped or accessed, we do not allocate it. If it is mapped, we allocate an
+ * array of pointers to the pages so we can easily vmap/vunmap it for CPU
+ * access.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/shmem_fs.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_mem_util.h>
+#include "dvbe.h"
+
+static struct dvbe_gem_object *dvbe_gem_alloc_object(struct drm_device *ddev,
+						     size_t size)
+{
+	struct dvbe_gem_object *obj;
+
+	BUG_ON((size & (PAGE_SIZE - 1)) != 0);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_object_init(ddev, &obj->base, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	return obj;
+}
+
+/*
+ * First time user-space requests access to a gem object, we allocate an array
+ * of all backing pages so our page-fault handler can map them efficiently.
+ * shmem_read_mapping_page_gfp allocates missing pages with the given GFP mask
+ * which we deduce from the tmpfs-dentry object.
+ */
+static int dvbe_gem_get_pages(struct dvbe_gem_object *obj)
+{
+	size_t i, page_count = obj->base.size / PAGE_SIZE;
+	struct page *page;
+	struct address_space *mapping;
+	gfp_t gfp;
+
+	if (obj->pages)
+		return 0;
+
+	obj->pages = drm_malloc_ab(page_count, sizeof(struct page*));
+	if (!obj->pages)
+		return -ENOMEM;
+
+	mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping;
+	gfp = mapping_gfp_mask(mapping) | GFP_KERNEL;
+
+	for (i = 0; i < page_count; ++i) {
+		page = shmem_read_mapping_page_gfp(mapping, i, gfp);
+		if (IS_ERR(page))
+			goto err_pages;
+
+		obj->pages[i] = page;
+	}
+
+	return 0;
+
+err_pages:
+	while (i--)
+		page_cache_release(obj->pages[i]);
+	drm_free_large(obj->pages);
+	obj->pages = NULL;
+	return PTR_ERR(page);
+}
+
+static void dvbe_gem_put_pages(struct dvbe_gem_object *obj)
+{
+	size_t i, page_count = obj->base.size / PAGE_SIZE;
+
+	if (!obj->pages)
+		return;
+
+	for (i = 0; i < page_count; ++i)
+		page_cache_release(obj->pages[i]);
+
+	drm_free_large(obj->pages);
+	obj->pages = NULL;
+}
+
+int dvbe_gem_vmap(struct dvbe_gem_object *obj)
+{
+	int ret, page_count = obj->base.size / PAGE_SIZE;
+
+	if (obj->vmapping)
+		return 0;
+
+	ret = dvbe_gem_get_pages(obj);
+	if (ret)
+		return ret;
+
+	obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
+	if (!obj->vmapping)
+		return -ENOMEM;
+
+	return 0;
+}
+
+void dvbe_gem_vunmap(struct dvbe_gem_object *obj)
+{
+	if (!obj->vmapping)
+		return;
+
+	vunmap(obj->vmapping);
+	obj->vmapping = NULL;
+}
+
+/* drm_gem_object_alloc() is not supported */
+int dvbe_gem_init_object(struct drm_gem_object *gobj)
+{
+	BUG();
+
+	return -EINVAL;
+}
+
+void dvbe_gem_free_object(struct drm_gem_object *gobj)
+{
+	struct dvbe_gem_object *obj = to_dvbe_bo(gobj);
+
+	if (gobj->map_list.map)
+		drm_gem_free_mmap_offset(gobj);
+	dvbe_gem_vunmap(obj);
+	dvbe_gem_put_pages(obj);
+	drm_gem_object_release(gobj);
+	kfree(obj);
+}
+
+/*
+ * On page-faults we need to calculate the page-offset ourself as the vma-offset
+ * is based on the fake-offset of drm_mmap() instead of any real offset.
+ * We simply insert the page via vm_insert_page() as it has been marked as
+ * VM_MIXEDMAP in the vma.
+ */
+int dvbe_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct dvbe_gem_object *obj = to_dvbe_bo(vma->vm_private_data);
+	unsigned long off, vaddr;
+	int ret;
+
+	if (!obj->pages)
+		return VM_FAULT_SIGBUS;
+
+	vaddr = (unsigned long)vmf->virtual_address;
+	off = (vaddr - vma->vm_start) >> PAGE_SHIFT;
+
+	ret = vm_insert_page(vma, vaddr, obj->pages[off]);
+	switch (ret) {
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	case -EAGAIN:
+		set_need_resched();
+		/* fallthrough */
+	case -ERESTARTSYS:
+	case 0:
+		return VM_FAULT_NOPAGE;
+	}
+
+	return VM_FAULT_SIGBUS;
+}
+
+int dvbe_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
+		     struct drm_mode_create_dumb *args)
+{
+	struct dvbe_gem_object *obj;
+	int ret;
+
+	args->pitch = args->width * ((args->bpp + 7) / 8);
+	args->size = args->pitch * args->height;
+
+	if (!args->size)
+		return -EINVAL;
+
+	args->size = roundup(args->size, PAGE_SIZE);
+	obj = dvbe_gem_alloc_object(ddev, args->size);
+	if (!obj)
+		return -ENOMEM;
+
+	ret = drm_gem_handle_create(dfile, &obj->base, &args->handle);
+	if (ret) {
+		drm_gem_object_unreference(&obj->base);
+		return ret;
+	}
+
+	drm_gem_object_unreference(&obj->base);
+	return 0;
+}
+
+int dvbe_dumb_destroy(struct drm_file *dfile, struct drm_device *ddev,
+		      uint32_t handle)
+{
+	return drm_gem_handle_delete(dfile, handle);
+}
+
+int dvbe_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
+			 uint32_t handle, uint64_t *offset)
+{
+	struct dvbe_gem_object *obj;
+	struct drm_gem_object *gobj;
+	int ret;
+
+	mutex_lock(&ddev->struct_mutex);
+	gobj = drm_gem_object_lookup(ddev, dfile, handle);
+	if (!gobj) {
+		ret = -ENOENT;
+		goto out_unlock;
+	}
+	obj = to_dvbe_bo(gobj);
+
+	ret = dvbe_gem_get_pages(obj);
+	if (ret)
+		goto out_unref;
+
+	if (!gobj->map_list.map) {
+		ret = drm_gem_create_mmap_offset(gobj);
+		if (ret)
+			goto out_unref;
+	}
+
+	*offset = gobj->map_list.hash.key << PAGE_SHIFT;
+
+out_unref:
+	drm_gem_object_unreference(gobj);
+out_unlock:
+	mutex_unlock(&ddev->struct_mutex);
+	return ret;
+}
+
+int dvbe_drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	int ret;
+
+	ret = drm_gem_mmap(filp, vma);
+	if (ret)
+		return ret;
+
+	vma->vm_flags &= ~VM_PFNMAP;
+	vma->vm_flags |= VM_MIXEDMAP;
+	vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/dvbe/dvbe_vesa.c b/drivers/gpu/drm/dvbe/dvbe_vesa.c
new file mode 100644
index 0000000..dc815cf
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_vesa.c
@@ -0,0 +1,242 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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.
+ */
+
+/*
+ * VESA BIOS Extension Layer
+ * This layer provides access to the VBE data for the dvbe driver. It reads the
+ * mode information from the initial boot screen_info and initializes the
+ * framebuffer for user-mode access.
+ *
+ * This driver requires the VESA mode to be a TRUECOLOR format with a bpp value
+ * of 8, 15, 16 or 32. All other layouts are unsupported.
+ */
+
+#include <asm/io.h>
+#include <asm/mtrr.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/screen_info.h>
+#include <drm/drmP.h>
+#include <video/vga.h>
+#include "dvbe.h"
+
+static void dvbe_vesa_read(const uint8_t *src, unsigned int format,
+			   uint8_t *r, uint8_t *g, uint8_t *b)
+{
+	uint32_t val;
+
+	switch (format) {
+	case DRM_FORMAT_RGB565:
+		val = *(uint16_t*)src;
+		*r = (val & 0xf800) >> 11;
+		*g = (val & 0x07e0) >> 5;
+		*b = (val & 0x001f) >> 0;
+		break;
+	case DRM_FORMAT_BGR565:
+		val = *(uint16_t*)src;
+		*b = (val & 0xf800) >> 11;
+		*g = (val & 0x07e0) >> 5;
+		*r = (val & 0x001f) >> 0;
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		val = *(uint32_t*)src;
+		*r = (val & 0x00ff0000) >> 16;
+		*g = (val & 0x0000ff00) >> 8;
+		*b = (val & 0x000000ff) >> 0;
+		break;
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		val = *(uint32_t*)src;
+		*b = (val & 0x00ff0000) >> 16;
+		*g = (val & 0x0000ff00) >> 8;
+		*r = (val & 0x000000ff) >> 0;
+		break;
+	default:
+		*r = 0;
+		*g = 0;
+		*b = 0;
+	}
+}
+
+static void dvbe_vesa_write(struct dvbe_device *dvbe, uint8_t *dst,
+			    uint8_t r, uint8_t g, uint8_t b)
+{
+	uint32_t val;
+
+	val = (r >> (8 - dvbe->vbe_red_size)) << dvbe->vbe_red_pos;
+	val |= (g >> (8 - dvbe->vbe_green_size)) << dvbe->vbe_green_pos;
+	val |= (b >> (8 - dvbe->vbe_blue_size)) << dvbe->vbe_blue_pos;
+
+	switch (dvbe->vbe_bpp) {
+	case 8:
+		*dst = val & 0xff;
+		break;
+	case 16:
+		*((uint16_t*)dst) = val & 0xffff;
+		break;
+	case 32:
+		*((uint32_t*)dst) = val & 0xffffffff;
+		break;
+	}
+}
+
+static void dvbe_vesa_blit(struct dvbe_device *dvbe, const uint8_t *src,
+			   unsigned int src_stride, unsigned int src_f,
+			   unsigned int src_bpp, unsigned int x, unsigned int y,
+			   unsigned int width, unsigned int height)
+{
+	uint8_t *dst, *d, r, g, b;
+	const uint8_t *s;
+	unsigned int i, j, sBpp, dBpp;
+
+	sBpp = src_bpp / 8;
+	dBpp = dvbe->vbe_bpp / 8;
+	src = src + y * src_stride + x * sBpp;
+	dst = dvbe->vbe_map + y * dvbe->vbe_stride + x * dBpp;
+
+	for (i = 0; i < height; ++i) {
+		s = src;
+		d = dst;
+		for (j = 0; j < width; ++j) {
+			dvbe_vesa_read(src, src_f, &r, &g, &b);
+			dvbe_vesa_write(dvbe, d, r, g, b);
+			s += sBpp;
+			d += dBpp;
+		}
+
+		src += src_stride;
+		dst += dvbe->vbe_stride;
+	}
+}
+
+int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb,
+		     unsigned int flags, unsigned int color,
+		     struct drm_clip_rect *clips, unsigned int num)
+{
+	unsigned int i, maxw, maxh;
+	unsigned int width, height, ret;
+	uint8_t *src;
+	bool annotated;
+
+	ret = dvbe_gem_vmap(fb->obj);
+	if (ret)
+		return ret;
+
+	annotated = flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY;
+	src = fb->obj->vmapping + fb->base.offsets[0];
+	maxw = min(fb->base.width, dvbe->vbe_width);
+	maxh = min(fb->base.height, dvbe->vbe_height);
+
+	if (!num) {
+		dvbe_vesa_blit(dvbe, src, fb->base.pitches[0],
+			       fb->base.pixel_format, fb->base.bits_per_pixel,
+			       0, 0, maxw, maxh);
+		return 0;
+	}
+
+	for (i = 0; i < num; ++i) {
+		if (annotated && !(i & 0x1))
+			continue;
+		if (clips[i].x2 <= clips[i].x1 || clips[i].y2 <= clips[i].y1)
+			continue;
+
+		/* clip to framebuffer size */
+		if (clips[i].x1 >= maxw ||
+		    clips[i].y1 >= maxh)
+			continue;
+		if (clips[i].x2 > maxw)
+			width = maxw - clips[i].x1;
+		else
+			width = clips[i].x2 - clips[i].x1;
+		if (clips[i].y2 > maxh)
+			height = maxh - clips[i].y1;
+		else
+			height = clips[i].y2 - clips[i].y1;
+
+		dvbe_vesa_blit(dvbe, src, fb->base.pitches[0],
+			       fb->base.pixel_format, fb->base.bits_per_pixel,
+			       clips[i].x1, clips[i].y1, width, height);
+	}
+
+	return 0;
+}
+
+int dvbe_vesa_init(struct dvbe_device *dvbe)
+{
+	int ret;
+
+	if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) {
+		dev_info(dvbe->ddev->dev, "no VBE capable device found\n");
+		return -ENODEV;
+	}
+
+	dvbe->vbe_addr = (unsigned long)screen_info.lfb_base;
+	dvbe->vbe_vsize = screen_info.lfb_size * 0x10000;
+	dvbe->vbe_width = screen_info.lfb_width;
+	dvbe->vbe_height = screen_info.lfb_height;
+	dvbe->vbe_stride = screen_info.lfb_linelength;
+	dvbe->vbe_depth = screen_info.lfb_depth;
+	dvbe->vbe_bpp = (dvbe->vbe_depth == 15) ? 16 : dvbe->vbe_depth;
+
+	if (dvbe->vbe_bpp != 8 && dvbe->vbe_bpp != 16 && dvbe->vbe_bpp != 32)
+		return -ENODEV;
+	if (!screen_info.red_pos && !screen_info.green_pos &&
+	    !screen_info.blue_pos)
+		return -ENODEV;
+	if (!screen_info.red_size && !screen_info.green_size &&
+	    !screen_info.blue_size)
+		return -ENODEV;
+
+	dvbe->vbe_red_size = screen_info.red_size;
+	dvbe->vbe_red_pos = screen_info.red_pos;
+	dvbe->vbe_green_size = screen_info.green_size;
+	dvbe->vbe_green_pos = screen_info.green_pos;
+	dvbe->vbe_blue_size = screen_info.blue_size;
+	dvbe->vbe_blue_pos = screen_info.blue_pos;
+
+	dvbe->vbe_size = dvbe->vbe_height * dvbe->vbe_stride;
+	if (dvbe->vbe_vsize < dvbe->vbe_size)
+		dvbe->vbe_vsize = dvbe->vbe_size;
+
+	if (!request_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize, "dvbe"))
+		return -EIO;
+
+	if (!request_region(0x3c0, 32, "dvbe")) {
+		ret = -EIO;
+		goto err_mem_region;
+	}
+
+	dvbe->vbe_map = ioremap(dvbe->vbe_addr, dvbe->vbe_size);
+	if (!dvbe->vbe_map) {
+		ret = -EIO;
+		goto err_region;
+	}
+
+	dev_info(dvbe->ddev->dev, "initialized VBE mode to %ux%u\n",
+		dvbe->vbe_width, dvbe->vbe_height);
+
+	return 0;
+
+err_region:
+	release_region(0x3c0, 32);
+err_mem_region:
+	release_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize);
+	return -ENODEV;
+}
+
+void dvbe_vesa_cleanup(struct dvbe_device *dvbe)
+{
+	iounmap(dvbe->vbe_map);
+	release_region(0x3c0, 32);
+	release_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize);
+}
