diff --git a/package/system/gpio-cdev/nu801/Makefile b/package/system/gpio-cdev/nu801/Makefile
index f91fbbc8ae34ad89b4d02cbf5532a5a13d7b44de..67b2a771ec83feb41ebabd3d2c4becf993c85ce0 100644
--- a/package/system/gpio-cdev/nu801/Makefile
+++ b/package/system/gpio-cdev/nu801/Makefile
@@ -20,7 +20,7 @@ define Package/nu801
   SECTION:=utils
   CATEGORY:=Utilities
   SUBMENU:=Userspace GPIO Drivers
-  DEPENDS:=@(TARGET_x86||TARGET_bcm53xx)
+  DEPENDS:=@(TARGET_ath79_nand||TARGET_bcm53xx||TARGET_x86)
   KCONFIG:=CONFIG_GPIO_CDEV=y
   TITLE:=NU801 LED Driver
 endef
diff --git a/target/linux/ath79/dts/qca9557_meraki_mr18.dts b/target/linux/ath79/dts/qca9557_meraki_mr18.dts
new file mode 100644
index 0000000000000000000000000000000000000000..a88e2bcd377a1040d3b51533376a4f95070c133d
--- /dev/null
+++ b/target/linux/ath79/dts/qca9557_meraki_mr18.dts
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+#include "qca955x.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/leds/common.h>
+
+/ {
+	compatible = "meraki,mr18", "qca,qca9558";
+	model = "Meraki MR18";
+
+	aliases {
+		label-mac-device = &eth0;
+		led-boot = &white;
+		led-failsafe = &orange;
+		led-running = &green;
+		led-upgrade = &white;
+	};
+
+	leds {
+		compatible = "gpio-leds";
+
+		white: white {
+			label = "white:power";
+			gpios = <&gpio 18 GPIO_ACTIVE_LOW>;
+		};
+
+		orange: orange {
+			label = "orange:power";
+			gpios = <&gpio 21 GPIO_ACTIVE_HIGH>;
+			panic-indicator;
+		};
+	};
+
+	uleds {
+		compatible = "virtual-leds";
+#if 0
+		/*
+		 * RGB leds are not supported by uleds driver.
+		 * but this is what the definitions for a as
+		 * of yet unwritten leds_nu801 would look like.
+		 */
+
+		rgbled-0 {
+			function = LED_FUNCTION_POWER;
+			color = <LED_COLOR_ID_RGB>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			led@0 {
+				reg = <0>;
+				color = <LED_COLOR_ID_RED>;
+			};
+
+			green: led@1 {
+				reg = <1>;
+				color = <LED_COLOR_ID_GREEN>;
+			};
+
+			led@2 {
+				reg = <2>;
+				color = <LED_COLOR_ID_BLUE>;
+			};
+		};
+
+#else
+		red {
+			label = "red:tricolor";
+			color = <LED_COLOR_ID_RED>;
+		};
+
+		green: green {
+			label = "green:tricolor";
+			color = <LED_COLOR_ID_GREEN>;
+		};
+
+		blue {
+			label = "blue:tricolor";
+			color = <LED_COLOR_ID_BLUE>;
+		};
+#endif
+	};
+
+	button {
+		compatible = "gpio-keys";
+
+		reset {
+			label = "Reset";
+			linux,code = <KEY_RESTART>;
+			gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
+			debounce-interval = <60>;
+		};
+
+	};
+};
+
+&nand {
+	status = "okay";
+
+	nand-ecc-mode = "soft";
+	nand-ecc-algo = "bch";
+	nand-ecc-strength = <4>;
+	nand-ecc-step-size = <512>;
+	nand-is-boot-medium;
+
+	partitions {
+		compatible = "fixed-partitions";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partition@0 {
+			label = "nandloader";
+			reg = <0x0 0x80000>;
+			read-only;
+		};
+
+		partition@80000 {
+			label = "kernel";
+			reg = <0x80000 0x800000>;
+		};
+
+		partition@880000 {
+			label = "recovery";
+			reg = <0x880000 0x800000>;
+		};
+
+		partition@1080000 {
+			label = "ubi";
+			reg = <0x1080000 0x6f00000>;
+		};
+
+		partition@7fe0000 {
+			/*
+			 * This is not always present. And if
+			 * it is, then Meraki (or contractor)
+			 * used a different ecc method than
+			 * the one we need for the UBI partition.
+			 * Reading this causes various reading
+			 * errors.
+			 *
+			 * As a result: Please don't convert
+			 * this to nvmem-cells. Instead there's
+			 * a ubi-volume "caldata" that has the
+			 * necessary data.
+			 */
+
+			label = "odm-caldata";
+			reg = <0x7fe0000 0x20000>;
+			read-only;
+		};
+	};
+};
+
+&pcie0 {
+	status = "okay";
+
+	wifi@0,0 {
+		compatible = "pci168c,0033";
+		reg = <0x0000 0 0 0 0>;
+		qca,no-eeprom;
+	};
+};
+
+&pcie1 {
+	status = "okay";
+
+	wifi@0,0 {
+		compatible = "pci168c,0033";
+		reg = <0x0000 0 0 0 0>;
+		qca,no-eeprom;
+	};
+};
+
+&uart {
+	status = "okay";
+};
+
+&mdio0 {
+	status = "okay";
+
+	phy: ethernet-phy@3 {
+		reg = <3>;
+	};
+};
+
+&eth0 {
+	status = "okay";
+	pll-data = <0xa6000000 0xa0000101 0x80001313>;
+	phy-handle = <&phy>;
+
+	gmac-config {
+		device = <&gmac>;
+		rgmii-enabled = <1>;
+		rxd-delay = <3>;
+		rxdv-delay = <3>;
+	};
+};
+
+&wmac {
+	status = "okay";
+	qca,no-eeprom;
+};
diff --git a/target/linux/ath79/image/lzma-loader/src/ar71xx_regs.h b/target/linux/ath79/image/lzma-loader/src/ar71xx_regs.h
index 19a4785bb44aed601d69ef92b74f9517ac3b675a..245042fdab74af1ab442f11614668a668872aad2 100644
--- a/target/linux/ath79/image/lzma-loader/src/ar71xx_regs.h
+++ b/target/linux/ath79/image/lzma-loader/src/ar71xx_regs.h
@@ -128,6 +128,17 @@
 #define AR9300_OTP_STATUS_SM_BUSY	0x1
 #define AR9300_OTP_READ_DATA	0x15f1c
 
+#define QCA955X_OTP_BASE        (AR71XX_APB_BASE + 0x00130000)
+#define QCA955X_OTP_REG_MEM_0	0x0000
+#define QCA955X_OTP_REG_INTF2	0x1008
+#define QCA955X_OTP_REG_STATUS0	0x1018
+#define QCA955X_OTP_STATUS0_EFUSE_VALID	BIT(2)
+
+#define QCA955X_OTP_REG_STATUS1		0x101c
+#define QCA955X_OTP_REG_LDO_CTRL	0x1024
+#define QCA955X_OTP_REG_LDO_STATUS	0x102c
+#define QCA955X_OTP_LDO_STATUS_POWER_ON		BIT(0)
+
 /*
  * DDR_CTRL block
  */
@@ -344,6 +355,7 @@
 
 #define QCA955X_RESET_REG_BOOTSTRAP		0xb0
 #define QCA955X_RESET_REG_EXT_INT_STATUS	0xac
+#define QCA955X_RESET_REG_RESET_MODULE		0x1c
 
 #define MISC_INT_ETHSW			BIT(12)
 #define MISC_INT_TIMER4			BIT(10)
@@ -436,6 +448,9 @@
 #define AR934X_RESET_MBOX		BIT(1)
 #define AR934X_RESET_I2S		BIT(0)
 
+#define QCA955X_RESET_SGMII_ANALOG	BIT(12)
+#define QCA955X_RESET_SGMII		BIT(8)
+
 #define AR933X_BOOTSTRAP_MDIO_GPIO_EN	BIT(18)
 #define AR933X_BOOTSTRAP_EEPBUSY	BIT(4)
 #define AR933X_BOOTSTRAP_REF_CLK_40	BIT(0)
@@ -722,4 +737,6 @@
 #define QCA955X_ETH_CFG_RGMII_GMAC0	BIT(0)
 #define QCA955X_ETH_CFG_SGMII_GMAC0	BIT(6)
 
+#define QCA955X_GMAC_REG_SGMII_SERDES	0x0018
+
 #endif /* __ASM_MACH_AR71XX_REGS_H */
diff --git a/target/linux/ath79/image/lzma-loader/src/board.c b/target/linux/ath79/image/lzma-loader/src/board.c
index 2f4dd6b1f6456a9b4a1036c0fc96907dee72e0ca..7b1e110ee2d902d353608f76e8273dbf4e17645c 100644
--- a/target/linux/ath79/image/lzma-loader/src/board.c
+++ b/target/linux/ath79/image/lzma-loader/src/board.c
@@ -10,6 +10,7 @@
 
 #include <stddef.h>
 #include "config.h"
+#include "printf.h"
 #include "ar71xx_regs.h"
 
 #define READREG(r)	*(volatile unsigned int *)(r)
@@ -50,7 +51,139 @@ static void tlwr1043nd_init(void)
 static inline void tlwr1043nd_init(void) {}
 #endif
 
+#ifdef CONFIG_BOARD_MERAKI_MR18
+
+static int mr18_extract_sgmii_res_cal(void)
+{
+	unsigned int base;
+	unsigned int reversed_sgmii_value;
+
+	unsigned int otp_value, otp_per_val, rbias_per, read_data;
+	unsigned int rbias_pos_or_neg;
+	unsigned int sgmii_res_cal_value;
+	int res_cal_val;
+
+	base = KSEG1ADDR(QCA955X_OTP_BASE);
+
+	WRITEREG(base + QCA955X_OTP_REG_INTF2, 0x7d);
+	WRITEREG(base + QCA955X_OTP_REG_LDO_CTRL, 0x00);
+
+	while (READREG(base + QCA955X_OTP_REG_LDO_STATUS) &
+		QCA955X_OTP_LDO_STATUS_POWER_ON)
+		;
+
+	READREG(base + QCA955X_OTP_REG_MEM_0 + 4);
+
+	while (!(READREG(base + QCA955X_OTP_REG_STATUS0) &
+		QCA955X_OTP_STATUS0_EFUSE_VALID))
+		;
+
+	read_data = READREG(base + QCA955X_OTP_REG_STATUS1);
+
+	if (!(read_data & 0x1fff))
+		return 0;
+
+	if (read_data & 0x00001000)
+		otp_value = (read_data & 0xfc0) >> 6;
+	else
+		otp_value = read_data & 0x3f;
+
+	if (otp_value > 31) {
+		otp_per_val = 63 - otp_value;
+		rbias_pos_or_neg = 1;
+	} else {
+		otp_per_val = otp_value;
+		rbias_pos_or_neg = 0;
+	}
+
+	rbias_per = otp_per_val * 15;
+
+	if (rbias_pos_or_neg == 1)
+		res_cal_val = (rbias_per + 34) / 21;
+	else if (rbias_per > 34)
+		res_cal_val = -((rbias_per - 34) / 21);
+	else
+		res_cal_val = (34 - rbias_per) / 21;
+
+	sgmii_res_cal_value = (8 + res_cal_val) & 0xf;
+
+	reversed_sgmii_value  = (sgmii_res_cal_value & 8) >> 3;
+	reversed_sgmii_value |= (sgmii_res_cal_value & 4) >> 1;
+	reversed_sgmii_value |= (sgmii_res_cal_value & 2) << 1;
+	reversed_sgmii_value |= (sgmii_res_cal_value & 1) << 3;
+	printf("SGMII cal value = 0x%x\n", reversed_sgmii_value);
+	return reversed_sgmii_value;
+}
+
+#define QCA955X_SGMII_SERDES_RES_CALIBRATION		BIT(23)
+#define QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK	0xf
+#define QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT	23
+#define QCA955X_SGMII_SERDES_LOCK_DETECT_STATUS		BIT(15)
+#define QCA955X_PLL_ETH_SGMII_SERDES_LOCK_DETECT	BIT(2)
+#define QCA955X_PLL_ETH_SGMII_SERDES_PLL_REFCLK		BIT(1)
+#define QCA955X_PLL_ETH_SGMII_SERDES_EN_PLL		BIT(0)
+#define QCA955X_PLL_CLK_CTRL_REG			0x08
+#define QCA955X_PLL_ETH_XMII_CONTROL_REG		0x28
+#define QCA955X_PLL_ETH_SGMII_CONTROL_REG		0x48
+#define QCA955X_PLL_ETH_SGMII_SERDES_REG		0x4c
+
+static void qca955x_device_reset_clear(unsigned int mask)
+{
+	unsigned int t, reg;
+
+	reg = KSEG1ADDR(AR71XX_RESET_BASE +
+			QCA955X_RESET_REG_RESET_MODULE);
+
+	t = READREG(reg);
+	WRITEREG(reg, t & ~mask);
+}
+
+static void mr18_setup_qca955x_eth_serdes_cal(unsigned int sgmii_value)
+{
+	unsigned int ethbase, pllbase, t;
+
+	ethbase = KSEG1ADDR(QCA955X_GMAC_BASE);
+	pllbase = KSEG1ADDR(AR71XX_PLL_BASE);
+
+	/* To Check the locking of the SGMII PLL */
+	t = READREG(ethbase + QCA955X_GMAC_REG_SGMII_SERDES);
+	t &= ~(QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK <<
+	       QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT);
+	t |= (sgmii_value & QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK) <<
+	     QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT;
+	WRITEREG(ethbase + QCA955X_GMAC_REG_SGMII_SERDES, t);
+
+	WRITEREG(pllbase + QCA955X_PLL_ETH_SGMII_SERDES_REG,
+		 QCA955X_PLL_ETH_SGMII_SERDES_LOCK_DETECT |
+		 QCA955X_PLL_ETH_SGMII_SERDES_PLL_REFCLK |
+		 QCA955X_PLL_ETH_SGMII_SERDES_EN_PLL)
+		;
+
+	qca955x_device_reset_clear(QCA955X_RESET_SGMII_ANALOG);
+	qca955x_device_reset_clear(QCA955X_RESET_SGMII);
+
+	while (!(READREG(ethbase + QCA955X_GMAC_REG_SGMII_SERDES) &
+		QCA955X_SGMII_SERDES_LOCK_DETECT_STATUS))
+		;
+}
+
+static inline void mr18_init(void)
+{
+	int res;
+
+	printf("Meraki MR18\n");
+
+	res = mr18_extract_sgmii_res_cal();
+	if (res >= 0)
+		mr18_setup_qca955x_eth_serdes_cal(res);
+
+}
+#else
+static inline void mr18_init(void) { }
+#endif
+
 void board_init(void)
 {
 	tlwr1043nd_init();
+	mr18_init();
 }
diff --git a/target/linux/ath79/image/nand.mk b/target/linux/ath79/image/nand.mk
index 10a548501065c57563709661e9c8aa1d9fe01d5d..4f34521a5050d569d520167dc540cd3449dc7434 100644
--- a/target/linux/ath79/image/nand.mk
+++ b/target/linux/ath79/image/nand.mk
@@ -11,6 +11,14 @@ define Build/dongwon-header
 	mv $@.tmp $@
 endef
 
+define Build/meraki-header
+        -$(STAGING_DIR_HOST)/bin/mkmerakifw \
+                -B $(1) -s \
+                -i $@ \
+                -o $@.new
+        @mv $@.new $@
+endef
+
 # attention: only zlib compression is allowed for the boot fs
 define Build/zyxel-buildkerneljffs
 	mkdir -p $@.tmp/boot
@@ -260,6 +268,23 @@ define Device/linksys_ea4500-v3
 endef
 TARGET_DEVICES += linksys_ea4500-v3
 
+define Device/meraki_mr18
+  SOC := qca9557
+  DEVICE_VENDOR := Meraki
+  DEVICE_MODEL := MR18
+  DEVICE_PACKAGES := kmod-spi-gpio nu801
+  KERNEL_SIZE := 8m
+  BLOCKSIZE := 128k
+  PAGESIZE := 2048
+  LOADER_TYPE := bin
+  KERNEL := kernel-bin | append-dtb | lzma | loader-kernel | meraki-header MR18
+# Initramfs-build fails due to size issues
+# KERNEL_INITRAMFS := $$(KERNEL)
+  KERNEL_INITRAMFS :=
+  IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
+endef
+TARGET_DEVICES += meraki_mr18
+
 # fake rootfs is mandatory, pad-offset 64 equals (1 * uimage_header)
 define Device/netgear_ath79_nand
   DEVICE_VENDOR := NETGEAR
diff --git a/target/linux/ath79/nand/base-files/etc/board.d/02_network b/target/linux/ath79/nand/base-files/etc/board.d/02_network
index 243c5bb7aaae14b080d579500e3db5f4facccfd0..60f085d3783c9066bf940a506e0854ea9eaf4bc8 100644
--- a/target/linux/ath79/nand/base-files/etc/board.d/02_network
+++ b/target/linux/ath79/nand/base-files/etc/board.d/02_network
@@ -8,7 +8,8 @@ ath79_setup_interfaces()
 
 	case "$board" in
 	aerohive,hiveap-121|\
-	glinet,gl-e750)
+	glinet,gl-e750|\
+	meraki,mr18)
 		ucidef_set_interface_lan "eth0"
 		;;
 	domywifi,dw33d)
@@ -102,6 +103,9 @@ ath79_setup_macs()
 		wan_mac=$(mtd_get_mac_binary art 0x0)
 		lan_mac=$(macaddr_add "$wan_mac" 1)
 		;;
+	meraki,mr18)
+		lan_mac=$(mtd_get_mac_binary_ubi board-config 102)
+		;;
 	netgear,wndr3700-v4|\
 	netgear,wndr4300|\
 	netgear,wndr4300sw|\
diff --git a/target/linux/ath79/nand/base-files/etc/hotplug.d/firmware/10-ath9k-eeprom b/target/linux/ath79/nand/base-files/etc/hotplug.d/firmware/10-ath9k-eeprom
index 36ab24e2dfe3549bae03e7c523fafc65dd12a40e..c4ccb04f5bc76511724c2c0775a98fb0313c19f0 100644
--- a/target/linux/ath79/nand/base-files/etc/hotplug.d/firmware/10-ath9k-eeprom
+++ b/target/linux/ath79/nand/base-files/etc/hotplug.d/firmware/10-ath9k-eeprom
@@ -12,6 +12,50 @@ case "$FIRMWARE" in
 	8dev,rambutan)
 		caldata_extract "caldata" 0x1000 0x800
 		;;
+	meraki,mr18)
+		. /lib/upgrade/nand.sh
+
+		if [ -n "$(nand_find_volume ubi0 caldata)" ]; then
+			caldata_extract_ubi "caldata" 0x1000 0x440
+		else
+			caldata_extract "odm-caldata" 0x1000 0x440
+		fi
+		ath9k_patch_mac $(macaddr_add $(mtd_get_mac_binary_ubi board-config 102) 1)
+		;;
+	*)
+		caldata_die "board $board is not supported yet"
+		;;
+	esac
+	;;
+"ath9k-eeprom-pci-0000:00:00.0.bin")
+	case $board in
+	meraki,mr18)
+		. /lib/upgrade/nand.sh
+
+		if [ -n "$(nand_find_volume ubi0 caldata)" ]; then
+			caldata_extract_ubi "caldata" 0x5000 0x440
+		else
+			caldata_extract "odm-caldata" 0x5000 0x440
+		fi
+		ath9k_patch_mac $(macaddr_add $(mtd_get_mac_binary_ubi board-config 102) 2)
+		;;
+	*)
+		caldata_die "board $board is not supported yet"
+		;;
+	esac
+	;;
+"ath9k-eeprom-pci-0000:01:00.0.bin")
+	case $board in
+	meraki,mr18)
+		. /lib/upgrade/nand.sh
+
+		if [ -n "$(nand_find_volume ubi0 caldata)" ]; then
+			caldata_extract_ubi "caldata" 0x9000 0x440
+		else
+			caldata_extract "odm-caldata" 0x9000 0x440
+		fi
+		ath9k_patch_mac $(macaddr_add $(mtd_get_mac_binary_ubi board-config 102) 3)
+		;;
 	*)
 		caldata_die "board $board is not supported yet"
 		;;