[Feature] add GA346 baseline version

Change-Id: Ic62933698569507dcf98240cdf5d9931ae34348f
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/Kconfig b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/Kconfig
new file mode 100644
index 0000000..81ece43
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/Kconfig
@@ -0,0 +1,76 @@
+config MTD_SPLIT
+	def_bool n
+	help
+	  Generic MTD split support.
+
+config MTD_SPLIT_SUPPORT
+	def_bool MTD = y
+
+comment "Rootfs partition parsers"
+
+config MTD_SPLIT_SQUASHFS_ROOT
+	bool "Squashfs based root partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+	default n
+	help
+	  This provides a parsing function which allows to detect the
+	  offset and size of the unused portion of a rootfs partition
+	  containing a squashfs.
+
+comment "Firmware partition parsers"
+
+config MTD_SPLIT_SEAMA_FW
+	bool "Seama firmware parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_WRGG_FW
+	bool "WRGG firmware parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_UIMAGE_FW
+	bool "uImage based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_FIT_FW
+	bool "FIT based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_LZMA_FW
+	bool "LZMA compressed kernel based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_TPLINK_FW
+	bool "TP-Link firmware parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_TRX_FW
+	bool "TRX image based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_BRNIMAGE_FW
+	bool "brnImage (brnboot image) firmware parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_EVA_FW
+	bool "EVA image based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_MINOR_FW
+	bool "Mikrotik NOR image based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
+
+config MTD_SPLIT_JIMAGE_FW
+	bool "JBOOT Image based firmware partition parser"
+	depends on MTD_SPLIT_SUPPORT
+	select MTD_SPLIT
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/Makefile b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/Makefile
new file mode 100644
index 0000000..206e754
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/Makefile
@@ -0,0 +1,13 @@
+obj-$(CONFIG_MTD_SPLIT)		+= mtdsplit.o
+obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o
+obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o
+obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o
+obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o
+obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o
+obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o
+obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o
+obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o
+obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o
+obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o
+obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o
+obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit.c
new file mode 100644
index 0000000..b2e51dc
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt)	"mtdsplit: " fmt
+
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#include "mtdsplit.h"
+
+#define UBI_EC_MAGIC			0x55424923	/* UBI# */
+
+struct squashfs_super_block {
+	__le32 s_magic;
+	__le32 pad0[9];
+	__le64 bytes_used;
+};
+
+int mtd_get_squashfs_len(struct mtd_info *master,
+			 size_t offset,
+			 size_t *squashfs_len)
+{
+	struct squashfs_super_block sb;
+	size_t retlen;
+	int err;
+
+	err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb);
+	if (err || (retlen != sizeof(sb))) {
+		pr_alert("error occured while reading from \"%s\"\n",
+			 master->name);
+		return -EIO;
+	}
+
+	if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) {
+		pr_alert("no squashfs found in \"%s\"\n", master->name);
+		return -EINVAL;
+	}
+
+	retlen = le64_to_cpu(sb.bytes_used);
+	if (retlen <= 0) {
+		pr_alert("squashfs is empty in \"%s\"\n", master->name);
+		return -ENODEV;
+	}
+
+	if (offset + retlen > master->size) {
+		pr_alert("squashfs has invalid size in \"%s\"\n",
+			 master->name);
+		return -EINVAL;
+	}
+
+	*squashfs_len = retlen;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mtd_get_squashfs_len);
+
+static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset)
+{
+	return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize;
+}
+
+int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
+			   enum mtdsplit_part_type *type)
+{
+	u32 magic;
+	size_t retlen;
+	int ret;
+
+	ret = mtd_read(mtd, offset, sizeof(magic), &retlen,
+		       (unsigned char *) &magic);
+	if (ret)
+		return ret;
+
+	if (retlen != sizeof(magic))
+		return -EIO;
+
+	if (le32_to_cpu(magic) == SQUASHFS_MAGIC) {
+		if (type)
+			*type = MTDSPLIT_PART_TYPE_SQUASHFS;
+		return 0;
+	} else if (magic == 0x19852003) {
+		if (type)
+			*type = MTDSPLIT_PART_TYPE_JFFS2;
+		return 0;
+	} else if (be32_to_cpu(magic) == UBI_EC_MAGIC) {
+		if (type)
+			*type = MTDSPLIT_PART_TYPE_UBI;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic);
+
+int mtd_find_rootfs_from(struct mtd_info *mtd,
+			 size_t from,
+			 size_t limit,
+			 size_t *ret_offset,
+			 enum mtdsplit_part_type *type)
+{
+	size_t offset;
+	int err;
+
+	for (offset = from; offset < limit;
+	     offset = mtd_next_eb(mtd, offset)) {
+		err = mtd_check_rootfs_magic(mtd, offset, type);
+		if (err)
+			continue;
+
+		*ret_offset = offset;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+EXPORT_SYMBOL_GPL(mtd_find_rootfs_from);
+
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit.h b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit.h
new file mode 100644
index 0000000..71d62a8
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ */
+
+#ifndef _MTDSPLIT_H
+#define _MTDSPLIT_H
+
+#define KERNEL_PART_NAME	"kernel"
+#define ROOTFS_PART_NAME	"rootfs"
+#define UBI_PART_NAME		"ubi"
+
+#define ROOTFS_SPLIT_NAME	"rootfs_data"
+
+enum mtdsplit_part_type {
+	MTDSPLIT_PART_TYPE_UNK = 0,
+	MTDSPLIT_PART_TYPE_SQUASHFS,
+	MTDSPLIT_PART_TYPE_JFFS2,
+	MTDSPLIT_PART_TYPE_UBI,
+};
+
+#ifdef CONFIG_MTD_SPLIT
+int mtd_get_squashfs_len(struct mtd_info *master,
+			 size_t offset,
+			 size_t *squashfs_len);
+
+int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
+			   enum mtdsplit_part_type *type);
+
+int mtd_find_rootfs_from(struct mtd_info *mtd,
+			 size_t from,
+			 size_t limit,
+			 size_t *ret_offset,
+			 enum mtdsplit_part_type *type);
+
+#else
+static inline int mtd_get_squashfs_len(struct mtd_info *master,
+				       size_t offset,
+				       size_t *squashfs_len)
+{
+	return -ENODEV;
+}
+
+static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
+					 enum mtdsplit_part_type *type)
+{
+	return -EINVAL;
+}
+
+static inline int mtd_find_rootfs_from(struct mtd_info *mtd,
+				       size_t from,
+				       size_t limit,
+				       size_t *ret_offset,
+				       enum mtdsplit_part_type *type)
+{
+	return -ENODEV;
+}
+#endif /* CONFIG_MTD_SPLIT */
+
+#endif /* _MTDSPLIT_H */
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_brnimage.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_brnimage.c
new file mode 100644
index 0000000..3f2d796
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_brnimage.c
@@ -0,0 +1,104 @@
+/*
+ *  Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ *  Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#include "mtdsplit.h"
+
+#define BRNIMAGE_NR_PARTS	2
+
+#define BRNIMAGE_ALIGN_BYTES	0x400
+#define BRNIMAGE_FOOTER_SIZE	12
+
+#define BRNIMAGE_MIN_OVERHEAD	(BRNIMAGE_FOOTER_SIZE)
+#define BRNIMAGE_MAX_OVERHEAD	(BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE)
+
+static int mtdsplit_parse_brnimage(struct mtd_info *master,
+				const struct mtd_partition **pparts,
+				struct mtd_part_parser_data *data)
+{
+	struct mtd_partition *parts;
+	uint32_t buf;
+	unsigned long rootfs_offset, rootfs_size, kernel_size;
+	size_t len;
+	int ret = 0;
+
+	for (rootfs_offset = 0; rootfs_offset < master->size;
+	     rootfs_offset += BRNIMAGE_ALIGN_BYTES) {
+		ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
+		if (!ret)
+			break;
+	}
+
+	if (ret)
+		return ret;
+
+	if (rootfs_offset >= master->size)
+		return -EINVAL;
+
+	ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len,
+			(void *)&buf);
+	if (ret)
+		return ret;
+
+	if (len != 4)
+		return -EIO;
+
+	kernel_size = le32_to_cpu(buf);
+
+	if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD))
+		return -EINVAL;
+
+	if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD))
+		return -EINVAL;
+
+	/*
+	 * The footer must be untouched as it contains the checksum of the
+	 * original brnImage (kernel + squashfs)!
+	 */
+	rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE;
+
+	parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = 0;
+	parts[0].size = kernel_size;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = rootfs_size;
+
+	*pparts = parts;
+	return BRNIMAGE_NR_PARTS;
+}
+
+static struct mtd_part_parser mtdsplit_brnimage_parser = {
+	.owner = THIS_MODULE,
+	.name = "brnimage-fw",
+	.parse_fn = mtdsplit_parse_brnimage,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_brnimage_init(void)
+{
+	register_mtd_parser(&mtdsplit_brnimage_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_brnimage_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_eva.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_eva.c
new file mode 100644
index 0000000..55004a6
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_eva.c
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ *  Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define EVA_NR_PARTS		2
+#define EVA_MAGIC		0xfeed1281
+#define EVA_FOOTER_SIZE		0x18
+#define EVA_DUMMY_SQUASHFS_SIZE	0x100
+
+struct eva_image_header {
+	uint32_t	magic;
+	uint32_t	size;
+};
+
+static int mtdsplit_parse_eva(struct mtd_info *master,
+				const struct mtd_partition **pparts,
+				struct mtd_part_parser_data *data)
+{
+	struct mtd_partition *parts;
+	struct eva_image_header hdr;
+	size_t retlen;
+	unsigned long kernel_size, rootfs_offset;
+	int err;
+
+	err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr);
+	if (err)
+		return err;
+
+	if (retlen != sizeof(hdr))
+		return -EIO;
+
+	if (le32_to_cpu(hdr.magic) != EVA_MAGIC)
+		return -EINVAL;
+
+	kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE;
+
+	/* rootfs starts at the next 0x10000 boundary: */
+	rootfs_offset = round_up(kernel_size, 0x10000);
+
+	/* skip the dummy EVA squashfs partition (with wrong endianness): */
+	rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE;
+
+	if (rootfs_offset >= master->size)
+		return -EINVAL;
+
+	err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
+	if (err)
+		return err;
+
+	parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = 0;
+	parts[0].size = kernel_size;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = master->size - rootfs_offset;
+
+	*pparts = parts;
+	return EVA_NR_PARTS;
+}
+
+static const struct of_device_id mtdsplit_eva_of_match_table[] = {
+	{ .compatible = "avm,eva-firmware" },
+	{},
+};
+
+static struct mtd_part_parser mtdsplit_eva_parser = {
+	.owner = THIS_MODULE,
+	.name = "eva-fw",
+	.of_match_table = mtdsplit_eva_of_match_table,
+	.parse_fn = mtdsplit_parse_eva,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_eva_init(void)
+{
+	register_mtd_parser(&mtdsplit_eva_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_eva_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_fit.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_fit.c
new file mode 100644
index 0000000..67ee33d
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_fit.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015 The Linux Foundation
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/types.h>
+#include <linux/byteorder/generic.h>
+#include <linux/slab.h>
+#include <linux/of_fdt.h>
+
+#include "mtdsplit.h"
+
+struct fdt_header {
+	uint32_t magic;			 /* magic word FDT_MAGIC */
+	uint32_t totalsize;		 /* total size of DT block */
+	uint32_t off_dt_struct;		 /* offset to structure */
+	uint32_t off_dt_strings;	 /* offset to strings */
+	uint32_t off_mem_rsvmap;	 /* offset to memory reserve map */
+	uint32_t version;		 /* format version */
+	uint32_t last_comp_version;	 /* last compatible version */
+
+	/* version 2 fields below */
+	uint32_t boot_cpuid_phys;	 /* Which physical CPU id we're
+					    booting on */
+	/* version 3 fields below */
+	uint32_t size_dt_strings;	 /* size of the strings block */
+
+	/* version 17 fields below */
+	uint32_t size_dt_struct;	 /* size of the structure block */
+};
+
+static int
+mtdsplit_fit_parse(struct mtd_info *mtd,
+		   const struct mtd_partition **pparts,
+	           struct mtd_part_parser_data *data)
+{
+	struct fdt_header hdr;
+	size_t hdr_len, retlen;
+	size_t offset;
+	size_t fit_offset, fit_size;
+	size_t rootfs_offset, rootfs_size;
+	struct mtd_partition *parts;
+	int ret;
+
+	hdr_len = sizeof(struct fdt_header);
+
+	/* Parse the MTD device & search for the FIT image location */
+	for(offset = 0; offset + hdr_len <= mtd->size; offset += mtd->erasesize) {
+		ret = mtd_read(mtd, offset, hdr_len, &retlen, (void*) &hdr);
+		if (ret) {
+			pr_err("read error in \"%s\" at offset 0x%llx\n",
+			       mtd->name, (unsigned long long) offset);
+			return ret;
+		}
+
+		if (retlen != hdr_len) {
+			pr_err("short read in \"%s\"\n", mtd->name);
+			return -EIO;
+		}
+
+		/* Check the magic - see if this is a FIT image */
+		if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) {
+			pr_debug("no valid FIT image found in \"%s\" at offset %llx\n",
+				 mtd->name, (unsigned long long) offset);
+			continue;
+		}
+
+		/* We found a FIT image. Let's keep going */
+		break;
+	}
+
+	fit_offset = offset;
+	fit_size = be32_to_cpu(hdr.totalsize);
+
+	if (fit_size == 0) {
+		pr_err("FIT image in \"%s\" at offset %llx has null size\n",
+		       mtd->name, (unsigned long long) fit_offset);
+		return -ENODEV;
+	}
+
+	/* Search for the rootfs partition after the FIT image */
+	ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size,
+				   &rootfs_offset, NULL);
+	if (ret) {
+		pr_info("no rootfs found after FIT image in \"%s\"\n",
+			mtd->name);
+		return ret;
+	}
+
+	rootfs_size = mtd->size - rootfs_offset;
+
+	parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = fit_offset;
+	parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = rootfs_size;
+
+	*pparts = parts;
+	return 2;
+}
+
+static const struct of_device_id mtdsplit_fit_of_match_table[] = {
+	{ .compatible = "denx,fit" },
+	{},
+};
+
+static struct mtd_part_parser uimage_parser = {
+	.owner = THIS_MODULE,
+	.name = "fit-fw",
+	.of_match_table = mtdsplit_fit_of_match_table,
+	.parse_fn = mtdsplit_fit_parse,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+/**************************************************
+ * Init
+ **************************************************/
+
+static int __init mtdsplit_fit_init(void)
+{
+	register_mtd_parser(&uimage_parser);
+
+	return 0;
+}
+
+module_init(mtdsplit_fit_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_jimage.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_jimage.c
new file mode 100644
index 0000000..1770dd4
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_jimage.c
@@ -0,0 +1,284 @@
+/*
+ *  Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com> 
+ *
+ *  Based on: mtdsplit_uimage.c
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE )
+
+#define STAG_SIZE 16
+#define STAG_ID 0x04
+#define STAG_MAGIC 0x2B24
+
+#define SCH2_SIZE 40
+#define SCH2_MAGIC 0x2124
+#define SCH2_VER 0x02
+
+/*
+ * Jboot image header,
+ * all data in little endian.
+ */
+
+struct jimage_header		//stag + sch2 jboot joined headers
+{
+	uint8_t stag_cmark;		// in factory 0xFF , in sysupgrade must be the same as stag_id
+	uint8_t stag_id;		// 0x04
+	uint16_t stag_magic;		//magic 0x2B24
+	uint32_t stag_time_stamp;	// timestamp calculated in jboot way
+	uint32_t stag_image_length;	// lentgh of kernel + sch2 header
+	uint16_t stag_image_checksum;	// negated jboot_checksum of sch2 + kernel
+	uint16_t stag_tag_checksum;	// negated jboot_checksum of stag header data
+	uint16_t sch2_magic;		// magic 0x2124
+	uint8_t sch2_cp_type;	// 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma
+	uint8_t sch2_version;	// 0x02 for sch2
+	uint32_t sch2_ram_addr;	// ram entry address
+	uint32_t sch2_image_len;	// kernel image length
+	uint32_t sch2_image_crc32;	// kernel image crc
+	uint32_t sch2_start_addr;	// ram start address
+	uint32_t sch2_rootfs_addr;	// rootfs flash address
+	uint32_t sch2_rootfs_len;	// rootfls length
+	uint32_t sch2_rootfs_crc32;	// rootfs crc32
+	uint32_t sch2_header_crc32;	// sch2 header crc32, durring calculation this area is replaced by zero
+	uint16_t sch2_header_length;	// sch2 header length: 0x28
+	uint16_t sch2_cmd_line_length;	// cmd line length, known zeros
+};
+
+static int
+read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
+		   size_t header_len)
+{
+	size_t retlen;
+	int ret;
+
+	ret = mtd_read(mtd, offset, header_len, &retlen, buf);
+	if (ret) {
+		pr_debug("read error in \"%s\"\n", mtd->name);
+		return ret;
+	}
+
+	if (retlen != header_len) {
+		pr_debug("short read in \"%s\"\n", mtd->name);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/**
+ * __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts
+ *
+ * @find_header: function to call for a block of data that will return offset
+ *      of a valid jImage header if found
+ */
+static int __mtdsplit_parse_jimage(struct mtd_info *master,
+				   const struct mtd_partition **pparts,
+				   struct mtd_part_parser_data *data,
+				   ssize_t (*find_header)(u_char *buf, size_t len))
+{
+	struct mtd_partition *parts;
+	u_char *buf;
+	int nr_parts;
+	size_t offset;
+	size_t jimage_offset;
+	size_t jimage_size = 0;
+	size_t rootfs_offset;
+	size_t rootfs_size = 0;
+	int jimage_part, rf_part;
+	int ret;
+	enum mtdsplit_part_type type;
+
+	nr_parts = 2;
+	parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	buf = vmalloc(MAX_HEADER_LEN);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto err_free_parts;
+	}
+
+	/* find jImage on erase block boundaries */
+	for (offset = 0; offset < master->size; offset += master->erasesize) {
+		struct jimage_header *header;
+
+		jimage_size = 0;
+
+		ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN);
+		if (ret)
+			continue;
+
+		ret = find_header(buf, MAX_HEADER_LEN);
+		if (ret < 0) {
+			pr_debug("no valid jImage found in \"%s\" at offset %llx\n",
+				 master->name, (unsigned long long) offset);
+			continue;
+		}
+		header = (struct jimage_header *)(buf + ret);
+
+		jimage_size = sizeof(*header) + header->sch2_image_len + ret;
+		if ((offset + jimage_size) > master->size) {
+			pr_debug("jImage exceeds MTD device \"%s\"\n",
+				 master->name);
+			continue;
+		}
+		break;
+	}
+
+	if (jimage_size == 0) {
+		pr_debug("no jImage found in \"%s\"\n", master->name);
+		ret = -ENODEV;
+		goto err_free_buf;
+	}
+
+	jimage_offset = offset;
+
+	if (jimage_offset == 0) {
+		jimage_part = 0;
+		rf_part = 1;
+
+		/* find the roots after the jImage */
+		ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size,
+					   master->size, &rootfs_offset, &type);
+		if (ret) {
+			pr_debug("no rootfs after jImage in \"%s\"\n",
+				 master->name);
+			goto err_free_buf;
+		}
+
+		rootfs_size = master->size - rootfs_offset;
+		jimage_size = rootfs_offset - jimage_offset;
+	} else {
+		rf_part = 0;
+		jimage_part = 1;
+
+		/* check rootfs presence at offset 0 */
+		ret = mtd_check_rootfs_magic(master, 0, &type);
+		if (ret) {
+			pr_debug("no rootfs before jImage in \"%s\"\n",
+				 master->name);
+			goto err_free_buf;
+		}
+
+		rootfs_offset = 0;
+		rootfs_size = jimage_offset;
+	}
+
+	if (rootfs_size == 0) {
+		pr_debug("no rootfs found in \"%s\"\n", master->name);
+		ret = -ENODEV;
+		goto err_free_buf;
+	}
+
+	parts[jimage_part].name = KERNEL_PART_NAME;
+	parts[jimage_part].offset = jimage_offset;
+	parts[jimage_part].size = jimage_size;
+
+	if (type == MTDSPLIT_PART_TYPE_UBI)
+		parts[rf_part].name = UBI_PART_NAME;
+	else
+		parts[rf_part].name = ROOTFS_PART_NAME;
+	parts[rf_part].offset = rootfs_offset;
+	parts[rf_part].size = rootfs_size;
+
+	vfree(buf);
+
+	*pparts = parts;
+	return nr_parts;
+
+err_free_buf:
+	vfree(buf);
+
+err_free_parts:
+	kfree(parts);
+	return ret;
+}
+
+static ssize_t jimage_verify_default(u_char *buf, size_t len)
+{
+	struct jimage_header *header = (struct jimage_header *)buf;
+
+	/* default sanity checks */
+	if (header->stag_magic != STAG_MAGIC) {
+		pr_debug("invalid jImage stag header magic: %04x\n",
+			 header->stag_magic);
+		return -EINVAL;
+	}
+	if (header->sch2_magic != SCH2_MAGIC) {
+		pr_debug("invalid jImage sch2 header magic: %04x\n",
+			 header->stag_magic);
+		return -EINVAL;
+	}
+	if (header->stag_cmark != header->stag_id) {
+		pr_debug("invalid jImage stag header cmark: %02x\n",
+			 header->stag_magic);
+		return -EINVAL;
+	}
+	if (header->stag_id != STAG_ID) {
+		pr_debug("invalid jImage stag header id: %02x\n",
+			 header->stag_magic);
+		return -EINVAL;
+	}
+	if (header->sch2_version != SCH2_VER) {
+		pr_debug("invalid jImage sch2 header version: %02x\n",
+			 header->stag_magic);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+mtdsplit_jimage_parse_generic(struct mtd_info *master,
+			      const struct mtd_partition **pparts,
+			      struct mtd_part_parser_data *data)
+{
+	return __mtdsplit_parse_jimage(master, pparts, data,
+				      jimage_verify_default);
+}
+
+static const struct of_device_id mtdsplit_jimage_of_match_table[] = {
+	{ .compatible = "amit,jimage" },
+	{},
+};
+
+static struct mtd_part_parser jimage_generic_parser = {
+	.owner = THIS_MODULE,
+	.name = "jimage-fw",
+	.of_match_table = mtdsplit_jimage_of_match_table,
+	.parse_fn = mtdsplit_jimage_parse_generic,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+/**************************************************
+ * Init
+ **************************************************/
+
+static int __init mtdsplit_jimage_init(void)
+{
+	register_mtd_parser(&jimage_generic_parser);
+
+	return 0;
+}
+
+module_init(mtdsplit_jimage_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_lzma.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_lzma.c
new file mode 100644
index 0000000..c58f7ae
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_lzma.c
@@ -0,0 +1,104 @@
+/*
+ *  Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/of.h>
+
+#include <asm/unaligned.h>
+
+#include "mtdsplit.h"
+
+#define LZMA_NR_PARTS		2
+#define LZMA_PROPERTIES_SIZE	5
+
+struct lzma_header {
+	u8 props[LZMA_PROPERTIES_SIZE];
+	u8 size_low[4];
+	u8 size_high[4];
+};
+
+static int mtdsplit_parse_lzma(struct mtd_info *master,
+			       const struct mtd_partition **pparts,
+			       struct mtd_part_parser_data *data)
+{
+	struct lzma_header hdr;
+	size_t hdr_len, retlen;
+	size_t rootfs_offset;
+	u32 t;
+	struct mtd_partition *parts;
+	int err;
+
+	hdr_len = sizeof(hdr);
+	err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
+	if (err)
+		return err;
+
+	if (retlen != hdr_len)
+		return -EIO;
+
+	/* verify LZMA properties */
+	if (hdr.props[0] >= (9 * 5 * 5))
+		return -EINVAL;
+
+	t = get_unaligned_le32(&hdr.props[1]);
+	if (!is_power_of_2(t))
+		return -EINVAL;
+
+	t = get_unaligned_le32(&hdr.size_high);
+	if (t)
+		return -EINVAL;
+
+	err = mtd_find_rootfs_from(master, master->erasesize, master->size,
+				   &rootfs_offset, NULL);
+	if (err)
+		return err;
+
+	parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = 0;
+	parts[0].size = rootfs_offset;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = master->size - rootfs_offset;
+
+	*pparts = parts;
+	return LZMA_NR_PARTS;
+}
+
+static const struct of_device_id mtdsplit_lzma_of_match_table[] = {
+	{ .compatible = "lzma" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mtdsplit_lzma_of_match_table);
+
+static struct mtd_part_parser mtdsplit_lzma_parser = {
+	.owner = THIS_MODULE,
+	.name = "lzma-fw",
+	.of_match_table = mtdsplit_lzma_of_match_table,
+	.parse_fn = mtdsplit_parse_lzma,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_lzma_init(void)
+{
+	register_mtd_parser(&mtdsplit_lzma_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_lzma_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_minor.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_minor.c
new file mode 100644
index 0000000..af6822e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_minor.c
@@ -0,0 +1,125 @@
+/*
+ *  MTD splitter for MikroTik NOR devices
+ *
+ *  Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ *  The rootfs is expected at erase-block boundary due to the use of
+ *  mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header
+ *  for two main reasons:
+ *  - the original header uses weakly defined types (int, enum...) which can
+ *    vary in length depending on build host (and the struct is not packed),
+ *    and the name field can have a different total length depending on
+ *    whether or not the yaffs code was _built_ with unicode support.
+ *  - the only field that could be of real use here (file_size_low) contains
+ *    invalid data in the header generated by kernel2minor, so we cannot use
+ *    it to infer the exact position of the rootfs and do away with
+ *    mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs).
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/string.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define YAFFS_OBJECT_TYPE_FILE	0x1
+#define YAFFS_OBJECTID_ROOT	0x1
+#define YAFFS_SUM_UNUSED	0xFFFF
+#define YAFFS_NAME		"kernel"
+
+#define MINOR_NR_PARTS		2
+
+/*
+ * This structure is based on yaffs_obj_hdr from yaffs_guts.h
+ * The weak types match upstream. The fields have cpu-endianness
+ */
+struct minor_header {
+	int yaffs_type;
+	int yaffs_obj_id;
+	u16 yaffs_sum_unused;
+	char yaffs_name[sizeof(YAFFS_NAME)];
+};
+
+static int mtdsplit_parse_minor(struct mtd_info *master,
+				const struct mtd_partition **pparts,
+				struct mtd_part_parser_data *data)
+{
+	struct minor_header hdr;
+	size_t hdr_len, retlen;
+	size_t rootfs_offset;
+	struct mtd_partition *parts;
+	int err;
+
+	hdr_len = sizeof(hdr);
+	err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
+	if (err)
+		return err;
+
+	if (retlen != hdr_len)
+		return -EIO;
+
+	/* match header */
+	if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE)
+		return -EINVAL;
+
+	if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT)
+		return -EINVAL;
+
+	if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED)
+		return -EINVAL;
+
+	if (memcmp(hdr.yaffs_name, YAFFS_NAME, sizeof(YAFFS_NAME)))
+		return -EINVAL;
+
+	err = mtd_find_rootfs_from(master, master->erasesize, master->size,
+				   &rootfs_offset, NULL);
+	if (err)
+		return err;
+
+	parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = 0;
+	parts[0].size = rootfs_offset;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = master->size - rootfs_offset;
+
+	*pparts = parts;
+	return MINOR_NR_PARTS;
+}
+
+static const struct of_device_id mtdsplit_minor_of_match_table[] = {
+	{ .compatible = "mikrotik,minor" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mtdsplit_minor_of_match_table);
+
+static struct mtd_part_parser mtdsplit_minor_parser = {
+	.owner = THIS_MODULE,
+	.name = "minor-fw",
+	.of_match_table = mtdsplit_minor_of_match_table,
+	.parse_fn = mtdsplit_parse_minor,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_minor_init(void)
+{
+	register_mtd_parser(&mtdsplit_minor_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_minor_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_seama.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_seama.c
new file mode 100644
index 0000000..5d49171
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_seama.c
@@ -0,0 +1,118 @@
+/*
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define SEAMA_MAGIC		0x5EA3A417
+#define SEAMA_NR_PARTS		2
+#define SEAMA_MIN_ROOTFS_OFFS	0x80000	/* 512KiB */
+
+struct seama_header {
+	__be32	magic;		/* should always be SEAMA_MAGIC. */
+	__be16	reserved;	/* reserved for  */
+	__be16	metasize;	/* size of the META data */
+	__be32	size;		/* size of the image */
+	u8	md5[16];	/* digest */
+};
+
+static int mtdsplit_parse_seama(struct mtd_info *master,
+				const struct mtd_partition **pparts,
+				struct mtd_part_parser_data *data)
+{
+	struct seama_header hdr;
+	size_t hdr_len, retlen, kernel_ent_size;
+	size_t rootfs_offset;
+	struct mtd_partition *parts;
+	enum mtdsplit_part_type type;
+	int err;
+
+	hdr_len = sizeof(hdr);
+	err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
+	if (err)
+		return err;
+
+	if (retlen != hdr_len)
+		return -EIO;
+
+	/* sanity checks */
+	if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC)
+		return -EINVAL;
+
+	kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) +
+			  be16_to_cpu(hdr.metasize);
+	if (kernel_ent_size > master->size)
+		return -EINVAL;
+
+	/* Check for the rootfs right after Seama entity with a kernel. */
+	err = mtd_check_rootfs_magic(master, kernel_ent_size, &type);
+	if (!err) {
+		rootfs_offset = kernel_ent_size;
+	} else {
+		/*
+		 * On some devices firmware entity might contain both: kernel
+		 * and rootfs. We can't determine kernel size so we just have to
+		 * look for rootfs magic.
+		 * Start the search from an arbitrary offset.
+		 */
+		err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS,
+					   master->size, &rootfs_offset, &type);
+		if (err)
+			return err;
+	}
+
+	parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize);
+	parts[0].size = rootfs_offset - parts[0].offset;
+
+	if (type == MTDSPLIT_PART_TYPE_UBI)
+		parts[1].name = UBI_PART_NAME;
+	else
+		parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = master->size - rootfs_offset;
+
+	*pparts = parts;
+	return SEAMA_NR_PARTS;
+}
+
+static const struct of_device_id mtdsplit_seama_of_match_table[] = {
+	{ .compatible = "seama" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mtdsplit_seama_of_match_table);
+
+static struct mtd_part_parser mtdsplit_seama_parser = {
+	.owner = THIS_MODULE,
+	.name = "seama-fw",
+	.of_match_table = mtdsplit_seama_of_match_table,
+	.parse_fn = mtdsplit_parse_seama,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_seama_init(void)
+{
+	register_mtd_parser(&mtdsplit_seama_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_seama_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_squashfs.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_squashfs.c
new file mode 100644
index 0000000..79e1f73
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_squashfs.c
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#include "mtdsplit.h"
+
+static int
+mtdsplit_parse_squashfs(struct mtd_info *master,
+			const struct mtd_partition **pparts,
+			struct mtd_part_parser_data *data)
+{
+	struct mtd_partition *part;
+	struct mtd_info *parent_mtd;
+	size_t part_offset;
+	size_t squashfs_len;
+	int err;
+
+	err = mtd_get_squashfs_len(master, 0, &squashfs_len);
+	if (err)
+		return err;
+
+	parent_mtd = mtdpart_get_master(master);
+	part_offset = mtdpart_get_offset(master);
+
+	part = kzalloc(sizeof(*part), GFP_KERNEL);
+	if (!part) {
+		pr_alert("unable to allocate memory for \"%s\" partition\n",
+			 ROOTFS_SPLIT_NAME);
+		return -ENOMEM;
+	}
+
+	part->name = ROOTFS_SPLIT_NAME;
+	part->offset = mtd_roundup_to_eb(part_offset + squashfs_len,
+					 parent_mtd) - part_offset;
+	part->size = mtd_rounddown_to_eb(master->size - part->offset, master);
+
+	*pparts = part;
+	return 1;
+}
+
+static struct mtd_part_parser mtdsplit_squashfs_parser = {
+	.owner = THIS_MODULE,
+	.name = "squashfs-split",
+	.parse_fn = mtdsplit_parse_squashfs,
+	.type = MTD_PARSER_TYPE_ROOTFS,
+};
+
+static int __init mtdsplit_squashfs_init(void)
+{
+	register_mtd_parser(&mtdsplit_squashfs_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_squashfs_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_tplink.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_tplink.c
new file mode 100644
index 0000000..8909c10
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_tplink.c
@@ -0,0 +1,176 @@
+/*
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *  Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define TPLINK_NR_PARTS		2
+#define TPLINK_MIN_ROOTFS_OFFS	0x80000	/* 512KiB */
+
+#define MD5SUM_LEN  16
+
+struct fw_v1 {
+	char		vendor_name[24];
+	char		fw_version[36];
+	uint32_t	hw_id;		/* hardware id */
+	uint32_t	hw_rev;		/* hardware revision */
+	uint32_t	unk1;
+	uint8_t		md5sum1[MD5SUM_LEN];
+	uint32_t	unk2;
+	uint8_t		md5sum2[MD5SUM_LEN];
+	uint32_t	unk3;
+	uint32_t	kernel_la;	/* kernel load address */
+	uint32_t	kernel_ep;	/* kernel entry point */
+	uint32_t	fw_length;	/* total length of the firmware */
+	uint32_t	kernel_ofs;	/* kernel data offset */
+	uint32_t	kernel_len;	/* kernel data length */
+	uint32_t	rootfs_ofs;	/* rootfs data offset */
+	uint32_t	rootfs_len;	/* rootfs data length */
+	uint32_t	boot_ofs;	/* bootloader data offset */
+	uint32_t	boot_len;	/* bootloader data length */
+	uint8_t		pad[360];
+} __attribute__ ((packed));
+
+struct fw_v2 {
+	char		fw_version[48]; /* 0x04: fw version string */
+	uint32_t	hw_id;		/* 0x34: hardware id */
+	uint32_t	hw_rev;		/* 0x38: FIXME: hardware revision? */
+	uint32_t	unk1;	        /* 0x3c: 0x00000000 */
+	uint8_t		md5sum1[MD5SUM_LEN]; /* 0x40 */
+	uint32_t	unk2;		/* 0x50: 0x00000000 */
+	uint8_t		md5sum2[MD5SUM_LEN]; /* 0x54 */
+	uint32_t	unk3;		/* 0x64: 0xffffffff */
+
+	uint32_t	kernel_la;	/* 0x68: kernel load address */
+	uint32_t	kernel_ep;	/* 0x6c: kernel entry point */
+	uint32_t	fw_length;	/* 0x70: total length of the image */
+	uint32_t	kernel_ofs;	/* 0x74: kernel data offset */
+	uint32_t	kernel_len;	/* 0x78: kernel data length */
+	uint32_t	rootfs_ofs;	/* 0x7c: rootfs data offset */
+	uint32_t	rootfs_len;	/* 0x80: rootfs data length */
+	uint32_t	boot_ofs;	/* 0x84: FIXME: seems to be unused */
+	uint32_t	boot_len;	/* 0x88: FIXME: seems to be unused */
+	uint16_t	unk4;		/* 0x8c: 0x55aa */
+	uint8_t		sver_hi;	/* 0x8e */
+	uint8_t		sver_lo;	/* 0x8f */
+	uint8_t		unk5;		/* 0x90: magic: 0xa5 */
+	uint8_t		ver_hi;         /* 0x91 */
+	uint8_t		ver_mid;        /* 0x92 */
+	uint8_t		ver_lo;         /* 0x93 */
+	uint8_t		pad[364];
+} __attribute__ ((packed));
+
+struct tplink_fw_header {
+	uint32_t version;
+	union {
+		struct fw_v1 v1;
+		struct fw_v2 v2;
+	};
+};
+
+static int mtdsplit_parse_tplink(struct mtd_info *master,
+				 const struct mtd_partition **pparts,
+				 struct mtd_part_parser_data *data)
+{
+	struct tplink_fw_header hdr;
+	size_t hdr_len, retlen, kernel_size;
+	size_t rootfs_offset;
+	struct mtd_partition *parts;
+	int err;
+
+	hdr_len = sizeof(hdr);
+	err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
+	if (err)
+		return err;
+
+	if (retlen != hdr_len)
+		return -EIO;
+
+	switch (le32_to_cpu(hdr.version)) {
+	case 1:
+		if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr))
+			return -EINVAL;
+
+		kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len);
+		rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs);
+		break;
+	case 2:
+	case 3:
+		if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr))
+			return -EINVAL;
+
+		kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len);
+		rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (kernel_size > master->size)
+		return -EINVAL;
+
+	/* Find the rootfs */
+	err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
+	if (err) {
+		/*
+		 * The size in the header might cover the rootfs as well.
+		 * Start the search from an arbitrary offset.
+		 */
+		err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS,
+					   master->size, &rootfs_offset, NULL);
+		if (err)
+			return err;
+	}
+
+	parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = 0;
+	parts[0].size = kernel_size;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = master->size - rootfs_offset;
+
+	*pparts = parts;
+	return TPLINK_NR_PARTS;
+}
+
+static const struct of_device_id mtdsplit_tplink_of_match_table[] = {
+	{ .compatible = "tplink,firmware" },
+	{},
+};
+
+static struct mtd_part_parser mtdsplit_tplink_parser = {
+	.owner = THIS_MODULE,
+	.name = "tplink-fw",
+	.of_match_table = mtdsplit_tplink_of_match_table,
+	.parse_fn = mtdsplit_parse_tplink,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_tplink_init(void)
+{
+	register_mtd_parser(&mtdsplit_tplink_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_tplink_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_trx.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_trx.c
new file mode 100644
index 0000000..b853ec9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_trx.c
@@ -0,0 +1,155 @@
+/*
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *  Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define TRX_MAGIC   0x30524448  /* "HDR0" */
+
+struct trx_header {
+	__le32 magic;
+	__le32 len;
+	__le32 crc32;
+	__le32 flag_version;
+	__le32 offset[4];
+};
+
+static int
+read_trx_header(struct mtd_info *mtd, size_t offset,
+		   struct trx_header *header)
+{
+	size_t header_len;
+	size_t retlen;
+	int ret;
+
+	header_len = sizeof(*header);
+	ret = mtd_read(mtd, offset, header_len, &retlen,
+		       (unsigned char *) header);
+	if (ret) {
+		pr_debug("read error in \"%s\"\n", mtd->name);
+		return ret;
+	}
+
+	if (retlen != header_len) {
+		pr_debug("short read in \"%s\"\n", mtd->name);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int
+mtdsplit_parse_trx(struct mtd_info *master,
+		   const struct mtd_partition **pparts,
+		   struct mtd_part_parser_data *data)
+{
+	struct mtd_partition *parts;
+	struct trx_header hdr;
+	int nr_parts;
+	size_t offset;
+	size_t trx_offset;
+	size_t trx_size = 0;
+	size_t rootfs_offset;
+	size_t rootfs_size = 0;
+	int ret;
+
+	nr_parts = 2;
+	parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	/* find trx image on erase block boundaries */
+	for (offset = 0; offset < master->size; offset += master->erasesize) {
+		trx_size = 0;
+
+		ret = read_trx_header(master, offset, &hdr);
+		if (ret)
+			continue;
+
+		if (hdr.magic != cpu_to_le32(TRX_MAGIC)) {
+			pr_debug("no valid trx header found in \"%s\" at offset %llx\n",
+				 master->name, (unsigned long long) offset);
+			continue;
+		}
+
+		trx_size = le32_to_cpu(hdr.len);
+		if ((offset + trx_size) > master->size) {
+			pr_debug("trx image exceeds MTD device \"%s\"\n",
+				 master->name);
+			continue;
+		}
+		break;
+	}
+
+	if (trx_size == 0) {
+		pr_debug("no trx header found in \"%s\"\n", master->name);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	trx_offset = offset + hdr.offset[0];
+	rootfs_offset = offset + hdr.offset[1];
+	rootfs_size = master->size - rootfs_offset;
+	trx_size = rootfs_offset - trx_offset;
+
+	if (rootfs_size == 0) {
+		pr_debug("no rootfs found in \"%s\"\n", master->name);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = trx_offset;
+	parts[0].size = trx_size;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = rootfs_size;
+
+	*pparts = parts;
+	return nr_parts;
+
+err:
+	kfree(parts);
+	return ret;
+}
+
+static const struct of_device_id trx_parser_of_match_table[] = {
+	{ .compatible = "openwrt,trx" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, trx_parser_of_match_table);
+
+static struct mtd_part_parser trx_parser = {
+	.owner = THIS_MODULE,
+	.name = "trx-fw",
+	.of_match_table = trx_parser_of_match_table,
+	.parse_fn = mtdsplit_parse_trx,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_trx_init(void)
+{
+	register_mtd_parser(&trx_parser);
+
+	return 0;
+}
+
+module_init(mtdsplit_trx_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_uimage.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_uimage.c
new file mode 100644
index 0000000..a6e50b5
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_uimage.c
@@ -0,0 +1,498 @@
+/*
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/version.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+/*
+ * uimage_header itself is only 64B, but it may be prepended with another data.
+ * Currently the biggest size is for Fon(Foxconn) devices: 64B + 32B
+ */
+#define MAX_HEADER_LEN		96
+
+#define IH_MAGIC	0x27051956	/* Image Magic Number		*/
+#define IH_NMLEN		32	/* Image Name Length		*/
+
+#define IH_OS_LINUX		5	/* Linux	*/
+
+#define IH_TYPE_KERNEL		2	/* OS Kernel Image		*/
+#define IH_TYPE_FILESYSTEM	7	/* Filesystem Image		*/
+
+/*
+ * Legacy format image header,
+ * all data in network byte order (aka natural aka bigendian).
+ */
+struct uimage_header {
+	uint32_t	ih_magic;	/* Image Header Magic Number	*/
+	uint32_t	ih_hcrc;	/* Image Header CRC Checksum	*/
+	uint32_t	ih_time;	/* Image Creation Timestamp	*/
+	uint32_t	ih_size;	/* Image Data Size		*/
+	uint32_t	ih_load;	/* Data	 Load  Address		*/
+	uint32_t	ih_ep;		/* Entry Point Address		*/
+	uint32_t	ih_dcrc;	/* Image Data CRC Checksum	*/
+	uint8_t		ih_os;		/* Operating System		*/
+	uint8_t		ih_arch;	/* CPU architecture		*/
+	uint8_t		ih_type;	/* Image Type			*/
+	uint8_t		ih_comp;	/* Compression Type		*/
+	uint8_t		ih_name[IH_NMLEN];	/* Image Name		*/
+};
+
+static int
+read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
+		   size_t header_len)
+{
+	size_t retlen;
+	int ret;
+
+	ret = mtd_read(mtd, offset, header_len, &retlen, buf);
+	if (ret) {
+		pr_debug("read error in \"%s\"\n", mtd->name);
+		return ret;
+	}
+
+	if (retlen != header_len) {
+		pr_debug("short read in \"%s\"\n", mtd->name);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/**
+ * __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts
+ *
+ * @find_header: function to call for a block of data that will return offset
+ *      and tail padding length of a valid uImage header if found
+ */
+static int __mtdsplit_parse_uimage(struct mtd_info *master,
+		   const struct mtd_partition **pparts,
+		   struct mtd_part_parser_data *data,
+		   ssize_t (*find_header)(u_char *buf, size_t len, int *extralen))
+{
+	struct mtd_partition *parts;
+	u_char *buf;
+	int nr_parts;
+	size_t offset;
+	size_t uimage_offset;
+	size_t uimage_size = 0;
+	size_t rootfs_offset;
+	size_t rootfs_size = 0;
+	int uimage_part, rf_part;
+	int ret;
+	int extralen;
+	enum mtdsplit_part_type type;
+
+	nr_parts = 2;
+	parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	buf = vmalloc(MAX_HEADER_LEN);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto err_free_parts;
+	}
+
+	/* find uImage on erase block boundaries */
+	for (offset = 0; offset < master->size; offset += master->erasesize) {
+		struct uimage_header *header;
+
+		uimage_size = 0;
+
+		ret = read_uimage_header(master, offset, buf, MAX_HEADER_LEN);
+		if (ret)
+			continue;
+
+		extralen = 0;
+		ret = find_header(buf, MAX_HEADER_LEN, &extralen);
+		if (ret < 0) {
+			pr_debug("no valid uImage found in \"%s\" at offset %llx\n",
+				 master->name, (unsigned long long) offset);
+			continue;
+		}
+		header = (struct uimage_header *)(buf + ret);
+
+		uimage_size = sizeof(*header) +
+				be32_to_cpu(header->ih_size) + ret + extralen;
+
+		if ((offset + uimage_size) > master->size) {
+			pr_debug("uImage exceeds MTD device \"%s\"\n",
+				 master->name);
+			continue;
+		}
+		break;
+	}
+
+	if (uimage_size == 0) {
+		pr_debug("no uImage found in \"%s\"\n", master->name);
+		ret = -ENODEV;
+		goto err_free_buf;
+	}
+
+	uimage_offset = offset;
+
+	if (uimage_offset == 0) {
+		uimage_part = 0;
+		rf_part = 1;
+
+		/* find the roots after the uImage */
+		ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size,
+					   master->size, &rootfs_offset, &type);
+		if (ret) {
+			pr_debug("no rootfs after uImage in \"%s\"\n",
+				 master->name);
+			goto err_free_buf;
+		}
+
+		rootfs_size = master->size - rootfs_offset;
+		uimage_size = rootfs_offset - uimage_offset;
+	} else {
+		rf_part = 0;
+		uimage_part = 1;
+
+		/* check rootfs presence at offset 0 */
+		ret = mtd_check_rootfs_magic(master, 0, &type);
+		if (ret) {
+			pr_debug("no rootfs before uImage in \"%s\"\n",
+				 master->name);
+			goto err_free_buf;
+		}
+
+		rootfs_offset = 0;
+		rootfs_size = uimage_offset;
+	}
+
+	if (rootfs_size == 0) {
+		pr_debug("no rootfs found in \"%s\"\n", master->name);
+		ret = -ENODEV;
+		goto err_free_buf;
+	}
+
+	parts[uimage_part].name = KERNEL_PART_NAME;
+	parts[uimage_part].offset = uimage_offset;
+	parts[uimage_part].size = uimage_size;
+
+	if (type == MTDSPLIT_PART_TYPE_UBI)
+		parts[rf_part].name = UBI_PART_NAME;
+	else
+		parts[rf_part].name = ROOTFS_PART_NAME;
+	parts[rf_part].offset = rootfs_offset;
+	parts[rf_part].size = rootfs_size;
+
+	vfree(buf);
+
+	*pparts = parts;
+	return nr_parts;
+
+err_free_buf:
+	vfree(buf);
+
+err_free_parts:
+	kfree(parts);
+	return ret;
+}
+
+static ssize_t uimage_verify_default(u_char *buf, size_t len, int *extralen)
+{
+	struct uimage_header *header = (struct uimage_header *)buf;
+
+	/* default sanity checks */
+	if (be32_to_cpu(header->ih_magic) != IH_MAGIC) {
+		pr_debug("invalid uImage magic: %08x\n",
+			 be32_to_cpu(header->ih_magic));
+		return -EINVAL;
+	}
+
+	if (header->ih_os != IH_OS_LINUX) {
+		pr_debug("invalid uImage OS: %08x\n",
+			 be32_to_cpu(header->ih_os));
+		return -EINVAL;
+	}
+
+	if (header->ih_type != IH_TYPE_KERNEL) {
+		pr_debug("invalid uImage type: %08x\n",
+			 be32_to_cpu(header->ih_type));
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+mtdsplit_uimage_parse_generic(struct mtd_info *master,
+			      const struct mtd_partition **pparts,
+			      struct mtd_part_parser_data *data)
+{
+	return __mtdsplit_parse_uimage(master, pparts, data,
+				      uimage_verify_default);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+static const struct of_device_id mtdsplit_uimage_of_match_table[] = {
+	{ .compatible = "denx,uimage" },
+	{},
+};
+#endif
+
+static struct mtd_part_parser uimage_generic_parser = {
+	.owner = THIS_MODULE,
+	.name = "uimage-fw",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+	.of_match_table = mtdsplit_uimage_of_match_table,
+#endif
+	.parse_fn = mtdsplit_uimage_parse_generic,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+#define FW_MAGIC_WNR2000V1	0x32303031
+#define FW_MAGIC_WNR2000V3	0x32303033
+#define FW_MAGIC_WNR2000V4	0x32303034
+#define FW_MAGIC_WNR2200	0x32323030
+#define FW_MAGIC_WNR612V2	0x32303631
+#define FW_MAGIC_WNR1000V2	0x31303031
+#define FW_MAGIC_WNR1000V2_VC	0x31303030
+#define FW_MAGIC_WNDR3700	0x33373030
+#define FW_MAGIC_WNDR3700V2	0x33373031
+#define FW_MAGIC_WPN824N	0x31313030
+
+static ssize_t uimage_verify_wndr3700(u_char *buf, size_t len, int *extralen)
+{
+	struct uimage_header *header = (struct uimage_header *)buf;
+	uint8_t expected_type = IH_TYPE_FILESYSTEM;
+
+	switch (be32_to_cpu(header->ih_magic)) {
+	case FW_MAGIC_WNR612V2:
+	case FW_MAGIC_WNR1000V2:
+	case FW_MAGIC_WNR1000V2_VC:
+	case FW_MAGIC_WNR2000V1:
+	case FW_MAGIC_WNR2000V3:
+	case FW_MAGIC_WNR2200:
+	case FW_MAGIC_WNDR3700:
+	case FW_MAGIC_WNDR3700V2:
+	case FW_MAGIC_WPN824N:
+		break;
+	case FW_MAGIC_WNR2000V4:
+		expected_type = IH_TYPE_KERNEL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (header->ih_os != IH_OS_LINUX ||
+	    header->ih_type != expected_type)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+mtdsplit_uimage_parse_netgear(struct mtd_info *master,
+			      const struct mtd_partition **pparts,
+			      struct mtd_part_parser_data *data)
+{
+	return __mtdsplit_parse_uimage(master, pparts, data,
+				      uimage_verify_wndr3700);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+static const struct of_device_id mtdsplit_uimage_netgear_of_match_table[] = {
+	{ .compatible = "netgear,uimage" },
+	{},
+};
+#endif
+
+static struct mtd_part_parser uimage_netgear_parser = {
+	.owner = THIS_MODULE,
+	.name = "netgear-fw",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+	.of_match_table = mtdsplit_uimage_netgear_of_match_table,
+#endif
+	.parse_fn = mtdsplit_uimage_parse_netgear,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+/**************************************************
+ * Edimax
+ **************************************************/
+
+#define FW_EDIMAX_OFFSET	20
+#define FW_MAGIC_EDIMAX		0x43535953
+
+static ssize_t uimage_find_edimax(u_char *buf, size_t len, int *extralen)
+{
+	u32 *magic;
+
+	if (len < FW_EDIMAX_OFFSET + sizeof(struct uimage_header)) {
+		pr_err("Buffer too small for checking Edimax header\n");
+		return -ENOSPC;
+	}
+
+	magic = (u32 *)buf;
+	if (be32_to_cpu(*magic) != FW_MAGIC_EDIMAX)
+		return -EINVAL;
+
+	if (!uimage_verify_default(buf + FW_EDIMAX_OFFSET, len, extralen))
+		return FW_EDIMAX_OFFSET;
+
+	return -EINVAL;
+}
+
+static int
+mtdsplit_uimage_parse_edimax(struct mtd_info *master,
+			      const struct mtd_partition **pparts,
+			      struct mtd_part_parser_data *data)
+{
+	return __mtdsplit_parse_uimage(master, pparts, data,
+				       uimage_find_edimax);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+static const struct of_device_id mtdsplit_uimage_edimax_of_match_table[] = {
+	{ .compatible = "edimax,uimage" },
+	{},
+};
+#endif
+
+static struct mtd_part_parser uimage_edimax_parser = {
+	.owner = THIS_MODULE,
+	.name = "edimax-fw",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+	.of_match_table = mtdsplit_uimage_edimax_of_match_table,
+#endif
+	.parse_fn = mtdsplit_uimage_parse_edimax,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+
+/**************************************************
+ * Fon(Foxconn)
+ **************************************************/
+
+#define FONFXC_PAD_LEN		32
+
+static ssize_t uimage_find_fonfxc(u_char *buf, size_t len, int *extralen)
+{
+	if (uimage_verify_default(buf, len, extralen) < 0)
+		return -EINVAL;
+
+	*extralen = FONFXC_PAD_LEN;
+
+	return 0;
+}
+
+static int
+mtdsplit_uimage_parse_fonfxc(struct mtd_info *master,
+			      const struct mtd_partition **pparts,
+			      struct mtd_part_parser_data *data)
+{
+	return __mtdsplit_parse_uimage(master, pparts, data,
+				       uimage_find_fonfxc);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+static const struct of_device_id mtdsplit_uimage_fonfxc_of_match_table[] = {
+	{ .compatible = "fonfxc,uimage" },
+	{},
+};
+#endif
+
+static struct mtd_part_parser uimage_fonfxc_parser = {
+	.owner = THIS_MODULE,
+	.name = "fonfxc-fw",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+	.of_match_table = mtdsplit_uimage_fonfxc_of_match_table,
+#endif
+	.parse_fn = mtdsplit_uimage_parse_fonfxc,
+};
+
+/**************************************************
+ * OKLI (OpenWrt Kernel Loader Image)
+ **************************************************/
+
+#define IH_MAGIC_OKLI	0x4f4b4c49
+
+static ssize_t uimage_verify_okli(u_char *buf, size_t len, int *extralen)
+{
+	struct uimage_header *header = (struct uimage_header *)buf;
+
+	/* default sanity checks */
+	if (be32_to_cpu(header->ih_magic) != IH_MAGIC_OKLI) {
+		pr_debug("invalid uImage magic: %08x\n",
+			 be32_to_cpu(header->ih_magic));
+		return -EINVAL;
+	}
+
+	if (header->ih_os != IH_OS_LINUX) {
+		pr_debug("invalid uImage OS: %08x\n",
+			 be32_to_cpu(header->ih_os));
+		return -EINVAL;
+	}
+
+	if (header->ih_type != IH_TYPE_KERNEL) {
+		pr_debug("invalid uImage type: %08x\n",
+			 be32_to_cpu(header->ih_type));
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+mtdsplit_uimage_parse_okli(struct mtd_info *master,
+			      const struct mtd_partition **pparts,
+			      struct mtd_part_parser_data *data)
+{
+	return __mtdsplit_parse_uimage(master, pparts, data,
+				      uimage_verify_okli);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+static const struct of_device_id mtdsplit_uimage_okli_of_match_table[] = {
+	{ .compatible = "openwrt,okli" },
+	{},
+};
+#endif
+
+static struct mtd_part_parser uimage_okli_parser = {
+	.owner = THIS_MODULE,
+	.name = "okli-fw",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
+	.of_match_table = mtdsplit_uimage_okli_of_match_table,
+#endif
+	.parse_fn = mtdsplit_uimage_parse_okli,
+};
+
+/**************************************************
+ * Init
+ **************************************************/
+
+static int __init mtdsplit_uimage_init(void)
+{
+	register_mtd_parser(&uimage_generic_parser);
+	register_mtd_parser(&uimage_netgear_parser);
+	register_mtd_parser(&uimage_edimax_parser);
+	register_mtd_parser(&uimage_fonfxc_parser);
+	register_mtd_parser(&uimage_okli_parser);
+
+	return 0;
+}
+
+module_init(mtdsplit_uimage_init);
diff --git a/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_wrgg.c b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_wrgg.c
new file mode 100644
index 0000000..dfd6058
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/mtd/mtdsplit/mtdsplit_wrgg.c
@@ -0,0 +1,142 @@
+/*
+ *  Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *  Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *  Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/of.h>
+
+#include "mtdsplit.h"
+
+#define WRGG_NR_PARTS		2
+#define WRGG_MIN_ROOTFS_OFFS	0x80000	/* 512KiB */
+#define WRGG03_MAGIC		0x20080321
+#define WRG_MAGIC		0x20040220
+
+struct wrgg03_header {
+	char		signature[32];
+	uint32_t	magic1;
+	uint32_t	magic2;
+	char		version[16];
+	char		model[16];
+	uint32_t	flag[2];
+	uint32_t	reserve[2];
+	char		buildno[16];
+	uint32_t	size;
+	uint32_t	offset;
+	char		devname[32];
+	char		digest[16];
+} __attribute__ ((packed));
+
+struct wrg_header {
+	char		signature[32];
+	uint32_t	magic1;
+	uint32_t	magic2;
+	uint32_t	size;
+	uint32_t	offset;
+	char		devname[32];
+	char		digest[16];
+} __attribute__ ((packed));
+
+
+static int mtdsplit_parse_wrgg(struct mtd_info *master,
+			       const struct mtd_partition **pparts,
+			       struct mtd_part_parser_data *data)
+{
+	struct wrgg03_header hdr;
+	size_t hdr_len, retlen, kernel_ent_size;
+	size_t rootfs_offset;
+	struct mtd_partition *parts;
+	enum mtdsplit_part_type type;
+	int err;
+
+	hdr_len = sizeof(hdr);
+	err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
+	if (err)
+		return err;
+
+	if (retlen != hdr_len)
+		return -EIO;
+
+	/* sanity checks */
+	if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) {
+		kernel_ent_size = hdr_len + be32_to_cpu(hdr.size);
+		/*
+		 * If this becomes silly big it's probably because the
+		 * WRGG image is little-endian.
+		 */
+		if (kernel_ent_size > master->size)
+			kernel_ent_size = hdr_len + le32_to_cpu(hdr.size);
+
+		/* Now what ?! It's neither */
+		if (kernel_ent_size > master->size)
+			return -EINVAL;
+	} else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) {
+		kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu(
+		                  ((struct wrg_header*)&hdr)->size);
+	} else {
+		return -EINVAL;
+	}
+
+	if (kernel_ent_size > master->size)
+		return -EINVAL;
+
+	/*
+	 * The size in the header covers the rootfs as well.
+	 * Start the search from an arbitrary offset.
+	 */
+	err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS,
+				   master->size, &rootfs_offset, &type);
+	if (err)
+		return err;
+
+	parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL);
+	if (!parts)
+		return -ENOMEM;
+
+	parts[0].name = KERNEL_PART_NAME;
+	parts[0].offset = 0;
+	parts[0].size = rootfs_offset;
+
+	parts[1].name = ROOTFS_PART_NAME;
+	parts[1].offset = rootfs_offset;
+	parts[1].size = master->size - rootfs_offset;
+
+	*pparts = parts;
+	return WRGG_NR_PARTS;
+}
+
+static const struct of_device_id mtdsplit_wrgg_of_match_table[] = {
+	{ .compatible = "wrg" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mtdsplit_wrgg_of_match_table);
+
+static struct mtd_part_parser mtdsplit_wrgg_parser = {
+	.owner = THIS_MODULE,
+	.name = "wrgg-fw",
+	.of_match_table = mtdsplit_wrgg_of_match_table,
+	.parse_fn = mtdsplit_parse_wrgg,
+	.type = MTD_PARSER_TYPE_FIRMWARE,
+};
+
+static int __init mtdsplit_wrgg_init(void)
+{
+	register_mtd_parser(&mtdsplit_wrgg_parser);
+
+	return 0;
+}
+
+subsys_initcall(mtdsplit_wrgg_init);