diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index eba32ad..ff04e41 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -12,7 +12,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
 		drm_info.o drm_debugfs.o drm_encoder_slave.o \
 		drm_trace_points.o drm_global.o drm_prime.o \
 		drm_rect.o drm_vma_manager.o drm_flip_work.o \
-		drm_modeset_lock.o drm_atomic.o drm_bridge.o
+		drm_modeset_lock.o drm_atomic.o drm_bridge.o drm_panic.o
 
 drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 3b14366..457ee91 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -861,6 +861,8 @@ static int __init drm_core_init(void)
 		goto err_p3;
 	}
 
+	drm_panic_init();
+
 	DRM_INFO("Initialized %s %d.%d.%d %s\n",
 		 CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);
 	return 0;
@@ -876,6 +878,7 @@ err_p1:
 
 static void __exit drm_core_exit(void)
 {
+	drm_panic_exit();
 	debugfs_remove(drm_debugfs_root);
 	drm_sysfs_destroy();
 
diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h
index b86dc9b..7463d9d 100644
--- a/drivers/gpu/drm/drm_internal.h
+++ b/drivers/gpu/drm/drm_internal.h
@@ -90,6 +90,10 @@ int drm_gem_open_ioctl(struct drm_device *dev, void *data,
 void drm_gem_open(struct drm_device *dev, struct drm_file *file_private);
 void drm_gem_release(struct drm_device *dev, struct drm_file *file_private);
 
+/* drm_panic.c */
+void drm_panic_init(void);
+void drm_panic_exit(void);
+
 /* drm_debugfs.c */
 #if defined(CONFIG_DEBUG_FS)
 int drm_debugfs_init(struct drm_minor *minor, int minor_id,
diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
new file mode 100644
index 0000000..e185c9d
--- /dev/null
+++ b/drivers/gpu/drm/drm_panic.c
@@ -0,0 +1,606 @@
+/*
+ * Copyright 2016 Noralf Trønnes
+ *
+ * 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 <asm/unaligned.h>
+#include <drm/drmP.h>
+#include <linux/console.h>
+#include <linux/debugfs.h>
+#include <linux/font.h>
+#include <linux/kernel.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+struct drm_panic_fb {
+	struct drm_framebuffer *fb;
+	void *vmem;
+	const struct font_desc *font;
+	unsigned int cols;
+	unsigned int rows;
+	unsigned int xpos;
+	unsigned int ypos;
+};
+
+#define DRM_PANIC_MAX_FBS	64
+static struct drm_panic_fb drm_panic_fbs[DRM_PANIC_MAX_FBS];
+
+#define DRM_PANIC_MAX_KMSGS	SZ_4K
+static char *drm_panic_kmsgs;
+static size_t drm_panic_kmsgs_pos;
+
+static bool drm_panic_active;
+
+static void drm_panic_log(const char *fmt, ...);
+
+static inline void drm_panic_draw_pixel(u8 *dst, u32 pixel_format, bool val)
+{
+	switch (pixel_format & ~DRM_FORMAT_BIG_ENDIAN) {
+
+	case DRM_FORMAT_C8:
+	case DRM_FORMAT_RGB332:
+	case DRM_FORMAT_BGR233:
+		*dst = val ? 0xff : 0x00;
+		break;
+
+	case DRM_FORMAT_XRGB4444:
+	case DRM_FORMAT_ARGB4444:
+	case DRM_FORMAT_XBGR4444:
+	case DRM_FORMAT_ABGR4444:
+		put_unaligned(val ? 0x0fff : 0x0000, (u16 *)dst);
+		break;
+
+	case DRM_FORMAT_RGBX4444:
+	case DRM_FORMAT_RGBA4444:
+	case DRM_FORMAT_BGRX4444:
+	case DRM_FORMAT_BGRA4444:
+		put_unaligned(val ? 0xfff0 : 0x0000, (u16 *)dst);
+		break;
+
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+		put_unaligned(val ? 0x7fff : 0x0000, (u16 *)dst);
+		break;
+
+	case DRM_FORMAT_RGBX5551:
+	case DRM_FORMAT_RGBA5551:
+	case DRM_FORMAT_BGRX5551:
+	case DRM_FORMAT_BGRA5551:
+		put_unaligned(val ? 0xfffe : 0x0000, (u16 *)dst);
+		break;
+
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+		put_unaligned(val ? 0xffff : 0x0000, (u16 *)dst);
+		break;
+
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+		dst[0] = val ? 0xff : 0x00;
+		dst[1] = val ? 0xff : 0x00;
+		dst[2] = val ? 0xff : 0x00;
+		break;
+
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		put_unaligned(val ? 0x00ffffff : 0x00000000, (u32 *)dst);
+		break;
+
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRA8888:
+		put_unaligned(val ? 0xffffff00 : 0x00000000, (u32 *)dst);
+		break;
+
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+	case DRM_FORMAT_XBGR2101010:
+	case DRM_FORMAT_ABGR2101010:
+		put_unaligned(val ? 0x3fffffff : 0x00000000, (u32 *)dst);
+		break;
+
+	case DRM_FORMAT_RGBX1010102:
+	case DRM_FORMAT_RGBA1010102:
+	case DRM_FORMAT_BGRX1010102:
+	case DRM_FORMAT_BGRA1010102:
+		put_unaligned(val ? 0xfffffffc : 0x00000000, (u32 *)dst);
+		break;
+	}
+}
+
+static void drm_panic_render(struct drm_panic_fb *pfb,
+			     const char *text, unsigned int len)
+{
+	const struct font_desc *font = pfb->font;
+	unsigned int pix_depth, pix_bpp, cpp;
+	unsigned int col = pfb->xpos;
+	unsigned int row = pfb->ypos;
+	unsigned int i, h, w;
+	void *dst, *pos;
+	u8 fontline;
+
+	if ((row + 1) * font->height > pfb->fb->height)
+		return;
+
+	if ((col + len) * font->width > pfb->fb->width)
+		return;
+
+	drm_fb_get_bpp_depth(pfb->fb->pixel_format, &pix_depth, &pix_bpp);
+	cpp = DIV_ROUND_UP(pix_bpp, 8);
+
+	/* TODO: should fb->offsets[0] be added here? */
+	dst = pfb->vmem + (row * font->height * pfb->fb->pitches[0]) +
+	      (col * font->width * cpp);
+
+	for (h = 0; h < font->height; h++) {
+		pos = dst;
+
+		for (i = 0; i < len; i++) {
+			fontline = *(u8 *)(font->data + text[i] * font->height + h);
+
+			for (w = 0; w < font->width; w++) {
+				drm_panic_draw_pixel(pos, pfb->fb->pixel_format,
+						     fontline & BIT(7 - w));
+				pos += cpp;
+			}
+		}
+
+		dst += pfb->fb->pitches[0];
+	}
+}
+
+static void drm_panic_scroll_up(struct drm_panic_fb *pfb)
+{
+	void *src = pfb->vmem + (pfb->font->height * pfb->fb->pitches[0]);
+	size_t len = (pfb->fb->height - pfb->font->height) *
+		     pfb->fb->pitches[0];
+
+	drm_panic_log("%s\n", __func__);
+
+	memmove(pfb->vmem, src, len);
+	memset(pfb->vmem + len, 0, pfb->font->height * pfb->fb->pitches[0]);
+}
+
+static void drm_panic_clear_screen(struct drm_panic_fb *pfb)
+{
+	memset(pfb->vmem, 0, pfb->fb->height * pfb->fb->pitches[0]);
+}
+
+static void drm_panic_log_msg(char *pre, const char *str, unsigned int len)
+{
+	char buf[512];
+
+	if (len > 510)
+		len = 510;
+
+	memcpy(buf, str, len);
+	buf[len] = '\n';
+	buf[len + 1] = '\0';
+
+	drm_panic_log("%s%s", pre, buf);
+}
+
+static void drm_panic_putcs_no_lf(struct drm_panic_fb *pfb,
+				  const char *str, unsigned int len)
+{
+	drm_panic_log("%s(len=%u) x=%u, y=%u\n", __func__, len,
+			pfb->xpos, pfb->ypos);
+
+	if (len <= 0)
+		return;
+
+	drm_panic_log_msg("", str, len);
+
+	drm_panic_render(pfb, str, len);
+
+}
+
+static void drm_panic_putcs(struct drm_panic_fb *pfb,
+			    const char *str, unsigned int num)
+{
+	unsigned int slen;
+	int len = num;
+	char *lf;
+
+	drm_panic_log("%s(num=%u)\n", __func__, num);
+
+	while (len > 0) {
+
+		if (pfb->ypos == pfb->rows) {
+			pfb->ypos--;
+			drm_panic_scroll_up(pfb);
+		}
+
+		lf = strpbrk(str, "\n");
+		if (lf)
+			slen = lf - str;
+		else
+			slen = len;
+
+		if (pfb->xpos + slen > pfb->cols)
+			slen = pfb->cols - pfb->xpos;
+
+		drm_panic_putcs_no_lf(pfb, str, slen);
+
+		len -= slen;
+		str += slen;
+		pfb->xpos += slen;
+
+		if (lf) {
+			str++;
+			len--;
+			pfb->xpos = 0;
+			pfb->ypos++;
+		}
+	}
+}
+
+static void drm_panic_write(const char *str, unsigned int num)
+{
+	unsigned int i;
+
+	if (!num)
+		return;
+
+	drm_panic_log("%s(num=%u)\n", __func__, num);
+
+	for (i = 0; i < DRM_PANIC_MAX_FBS; i++) {
+		if (!drm_panic_fbs[i].fb)
+			break;
+		drm_panic_putcs(&drm_panic_fbs[i], str, num);
+	}
+}
+
+/* this one is serialized by console_lock() */
+static void drm_panic_console_write(struct console *con,
+				    const char *str, unsigned int num)
+{
+	unsigned int i;
+
+	drm_panic_log_msg("->", str, num);
+
+	/* Buffer up messages to be replayed on panic */
+	if (!drm_panic_active) {
+		for (i = 0; i < num; i++) {
+			drm_panic_kmsgs[drm_panic_kmsgs_pos++] = *str++;
+			if (drm_panic_kmsgs_pos == DRM_PANIC_MAX_KMSGS)
+				drm_panic_kmsgs_pos = 0;
+		}
+		return;
+	}
+
+	drm_panic_write(str, num);
+}
+
+static struct console drm_panic_console = {
+	.name =         "drmpanic",
+	.write =        drm_panic_console_write,
+	.flags =        CON_PRINTBUFFER | CON_ENABLED,
+	.index =        0,
+};
+
+/*
+ * The panic() function makes sure that only one CPU is allowed to run it's
+ * code. So when this handler is called, we're alone. No racing with
+ * console.write() is possible.
+ */
+static int drm_panic_handler(struct notifier_block *this, unsigned long ev,
+			     void *ptr)
+{
+	const struct font_desc *font;
+	struct drm_framebuffer *fb;
+	struct drm_panic_fb *pfb;
+	struct drm_minor *minor;
+	unsigned int fbs = 0;
+	void *vmem;
+	int i;
+
+	drm_panic_log("%s\n", __func__);
+
+	drm_panic_active = true;
+
+	drm_minor_for_each(minor, DRM_MINOR_LEGACY, i) {
+		drm_panic_log("Found minor=%d\n", minor->index);
+		if (!minor->dev || !minor->dev->driver ||
+		    !minor->dev->driver->panic) {
+			drm_panic_log("Skipping: No panic handler\n");
+			continue;
+		}
+
+		fb = minor->dev->driver->panic(minor->dev, &vmem);
+		if (!fb) {
+			drm_panic_log("Skipping: Driver returned NULL\n");
+			continue;
+		}
+
+		if (!fb || !vmem || fb->dev != minor->dev || !fb->pitches[0]) {
+			drm_panic_log("Skipping: Failed check\n");
+			continue;
+		}
+
+		/* only 8-bit wide fonts are supported */
+		font = get_default_font(fb->width, fb->height, BIT(7), -1);
+		if (!font) {
+			drm_panic_log("Skipping: No font available\n");
+			continue;
+		}
+
+		pfb = &drm_panic_fbs[fbs++];
+
+		pfb->fb = fb;
+		pfb->vmem = vmem;
+		pfb->font = font;
+		pfb->cols = fb->width / font->width;
+		pfb->rows = fb->height / font->height;
+
+		drm_panic_clear_screen(pfb);
+
+		drm_panic_log("    %ux%u -> %ux%u, %s, %s\n", fb->width,
+				fb->height, pfb->cols, pfb->rows, font->name,
+				drm_get_format_name(fb->pixel_format));
+	}
+
+	if (drm_panic_kmsgs[0]) {
+		/* safeguard in case we interrupted drm_panic_console_write */
+		if (drm_panic_kmsgs_pos >= DRM_PANIC_MAX_KMSGS)
+			drm_panic_kmsgs_pos = 0;
+
+		drm_panic_write(&drm_panic_kmsgs[drm_panic_kmsgs_pos],
+				DRM_PANIC_MAX_KMSGS - drm_panic_kmsgs_pos);
+		drm_panic_write(drm_panic_kmsgs, drm_panic_kmsgs_pos);
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block drm_panic_block = {
+	.notifier_call = drm_panic_handler,
+};
+
+
+
+#ifdef CONFIG_DEBUG_FS
+
+/* Out of band logging is useful at least in the initial development phase */
+#define DRM_PANIC_LOG_SIZE	SZ_64K
+#define DRM_PANIC_LOG_LINE	128
+#define DRM_PANIC_LOG_ENTRIES	(DRM_PANIC_LOG_SIZE / DRM_PANIC_LOG_LINE)
+
+static char *log_buf;
+static size_t log_pos;
+static struct dentry *drm_panic_logfs_root;
+
+static void drm_panic_log(const char *fmt, ...)
+{
+	va_list args;
+	u32 rem_nsec;
+	char *text;
+	size_t len;
+	u64 sec;
+
+	if (!log_buf || oops_in_progress)
+		return;
+
+	va_start(args, fmt);
+
+	if (log_pos >= DRM_PANIC_LOG_ENTRIES)
+		log_pos = 0;
+
+	text = log_buf + (log_pos++ * DRM_PANIC_LOG_LINE);
+	if (log_pos == DRM_PANIC_LOG_ENTRIES)
+		log_pos = 0;
+
+	sec = div_u64_rem(local_clock(), 1000000000, &rem_nsec);
+
+	len = scnprintf(text, DRM_PANIC_LOG_LINE, "[%5llu.%06u] ", sec,
+			rem_nsec / 1000);
+
+	vscnprintf(text + len, DRM_PANIC_LOG_LINE - len, fmt, args);
+
+	/* Make sure to always have a newline in case of overflow */
+	if (text[DRM_PANIC_LOG_LINE - 2] != '\0')
+		text[DRM_PANIC_LOG_LINE - 2] = '\n';
+
+	va_end(args);
+}
+
+static int drm_panic_log_show(struct seq_file *m, void *v)
+{
+	size_t pos = log_pos;
+	unsigned int i;
+	char *text;
+
+	for (i = 0; i < DRM_PANIC_LOG_ENTRIES; i++) {
+		text = log_buf + (pos++ * DRM_PANIC_LOG_LINE);
+		if (pos == DRM_PANIC_LOG_ENTRIES)
+			pos = 0;
+		if (*text == '\0')
+			continue;
+		seq_puts(m, text);
+	}
+
+	return 0;
+}
+
+static int drm_panic_log_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, drm_panic_log_show, NULL);
+}
+
+static const struct file_operations drm_panic_log_ops = {
+	.owner   = THIS_MODULE,
+	.open    = drm_panic_log_open,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = single_release,
+};
+
+/*
+ * Fake/simulate panic() at different levels:
+ * 1: only trigger panic handling internally
+ * 2: add local_irq_disable()
+ * 3: add bust_spinlocks();
+ * 100: don't fake it, do call panic()
+ */
+static int drm_text_fake_panic(unsigned int level)
+{
+#ifndef MODULE
+	int old_loglevel = console_loglevel;
+#endif
+
+	if (!level && level != 100 && level > 3)
+		return -EINVAL;
+
+	if (level == 100)
+		panic("TESTING");
+
+	if (level > 1)
+		local_irq_disable();
+
+#ifndef MODULE
+	console_verbose();
+#endif
+	if (level > 2)
+		bust_spinlocks(1);
+
+	pr_emerg("Kernel panic - not syncing: FAKING=%u, oops_in_progress=%d\n",
+		 level, oops_in_progress);
+
+#ifdef CONFIG_DEBUG_BUGVERBOSE
+	dump_stack();
+#endif
+	/* simulate calling panic_notifier_list */
+	drm_panic_handler(NULL, 0, NULL);
+
+	if (level > 2)
+		bust_spinlocks(0);
+
+#ifndef MODULE
+	console_flush_on_panic();
+#endif
+	pr_emerg("---[ end Kernel panic - not syncing: FAKING\n");
+
+	if (level > 1)
+		local_irq_enable();
+
+#ifndef MODULE
+	console_loglevel = old_loglevel;
+#endif
+
+	return 0;
+}
+
+static ssize_t drm_text_panic_write(struct file *file,
+				    const char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	unsigned long long val;
+	ssize_t ret = 0;
+	char buf[24];
+	size_t size;
+
+	size = min(sizeof(buf) - 1, count);
+	if (copy_from_user(buf, user_buf, size))
+		return -EFAULT;
+
+	buf[size] = '\0';
+	ret = kstrtoull(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	ret = drm_text_fake_panic(val);
+
+	return ret < 0 ? ret : count;
+}
+
+static const struct file_operations drm_text_panic_ops = {
+	.write =        drm_text_panic_write,
+	.open =         simple_open,
+	.llseek =       default_llseek,
+};
+
+static int drm_panic_logfs_init(void)
+{
+	drm_panic_logfs_root = debugfs_create_dir("drm-panic", NULL);
+	if (!drm_panic_logfs_root)
+		return -ENOMEM;
+
+	if (!debugfs_create_file("log", S_IRUGO, drm_panic_logfs_root, NULL,
+			    &drm_panic_log_ops))
+		goto err_remove;
+
+	log_buf = kzalloc(DRM_PANIC_LOG_SIZE, GFP_KERNEL);
+	if (!log_buf)
+		goto err_remove;
+
+	debugfs_create_file("panic", S_IWUSR, drm_panic_logfs_root, NULL,
+			    &drm_text_panic_ops);
+
+	return 0;
+
+err_remove:
+	debugfs_remove_recursive(drm_panic_logfs_root);
+
+	return -ENOMEM;
+}
+
+static void drm_panic_logfs_exit(void)
+{
+	debugfs_remove_recursive(drm_panic_logfs_root);
+	kfree(log_buf);
+	log_buf = NULL;
+}
+
+#else
+
+static int drm_panic_logfs_init(void)
+{
+}
+
+static void drm_panic_logfs_exit(void)
+{
+}
+
+static void drm_panic_log(const char *fmt, ...)
+{
+}
+
+#endif
+
+
+void __init drm_panic_init(void)
+{
+	drm_panic_kmsgs = kzalloc(DRM_PANIC_MAX_KMSGS, GFP_KERNEL);
+	if (!drm_panic_kmsgs) {
+		DRM_ERROR("Failed to setup panic handler\n");
+		return;
+	}
+
+	drm_panic_logfs_init();
+drm_panic_log("%s\n", __func__);
+	register_console(&drm_panic_console);
+	atomic_notifier_chain_register(&panic_notifier_list,
+				       &drm_panic_block);
+}
+
+void __exit drm_panic_exit(void)
+{
+	if (!drm_panic_kmsgs)
+		return;
+
+	drm_panic_logfs_exit();
+	atomic_notifier_chain_unregister(&panic_notifier_list,
+					 &drm_panic_block);
+	unregister_console(&drm_panic_console);
+	kfree(drm_panic_kmsgs);
+}
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index bc7006e..4e84654 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -550,6 +550,28 @@ struct drm_driver {
 			  bool from_open);
 	void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv);
 
+	/**
+	 * @panic:
+	 *
+	 * This function is called on panic() asking for a framebuffer to
+	 * display the panic messages on. It also needs the virtual address
+	 * of the backing buffer.
+	 * This function is optional.
+	 *
+	 * NOTE:
+	 *
+	 * This function is called in an atomic notifier chain and it cannot
+	 * sleep. Care must be taken so the machine is not killed even harder,
+	 * preventing output from going out on serial/netconsole.
+	 *
+	 * RETURNS:
+	 *
+	 * Framebuffer that should be used to display the panic messages,
+	 * alongside the virtual address of the backing buffer, or NULL if
+	 * the driver is unable to provide this.
+	 */
+	struct drm_framebuffer *(*panic)(struct drm_device *dev, void **vmem);
+
 	int (*debugfs_init)(struct drm_minor *minor);
 	void (*debugfs_cleanup)(struct drm_minor *minor);
 
