diff --git a/docs/user/supported_devices.rst b/docs/user/supported_devices.rst
index 4820d9ace83aa5727cded421f9e6aac60073d6b7..0f406d0bd464217b027db934c50b4aa2b72bf883 100644
--- a/docs/user/supported_devices.rst
+++ b/docs/user/supported_devices.rst
@@ -203,6 +203,10 @@ ath79-generic
 
   - Raccoon
 
+* Plasma Cloud
+
+  - PA300
+
 * TP-Link
 
   - Archer C6 (v2)
diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
index 213eb150658c128444692a6cfb7ffc8690938fda..b583222ff49bd74563c24010cd8f118ee9cf2cd1 100644
--- a/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
+++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
@@ -53,6 +53,7 @@ function M.is_outdoor_device()
 
 	elseif M.match('ath79', 'generic', {
 		'devolo,dvl1750x',
+		'plasmacloud,pa300',
 		'tplink,cpe220-v3',
 	}) then
 		return true
diff --git a/patches/openwrt/0018-ath79-Fix-fallback-to-bootloader-cmdline-on-empty-DT-bootargs.patch b/patches/openwrt/0018-ath79-Fix-fallback-to-bootloader-cmdline-on-empty-DT-bootargs.patch
new file mode 100644
index 0000000000000000000000000000000000000000..5a3951c0a9e43f9ddf06b975f943191e7aa725c1
--- /dev/null
+++ b/patches/openwrt/0018-ath79-Fix-fallback-to-bootloader-cmdline-on-empty-DT-bootargs.patch
@@ -0,0 +1,431 @@
+From: Sven Eckelmann <sven@narfation.org>
+Date: Mon, 23 Nov 2020 16:57:31 +0100
+Subject: ath79: Fix fallback to bootloader cmdline on empty DT bootargs
+
+The MIPS code is supposed to fall back to u-boots bootargs whenever the
+/chosen/bootargs property is missing. But this feature was accidentally
+disabled when the boot_command_line was initialized with an empty space
+just to work around problems with early_init_dt_scan_chosen.
+
+But this feature is necessary for some boards which have a dualboot
+mechanism and whose u-boot is calculating the correct partition at runtime
+without writing this information back to the u-boot-env.
+
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Origin: backport, https://github.com/openwrt/openwrt/commit/727eebbad1b9dea91174ea675cb64ea13484f790
+
+diff --git a/target/linux/ath79/patches-4.14/0038-MIPS-Setup-boot_command_line-before-plat_mem_setup.patch b/target/linux/ath79/patches-4.14/0038-MIPS-Setup-boot_command_line-before-plat_mem_setup.patch
+new file mode 100644
+index 0000000000000000000000000000000000000000..90d2ca7a0cb16d3f1e71779bfa551f498fd59755
+--- /dev/null
++++ b/target/linux/ath79/patches-4.14/0038-MIPS-Setup-boot_command_line-before-plat_mem_setup.patch
+@@ -0,0 +1,82 @@
++From: Paul Burton <paul.burton@mips.com>
++Date: Tue, 16 Jan 2018 16:47:57 +0100
++Subject: MIPS: Setup boot_command_line before plat_mem_setup
++
++Platforms using DT will typically call __dt_setup_arch from
++plat_mem_setup. This in turn calls early_init_dt_scan. When
++CONFIG_CMDLINE is set, this leads to its value being copied into
++boot_command_line by early_init_dt_scan_chosen. If this happens before
++the code setting up boot_command_line in arch_mem_init runs, that code
++will go on to append CONFIG_CMDLINE (via builtin_cmdline) to
++boot_command_line again, duplicating it. For some command line
++parameters (eg. earlycon) this can be a problem. Set up
++boot_command_line before early_init_dt_scan_chosen gets called such that
++it will not write CONFIG_CMDLINE in this scenario & the arguments aren't
++duplicated.
++
++Origin: upstream, https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8ce355cf2e38afdb364d03d12b23d9cf44c3b7f1
++Signed-off-by: Paul Burton <paul.burton@mips.com>
++Acked-by: Mathieu Malaterre <malat@debian.org>
++Cc: Ralf Baechle <ralf@linux-mips.org>
++Cc: Maarten ter Huurne <maarten@treewalker.org>
++Cc: linux-mips@linux-mips.org
++Patchwork: https://patchwork.linux-mips.org/patch/18483/
++Signed-off-by: James Hogan <jhogan@kernel.org>
++
++diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c
++index abd7ee9e90ab0dd0d01d970c84d90a2c4edf609b..c0f51fd4e0b3970e4f019094091578504e2f56eb 100644
++--- a/arch/mips/kernel/setup.c
+++++ b/arch/mips/kernel/setup.c
++@@ -833,25 +833,6 @@ static void __init arch_mem_init(char **cmdline_p)
++ 	struct memblock_region *reg;
++ 	extern void plat_mem_setup(void);
++ 
++-	/* call board setup routine */
++-	plat_mem_setup();
++-
++-	/*
++-	 * Make sure all kernel memory is in the maps.  The "UP" and
++-	 * "DOWN" are opposite for initdata since if it crosses over
++-	 * into another memory section you don't want that to be
++-	 * freed when the initdata is freed.
++-	 */
++-	arch_mem_addpart(PFN_DOWN(__pa_symbol(&_text)) << PAGE_SHIFT,
++-			 PFN_UP(__pa_symbol(&_edata)) << PAGE_SHIFT,
++-			 BOOT_MEM_RAM);
++-	arch_mem_addpart(PFN_UP(__pa_symbol(&__init_begin)) << PAGE_SHIFT,
++-			 PFN_DOWN(__pa_symbol(&__init_end)) << PAGE_SHIFT,
++-			 BOOT_MEM_INIT_RAM);
++-
++-	pr_info("Determined physical RAM map:\n");
++-	print_memory_map();
++-
++ #if defined(CONFIG_CMDLINE_BOOL) && defined(CONFIG_CMDLINE_OVERRIDE)
++ 	strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
++ #else
++@@ -879,6 +860,26 @@ static void __init arch_mem_init(char **cmdline_p)
++ 	}
++ #endif
++ #endif
+++
+++	/* call board setup routine */
+++	plat_mem_setup();
+++
+++	/*
+++	 * Make sure all kernel memory is in the maps.  The "UP" and
+++	 * "DOWN" are opposite for initdata since if it crosses over
+++	 * into another memory section you don't want that to be
+++	 * freed when the initdata is freed.
+++	 */
+++	arch_mem_addpart(PFN_DOWN(__pa_symbol(&_text)) << PAGE_SHIFT,
+++			 PFN_UP(__pa_symbol(&_edata)) << PAGE_SHIFT,
+++			 BOOT_MEM_RAM);
+++	arch_mem_addpart(PFN_UP(__pa_symbol(&__init_begin)) << PAGE_SHIFT,
+++			 PFN_DOWN(__pa_symbol(&__init_end)) << PAGE_SHIFT,
+++			 BOOT_MEM_INIT_RAM);
+++
+++	pr_info("Determined physical RAM map:\n");
+++	print_memory_map();
+++
++ 	strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
++ 
++ 	*cmdline_p = command_line;
+diff --git a/target/linux/ath79/patches-4.14/0039-MIPS-Fix-CONFIG_CMDLINE-handling.patch b/target/linux/ath79/patches-4.14/0039-MIPS-Fix-CONFIG_CMDLINE-handling.patch
+new file mode 100644
+index 0000000000000000000000000000000000000000..ab040c459454b1670b08afa2082e06637280da52
+--- /dev/null
++++ b/target/linux/ath79/patches-4.14/0039-MIPS-Fix-CONFIG_CMDLINE-handling.patch
+@@ -0,0 +1,119 @@
++From: Paul Burton <paul.burton@mips.com>
++Date: Thu, 27 Sep 2018 22:59:18 +0000
++Subject: MIPS: Fix CONFIG_CMDLINE handling
++
++Commit 8ce355cf2e38 ("MIPS: Setup boot_command_line before
++plat_mem_setup") fixed a problem for systems which have
++CONFIG_CMDLINE_BOOL=y & use a DT with a chosen node that has either no
++bootargs property or an empty one. In this configuration
++early_init_dt_scan_chosen() copies CONFIG_CMDLINE into
++boot_command_line, but the MIPS code doesn't know this so it appends
++CONFIG_CMDLINE (via builtin_cmdline) to boot_command_line again. The
++result is that boot_command_line contains the arguments from
++CONFIG_CMDLINE twice.
++
++That commit took the approach of simply setting up boot_command_line
++from the MIPS code before early_init_dt_scan_chosen() runs, causing it
++not to copy CONFIG_CMDLINE to boot_command_line if a chosen node with no
++bootargs property is found.
++
++Unfortunately this is problematic for systems which do have a non-empty
++bootargs property & CONFIG_CMDLINE_BOOL=y. There
++early_init_dt_scan_chosen() will overwrite boot_command_line with the
++arguments from DT, which means we lose those from CONFIG_CMDLINE
++entirely. This breaks CONFIG_MIPS_CMDLINE_DTB_EXTEND. If we have
++CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER or
++CONFIG_MIPS_CMDLINE_BUILTIN_EXTEND selected and the DT has a bootargs
++property which we should ignore, it will instead be honoured breaking
++those configurations too.
++
++Fix this by reverting commit 8ce355cf2e38 ("MIPS: Setup
++boot_command_line before plat_mem_setup") to restore the former
++behaviour, and fixing the CONFIG_CMDLINE duplication issue by
++initializing boot_command_line to a non-empty string that
++early_init_dt_scan_chosen() will not overwrite with CONFIG_CMDLINE.
++
++This is a little ugly, but cleanup in this area is on its way. In the
++meantime this is at least easy to backport & contains the ugliness
++within arch/mips/.
++
++Origin: upstream, https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=951d223c6c16ed5d2a71a4d1f13c1e65d6882156
++Signed-off-by: Paul Burton <paul.burton@mips.com>
++Fixes: 8ce355cf2e38 ("MIPS: Setup boot_command_line before plat_mem_setup")
++References: https://patchwork.linux-mips.org/patch/18804/
++Patchwork: https://patchwork.linux-mips.org/patch/20813/
++Cc: Frank Rowand <frowand.list@gmail.com>
++Cc: Jaedon Shin <jaedon.shin@gmail.com>
++Cc: Mathieu Malaterre <malat@debian.org>
++Cc: Rob Herring <robh+dt@kernel.org>
++Cc: devicetree@vger.kernel.org
++Cc: linux-kernel@vger.kernel.org
++Cc: linux-mips@linux-mips.org
++Cc: stable@vger.kernel.org # v4.16+
++
++diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c
++index c0f51fd4e0b3970e4f019094091578504e2f56eb..71070ec8da136495d2efed0aa79dff44149d565f 100644
++--- a/arch/mips/kernel/setup.c
+++++ b/arch/mips/kernel/setup.c
++@@ -833,6 +833,34 @@ static void __init arch_mem_init(char **cmdline_p)
++ 	struct memblock_region *reg;
++ 	extern void plat_mem_setup(void);
++ 
+++	/*
+++	 * Initialize boot_command_line to an innocuous but non-empty string in
+++	 * order to prevent early_init_dt_scan_chosen() from copying
+++	 * CONFIG_CMDLINE into it without our knowledge. We handle
+++	 * CONFIG_CMDLINE ourselves below & don't want to duplicate its
+++	 * content because repeating arguments can be problematic.
+++	 */
+++	strlcpy(boot_command_line, " ", COMMAND_LINE_SIZE);
+++
+++	/* call board setup routine */
+++	plat_mem_setup();
+++
+++	/*
+++	 * Make sure all kernel memory is in the maps.  The "UP" and
+++	 * "DOWN" are opposite for initdata since if it crosses over
+++	 * into another memory section you don't want that to be
+++	 * freed when the initdata is freed.
+++	 */
+++	arch_mem_addpart(PFN_DOWN(__pa_symbol(&_text)) << PAGE_SHIFT,
+++			 PFN_UP(__pa_symbol(&_edata)) << PAGE_SHIFT,
+++			 BOOT_MEM_RAM);
+++	arch_mem_addpart(PFN_UP(__pa_symbol(&__init_begin)) << PAGE_SHIFT,
+++			 PFN_DOWN(__pa_symbol(&__init_end)) << PAGE_SHIFT,
+++			 BOOT_MEM_INIT_RAM);
+++
+++	pr_info("Determined physical RAM map:\n");
+++	print_memory_map();
+++
++ #if defined(CONFIG_CMDLINE_BOOL) && defined(CONFIG_CMDLINE_OVERRIDE)
++ 	strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
++ #else
++@@ -860,26 +888,6 @@ static void __init arch_mem_init(char **cmdline_p)
++ 	}
++ #endif
++ #endif
++-
++-	/* call board setup routine */
++-	plat_mem_setup();
++-
++-	/*
++-	 * Make sure all kernel memory is in the maps.  The "UP" and
++-	 * "DOWN" are opposite for initdata since if it crosses over
++-	 * into another memory section you don't want that to be
++-	 * freed when the initdata is freed.
++-	 */
++-	arch_mem_addpart(PFN_DOWN(__pa_symbol(&_text)) << PAGE_SHIFT,
++-			 PFN_UP(__pa_symbol(&_edata)) << PAGE_SHIFT,
++-			 BOOT_MEM_RAM);
++-	arch_mem_addpart(PFN_UP(__pa_symbol(&__init_begin)) << PAGE_SHIFT,
++-			 PFN_DOWN(__pa_symbol(&__init_end)) << PAGE_SHIFT,
++-			 BOOT_MEM_INIT_RAM);
++-
++-	pr_info("Determined physical RAM map:\n");
++-	print_memory_map();
++-
++ 	strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
++ 
++ 	*cmdline_p = command_line;
+diff --git a/target/linux/ath79/patches-4.14/0040-MIPS-cmdline-Clean-up-boot_command_line-initializati.patch b/target/linux/ath79/patches-4.14/0040-MIPS-cmdline-Clean-up-boot_command_line-initializati.patch
+new file mode 100644
+index 0000000000000000000000000000000000000000..2cba97d5e85bb863ca95c0715110b69e4964ea51
+--- /dev/null
++++ b/target/linux/ath79/patches-4.14/0040-MIPS-cmdline-Clean-up-boot_command_line-initializati.patch
+@@ -0,0 +1,196 @@
++From: Paul Burton <paul.burton@mips.com>
++Date: Wed, 9 Oct 2019 23:09:45 +0000
++Subject: MIPS: cmdline: Clean up boot_command_line initialization
++
++Our current code to initialize boot_command_line is a mess. Some of this
++is due to the addition of too many options over the years, and some of
++this is due to workarounds for early_init_dt_scan_chosen() performing
++actions specific to options from other architectures that probably
++shouldn't be in generic code.
++
++Clean this up by introducing a new bootcmdline_init() function that
++simplifies the initialization somewhat. The major changes are:
++
++- Because bootcmdline_init() is a function it can return early in the
++  CONFIG_CMDLINE_OVERRIDE case.
++
++- We clear boot_command_line rather than inheriting whatever
++  early_init_dt_scan_chosen() may have left us. This means we no longer
++  need to set boot_command_line to a space character in an attempt to
++  prevent early_init_dt_scan_chosen() from copying CONFIG_CMDLINE into
++  boot_command_line without us knowing about it.
++
++- Indirection via USE_PROM_CMDLINE, USE_DTB_CMDLINE, EXTEND_WITH_PROM &
++  BUILTIN_EXTEND_WITH_PROM macros is removed; they seemingly served only
++  to obfuscate the code.
++
++- The logic is cleaner, clearer & commented.
++
++Two minor drawbacks of this approach are:
++
++1) We call of_scan_flat_dt(), which means we scan through the DT again.
++   The overhead is fairly minimal & shouldn't be noticeable.
++
++2) cmdline_scan_chosen() duplicates a small amount of the logic from
++   early_init_dt_scan_chosen(). Alternatives might be to allow the
++   generic FDT code to keep & expose a copy of the arguments taken from
++   the /chosen node's bootargs property, or to introduce a function like
++   early_init_dt_scan_chosen() that retrieves them without modification
++   to handle CONFIG_CMDLINE. Neither of these sounds particularly
++   cleaner though, and this way we at least keep the extra work in
++   arch/mips.
++
++Origin: backport, https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7784cac697351f0cc0a4bb619594c0c99348c5aa
++Signed-off-by: Paul Burton <paul.burton@mips.com>
++Cc: linux-mips@vger.kernel.org
++
++diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c
++index 71070ec8da136495d2efed0aa79dff44149d565f..c2b659a2e2ce0f2074f4fc13cd24283d66ac5594 100644
++--- a/arch/mips/kernel/setup.c
+++++ b/arch/mips/kernel/setup.c
++@@ -822,26 +822,94 @@ static void __init request_crashkernel(struct resource *res)
++ }
++ #endif /* !defined(CONFIG_KEXEC)  */
++ 
++-#define USE_PROM_CMDLINE	IS_ENABLED(CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER)
++-#define USE_DTB_CMDLINE		IS_ENABLED(CONFIG_MIPS_CMDLINE_FROM_DTB)
++-#define EXTEND_WITH_PROM	IS_ENABLED(CONFIG_MIPS_CMDLINE_DTB_EXTEND)
++-#define BUILTIN_EXTEND_WITH_PROM	\
++-	IS_ENABLED(CONFIG_MIPS_CMDLINE_BUILTIN_EXTEND)
+++static void __init bootcmdline_append(const char *s, size_t max)
+++{
+++	if (!s[0] || !max)
+++		return;
+++
+++	if (boot_command_line[0])
+++		strlcat(boot_command_line, " ", COMMAND_LINE_SIZE);
+++
+++	strlcat(boot_command_line, s, max);
+++}
+++
+++static int __init bootcmdline_scan_chosen(unsigned long node, const char *uname,
+++					  int depth, void *data)
+++{
+++	bool *dt_bootargs = data;
+++	const char *p;
+++	int l;
+++
+++	if (depth != 1 || !data ||
+++	    (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
+++		return 0;
+++
+++	p = of_get_flat_dt_prop(node, "bootargs", &l);
+++	if (p != NULL && l > 0) {
+++		bootcmdline_append(p, min(l, COMMAND_LINE_SIZE));
+++		*dt_bootargs = true;
+++	}
+++
+++	return 1;
+++}
+++
+++static void __init bootcmdline_init(char **cmdline_p)
+++{
+++	bool dt_bootargs = false;
+++
+++	/*
+++	 * If CMDLINE_OVERRIDE is enabled then initializing the command line is
+++	 * trivial - we simply use the built-in command line unconditionally &
+++	 * unmodified.
+++	 */
+++	if (IS_ENABLED(CONFIG_CMDLINE_OVERRIDE)) {
+++		strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
+++		return;
+++	}
+++
+++	/*
+++	 * If the user specified a built-in command line &
+++	 * MIPS_CMDLINE_BUILTIN_EXTEND, then the built-in command line is
+++	 * prepended to arguments from the bootloader or DT so we'll copy them
+++	 * to the start of boot_command_line here. Otherwise, empty
+++	 * boot_command_line to undo anything early_init_dt_scan_chosen() did.
+++	 */
+++	if (IS_ENABLED(CONFIG_MIPS_CMDLINE_BUILTIN_EXTEND))
+++		strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
+++	else
+++		boot_command_line[0] = 0;
+++
+++	/*
+++	 * If we're configured to take boot arguments from DT, look for those
+++	 * now.
+++	 */
+++	if (IS_ENABLED(CONFIG_MIPS_CMDLINE_FROM_DTB))
+++		of_scan_flat_dt(bootcmdline_scan_chosen, &dt_bootargs);
+++
+++	/*
+++	 * If we didn't get any arguments from DT (regardless of whether that's
+++	 * because we weren't configured to look for them, or because we looked
+++	 * & found none) then we'll take arguments from the bootloader.
+++	 * plat_mem_setup() should have filled arcs_cmdline with arguments from
+++	 * the bootloader.
+++	 */
+++	if (IS_ENABLED(CONFIG_MIPS_CMDLINE_DTB_EXTEND) || !dt_bootargs)
+++		bootcmdline_append(arcs_cmdline, COMMAND_LINE_SIZE);
+++
+++	/*
+++	 * If the user specified a built-in command line & we didn't already
+++	 * prepend it, we append it to boot_command_line here.
+++	 */
+++	if (IS_ENABLED(CONFIG_CMDLINE_BOOL) &&
+++	    !IS_ENABLED(CONFIG_MIPS_CMDLINE_BUILTIN_EXTEND))
+++		bootcmdline_append(builtin_cmdline, COMMAND_LINE_SIZE);
+++}
++ 
++ static void __init arch_mem_init(char **cmdline_p)
++ {
++ 	struct memblock_region *reg;
++ 	extern void plat_mem_setup(void);
++ 
++-	/*
++-	 * Initialize boot_command_line to an innocuous but non-empty string in
++-	 * order to prevent early_init_dt_scan_chosen() from copying
++-	 * CONFIG_CMDLINE into it without our knowledge. We handle
++-	 * CONFIG_CMDLINE ourselves below & don't want to duplicate its
++-	 * content because repeating arguments can be problematic.
++-	 */
++-	strlcpy(boot_command_line, " ", COMMAND_LINE_SIZE);
++-
++ 	/* call board setup routine */
++ 	plat_mem_setup();
++ 
++@@ -861,35 +929,8 @@ static void __init arch_mem_init(char **cmdline_p)
++ 	pr_info("Determined physical RAM map:\n");
++ 	print_memory_map();
++ 
++-#if defined(CONFIG_CMDLINE_BOOL) && defined(CONFIG_CMDLINE_OVERRIDE)
++-	strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
++-#else
++-	if ((USE_PROM_CMDLINE && arcs_cmdline[0]) ||
++-	    (USE_DTB_CMDLINE && !boot_command_line[0]))
++-		strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);
++-
++-	if (EXTEND_WITH_PROM && arcs_cmdline[0]) {
++-		if (boot_command_line[0])
++-			strlcat(boot_command_line, " ", COMMAND_LINE_SIZE);
++-		strlcat(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);
++-	}
++-
++-#if defined(CONFIG_CMDLINE_BOOL)
++-	if (builtin_cmdline[0]) {
++-		if (boot_command_line[0])
++-			strlcat(boot_command_line, " ", COMMAND_LINE_SIZE);
++-		strlcat(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
++-	}
++-
++-	if (BUILTIN_EXTEND_WITH_PROM && arcs_cmdline[0]) {
++-		if (boot_command_line[0])
++-			strlcat(boot_command_line, " ", COMMAND_LINE_SIZE);
++-		strlcat(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);
++-	}
++-#endif
++-#endif
+++	bootcmdline_init(cmdline_p);
++ 	strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
++-
++ 	*cmdline_p = command_line;
++ 
++ 	parse_early_param();
diff --git a/patches/openwrt/0019-ath79-Add-support-for-Plasma-Cloud-PA300.patch b/patches/openwrt/0019-ath79-Add-support-for-Plasma-Cloud-PA300.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fa3172531f0961c20dbb1a79d82810803f3f60f4
--- /dev/null
+++ b/patches/openwrt/0019-ath79-Add-support-for-Plasma-Cloud-PA300.patch
@@ -0,0 +1,408 @@
+From: Sven Eckelmann <sven@narfation.org>
+Date: Mon, 23 Nov 2020 13:41:34 +0100
+Subject: ath79: Add support for Plasma Cloud PA300
+
+Device specifications:
+
+* Qualcomm/Atheros QCA9533 v2
+* 650/600/217 MHz (CPU/DDR/AHB)
+* 64 MB of RAM
+* 16 MB of SPI NOR flash (mx25l12805d)
+  - 2x 7 MB available; but one of the 7 MB regions is the recovery image
+* 2x 10/100 Mbps Ethernet
+* 2T2R 2.4 GHz Wi-Fi
+* multi-color LED (controlled via red/green/blue GPIOs)
+* 1x GPIO-button (reset)
+* external h/w watchdog (enabled by default)
+* TTL pins are on board (arrow points to VCC, then follows: GND, TX, RX)
+* 2x fast ethernet
+  - eth0
+    + Label: Ethernet 1
+    + 24V passive POE (mode B)
+    + used as WAN interface
+  - eth1
+    + Label: Ethernet 2
+    + 802.3af POE
+    + builtin switch port 2
+    + used as LAN interface
+* 12-24V 1A DC
+* internal antennas
+
+Flashing instructions:
+
+The tool ap51-flash (https://github.com/ap51-flash/ap51-flash) should be
+used to transfer the factory image to the u-boot when the device boots up.
+
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Origin: backport, https://github.com/openwrt/openwrt/commit/8028debedbedb2640cf5fac230bce82453e34a7e
+
+diff --git a/package/boot/uboot-envtools/files/ath79 b/package/boot/uboot-envtools/files/ath79
+index b5afbc9b444a921f6aef9ae81e5dc4f2ef7f9910..8808e81682f70583c4b2a888f8ddca9448277919 100644
+--- a/package/boot/uboot-envtools/files/ath79
++++ b/package/boot/uboot-envtools/files/ath79
+@@ -39,6 +39,9 @@ netgear,wndr3700|\
+ netgear,wndr3700-v2)
+ 	ubootenv_add_uci_config "/dev/mtd1" "0x0" "0x20000" "0x10000"
+ 	;;
++plasmacloud,pa300)
++	ubootenv_add_uci_config "/dev/mtd1" "0x0" "0x40000" "0x40000"
++	;;
+ esac
+ 
+ config_load ubootenv
+diff --git a/scripts/om-fwupgradecfg-gen.sh b/scripts/om-fwupgradecfg-gen.sh
+index 4a7094055ff67aab83e796fc30f9913ae2c30fe2..e271fa0dacad837191eaab7683ad8880d183a75e 100755
+--- a/scripts/om-fwupgradecfg-gen.sh
++++ b/scripts/om-fwupgradecfg-gen.sh
+@@ -7,7 +7,7 @@
+ #
+ 
+ usage() {
+-	echo "Usage: $0 <OM2P|OM5P|OM5PAC|MR600|MR900|MR1750|A60|A42|A62> <out file path> <kernel path> <rootfs path>"
++	echo "Usage: $0 <OM2P|OM5P|OM5PAC|MR600|MR900|MR1750|A60|A42|A62|PA300> <out file path> <kernel path> <rootfs path>"
+ 	rm -f $CFG_OUT
+ 	exit 1
+ }
+@@ -20,6 +20,7 @@ KERNEL_PATH=$3
+ ROOTFS_PATH=$4
+ 
+ case $CE_TYPE in
++	PA300|\
+ 	OM2P)
+ 		MAX_PART_SIZE=7168
+ 		KERNEL_FLASH_ADDR=0x1c0000
+diff --git a/target/linux/ath79/base-files/etc/board.d/02_network b/target/linux/ath79/base-files/etc/board.d/02_network
+index b8fac8816c9a2b2a87a5d1335b41127666afe2e4..846e2807ede8ec828a2f519f9b33c0d1dfdd5129 100755
+--- a/target/linux/ath79/base-files/etc/board.d/02_network
++++ b/target/linux/ath79/base-files/etc/board.d/02_network
+@@ -115,6 +115,7 @@ ath79_setup_interfaces()
+ 		;;
+ 	comfast,cf-e110n-v2|\
+ 	comfast,cf-e120a-v3|\
++	plasmacloud,pa300|\
+ 	tplink,cpe220-v3|\
+ 	ubnt,nanostation-m|\
+ 	ubnt,routerstation)
+diff --git a/target/linux/ath79/base-files/lib/upgrade/dualboot_datachk.sh b/target/linux/ath79/base-files/lib/upgrade/dualboot_datachk.sh
+new file mode 100644
+index 0000000000000000000000000000000000000000..68733ccf154582e29b6416ad8daa7eb7a81bcc7e
+--- /dev/null
++++ b/target/linux/ath79/base-files/lib/upgrade/dualboot_datachk.sh
+@@ -0,0 +1,104 @@
++# The U-Boot loader with the datachk patchset for dualbooting requires image
++# sizes and checksums to be provided in the U-Boot environment.
++# The devices come with 2 main partitions - while one is active
++# sysupgrade will flash the other. The boot order is changed to boot the
++# newly flashed partition. If the new partition can't be booted due to
++# upgrade failures the previously used partition is loaded.
++
++platform_do_upgrade_dualboot_datachk() {
++	local tar_file="$1"
++	local restore_backup
++	local primary_kernel_mtd
++
++	local setenv_script="/tmp/fw_env_upgrade"
++
++	local inactive_mtd="$(find_mtd_index $PART_NAME)"
++	local inactive_offset="$(cat /sys/class/mtd/mtd${inactive_mtd}/offset)"
++	local total_size="$(cat /sys/class/mtd/mtd${inactive_mtd}/size)"
++	local flash_start_mem=0x9f000000
++
++	# detect to which flash region the new image is written to.
++	#
++	# 1. check what is the mtd index for the first flash region on this
++	#    device
++	# 2. check if the target partition ("inactive") has the mtd index of
++	#    the first flash region
++	#
++	#    - when it is: the new bootseq will be 1,2 and the first region is
++	#      modified
++	#    - when it isnt: bootseq will be 2,1 and the second region is
++	#      modified
++	#
++	# The detection has to be done via the hardcoded mtd partition because
++	# the current boot might be done with the fallback region. Let us
++	# assume that the current bootseq is 1,2. The bootloader detected that
++	# the image in flash region 1 is corrupt and thus switches to flash
++	# region 2. The bootseq in the u-boot-env is now still the same and
++	# the sysupgrade code can now only rely on the actual mtd indexes and
++	# not the bootseq variable to detect the currently booted flash
++	# region/image.
++	#
++	# In the above example, an implementation which uses bootseq ("1,2") to
++	# detect the currently booted image would assume that region 1 is booted
++	# and then overwrite the variables for the wrong flash region (aka the
++	# one which isn't modified). This could result in a device which doesn't
++	# boot anymore to Linux until it was reflashed with ap51-flash.
++	local next_boot_part="1"
++	case "$(board_name)" in
++	plasmacloud,pa300)
++		primary_kernel_mtd=3
++		;;
++	*)
++		echo "failed to detect primary kernel mtd partition for board"
++		return 1
++		;;
++	esac
++	[ "$inactive_mtd" = "$primary_kernel_mtd" ] || next_boot_part="2"
++
++	local board_dir=$(tar tf $tar_file | grep -m 1 '^sysupgrade-.*/$')
++	board_dir=${board_dir%/}
++
++	local kernel_length=$(tar xf $tar_file ${board_dir}/kernel -O | wc -c)
++	local rootfs_length=$(tar xf $tar_file ${board_dir}/root -O | wc -c)
++	# rootfs without EOF marker
++	rootfs_length=$((rootfs_length-4))
++
++	local kernel_md5=$(tar xf $tar_file ${board_dir}/kernel -O | md5sum); kernel_md5="${kernel_md5%% *}"
++	# md5 checksum of rootfs with EOF marker
++	local rootfs_md5=$(tar xf $tar_file ${board_dir}/root -O | dd bs=1 count=$rootfs_length | md5sum); rootfs_md5="${rootfs_md5%% *}"
++
++	#
++	# add tar support to get_image() to use default_do_upgrade() instead?
++	#
++
++	# take care of restoring a saved config
++	[ -n "$UPGRADE_BACKUP" ] && restore_backup="${MTD_CONFIG_ARGS} -j ${UPGRADE_BACKUP}"
++
++	mtd -q erase inactive
++	tar xf $tar_file ${board_dir}/root -O | mtd -n -p $kernel_length $restore_backup write - $PART_NAME
++	tar xf $tar_file ${board_dir}/kernel -O | mtd -n write - $PART_NAME
++
++	# prepare new u-boot env
++	if [ "$next_boot_part" = "1" ]; then
++		echo "bootseq 1,2" > $setenv_script
++	else
++		echo "bootseq 2,1" > $setenv_script
++	fi
++
++	printf "kernel_size_%i %i\n" $next_boot_part $((kernel_length / 1024)) >> $setenv_script
++	printf "vmlinux_start_addr 0x%08x\n" $((flash_start_mem + inactive_offset)) >> $setenv_script
++	printf "vmlinux_size 0x%08x\n" ${kernel_length} >> $setenv_script
++	printf "vmlinux_checksum %s\n" ${kernel_md5} >> $setenv_script
++
++	printf "rootfs_size_%i %i\n" $next_boot_part $(((total_size-kernel_length) / 1024)) >> $setenv_script
++	printf "rootfs_start_addr 0x%08x\n" $((flash_start_mem+inactive_offset+kernel_length)) >> $setenv_script
++	printf "rootfs_size 0x%08x\n" ${rootfs_length} >> $setenv_script
++	printf "rootfs_checksum %s\n" ${rootfs_md5} >> $setenv_script
++
++	# store u-boot env changes
++	mkdir -p /var/lock
++	fw_setenv -s $setenv_script || {
++		echo "failed to update U-Boot environment"
++		return 1
++	}
++}
+diff --git a/target/linux/ath79/base-files/lib/upgrade/platform.sh b/target/linux/ath79/base-files/lib/upgrade/platform.sh
+index f3e19a5694f1f9c6132a42d0740873e522d0b8e3..c4f869932a02ef353e694b50496c3b2ed5d59f12 100644
+--- a/target/linux/ath79/base-files/lib/upgrade/platform.sh
++++ b/target/linux/ath79/base-files/lib/upgrade/platform.sh
+@@ -5,6 +5,9 @@
+ PART_NAME=firmware
+ REQUIRE_IMAGE_METADATA=1
+ 
++RAMFS_COPY_BIN='fw_printenv fw_setenv'
++RAMFS_COPY_DATA='/etc/fw_env.config /var/lock/fw_printenv.lock'
++
+ redboot_fis_do_upgrade() {
+ 	local append
+ 	local sysup_file="$1"
+@@ -43,6 +46,10 @@ platform_do_upgrade() {
+ 	jjplus,ja76pf2)
+ 		redboot_fis_do_upgrade "$1" linux
+ 		;;
++	plasmacloud,pa300)
++		PART_NAME="inactive"
++		platform_do_upgrade_dualboot_datachk "$1"
++		;;
+ 	ubnt,routerstation|\
+ 	ubnt,routerstation-pro)
+ 		redboot_fis_do_upgrade "$1" kernel
+diff --git a/target/linux/ath79/dts/qca9533_plasmacloud_pa300.dts b/target/linux/ath79/dts/qca9533_plasmacloud_pa300.dts
+new file mode 100644
+index 0000000000000000000000000000000000000000..8de89292eaa2e655066342ebfac05dbeb6a3c4f3
+--- /dev/null
++++ b/target/linux/ath79/dts/qca9533_plasmacloud_pa300.dts
+@@ -0,0 +1,8 @@
++// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
++
++#include "qca9533_plasmacloud_pa300.dtsi"
++
++/ {
++	compatible = "plasmacloud,pa300", "qca,qca9533";
++	model = "Plasma Cloud PA300";
++};
+diff --git a/target/linux/ath79/dts/qca9533_plasmacloud_pa300.dtsi b/target/linux/ath79/dts/qca9533_plasmacloud_pa300.dtsi
+new file mode 100644
+index 0000000000000000000000000000000000000000..d8fc78dc1edeb2d8677336f25fcae2a85e05ee3f
+--- /dev/null
++++ b/target/linux/ath79/dts/qca9533_plasmacloud_pa300.dtsi
+@@ -0,0 +1,140 @@
++// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
++/dts-v1/;
++#include "qca953x.dtsi"
++
++#include <dt-bindings/gpio/gpio.h>
++#include <dt-bindings/input/input.h>
++
++/ {
++	chosen {
++		/delete-property/ bootargs;
++	};
++
++	aliases {
++		led-boot = &led_status_green;
++		led-failsafe = &led_status_green;
++		led-running = &led_status_green;
++		led-upgrade = &led_status_green;
++		label-mac-device = &eth0;
++	};
++
++	keys {
++		compatible = "gpio-keys";
++
++		pinctrl-names = "default";
++
++		reset {
++			label = "reset";
++			linux,code = <KEY_RESTART>;
++			gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
++		};
++	};
++
++	leds {
++		compatible = "gpio-leds";
++
++		status_red {
++			label = "red:status";
++			gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
++		};
++
++		led_status_green: status_green {
++			label = "green:status";
++			gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
++		};
++
++		status_blue {
++			label = "blue:status";
++			gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
++		};
++	};
++
++	watchdog {
++		compatible = "linux,wdt-gpio";
++		gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
++		hw_algo = "toggle";
++		/* hw_margin_ms is actually 300s but driver limits it to 60s */
++		hw_margin_ms = <60000>;
++		always-running;
++	};
++};
++
++&uart {
++	status = "okay";
++};
++
++&spi {
++	status = "okay";
++
++	flash@0 {
++		compatible = "jedec,spi-nor";
++		reg = <0>;
++		spi-max-frequency = <25000000>;
++
++		/* partitions are passed via bootloader */
++		partitions {
++			compatible = "fixed-partitions";
++			#address-cells = <1>;
++			#size-cells = <1>;
++
++			partition@0 {
++				label = "u-boot";
++				reg = <0x000000 0x040000>;
++				read-only;
++			};
++
++			partition@40000 {
++				label = "u-boot-env";
++				reg = <0x040000 0x040000>;
++			};
++
++			partition@80000 {
++				label = "custom";
++				reg = <0x080000 0x140000>;
++				read-only;
++			};
++
++			partition@1c0000 {
++				label = "inactive";
++				reg = <0x1c0000 0x700000>;
++			};
++
++			partition@8c0000 {
++				label = "inactive2";
++				reg = <0x8c0000 0x700000>;
++			};
++
++			art: partition@fc0000 {
++				label = "ART";
++				reg = <0xfc0000 0x040000>;
++				read-only;
++			};
++		};
++	};
++};
++
++&eth0 {
++	status = "okay";
++
++	phy-handle = <&swphy4>;
++
++	mtd-mac-address = <&art 0x0>;
++};
++
++&eth1 {
++	/* Workaround: keep the Ethernet interfaces order/mapping correct
++	 * (GMAC0 -> eth0, GMAC1 -> eth1, same as in old ar71xx target)
++	 */
++	compatible = "qca,qca9530-eth", "syscon", "simple-mfd";
++
++	mtd-mac-address = <&art 0x0>;
++	mtd-mac-address-increment = <1>;
++};
++
++&wmac {
++	status = "okay";
++
++	mtd-cal-data = <&art 0x1000>;
++	mtd-mac-address = <&art 0x0>;
++	mtd-mac-address-increment = <2>;
++};
+diff --git a/target/linux/ath79/image/generic.mk b/target/linux/ath79/image/generic.mk
+index 892ef10f870e347c8a1509cecd35bce4b5e98bee..3c1db9b3204d6784580f87ea9adb7a2ba98d285b 100644
+--- a/target/linux/ath79/image/generic.mk
++++ b/target/linux/ath79/image/generic.mk
+@@ -646,6 +646,23 @@ define Device/pisen_wmm003n
+ endef
+ TARGET_DEVICES += pisen_wmm003n
+ 
++define Device/plasmacloud_pa300-common
++  ATH_SOC := qca9533
++  DEVICE_PACKAGES := uboot-envtools
++  IMAGE_SIZE := 7168k
++  BLOCKSIZE := 64k
++  IMAGES += factory.bin
++  KERNEL := kernel-bin | append-dtb | lzma | uImage lzma | pad-to $$(BLOCKSIZE)
++  IMAGE/factory.bin := append-rootfs | pad-rootfs | openmesh-image ce_type=PA300
++  IMAGE/sysupgrade.bin := append-rootfs | pad-rootfs | sysupgrade-tar rootfs=$$$$@ | append-metadata
++endef
++
++define Device/plasmacloud_pa300
++  $(Device/plasmacloud_pa300-common)
++  DEVICE_TITLE := Plasma Cloud PA300
++endef
++TARGET_DEVICES += plasmacloud_pa300
++
+ define Device/netgear_wndr3800
+   $(Device/netgear_wndr3x00)
+   DEVICE_TITLE := NETGEAR WNDR3800
diff --git a/targets/ath79-generic b/targets/ath79-generic
index f82c454282e69eaf22a1c3fe9b222d28c9b2a65e..19ce8bacd1c8f5d9eb45331a8d60979f042fd3c7 100644
--- a/targets/ath79-generic
+++ b/targets/ath79-generic
@@ -71,6 +71,10 @@ device('ocedo-raccoon', 'ocedo_raccoon', {
 	factory = false,
 })
 
+-- Plasma Cloud
+
+device('plasma-cloud-pa300', 'plasmacloud_pa300')
+
 -- TP-Link
 
 device('tp-link-archer-c2-v3', 'tplink_archer-c2-v3', {