diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index a6617c07229a..c3496c5ac2bb 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -175,6 +175,14 @@ config BOOTSPLASH
           If unsure, say N.
           This is typically used by distributors and system integrators.
 
+config BOOTSPLASH_FILE
+        string "Default full path to bootsplash file"
+        depends on BOOTSPLASH
+        default "/bootsplash"
+        help
+          This file will be looked for in the initramfs and, if found, loaded
+          and used as a bootsplash.
+
 config STI_CONSOLE
         bool "STI text console"
         depends on PARISC
diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile
index 66895321928e..6a8d1bab8a01 100644
--- a/drivers/video/fbdev/core/Makefile
+++ b/drivers/video/fbdev/core/Makefile
@@ -31,4 +31,4 @@ obj-$(CONFIG_FB_SVGALIB)       += svgalib.o
 obj-$(CONFIG_FB_DDC)           += fb_ddc.o
 
 obj-$(CONFIG_BOOTSPLASH)       += bootsplash.o bootsplash_render.o \
-                                  dummyblit.o
+                                  bootsplash_load.o dummyblit.o
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
index e98c05dd8bc0..3253506d26a7 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -32,6 +32,7 @@
 #include <linux/vt_kern.h>
 #include <linux/workqueue.h>
 
+#include "bootsplash_file.h"
 #include "bootsplash_internal.h"
 
 
@@ -116,12 +117,19 @@ static bool is_fb_compatible(struct fb_info *info)
  */
 void bootsplash_render_full(struct fb_info *info)
 {
+	mutex_lock(&splash_global.data_lock);
+
 	if (!is_fb_compatible(info))
-		return;
+		goto out;
 
 	bootsplash_do_render_background(info);
 
+	bootsplash_do_render_pictures(info);
+
 	bootsplash_do_render_flush(info);
+
+out:
+	mutex_unlock(&splash_global.data_lock);
 }
 
 
@@ -135,6 +143,7 @@ bool bootsplash_would_render_now(void)
 {
 	return !oops_in_progress
 		&& !console_blanked
+		&& splash_global.filebuf
 		&& bootsplash_is_enabled();
 }
 
@@ -231,11 +240,33 @@ static ssize_t splash_store_enabled(struct device *device,
 	return count;
 }
 
+static ssize_t splash_store_drop_splash(struct device *device,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	if (!buf || !count || !splash_global.filebuf)
+		return count;
+
+	/* Stop the splash first so we get our text console
+	 * if we're currently on one.
+	 */
+	mutex_lock(&splash_global.data_lock);
+
+	bootsplash_disable();
+	bootsplash_free_locked();
+
+	mutex_unlock(&splash_global.data_lock);
+
+	return count;
+}
+
 static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled);
+static DEVICE_ATTR(drop_splash, 0200, NULL, splash_store_drop_splash);
 
 
 static struct attribute *splash_dev_attrs[] = {
 	&dev_attr_enabled.attr,
+	&dev_attr_drop_splash.attr,
 	NULL
 };
 
@@ -279,8 +310,11 @@ static struct platform_driver splash_driver = {
 
 void bootsplash_init(void)
 {
+	loff_t len;
+	void *mem;
+
 	/* Initialized already? */
-	if (splash_global.wq)
+	if (splash_global.filebuf)
 		return;
 
 
@@ -311,6 +345,7 @@ void bootsplash_init(void)
 	}
 
 	spin_lock_init(&splash_global.state_lock);
+	mutex_init(&splash_global.data_lock);
 	if (!splash_global.wq)
 		splash_global.wq = alloc_workqueue("bootsplash",
 						WQ_UNBOUND | WQ_FREEZABLE,
@@ -320,6 +355,21 @@ void bootsplash_init(void)
 		goto err;
 	}
 
+	/* Default splash file to load */
+	if (!splash_global.filename)
+		splash_global.filename = CONFIG_BOOTSPLASH_FILE
+					 "." __stringify(BOOTSPLASH_VERSION);
+		// TODO: Remove the version suffix when upstreaming
+
+	/* Load splash from file in initramfs */
+	if (kernel_read_file_from_path(splash_global.filename, &mem,
+					&len, 2*1024*1024, READING_UNKNOWN)) {
+		pr_err("Failed to read file: %s\n", splash_global.filename);
+		goto err;
+	}
+
+	bootsplash_activate_buf(mem, len);
+
 	return;
 
 err_device:
@@ -335,3 +385,6 @@ void bootsplash_init(void)
 
 module_param_named(enable, splash_global.enabled, bool, 0444);
 MODULE_PARM_DESC(enable, "Enable/disable kernel bootsplash");
+
+module_param_named(filename, splash_global.filename, charp, 0444);
+MODULE_PARM_DESC(filename, "Bootsplash file");
diff --git a/drivers/video/fbdev/core/bootsplash_file.h b/drivers/video/fbdev/core/bootsplash_file.h
new file mode 100644
index 000000000000..f33577e062ca
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_file.h
@@ -0,0 +1,112 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * Authors:
+ * Max Staudt <mstaudt@suse.com>
+ *
+ *
+ * Very loosely based on the old kernel splash project at
+ * http://www.bootsplash.org/ and SUSE improvements.
+ */
+
+#ifndef __BOOTSPLASH_FILE_H
+#define __BOOTSPLASH_FILE_H
+
+
+#define BOOTSPLASH_VERSION 55559
+
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+
+/*
+ * On-disk types
+ *
+ * A splash file consists of:
+ *  - One single 'struct splash_file_header'
+ *  - An array of 'struct splash_pic_header'
+ *  - An array of raw data blocks, each padded to 16 bytes and
+ *    preceded by a 'struct splash_blob_header'
+ *
+ * A single-frame splash may look like this:
+ *
+ * +--------------------+
+ * |                    |
+ * | splash_file_header |
+ * |  -> num_blobs = 1  |
+ * |  -> num_pics = 1   |
+ * |                    |
+ * +--------------------+
+ * |                    |
+ * | splash_pic_header  |
+ * |                    |
+ * +--------------------+
+ * |                    |
+ * | splash_blob_header |
+ * |  -> type = 0       |
+ * |  -> picture_id = 0 |
+ * |                    |
+ * | (raw RGB data)     |
+ * | (pad to 16 bytes)  |
+ * |                    |
+ * +--------------------+
+ *
+ * All multi-byte values are stored on disk as big endian, and
+ * will be converted to CPU endianness in-memory during loading.
+ */
+
+struct splash_file_header {
+	u8  id[16]; /* "Linux bootsplash" (no trailing NUL) */
+
+	/* Splash file format version to avoid clashes */
+	u16 version;
+
+	/* The background color */
+	u8 bg_red;
+	u8 bg_green;
+	u8 bg_blue;
+	u8 bg_reserved;
+
+	/* Number of pic/blobs so we can allocate memory for internal
+	 * structures ahead of time when reading the file
+	 */
+	u16 num_blobs;
+	u8 num_pics;
+
+	u8 padding[103];
+} __attribute__((__packed__));
+
+
+struct splash_pic_header {
+	u16 width;
+	u16 height;
+
+	/* Number of data packages associated with this picture.
+	 * Currently, the only use for more than 1 is for animations.
+	 */
+	u8 num_blobs;
+
+	u8 padding[11];
+} __attribute__((__packed__));
+
+
+struct splash_blob_header {
+	/* Length of the data block in bytes. */
+	u32 length;
+
+	/* Type of the contents.
+	 *  0 - Raw RGB data.
+	 */
+	u16 type;
+
+	/* Picture this blob is associated with.
+	 * Blobs will be added to a picture in the order they are
+	 * found in the file.
+	 */
+	u8 picture_id;
+
+	u8 padding[9];
+} __attribute__((__packed__));
+
+#endif
diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
index c0653dd7807b..791591d10d6b 100644
--- a/drivers/video/fbdev/core/bootsplash_internal.h
+++ b/drivers/video/fbdev/core/bootsplash_internal.h
@@ -22,12 +22,31 @@
 #include <linux/spinlock.h>
 #include <linux/workqueue.h>
 
+#include "bootsplash_file.h"
+
 
 /*
  * Runtime types
  */
 
+struct splash_blob_priv {
+	struct splash_blob_header *blob_header;
+	void *data;
+};
+
+
+struct splash_pic_priv {
+	struct splash_pic_header *pic_header;
+
+	struct splash_blob_priv *blobs;
+	u16 blobs_loaded;
+};
+
+
 struct splash_priv {
+	/* Bootup and runtime state */
+	char *filename;
+
 	/* This lock only synchronizes the enabled/disabled state
 	 *
 	 * Note: fbcon.c uses this twice, by calling
@@ -49,6 +68,18 @@ struct splash_priv {
 	 * during suspend.
 	 */
 	struct workqueue_struct *wq;
+
+	/* Splash data structures including lock for everything below */
+	struct mutex data_lock;
+
+	struct fb_info *splash_fb;
+
+	union {
+		u8 *filebuf;
+		struct splash_file_header *header;
+	};
+
+	struct splash_pic_priv *pics;
 };
 
 
@@ -67,6 +98,11 @@ extern struct splash_priv splash_global;
  */
 
 void bootsplash_do_render_background(struct fb_info *info);
+void bootsplash_do_render_pictures(struct fb_info *info);
 void bootsplash_do_render_flush(struct fb_info *info);
 
+
+void bootsplash_free_locked(void);
+int bootsplash_activate_buf(char *buf, long buflen);
+
 #endif
diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c
new file mode 100644
index 000000000000..2f983a74664c
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_load.c
@@ -0,0 +1,242 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Loading and freeing functions)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@suse.com>
+ *
+ *
+ * Very loosely based on the old kernel splash project at
+ * http://www.bootsplash.org/ and SUSE improvements.
+ */
+
+#define pr_fmt(fmt) "bootsplash: " fmt
+
+
+#include <linux/bootsplash.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+
+#include "bootsplash_file.h"
+#include "bootsplash_internal.h"
+
+
+
+
+/* Free all vmalloc()'d resources.
+ *
+ * To be called with the bootsplash data_lock held.
+ */
+void bootsplash_free_locked(void)
+{
+	if (splash_global.filebuf && splash_global.pics) {
+		unsigned int i;
+
+		for (i = 0; i < splash_global.header->num_pics; i++) {
+			struct splash_pic_priv *pp = &splash_global.pics[i];
+
+			vfree(pp->blobs);
+		}
+
+		vfree(splash_global.pics);
+		splash_global.pics = NULL;
+	}
+
+	if (splash_global.filebuf) {
+		vfree(splash_global.filebuf);
+		splash_global.filebuf = NULL;
+	}
+}
+
+
+
+
+/*
+ * Load a splash screen from a memory buffer.
+ *
+ * Parsing, and sanity checks.
+ *
+ * Once a vmalloc'd buffer is passed to this function, the bootsplash
+ * code is responsible for freeing it - even in an error case.
+ */
+
+int bootsplash_activate_buf(char *buf, long buflen)
+{
+	unsigned int i;
+	char *walker;
+
+	if (splash_global.filebuf) {
+		pr_err("Won't load splash screen on top of another one.\n");
+
+		vfree(buf);
+		return -ENOMEM;
+	}
+
+
+	if (buflen < sizeof(struct splash_file_header)
+#ifdef __BIG_ENDIAN
+	    || memcmp(buf, "Linux bootsplash",
+#else
+	    || memcmp(buf, "hsalpstoob xuniL",
+#endif
+		      sizeof(splash_global.header->id))) {
+		pr_err("Not a bootsplash file.\n");
+
+		vfree(buf);
+		return -EINVAL;
+	}
+
+	pr_info("Loading splash file (%li bytes)\n", buflen);
+
+
+	mutex_lock(&splash_global.data_lock);
+
+	splash_global.filebuf = buf;
+
+
+	/* Sanity checks */
+	if (splash_global.header->version != BOOTSPLASH_VERSION) {
+		pr_err("Loaded v%d file, but we only support version %d\n",
+			splash_global.header->version,
+			BOOTSPLASH_VERSION);
+
+		goto err;
+	}
+
+	if (buflen < sizeof(struct splash_file_header)
+		+ splash_global.header->num_pics
+			* sizeof(struct splash_pic_header)
+		+ splash_global.header->num_blobs
+			* sizeof(struct splash_blob_header)) {
+		pr_err("File incomplete.\n");
+
+		goto err;
+	}
+
+
+	/* Read picture headers */
+
+	if (splash_global.header->num_pics) {
+		splash_global.pics = vzalloc(splash_global.header->num_pics
+					     * sizeof(struct splash_pic_priv));
+		if (!splash_global.pics)
+			goto err;
+	}
+
+	walker = splash_global.filebuf + sizeof(struct splash_file_header);
+	for (i = 0; i < splash_global.header->num_pics; i++) {
+		struct splash_pic_priv *pp = &splash_global.pics[i];
+		struct splash_pic_header *ph = (void *)walker;
+
+		pr_debug("Picture %u: Size %ux%u\n", i, ph->width, ph->height);
+
+		if (ph->num_blobs < 1) {
+			pr_err("Picture %u: Zero blobs? Aborting load.\n", i);
+
+			goto err;
+		}
+
+		pp->pic_header = ph;
+		pp->blobs = vzalloc(ph->num_blobs
+					* sizeof(struct splash_blob_priv));
+		if (!pp->blobs) {
+			pr_err("Could not allocate pointer array for picture %d.\n",
+			       i);
+
+			ph->num_blobs = 0;
+		}
+
+		walker += sizeof(struct splash_pic_header);
+	}
+
+
+
+	/* Read blob headers */
+	for (i = 0; i < splash_global.header->num_blobs; i++) {
+		struct splash_blob_header *bh = (void *)walker;
+		struct splash_pic_priv *pp;
+
+		if (walker + sizeof(struct splash_blob_header) > buf + buflen)
+			goto err;
+
+		walker += sizeof(struct splash_blob_header);
+
+		if (walker + bh->length > buf + buflen)
+			goto err;
+
+		if (bh->picture_id >= splash_global.header->num_pics)
+			goto nextblob;
+
+		pp = &splash_global.pics[bh->picture_id];
+
+		pr_debug("Blob %u, pic %u, blobs_loaded %u, num_blobs %u.\n",
+			 i, bh->picture_id,
+			 pp->blobs_loaded, pp->pic_header->num_blobs);
+
+		if (pp->blobs_loaded >= pp->pic_header->num_blobs)
+			goto nextblob;
+
+		switch (bh->type) {
+		case 0:
+			/* Raw 24-bit packed pixels */
+			if (bh->length != pp->pic_header->width
+					* pp->pic_header->height * 3) {
+				pr_err("Blob %u, type 1: Length doesn't match picture.\n",
+				       i);
+
+				goto err;
+			}
+			break;
+		default:
+			pr_warn("Blob %u, unknown type %u.\n", i, bh->type);
+			goto nextblob;
+		}
+
+		pp->blobs[pp->blobs_loaded].blob_header = bh;
+		pp->blobs[pp->blobs_loaded].data = walker;
+		pp->blobs_loaded++;
+
+nextblob:
+		walker += bh->length;
+		if (bh->length % 16)
+			walker += 16 - (bh->length % 16);
+	}
+
+	if (walker != buf + buflen)
+		pr_warn("Trailing data in splash file.\n");
+
+	/* Walk over pictures and ensure all blob slots are filled */
+	for (i = 0; i < splash_global.header->num_pics; i++) {
+		struct splash_pic_priv *pp = &splash_global.pics[i];
+
+		if (pp->blobs_loaded != pp->pic_header->num_blobs) {
+			pr_err("Picture %u doesn't have all blob slots filled.\n",
+			       i);
+
+			goto err;
+		}
+	}
+
+
+	/* Force a full redraw when the splash is re-activated */
+	splash_global.splash_fb = NULL;
+
+	pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n",
+		buflen, splash_global.header->num_pics,
+		splash_global.header->num_blobs);
+
+	mutex_unlock(&splash_global.data_lock);
+
+	return 0;
+
+
+err:
+	bootsplash_free_locked();
+	mutex_unlock(&splash_global.data_lock);
+	return -EINVAL;
+}
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
index 460ae0168cb0..72d9867c4656 100644
--- a/drivers/video/fbdev/core/bootsplash_render.c
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -20,6 +20,7 @@
 #include <linux/printk.h>
 #include <linux/types.h>
 
+#include "bootsplash_file.h"
 #include "bootsplash_internal.h"
 
 
@@ -70,16 +71,73 @@ static inline u32 pack_pixel(struct fb_var_screeninfo *dst_var,
 }
 
 
+/*
+ * Copy from source and blend into the destination picture.
+ * Currently assumes that the source picture is 24bpp.
+ * Currently assumes that the destination is <= 32bpp.
+ */
+static int splash_convert_to_fb(u8 *dst,
+				struct fb_var_screeninfo *dst_var,
+				unsigned int dst_stride,
+				unsigned int dst_xoff,
+				unsigned int dst_yoff,
+				u8 *src,
+				unsigned int src_width,
+				unsigned int src_height)
+{
+	unsigned int x, y;
+	unsigned int src_stride = 3 * src_width; /* Assume 24bpp packed */
+	u32 dst_octpp = dst_var->bits_per_pixel / 8;
+
+	dst_xoff += dst_var->xoffset;
+	dst_yoff += dst_var->yoffset;
+
+	/* Copy with stride and pixel size adjustment */
+	for (y = 0;
+	     y < src_height && y + dst_yoff < dst_var->yres_virtual;
+	     y++) {
+		u8 *srcline = src
+				+ (y * src_stride);
+		u8 *dstline = dst
+				+ ((y + dst_yoff) * dst_stride)
+				+ (dst_xoff * dst_octpp);
+
+		for (x = 0;
+		     x < src_width && x + dst_xoff < dst_var->xres_virtual;
+		     x++) {
+			u8 red, green, blue;
+			u32 dstpix;
+
+			/* Read pixel */
+			red = *srcline++;
+			green = *srcline++;
+			blue = *srcline++;
+
+			/* Write pixel */
+			dstpix = pack_pixel(dst_var, red, green, blue);
+			memcpy(dstline, &dstpix, dst_octpp);
+
+			dstline += dst_octpp;
+		}
+	}
+
+	return 0;
+}
+
+
 void bootsplash_do_render_background(struct fb_info *info)
 {
 	unsigned int x, y;
 	u32 dstpix;
 	u32 dst_octpp = info->var.bits_per_pixel / 8;
 
+	if (!splash_global.filebuf)
+		return;
+
 	dstpix = pack_pixel(&info->var,
-			    0,
-			    0,
-			    0);
+			    splash_global.header->bg_red,
+			    splash_global.header->bg_green,
+			    splash_global.header->bg_blue);
 
 	for (y = 0; y < info->var.yres_virtual; y++) {
 		u8 *dstline = info->screen_buffer + (y * info->fix.line_length);
@@ -93,6 +151,48 @@ void bootsplash_do_render_background(struct fb_info *info)
 }
 
 
+void bootsplash_do_render_pictures(struct fb_info *info)
+{
+	unsigned int i;
+
+	if (!splash_global.filebuf)
+		return;
+
+	for (i = 0; i < splash_global.header->num_pics; i++) {
+		struct splash_blob_priv *bp;
+		struct splash_pic_priv *pp = &splash_global.pics[i];
+		long dst_xoff, dst_yoff;
+
+		if (pp->blobs_loaded < 1)
+			continue;
+
+		bp = &pp->blobs[0];
+
+		if (!bp || bp->blob_header->type != 0)
+			continue;
+
+		dst_xoff = (info->var.xres - pp->pic_header->width) / 2;
+		dst_yoff = (info->var.yres - pp->pic_header->height) / 2;
+
+		if (dst_xoff < 0
+		    || dst_yoff < 0
+		    || dst_xoff + pp->pic_header->width > info->var.xres
+		    || dst_yoff + pp->pic_header->height > info->var.yres) {
+			pr_warn("Picture %u is out of bounds at current resolution: %dx%d\n",
+				i, info->var.xres, info->var.yres);
+
+			continue;
+		}
+
+		/* Draw next splash frame */
+		splash_convert_to_fb(info->screen_buffer, &info->var,
+				info->fix.line_length, dst_xoff, dst_yoff,
+				bp->data,
+				pp->pic_header->width, pp->pic_header->height);
+	}
+}
+
+
 void bootsplash_do_render_flush(struct fb_info *info)
 {
 	/* FB drivers using deferred_io (such as Xen) need to sync the
