[rdk-b][common][bsp][Refactor and sync kernel/wifi from Openwrt]

[Description]
Refactor and sync kernel/wifi from Openwrt

[Release-log]
N/A

diff --git a/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/999-update-uapi-header-files-for-bridger.patch b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/999-update-uapi-header-files-for-bridger.patch
new file mode 100644
index 0000000..107cf49
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/999-update-uapi-header-files-for-bridger.patch
@@ -0,0 +1,194 @@
+diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
+index 1b3c2b6..00bbbf8 100644
+--- a/include/uapi/linux/if_bridge.h
++++ b/include/uapi/linux/if_bridge.h
+@@ -130,6 +130,7 @@ enum {
+ #define BRIDGE_VLAN_INFO_RANGE_BEGIN	(1<<3) /* VLAN is start of vlan range */
+ #define BRIDGE_VLAN_INFO_RANGE_END	(1<<4) /* VLAN is end of vlan range */
+ #define BRIDGE_VLAN_INFO_BRENTRY	(1<<5) /* Global bridge VLAN entry */
++#define BRIDGE_VLAN_INFO_ONLY_OPTS	(1<<6) /* Skip create/delete/flags */
+ 
+ struct bridge_vlan_info {
+ 	__u16 flags;
+@@ -156,6 +157,112 @@ struct bridge_vlan_xstats {
+ 	__u32 pad2;
+ };
+ 
++/* Bridge vlan RTM header */
++struct br_vlan_msg {
++	__u8 family;
++	__u8 reserved1;
++	__u16 reserved2;
++	__u32 ifindex;
++};
++
++enum {
++	BRIDGE_VLANDB_DUMP_UNSPEC,
++	BRIDGE_VLANDB_DUMP_FLAGS,
++	__BRIDGE_VLANDB_DUMP_MAX,
++};
++#define BRIDGE_VLANDB_DUMP_MAX (__BRIDGE_VLANDB_DUMP_MAX - 1)
++
++/* flags used in BRIDGE_VLANDB_DUMP_FLAGS attribute to affect dumps */
++#define BRIDGE_VLANDB_DUMPF_STATS	(1 << 0) /* Include stats in the dump */
++#define BRIDGE_VLANDB_DUMPF_GLOBAL	(1 << 1) /* Dump global vlan options only */
++
++/* Bridge vlan RTM attributes
++ * [BRIDGE_VLANDB_ENTRY] = {
++ *     [BRIDGE_VLANDB_ENTRY_INFO]
++ *     ...
++ * }
++ * [BRIDGE_VLANDB_GLOBAL_OPTIONS] = {
++ *     [BRIDGE_VLANDB_GOPTS_ID]
++ *     ...
++ * }
++ */
++enum {
++	BRIDGE_VLANDB_UNSPEC,
++	BRIDGE_VLANDB_ENTRY,
++	BRIDGE_VLANDB_GLOBAL_OPTIONS,
++	__BRIDGE_VLANDB_MAX,
++};
++#define BRIDGE_VLANDB_MAX (__BRIDGE_VLANDB_MAX - 1)
++
++enum {
++	BRIDGE_VLANDB_ENTRY_UNSPEC,
++	BRIDGE_VLANDB_ENTRY_INFO,
++	BRIDGE_VLANDB_ENTRY_RANGE,
++	BRIDGE_VLANDB_ENTRY_STATE,
++	BRIDGE_VLANDB_ENTRY_TUNNEL_INFO,
++	BRIDGE_VLANDB_ENTRY_STATS,
++	BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
++	__BRIDGE_VLANDB_ENTRY_MAX,
++};
++#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1)
++
++/* [BRIDGE_VLANDB_ENTRY] = {
++ *     [BRIDGE_VLANDB_ENTRY_TUNNEL_INFO] = {
++ *         [BRIDGE_VLANDB_TINFO_ID]
++ *         ...
++ *     }
++ * }
++ */
++enum {
++	BRIDGE_VLANDB_TINFO_UNSPEC,
++	BRIDGE_VLANDB_TINFO_ID,
++	BRIDGE_VLANDB_TINFO_CMD,
++	__BRIDGE_VLANDB_TINFO_MAX,
++};
++#define BRIDGE_VLANDB_TINFO_MAX (__BRIDGE_VLANDB_TINFO_MAX - 1)
++
++/* [BRIDGE_VLANDB_ENTRY] = {
++ *     [BRIDGE_VLANDB_ENTRY_STATS] = {
++ *         [BRIDGE_VLANDB_STATS_RX_BYTES]
++ *         ...
++ *     }
++ *     ...
++ * }
++ */
++enum {
++	BRIDGE_VLANDB_STATS_UNSPEC,
++	BRIDGE_VLANDB_STATS_RX_BYTES,
++	BRIDGE_VLANDB_STATS_RX_PACKETS,
++	BRIDGE_VLANDB_STATS_TX_BYTES,
++	BRIDGE_VLANDB_STATS_TX_PACKETS,
++	BRIDGE_VLANDB_STATS_PAD,
++	__BRIDGE_VLANDB_STATS_MAX,
++};
++#define BRIDGE_VLANDB_STATS_MAX (__BRIDGE_VLANDB_STATS_MAX - 1)
++
++enum {
++	BRIDGE_VLANDB_GOPTS_UNSPEC,
++	BRIDGE_VLANDB_GOPTS_ID,
++	BRIDGE_VLANDB_GOPTS_RANGE,
++	BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING,
++	BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION,
++	BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
++	BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT,
++	BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT,
++	BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
++	BRIDGE_VLANDB_GOPTS_PAD,
++	BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERIER,
++	BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE,
++	__BRIDGE_VLANDB_GOPTS_MAX
++};
++#define BRIDGE_VLANDB_GOPTS_MAX (__BRIDGE_VLANDB_GOPTS_MAX - 1)
++
+ /* Bridge multicast database attributes
+  * [MDBA_MDB] = {
+  *     [MDBA_MDB_ENTRY] = {
+diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
+index a6aa466..6abd5a1 100644
+--- a/include/uapi/linux/pkt_cls.h
++++ b/include/uapi/linux/pkt_cls.h
+@@ -16,9 +16,37 @@ enum {
+ 	TCA_ACT_STATS,
+ 	TCA_ACT_PAD,
+ 	TCA_ACT_COOKIE,
++	TCA_ACT_FLAGS,
++	TCA_ACT_HW_STATS,
++	TCA_ACT_USED_HW_STATS,
+ 	__TCA_ACT_MAX
+ };
+ 
++/* See other TCA_ACT_FLAGS_ * flags in include/net/act_api.h. */
++#define TCA_ACT_FLAGS_NO_PERCPU_STATS 1 /* Don't use percpu allocator for
++					 * actions stats.
++					 */
++
++/* tca HW stats type
++ * When user does not pass the attribute, he does not care.
++ * It is the same as if he would pass the attribute with
++ * all supported bits set.
++ * In case no bits are set, user is not interested in getting any HW statistics.
++ */
++#define TCA_ACT_HW_STATS_IMMEDIATE (1 << 0) /* Means that in dump, user
++					     * gets the current HW stats
++					     * state from the device
++					     * queried at the dump time.
++					     */
++#define TCA_ACT_HW_STATS_DELAYED (1 << 1) /* Means that in dump, user gets
++					   * HW stats that might be out of date
++					   * for some time, maybe couple of
++					   * seconds. This is the case when
++					   * driver polls stats updates
++					   * periodically or when it gets async
++					   * stats update from the device.
++					   */
++
+ #define TCA_ACT_MAX __TCA_ACT_MAX
+ #define TCA_OLD_COMPAT (TCA_ACT_MAX+1)
+ #define TCA_ACT_MAX_PRIO 32
+diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
+index 96eca6e..ff43cb9 100644
+--- a/include/uapi/linux/rtnetlink.h
++++ b/include/uapi/linux/rtnetlink.h
+@@ -164,6 +164,13 @@ enum {
+ 	RTM_GETNEXTHOP,
+ #define RTM_GETNEXTHOP	RTM_GETNEXTHOP
+ 
++	RTM_NEWVLAN = 112,
++#define RTM_NEWNVLAN	RTM_NEWVLAN
++	RTM_DELVLAN,
++#define RTM_DELVLAN	RTM_DELVLAN
++	RTM_GETVLAN,
++#define RTM_GETVLAN	RTM_GETVLAN
++
+ 	__RTM_MAX,
+ #define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
+ };
+@@ -717,6 +724,8 @@ enum rtnetlink_groups {
+ #define RTNLGRP_IPV6_MROUTE_R	RTNLGRP_IPV6_MROUTE_R
+ 	RTNLGRP_NEXTHOP,
+ #define RTNLGRP_NEXTHOP		RTNLGRP_NEXTHOP
++	RTNLGRP_BRVLAN,
++#define RTNLGRP_BRVLAN		RTNLGRP_BRVLAN
+ 	__RTNLGRP_MAX
+ };
+ #define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc
index 6d84bd1..4f141ed 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc
+++ b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc
@@ -277,4 +277,5 @@
     file://900-v5.9-0001-dt-bindings-Add-multicolor-class-dt-bindings-documen.patch \
     file://900-v5.9-0002-leds-Add-multicolor-ID-to-the-color-ID-list.patch \
     file://900-v5.9-0003-leds-add-RGB-color-option-as-that-is-different-from-.patch \
+    file://999-update-uapi-header-files-for-bridger.patch \
     "
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts
index a050c88..40c41b6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts
@@ -68,7 +68,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -102,9 +102,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -112,33 +109,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -179,12 +167,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts
index 4c4f987..925a852 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts
@@ -93,6 +93,7 @@
 			pause;
 			link-gpio = <&pio 47 0>;
 			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -100,35 +101,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts
index 6af58aa..32f9320 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts
@@ -59,7 +59,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -93,9 +93,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -103,33 +100,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -170,12 +158,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts
index e5024b5..61b68af 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts
index 15e9773..629d509 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts
index be89622..09e41f6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts
index 470e9f3..1603714 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts
@@ -62,6 +62,9 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
+			link-gpio = <&pio 47 0>;
+			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -69,33 +72,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts
index a56da0d..c074239 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts
index 4fe4946..dd02baf 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts
index 187a8a5..1dc1551 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts
index f9fefa0..4cbfe41 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts
index a050c88..40c41b6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts
@@ -68,7 +68,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -102,9 +102,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -112,33 +109,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -179,12 +167,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts
index 4c4f987..925a852 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts
@@ -93,6 +93,7 @@
 			pause;
 			link-gpio = <&pio 47 0>;
 			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -100,35 +101,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts
index 6af58aa..32f9320 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts
@@ -59,7 +59,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -93,9 +93,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -103,33 +100,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -170,12 +158,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts
index e5024b5..61b68af 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts
index 15e9773..629d509 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts
index be89622..09e41f6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts
index 470e9f3..1603714 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts
@@ -62,6 +62,9 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
+			link-gpio = <&pio 47 0>;
+			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -69,33 +72,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts
index a56da0d..c074239 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts
index 4fe4946..dd02baf 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts
index 187a8a5..1dc1551 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts
index f9fefa0..4cbfe41 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c
index 1756ff7..f1812bb 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c
@@ -133,7 +133,7 @@
 		   &snand_cap_program_load_x4),
 	SNAND_INFO("GD5F2GQ5UExxG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x52),
 		   SNAND_MEMORG_2G_2K_128,
-		   &snand_cap_read_from_cache_quad_q2d,
+		   &snand_cap_read_from_cache_quad_a8d,
 		   &snand_cap_program_load_x4),
 	SNAND_INFO("GD5F4GQ4UCxIG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0xb4),
 		   SNAND_MEMORG_4G_4K_256,
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index bd70441..6955aad 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -188,6 +188,16 @@
 	return _mtk_mdio_read(eth, phy_addr, phy_reg);
 }
 
+static int mtk_mdio_reset(struct mii_bus *bus)
+{
+	/* The mdiobus_register will trigger a reset pulse when enabling Bus reset,
+	 * we just need to wait until device ready.
+	 */
+	mdelay(20);
+
+	return 0;
+}
+
 static int mt7621_gmac0_rgmii_adjust(struct mtk_eth *eth,
 				     phy_interface_t interface)
 {
@@ -626,6 +636,7 @@
 	eth->mii_bus->name = "mdio";
 	eth->mii_bus->read = mtk_mdio_read;
 	eth->mii_bus->write = mtk_mdio_write;
+	eth->mii_bus->reset = mtk_mdio_reset;
 	eth->mii_bus->priv = eth;
 	eth->mii_bus->parent = eth->dev;
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
index 60939f2..54674ad 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -581,7 +581,7 @@
 /* Mac control registers */
 #define MTK_MAC_MCR(x)		(0x10100 + (x * 0x100))
 #define MAC_MCR_MAX_RX_1536	BIT(24)
-#define MAC_MCR_IPG_CFG		(BIT(18) | BIT(16))
+#define MAC_MCR_IPG_CFG		(BIT(18) | BIT(16) | BIT(12))
 #define MAC_MCR_FORCE_MODE	BIT(15)
 #define MAC_MCR_TX_EN		BIT(14)
 #define MAC_MCR_RX_EN		BIT(13)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c
index 1540bf8..6b937d5 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c
@@ -1571,7 +1571,8 @@
 
 	if (IS_HQOS_MODE || skb->mark >= MAX_PPPQ_PORT_NUM)
 		qid = skb->mark & (MTK_QDMA_TX_MASK);
-	else if (IS_PPPQ_MODE && (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev)))
+	else if (IS_PPPQ_MODE && (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev) ||
+		 (FROM_WED(skb) && IS_DSA_LAN(dev))))
 		qid = port_id & MTK_QDMA_TX_MASK;
 	else
 		qid = 0;
@@ -1607,7 +1608,8 @@
 			else
 				entry.ipv4_hnapt.iblk2.fqos =
 					(!IS_PPPQ_MODE || (IS_PPPQ_MODE &&
-					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev))));
+					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev) ||
+					 (FROM_WED(skb) && IS_DSA_LAN(dev)))));
 		} else {
 			entry.ipv4_hnapt.iblk2.fqos = 0;
 		}
@@ -1641,7 +1643,8 @@
 			else
 				entry.ipv6_5t_route.iblk2.fqos =
 					(!IS_PPPQ_MODE || (IS_PPPQ_MODE &&
-					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev))));
+					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev) ||
+					 (FROM_WED(skb) && IS_DSA_LAN(dev)))));
 		} else {
 			entry.ipv6_5t_route.iblk2.fqos = 0;
 		}
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
index c0abdc7..146f15f 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
@@ -725,6 +725,11 @@
 	ret = upper_ret-lower_ret;
 	if (ret == 1) {
 		ret = 0;
+		/* Make sure we use upper_idx in our calibration system */
+		cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+			MTK_PHY_DA_RX_PSBN_TBT_MASK | MTK_PHY_DA_RX_PSBN_HBT_MASK |
+			MTK_PHY_DA_RX_PSBN_GBE_MASK | MTK_PHY_DA_RX_PSBN_LP_MASK,
+			upper_idx << 12 | upper_idx << 8 | upper_idx << 4 | upper_idx);
 		dev_info(&phydev->mdio.dev, "TX-VCM SW cal result: 0x%x\n", upper_idx);
 	} else if (lower_idx == TXRESERVE_MIN && upper_ret == 1 && lower_ret == 1) {
 		ret = 0;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h
index 70db398..a790c62 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h
@@ -13,6 +13,7 @@
  DEVICE_ATTR_DECLARED(RG_USB20_HSTX_SRCTRL);
  DEVICE_ATTR_DECLARED(RG_USB20_DISCTH);
  DEVICE_ATTR_DECLARED(RG_CHGDT_EN);
+ DEVICE_ATTR_DECLARED(RG_USB20_PHY_REV);
  DEVICE_ATTR_DECLARED(reg);
 
  #define HQA_INFORMACTION_COLLECTS() do {\
@@ -22,5 +23,6 @@
 	ECHO_HQA(USB20_PHY_USBPHYACR5, RG_USB20_HSTX_SRCTRL, 3); \
 	ECHO_HQA(USB20_PHY_USBPHYACR6, RG_USB20_DISCTH, 4); \
 	ECHO_HQA(USB20_PHY_U2PHYBC12C, RG_CHGDT_EN, 1); \
+	ECHO_HQA(USB20_PHY_USBPHYACR6, RG_USB20_PHY_REV, 2); \
 	} while (0)
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h
index e898a26..80b786a 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h
@@ -13,5 +13,6 @@
 UNUSUAL_DEVICE_ATTR(RG_USB20_HSTX_SRCTRL),
 UNUSUAL_DEVICE_ATTR(RG_USB20_DISCTH),
 UNUSUAL_DEVICE_ATTR(RG_CHGDT_EN),
+UNUSUAL_DEVICE_ATTR(RG_USB20_PHY_REV),
 UNUSUAL_DEVICE_ATTR(reg),
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-preemphasic.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-preemphasic.c
new file mode 100644
index 0000000..5041dbc
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-preemphasic.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * xHCI host controller toolkit driver for pre-emphasic
+ *
+ * Copyright (C) 2021  MediaTek Inc.
+ *
+ *  Author: Zhanyong Wang <zhanyong.wang@mediatek.com>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include "xhci-mtk.h"
+#include "xhci-mtk-test.h"
+#include "xhci-mtk-unusual.h"
+
+static ssize_t RG_USB20_PHY_REV_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+	struct usb_hcd *hcd = mtk->hcd;
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+	struct device_node  *node = dev->of_node;
+	ssize_t cnt = 0;
+	void __iomem *addr;
+	u32 val;
+	u32 i;
+	int ports;
+	char str[32];
+	int index = 0;
+	u32 io, length;
+	int ret;
+
+	ports = mtk->num_u3_ports + mtk->num_u2_ports;
+	cnt += sprintf(buf + cnt, " RG_USB20_PHY_REV usage:\n");
+	cnt += sprintf(buf + cnt,
+                "   echo u2p index 2b00 > RG_USB20_PHY_REV\n");
+	if (mtk->num_u3_ports + 1 != ports)
+		cnt += sprintf(buf + cnt, "	parameter: u2p: %i ~ %i\n",
+					mtk->num_u3_ports + 1, ports);
+	else
+		cnt += sprintf(buf + cnt, "	parameter: u2p: %i\n",
+					mtk->num_u3_ports + 1);
+
+	if (mtk->num_u2_ports > 1)
+		cnt += sprintf(buf + cnt, "	parameter: index: 0 ~ %i\n",
+			       mtk->num_u2_ports);
+	else
+		cnt += sprintf(buf + cnt, "	parameter: index: 0\n");
+
+	cnt += sprintf(buf + cnt, " e.g.: echo 2 0 2b10 > RG_USB20_PHY_REV\n");
+	cnt += sprintf(buf + cnt,
+		"  port2 binding phy 0, enable 2b'10 as RG_USB20_PHY_REV\n");
+
+	cnt += sprintf(buf + cnt,
+			"\n=========current HQA setting check=========\n");
+	for (i = 1; i <= ports; i++) {
+		addr = &xhci->op_regs->port_status_base +
+			NUM_PORT_REGS * ((i - 1) & 0xff);
+		val = readl(addr);
+		if (i <= mtk->num_u3_ports) {
+			cnt += sprintf(buf + cnt,
+				       "USB30 Port%i: 0x%08X\n", i, val);
+		} else {
+			cnt += sprintf(buf + cnt,
+				       "USB20 Port%i: 0x%08X\n", i, val);
+
+			ret = query_phy_addr(node,
+					 &index, &io, &length, PHY_TYPE_USB2);
+			if (ret && ret != -EACCES) {
+				if (ret == -EPERM)
+					cnt += sprintf(buf + cnt,
+					"USB20 Port%i (Phy%i: absent)\n",
+					i, index);
+				else
+					cnt += sprintf(buf + cnt,
+					"USB20 Port%i (Phy%i) failure %i\n",
+						 i, index, ret);
+				continue;
+			}
+
+			cnt += sprintf(buf + cnt,
+				"USB20 Port%i (Phy%i:%sable): 0x%08X 0x%08X\n",
+				i, index, ret ? " dis" : " en", io, length);
+
+			addr   = ioremap_nocache(io, length);
+			addr  += (length != 0x100) ? 0x300 : 0;
+
+			HQA_INFORMACTION_COLLECTS();
+
+			iounmap(addr);
+			index ++;
+		}
+	}
+
+	if (mtk->hqa_pos) {
+		cnt += sprintf(buf + cnt, "%s", mtk->hqa_buf);
+		mtk->hqa_pos = 0;
+	}
+
+	return cnt;
+}
+
+static ssize_t RG_USB20_PHY_REV_store(struct device *dev,
+                        struct device_attribute *attr,
+                        const char *buf, size_t n)
+{
+	u32 val;
+	u32 io;
+	u32 length;
+	int ports;
+	int words;
+	int port;
+	int index;
+	int ret;
+	char *str = NULL;
+	void __iomem *addr;
+	struct xhci_hcd_mtk *mtk  = dev_get_drvdata(dev);
+	struct device_node  *node = dev->of_node;
+
+	ports = mtk->num_u3_ports + mtk->num_u2_ports;
+	mtk->hqa_pos = 0;
+
+	memset(mtk->hqa_buf, 0, mtk->hqa_size);
+
+	str = kzalloc(n, GFP_ATOMIC);
+
+	hqa_info(mtk, "RG_USB20_PHY_REV(%lu): %s\n", n, buf);
+
+	words = sscanf(buf, "%i %i 2b%2[0,1]", &port, &index, str);
+	if ((words != 3) ||
+	    (port < mtk->num_u3_ports || port > ports)) {
+		hqa_info(mtk, "Check params(%i):\" %i %i %s\", Please!\n",
+			words, port, index, str);
+
+		ret = -EINVAL;
+		goto error;
+	}
+
+	hqa_info(mtk, " params: %i %i %s\n",
+		port, index, str);
+
+	ret = query_phy_addr(node, &index, &io, &length, PHY_TYPE_USB2);
+	if (ret && ret != -EACCES)
+		goto error;
+
+	io += (length != 0x100) ? 0x300 : 0;
+	io += USB20_PHY_USBPHYACR6;
+
+	addr = ioremap_nocache(io, 4);
+	val = binary_write_width2(addr, SHFT_RG_USB20_PHY_REV, str);
+	hqa_info(mtk, "Port%i(Phy%i)[0x%08X]: 0x%08X but 0x%08X\n",
+		port, index, io, val, readl(addr));
+
+	iounmap(addr);
+	ret = n;
+
+error:
+	kfree(str);
+	return ret;
+}
+DEVICE_ATTR_RW(RG_USB20_PHY_REV);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c
index 01029fb..b02720d 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c
@@ -35,6 +35,27 @@
 	return val;
 }
 
+u32 binary_write_width2(u32 __iomem *addr, u32 shift, const char *buf)
+{
+	u32 val = 0;
+
+	if (!strncmp(buf, STRNG_0_WIDTH_2, BIT_WIDTH_2))
+		val = 0;
+	else if (!strncmp(buf, STRNG_1_WIDTH_2, BIT_WIDTH_2))
+		val = 1;
+	else if (!strncmp(buf, STRNG_2_WIDTH_2, BIT_WIDTH_2))
+		val = 2;
+	else if (!strncmp(buf, STRNG_3_WIDTH_2, BIT_WIDTH_2))
+		val = 3;
+	else
+		val = 0xFFFFFFFF;
+
+	if (val <= 3)
+		val = usb20hqa_write(addr, shift, MSK_WIDTH_2, val);
+
+	return val;
+}
+
 u32 binary_write_width3(u32 __iomem *addr, u32 shift, const char *buf)
 {
 	u32 val = 0;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h
index 0bc6dd8..3850ccf 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h
@@ -92,6 +92,11 @@
 #define SHFT_RG_CHGDT_EN		0
 #define BV_RG_CHGDT_EN			BIT(0)
 
+#define NAME_RG_USB20_PHY_REV		"RG_USB20_PHY_REV"
+/* #define USB20_PHY_USBPHYACR6		0x18 */
+#define SHFT_RG_USB20_PHY_REV		30
+#define BV_RG_USB20_PHY_REV		GENMASK(31, 30)
+
 #define ECHO_HQA(reg, _bd, _bw)  do {\
 	val = usb20hqa_read(addr + (reg), \
 		 SHFT_##_bd, \
@@ -127,6 +132,8 @@
 
 u32 binary_write_width1(u32 __iomem *addr,
 				u32 shift, const char *buf);
+u32 binary_write_width2(u32 __iomem *addr,
+				u32 shift, const char *buf);
 u32 binary_write_width3(u32 __iomem *addr,
 				u32 shift, const char *buf);
 u32 binary_write_width4(u32 __iomem *addr,
@@ -168,6 +175,11 @@
 {
 	return 0;
 };
+static inline u32 binary_write_width2(u32 __iomem *addr,
+					u32 shift, const char *buf)
+{
+	return 0;
+};
 static inline u32 binary_write_width3(u32 __iomem *addr,
 					u32 shift, const char *buf)
 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
index 47c1ab3..5da593d 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
@@ -1,4 +1,4 @@
-From bc8244ada5c668374813f7f9b73d990bf2695aaf Mon Sep 17 00:00:00 2001
+From 7c81104d65728fb1c0f156c46e3cfc5dec24b119 Mon Sep 17 00:00:00 2001
 From: Sujuan Chen <sujuan.chen@mediatek.com>
 Date: Wed, 15 Jun 2022 14:38:54 +0800
 Subject: [PATCH 8/8] 9997-add-wed-rx-support-for-mt7896
@@ -13,13 +13,13 @@
  drivers/net/ethernet/mediatek/mtk_wed_ccif.c  | 133 ++++
  drivers/net/ethernet/mediatek/mtk_wed_ccif.h  |  45 ++
  .../net/ethernet/mediatek/mtk_wed_debugfs.c   |  90 +++
- drivers/net/ethernet/mediatek/mtk_wed_mcu.c   | 561 ++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_wed_mcu.c   | 586 ++++++++++++++++
  drivers/net/ethernet/mediatek/mtk_wed_mcu.h   | 125 ++++
  drivers/net/ethernet/mediatek/mtk_wed_regs.h  | 145 +++-
- drivers/net/ethernet/mediatek/mtk_wed_wo.c    | 588 ++++++++++++++++
- drivers/net/ethernet/mediatek/mtk_wed_wo.h    | 336 ++++++++++
- include/linux/soc/mediatek/mtk_wed.h          |  64 +-
- 14 files changed, 2643 insertions(+), 69 deletions(-)
+ drivers/net/ethernet/mediatek/mtk_wed_wo.c    | 581 ++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_wed_wo.h    | 327 +++++++++
+ include/linux/soc/mediatek/mtk_wed.h          |  75 ++-
+ 14 files changed, 2796 insertions(+), 75 deletions(-)
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ccif.c
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ccif.h
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_mcu.c
@@ -28,7 +28,7 @@
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_wo.h
 
 diff --git a/arch/arm64/boot/dts/mediatek/mt7986a.dtsi b/arch/arm64/boot/dts/mediatek/mt7986a.dtsi
-index 644255b35..ddcc0b809 100644
+index 87d2b11a9..6abc06db8 100644
 --- a/arch/arm64/boot/dts/mediatek/mt7986a.dtsi
 +++ b/arch/arm64/boot/dts/mediatek/mt7986a.dtsi
 @@ -65,6 +65,12 @@
@@ -170,7 +170,7 @@
  		resets = <&ethsysrst 0>;
  		reset-names = "wocpu_rst";
 diff --git a/drivers/net/ethernet/mediatek/Makefile b/drivers/net/ethernet/mediatek/Makefile
-index 3528f1b..0c724a5 100644
+index 3528f1b3c..0c724a55c 100644
 --- a/drivers/net/ethernet/mediatek/Makefile
 +++ b/drivers/net/ethernet/mediatek/Makefile
 @@ -10,5 +10,5 @@ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o
@@ -181,7 +181,7 @@
 +obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o mtk_wed_wo.o mtk_wed_mcu.o mtk_wed_ccif.o
  obj-$(CONFIG_NET_MEDIATEK_HNAT)			+= mtk_hnat/
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c
-index 48b0353..0750def 100644
+index 48b0353bb..75527956b 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed.c
 +++ b/drivers/net/ethernet/mediatek/mtk_wed.c
 @@ -13,11 +13,19 @@
@@ -338,7 +338,7 @@
  	for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
  		void *page = page_list[page_idx++];
  
-@@ -205,6 +316,42 @@ free_pagelist:
+@@ -205,6 +316,42 @@ mtk_wed_free_buffer(struct mtk_wed_device *dev)
  	kfree(page_list);
  }
  
@@ -1128,7 +1128,7 @@
  
  	mtk_wed_hw_add_debugfs(hw);
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed.h b/drivers/net/ethernet/mediatek/mtk_wed.h
-index 9b17b74..8ef5253 100644
+index 9b17b7405..8ef5253ca 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed.h
 +++ b/drivers/net/ethernet/mediatek/mtk_wed.h
 @@ -13,6 +13,7 @@
@@ -1232,7 +1232,7 @@
  #endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_ccif.c b/drivers/net/ethernet/mediatek/mtk_wed_ccif.c
 new file mode 100644
-index 0000000..22ef337
+index 000000000..22ef337d0
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_ccif.c
 @@ -0,0 +1,133 @@
@@ -1371,7 +1371,7 @@
 +}
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_ccif.h b/drivers/net/ethernet/mediatek/mtk_wed_ccif.h
 new file mode 100644
-index 0000000..68ade44
+index 000000000..68ade449c
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_ccif.h
 @@ -0,0 +1,45 @@
@@ -1421,7 +1421,7 @@
 +
 +#endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
-index f420f18..4a9e684 100644
+index f420f187e..4a9e684ed 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
 @@ -2,6 +2,7 @@
@@ -1563,10 +1563,10 @@
  }
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
 new file mode 100644
-index 0000000..bd1ab95
+index 000000000..723bdfd55
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
-@@ -0,0 +1,561 @@
+@@ -0,0 +1,586 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +
 +#include <linux/skbuff.h>
@@ -1744,8 +1744,10 @@
 +void
 +mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo, struct sk_buff *skb)
 +{
++	struct mtk_wed_device *wed = wo->hw->wed_dev;
 +	struct wed_cmd_hdr *hdr = (struct wed_cmd_hdr *)skb->data;
 +	struct wed_wo_log *record;
++	struct wo_cmd_rxcnt_t *rxcnt;
 +	char *msg = (char *)(skb->data + sizeof(struct wed_cmd_hdr));
 +	u16 msg_len = skb->len - sizeof(struct wed_cmd_hdr);
 +	u32 i, cnt = 0;
@@ -1771,7 +1773,14 @@
 +				 record[i].mod);
 +		}
 +		break;
++	case WO_EVT_RXCNT_INFO:
++		cnt = *(u32 *)msg;
++		rxcnt = (struct wo_cmd_rxcnt_t *)((u32 *)msg+1);
 +
++		for (i = 0; i < cnt; i++)
++			if (wed->wlan.update_wo_rxcnt)
++				wed->wlan.update_wo_rxcnt(wed, rxcnt);
++		break;
 +	default:
 +		break;
 +	}
@@ -1942,7 +1951,10 @@
 +wo_mcu_parse_response(struct mtk_wed_wo *wo, int cmd,
 +			  struct sk_buff *skb, int seq)
 +{
++	struct mtk_wed_device *wed = wo->hw->wed_dev;
 +	struct wed_cmd_hdr  *hdr;
++	struct wo_cmd_rxcnt_t *rxcnt = NULL;
++	u32 i, cnt = 0;
 +
 +	if (!skb) {
 +		dev_err(wo->hw->dev, "Message %08x (seq %d) timeout\n",
@@ -1957,7 +1969,20 @@
 +		return -EAGAIN;
 +	}
 +
-+	//skb_pull(skb, sizeof(struct wed_cmd_hdr));
++	skb_pull(skb, sizeof(struct wed_cmd_hdr));
++
++	switch (cmd) {
++	case WO_CMD_RXCNT_INFO:
++		cnt = *(u32 *)skb->data;
++		rxcnt = (struct wo_cmd_rxcnt_t *)((u32 *)skb->data+1);
++
++		for (i = 0; i < cnt; i++)
++			if (wed->wlan.update_wo_rxcnt)
++				wed->wlan.update_wo_rxcnt(wed, rxcnt);
++		break;
++	default:
++		break;
++	}
 +
 +	return 0;
 +}
@@ -2130,7 +2155,7 @@
 +
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.h b/drivers/net/ethernet/mediatek/mtk_wed_mcu.h
 new file mode 100644
-index 0000000..6a5ac76
+index 000000000..6a5ac7672
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.h
 @@ -0,0 +1,125 @@
@@ -2260,7 +2285,7 @@
 +
 +#endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_regs.h b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
-index e107de7..9d021e2 100644
+index e107de7ba..9d021e2da 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
 @@ -4,6 +4,8 @@
@@ -2524,7 +2549,7 @@
  #endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_wo.c b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
 new file mode 100644
-index 0000000..e101f17
+index 000000000..67dcffb26
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
 @@ -0,0 +1,581 @@
@@ -3111,10 +3136,10 @@
 +}
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_wo.h b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
 new file mode 100644
-index 0000000..d962e3a
+index 000000000..d962e3a33
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
-@@ -0,0 +1,336 @@
+@@ -0,0 +1,327 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
 +
@@ -3259,15 +3284,6 @@
 +	bool done:1;
 +};
 +
-+struct wo_cmd_rxcnt_t {
-+	u16 wlan_idx;
-+	u16 tid;
-+	u32 rx_pkt_cnt;
-+	u32 rx_byte_cnt;
-+	u32 rx_err_cnt;
-+	u32 rx_drop_cnt;
-+};
-+
 +struct wo_cmd_query {
 +	u32 query0;
 +	u32 query1;
@@ -3452,7 +3468,7 @@
 +#endif
 +
 diff --git a/include/linux/soc/mediatek/mtk_wed.h b/include/linux/soc/mediatek/mtk_wed.h
-index ffd547a..9a9cc1b 100644
+index ffd547a4c..c74dd4aad 100644
 --- a/include/linux/soc/mediatek/mtk_wed.h
 +++ b/include/linux/soc/mediatek/mtk_wed.h
 @@ -7,6 +7,9 @@
@@ -3465,7 +3481,7 @@
  
  enum {
  	MTK_NO_WED,
-@@ -33,6 +36,24 @@ struct mtk_wed_ring {
+@@ -33,6 +36,33 @@ struct mtk_wed_ring {
  	void __iomem *wpdma;
  };
  
@@ -3487,10 +3503,19 @@
 +	dma_addr_t desc_phys;
 +};
 +
++struct wo_cmd_rxcnt_t {
++	u16 wlan_idx;
++	u16 tid;
++	u32 rx_pkt_cnt;
++	u32 rx_byte_cnt;
++	u32 rx_err_cnt;
++	u32 rx_drop_cnt;
++};
++
  struct mtk_wed_device {
  #ifdef CONFIG_NET_MEDIATEK_SOC_WED
  	const struct mtk_wed_ops *ops;
-@@ -42,39 +63,57 @@ struct mtk_wed_device {
+@@ -42,39 +63,59 @@ struct mtk_wed_device {
  	int wdma_idx;
  	int irq;
  	u8 ver;
@@ -3550,10 +3575,12 @@
 +		u32 (*init_rx_buf)(struct mtk_wed_device *wed,
 +				   int pkt_num);
 +		void (*release_rx_buf)(struct mtk_wed_device *wed);
++		void (*update_wo_rxcnt)(struct  mtk_wed_device *wed,
++				struct wo_cmd_rxcnt_t *rxcnt);
  	} wlan;
  #endif
  };
-@@ -85,6 +124,10 @@ struct mtk_wed_ops {
+@@ -85,6 +126,10 @@ struct mtk_wed_ops {
  			     void __iomem *regs);
  	int (*txfree_ring_setup)(struct mtk_wed_device *dev,
  				 void __iomem *regs);
@@ -3564,7 +3591,7 @@
  	void (*detach)(struct mtk_wed_device *dev);
  
  	void (*stop)(struct mtk_wed_device *dev);
-@@ -96,6 +139,8 @@ struct mtk_wed_ops {
+@@ -96,6 +141,8 @@ struct mtk_wed_ops {
  
  	u32 (*irq_get)(struct mtk_wed_device *dev, u32 mask);
  	void (*irq_set_mask)(struct mtk_wed_device *dev, u32 mask);
@@ -3573,7 +3600,7 @@
  };
  
  extern const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
-@@ -128,6 +173,10 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
+@@ -128,6 +175,10 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
  	(_dev)->ops->tx_ring_setup(_dev, _ring, _regs)
  #define mtk_wed_device_txfree_ring_setup(_dev, _regs) \
  	(_dev)->ops->txfree_ring_setup(_dev, _regs)
@@ -3584,7 +3611,7 @@
  #define mtk_wed_device_reg_read(_dev, _reg) \
  	(_dev)->ops->reg_read(_dev, _reg)
  #define mtk_wed_device_reg_write(_dev, _reg, _val) \
-@@ -136,6 +185,8 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
+@@ -136,6 +187,8 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
  	(_dev)->ops->irq_get(_dev, _mask)
  #define mtk_wed_device_irq_set_mask(_dev, _mask) \
  	(_dev)->ops->irq_set_mask(_dev, _mask)
@@ -3593,7 +3620,7 @@
  #else
  static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
  {
-@@ -145,10 +196,13 @@ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+@@ -145,10 +198,13 @@ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
  #define mtk_wed_device_start(_dev, _mask) do {} while (0)
  #define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) -ENODEV
  #define mtk_wed_device_txfree_ring_setup(_dev, _ring, _regs) -ENODEV
@@ -3608,5 +3635,5 @@
  
  #endif
 -- 
-2.18.0
+2.32.0
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch
index 6442853..32daccb 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch
@@ -703,10 +703,12 @@
 index 9a9cc1b..31f4a26 100644
 --- a/include/linux/soc/mediatek/mtk_wed.h
 +++ b/include/linux/soc/mediatek/mtk_wed.h
-@@ -114,23 +114,27 @@ struct mtk_wed_device {
+@@ -114,25 +114,29 @@ struct mtk_wed_device {
  		u32 (*init_rx_buf)(struct mtk_wed_device *wed,
  				   int pkt_num);
  		void (*release_rx_buf)(struct mtk_wed_device *wed);
+ 		void (*update_wo_rxcnt)(struct  mtk_wed_device *wed,
+ 				struct wo_cmd_rxcnt_t *rxcnt);
 +		void (*ser_trigger)(struct mtk_wed_device *wed);
  	} wlan;
 +	struct completion fe_reset_done;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-update-net-bridge-for-bridger.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-update-net-bridge-for-bridger.patch
new file mode 100644
index 0000000..6fe3733
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-update-net-bridge-for-bridger.patch
@@ -0,0 +1,1823 @@
+diff --git a/include/net/switchdev.h b/include/net/switchdev.h
+index 191dc34..d4d71d9 100644
+--- a/include/net/switchdev.h
++++ b/include/net/switchdev.h
+@@ -77,6 +77,7 @@ struct switchdev_obj {
+ struct switchdev_obj_port_vlan {
+ 	struct switchdev_obj obj;
+ 	u16 flags;
++	u16 vid;
+ 	u16 vid_begin;
+ 	u16 vid_end;
+ };
+@@ -117,6 +118,7 @@ enum switchdev_notifier_type {
+ struct switchdev_notifier_info {
+ 	struct net_device *dev;
+ 	struct netlink_ext_ack *extack;
++	const void *ctx;
+ };
+ 
+ struct switchdev_notifier_fdb_info {
+diff --git a/net/bridge/Makefile b/net/bridge/Makefile
+index ac9ef33..49da7ae 100644
+--- a/net/bridge/Makefile
++++ b/net/bridge/Makefile
+@@ -20,7 +20,7 @@ obj-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
+ 
+ bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
+ 
+-bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o
++bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o
+ 
+ bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
+ 
+diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c
+index da5ed4c..eeabfbc 100644
+--- a/net/bridge/br_mdb.c
++++ b/net/bridge/br_mdb.c
+@@ -16,7 +16,37 @@
+ 
+ #include "br_private.h"
+ 
+-static int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
++static size_t __br_rports_one_size(void)
++{
++	return nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PORT */
++	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_TIMER */
++	       nla_total_size(sizeof(u8)) +  /* MDBA_ROUTER_PATTR_TYPE */
++	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET_TIMER */
++	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET6_TIMER */
++	       nla_total_size(sizeof(u32));  /* MDBA_ROUTER_PATTR_VID */
++}
++
++size_t br_rports_size(const struct net_bridge_mcast *brmctx)
++{
++	struct net_bridge_mcast_port *pmctx;
++	size_t size = nla_total_size(0); /* MDBA_ROUTER */
++
++	rcu_read_lock();
++	hlist_for_each_entry_rcu(pmctx, &brmctx->ip4_mc_router_list,
++				 ip4_rlist)
++		size += __br_rports_one_size();
++
++#if IS_ENABLED(CONFIG_IPV6)
++	hlist_for_each_entry_rcu(pmctx, &brmctx->ip6_mc_router_list,
++				 ip6_rlist)
++		size += __br_rports_one_size();
++#endif
++	rcu_read_unlock();
++
++	return size;
++}
++
++int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
+ 			       struct net_device *dev)
+ {
+ 	struct net_bridge *br = netdev_priv(dev);
+diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
+index cbcbc19..887e767 100644
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -562,7 +562,7 @@ static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
+ 	return err;
+ }
+ 
+-static int br_process_vlan_info(struct net_bridge *br,
++int br_process_vlan_info(struct net_bridge *br,
+ 				struct net_bridge_port *p, int cmd,
+ 				struct bridge_vlan_info *vinfo_curr,
+ 				struct bridge_vlan_info **vinfo_last,
+@@ -1578,7 +1578,7 @@ static int br_fill_linkxstats(struct sk_buff *skb,
+ 		pvid = br_get_pvid(vg);
+ 		list_for_each_entry(v, &vg->vlan_list, vlist) {
+ 			struct bridge_vlan_xstats vxi;
+-			struct br_vlan_stats stats;
++			struct pcpu_sw_netstats stats;
+ 
+ 			if (++vl_idx < *prividx)
+ 				continue;
+@@ -1652,6 +1652,7 @@ int __init br_netlink_init(void)
+ 	int err;
+ 
+ 	br_mdb_init();
++	br_vlan_rtnl_init();
+ 	rtnl_af_register(&br_af_ops);
+ 
+ 	err = rtnl_link_register(&br_link_ops);
+@@ -1669,6 +1670,7 @@ int __init br_netlink_init(void)
+ void br_netlink_fini(void)
+ {
+ 	br_mdb_uninit();
++	br_vlan_rtnl_uninit();
+ 	rtnl_af_unregister(&br_af_ops);
+ 	rtnl_link_unregister(&br_link_ops);
+ }
+diff --git a/net/bridge/br_netlink_tunnel.c b/net/bridge/br_netlink_tunnel.c
+index afee292..3bbbd66 100644
+--- a/net/bridge/br_netlink_tunnel.c
++++ b/net/bridge/br_netlink_tunnel.c
+@@ -26,7 +26,7 @@ static size_t __get_vlan_tinfo_size(void)
+ 		  nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */
+ }
+ 
+-static bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
++bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
+ 			       struct net_bridge_vlan *v_last)
+ {
+ 	__be32 tunid_curr = tunnel_id_to_key32(v_curr->tinfo.tunnel_id);
+@@ -193,7 +193,7 @@ static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX +
+ 	[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 },
+ };
+ 
+-static int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
++int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
+ 			       u16 vid, u32 tun_id, bool *changed)
+ {
+ 	int err = 0;
+diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
+index 4bd9e9b..4620f70 100644
+--- a/net/bridge/br_private.h
++++ b/net/bridge/br_private.h
+@@ -95,6 +95,60 @@ struct br_vlan_stats {
+ 	struct u64_stats_sync syncp;
+ };
+ 
++/* net_bridge_mcast_port must be always defined due to forwarding stubs */
++struct net_bridge_mcast_port {
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	struct net_bridge_port		*port;
++	struct net_bridge_vlan		*vlan;
++
++	struct bridge_mcast_own_query	ip4_own_query;
++	struct timer_list		ip4_mc_router_timer;
++	struct hlist_node		ip4_rlist;
++#if IS_ENABLED(CONFIG_IPV6)
++	struct bridge_mcast_own_query	ip6_own_query;
++	struct timer_list		ip6_mc_router_timer;
++	struct hlist_node		ip6_rlist;
++#endif /* IS_ENABLED(CONFIG_IPV6) */
++	unsigned char			multicast_router;
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
++};
++
++/* net_bridge_mcast must be always defined due to forwarding stubs */
++struct net_bridge_mcast {
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	struct net_bridge		*br;
++	struct net_bridge_vlan		*vlan;
++
++	u32				multicast_last_member_count;
++	u32				multicast_startup_query_count;
++
++	u8				multicast_querier;
++	u8				multicast_igmp_version;
++	u8				multicast_router;
++#if IS_ENABLED(CONFIG_IPV6)
++	u8				multicast_mld_version;
++#endif
++	unsigned long			multicast_last_member_interval;
++	unsigned long			multicast_membership_interval;
++	unsigned long			multicast_querier_interval;
++	unsigned long			multicast_query_interval;
++	unsigned long			multicast_query_response_interval;
++	unsigned long			multicast_startup_query_interval;
++	struct hlist_head		ip4_mc_router_list;
++	struct timer_list		ip4_mc_router_timer;
++	struct bridge_mcast_other_query	ip4_other_query;
++	struct bridge_mcast_own_query	ip4_own_query;
++	struct bridge_mcast_querier	ip4_querier;
++#if IS_ENABLED(CONFIG_IPV6)
++	struct hlist_head		ip6_mc_router_list;
++	struct timer_list		ip6_mc_router_timer;
++	struct bridge_mcast_other_query	ip6_other_query;
++	struct bridge_mcast_own_query	ip6_own_query;
++	struct bridge_mcast_querier	ip6_querier;
++#endif /* IS_ENABLED(CONFIG_IPV6) */
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
++};
++
+ struct br_tunnel_info {
+ 	__be64				tunnel_id;
+ 	struct metadata_dst __rcu	*tunnel_dst;
+@@ -104,6 +158,8 @@ struct br_tunnel_info {
+ enum {
+ 	BR_VLFLAG_PER_PORT_STATS = BIT(0),
+ 	BR_VLFLAG_ADDED_BY_SWITCHDEV = BIT(1),
++	BR_VLFLAG_MCAST_ENABLED = BIT(2),
++	BR_VLFLAG_GLOBAL_MCAST_ENABLED = BIT(3),
+ };
+ 
+ /**
+@@ -113,12 +169,16 @@ enum {
+  * @vid: VLAN id
+  * @flags: bridge vlan flags
+  * @priv_flags: private (in-kernel) bridge vlan flags
++ * @state: STP state (e.g. blocking, learning, forwarding)
+  * @stats: per-cpu VLAN statistics
+  * @br: if MASTER flag set, this points to a bridge struct
+  * @port: if MASTER flag unset, this points to a port struct
+  * @refcnt: if MASTER flag set, this is bumped for each port referencing it
+  * @brvlan: if MASTER flag unset, this points to the global per-VLAN context
+  *          for this VLAN entry
++ * @br_mcast_ctx: if MASTER flag set, this is the global vlan multicast context
++ * @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast
++ *                  context
+  * @vlist: sorted list of VLAN entries
+  * @rcu: used for entry destruction
+  *
+@@ -133,7 +193,8 @@ struct net_bridge_vlan {
+ 	u16				vid;
+ 	u16				flags;
+ 	u16				priv_flags;
+-	struct br_vlan_stats __percpu	*stats;
++	u8				state;
++	struct pcpu_sw_netstats __percpu *stats;
+ 	union {
+ 		struct net_bridge	*br;
+ 		struct net_bridge_port	*port;
+@@ -145,6 +206,11 @@ struct net_bridge_vlan {
+ 
+ 	struct br_tunnel_info		tinfo;
+ 
++	union {
++		struct net_bridge_mcast		br_mcast_ctx;
++		struct net_bridge_mcast_port	port_mcast_ctx;
++	};
++
+ 	struct list_head		vlist;
+ 
+ 	struct rcu_head			rcu;
+@@ -170,6 +236,7 @@ struct net_bridge_vlan_group {
+ 	struct list_head		vlan_list;
+ 	u16				num_vlans;
+ 	u16				pvid;
++	u8				pvid_state;
+ };
+ 
+ struct net_bridge_fdb_key {
+@@ -497,6 +564,67 @@ static inline bool br_vlan_should_use(const struct net_bridge_vlan *v)
+ 	return true;
+ }
+ 
++static inline bool br_vlan_valid_id(u16 vid, struct netlink_ext_ack *extack)
++{
++	bool ret = vid > 0 && vid < VLAN_VID_MASK;
++
++	if (!ret)
++		NL_SET_ERR_MSG_MOD(extack, "Vlan id is invalid");
++
++	return ret;
++}
++
++static inline bool br_vlan_valid_range(const struct bridge_vlan_info *cur,
++				       const struct bridge_vlan_info *last,
++				       struct netlink_ext_ack *extack)
++{
++	/* pvid flag is not allowed in ranges */
++	if (cur->flags & BRIDGE_VLAN_INFO_PVID) {
++		NL_SET_ERR_MSG_MOD(extack, "Pvid isn't allowed in a range");
++		return false;
++	}
++
++	/* when cur is the range end, check if:
++	 *  - it has range start flag
++	 *  - range ids are invalid (end is equal to or before start)
++	 */
++	if (last) {
++		if (cur->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
++			NL_SET_ERR_MSG_MOD(extack, "Found a new vlan range start while processing one");
++			return false;
++		} else if (!(cur->flags & BRIDGE_VLAN_INFO_RANGE_END)) {
++			NL_SET_ERR_MSG_MOD(extack, "Vlan range end flag is missing");
++			return false;
++		} else if (cur->vid <= last->vid) {
++			NL_SET_ERR_MSG_MOD(extack, "End vlan id is less than or equal to start vlan id");
++			return false;
++		}
++	}
++
++	/* check for required range flags */
++	if (!(cur->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
++			    BRIDGE_VLAN_INFO_RANGE_END))) {
++		NL_SET_ERR_MSG_MOD(extack, "Both vlan range flags are missing");
++		return false;
++	}
++
++	return true;
++}
++
++static inline u8 br_vlan_multicast_router(const struct net_bridge_vlan *v)
++{
++	u8 mcast_router = MDB_RTR_TYPE_DISABLED;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	if (!br_vlan_is_master(v))
++		mcast_router = v->port_mcast_ctx.multicast_router;
++	else
++		mcast_router = v->br_mcast_ctx.multicast_router;
++#endif
++
++	return mcast_router;
++}
++
+ static inline int br_opt_get(const struct net_bridge *br,
+ 			     enum net_bridge_opts opt)
+ {
+@@ -676,8 +804,10 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
+ 			struct sk_buff *skb, bool local_rcv, bool local_orig);
+ int br_multicast_set_router(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
++int br_multicast_set_vlan_router(struct net_bridge_vlan *v, u8 mcast_router);
+ int br_multicast_toggle(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
++
+ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
+ #if IS_ENABLED(CONFIG_IPV6)
+@@ -708,6 +838,17 @@ void br_mdb_init(void);
+ void br_mdb_uninit(void);
+ void br_multicast_host_join(struct net_bridge_mdb_entry *mp, bool notify);
+ void br_multicast_host_leave(struct net_bridge_mdb_entry *mp, bool notify);
++int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
++			       struct net_device *dev);
++int br_multicast_dump_querier_state(struct sk_buff *skb,
++				    const struct net_bridge_mcast *brmctx,
++				    int nest_attr);
++size_t br_multicast_querier_state_size(void);
++size_t br_rports_size(const struct net_bridge_mcast *brmctx);
++void br_multicast_set_query_intvl(struct net_bridge_mcast *brmctx,
++				  unsigned long val);
++void br_multicast_set_startup_query_intvl(struct net_bridge_mcast *brmctx,
++					  unsigned long val);
+ 
+ #define mlock_dereference(X, br) \
+ 	rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock))
+@@ -760,6 +901,49 @@ static inline int br_multicast_igmp_type(const struct sk_buff *skb)
+ {
+ 	return BR_INPUT_SKB_CB(skb)->igmp;
+ }
++static inline bool
++br_rports_have_mc_router(const struct net_bridge_mcast *brmctx)
++{
++#if IS_ENABLED(CONFIG_IPV6)
++	return !hlist_empty(&brmctx->ip4_mc_router_list) ||
++	       !hlist_empty(&brmctx->ip6_mc_router_list);
++#else
++	return !hlist_empty(&brmctx->ip4_mc_router_list);
++#endif
++}
++
++static inline bool
++br_multicast_ctx_options_equal(const struct net_bridge_mcast *brmctx1,
++			       const struct net_bridge_mcast *brmctx2)
++{
++	return brmctx1->multicast_igmp_version ==
++	       brmctx2->multicast_igmp_version &&
++	       brmctx1->multicast_last_member_count ==
++	       brmctx2->multicast_last_member_count &&
++	       brmctx1->multicast_startup_query_count ==
++	       brmctx2->multicast_startup_query_count &&
++	       brmctx1->multicast_last_member_interval ==
++	       brmctx2->multicast_last_member_interval &&
++	       brmctx1->multicast_membership_interval ==
++	       brmctx2->multicast_membership_interval &&
++	       brmctx1->multicast_querier_interval ==
++	       brmctx2->multicast_querier_interval &&
++	       brmctx1->multicast_query_interval ==
++	       brmctx2->multicast_query_interval &&
++	       brmctx1->multicast_query_response_interval ==
++	       brmctx2->multicast_query_response_interval &&
++	       brmctx1->multicast_startup_query_interval ==
++	       brmctx2->multicast_startup_query_interval &&
++	       brmctx1->multicast_querier == brmctx2->multicast_querier &&
++	       brmctx1->multicast_router == brmctx2->multicast_router &&
++	       !br_rports_have_mc_router(brmctx1) &&
++	       !br_rports_have_mc_router(brmctx2) &&
++#if IS_ENABLED(CONFIG_IPV6)
++	       brmctx1->multicast_mld_version ==
++	       brmctx2->multicast_mld_version &&
++#endif
++	       true;
++}
+ #else
+ static inline int br_multicast_rcv(struct net_bridge *br,
+ 				   struct net_bridge_port *port,
+@@ -907,10 +1091,21 @@ void nbp_vlan_flush(struct net_bridge_port *port);
+ int nbp_vlan_init(struct net_bridge_port *port, struct netlink_ext_ack *extack);
+ int nbp_get_num_vlan_infos(struct net_bridge_port *p, u32 filter_mask);
+ void br_vlan_get_stats(const struct net_bridge_vlan *v,
+-		       struct br_vlan_stats *stats);
++		       struct pcpu_sw_netstats *stats);
+ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event);
+ int br_vlan_bridge_event(struct net_device *dev, unsigned long event,
+ 			 void *ptr);
++void br_vlan_rtnl_init(void);
++void br_vlan_rtnl_uninit(void);
++void br_vlan_notify(const struct net_bridge *br,
++		    const struct net_bridge_port *p,
++		    u16 vid, u16 vid_range,
++		    int cmd);
++int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
++		   const void *ctx, bool adding, struct notifier_block *nb,
++		   struct netlink_ext_ack *extack);
++bool br_vlan_can_enter_range(struct net_bridge_vlan *v_curr,
++			     struct net_bridge_vlan *range_end);
+ 
+ void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+ 				    struct net_device_path_ctx *ctx,
+@@ -969,6 +1164,10 @@ static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)
+ 	return vg->pvid;
+ }
+ 
++static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
++{
++	return v->vid == pvid ? v->flags | BRIDGE_VLAN_INFO_PVID : v->flags;
++}
+ #else
+ static inline bool br_allowed_ingress(const struct net_bridge *br,
+ 				      struct net_bridge_vlan_group *vg,
+@@ -1111,7 +1310,7 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu(
+ }
+ 
+ static inline void br_vlan_get_stats(const struct net_bridge_vlan *v,
+-				     struct br_vlan_stats *stats)
++				     struct pcpu_sw_netstats *stats)
+ {
+ }
+ 
+@@ -1125,6 +1324,88 @@ static inline int br_vlan_bridge_event(struct net_device *dev,
+ {
+ 	return 0;
+ }
++
++static inline void br_vlan_rtnl_init(void)
++{
++}
++
++static inline void br_vlan_rtnl_uninit(void)
++{
++}
++
++static inline void br_vlan_notify(const struct net_bridge *br,
++				  const struct net_bridge_port *p,
++				  u16 vid, u16 vid_range,
++				  int cmd)
++{
++}
++
++static inline bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
++					   const struct net_bridge_vlan *range_end)
++{
++	return true;
++}
++
++static inline int br_vlan_replay(struct net_device *br_dev,
++				 struct net_device *dev, const void *ctx,
++				 bool adding, struct notifier_block *nb,
++				 struct netlink_ext_ack *extack)
++{
++	return -EOPNOTSUPP;
++}
++#endif
++
++/* br_vlan_options.c */
++#ifdef CONFIG_BRIDGE_VLAN_FILTERING
++bool br_vlan_opts_eq_range(struct net_bridge_vlan *v_curr,
++			   struct net_bridge_vlan *range_end);
++bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v);
++size_t br_vlan_opts_nl_size(void);
++int br_vlan_process_options(const struct net_bridge *br,
++			    struct net_bridge_port *p,
++			    struct net_bridge_vlan *range_start,
++			    struct net_bridge_vlan *range_end,
++			    struct nlattr **tb,
++			    struct netlink_ext_ack *extack);
++bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
++					 const struct net_bridge_vlan *r_end);
++bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
++			      const struct net_bridge_vlan *v_opts);
++
++/* vlan state manipulation helpers using *_ONCE to annotate lock-free access */
++static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
++{
++	return READ_ONCE(v->state);
++}
++
++static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
++{
++	WRITE_ONCE(v->state, state);
++}
++
++static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
++{
++	return READ_ONCE(vg->pvid_state);
++}
++
++static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,
++					  u8 state)
++{
++	WRITE_ONCE(vg->pvid_state, state);
++}
++
++/* learn_allow is true at ingress and false at egress */
++static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
++{
++	switch (state) {
++	case BR_STATE_LEARNING:
++		return learn_allow;
++	case BR_STATE_FORWARDING:
++		return true;
++	default:
++		return false;
++	}
++}
+ #endif
+ 
+ struct nf_br_ops {
+@@ -1196,6 +1477,12 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
+ int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);
+ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq, struct net_device *dev,
+ 	       u32 filter_mask, int nlflags);
++int br_process_vlan_info(struct net_bridge *br,
++			 struct net_bridge_port *p, int cmd,
++			 struct bridge_vlan_info *vinfo_curr,
++			 struct bridge_vlan_info **vinfo_last,
++			 bool *changed,
++			 struct netlink_ext_ack *extack);
+ 
+ #ifdef CONFIG_SYSFS
+ /* br_sysfs_if.c */
+diff --git a/net/bridge/br_private_tunnel.h b/net/bridge/br_private_tunnel.h
+index 2bdef2e..25be963 100644
+--- a/net/bridge/br_private_tunnel.h
++++ b/net/bridge/br_private_tunnel.h
+@@ -42,6 +42,10 @@ int br_handle_ingress_vlan_tunnel(struct sk_buff *skb,
+ 				  struct net_bridge_vlan_group *vg);
+ int br_handle_egress_vlan_tunnel(struct sk_buff *skb,
+ 				 struct net_bridge_vlan *vlan);
++bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
++			struct net_bridge_vlan *v_last);
++int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
++			u16 vid, u32 tun_id, bool *changed);
+ #else
+ static inline int vlan_tunnel_init(struct net_bridge_vlan_group *vg)
+ {
+diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
+index bcfd169..2b5950c 100644
+--- a/net/bridge/br_vlan.c
++++ b/net/bridge/br_vlan.c
+@@ -34,13 +34,15 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
+ 	return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
+ }
+ 
+-static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid)
++static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg,
++			    const struct net_bridge_vlan *v)
+ {
+-	if (vg->pvid == vid)
++	if (vg->pvid == v->vid)
+ 		return false;
+ 
+ 	smp_wmb();
+-	vg->pvid = vid;
++	br_vlan_set_pvid_state(vg, v->state);
++	vg->pvid = v->vid;
+ 
+ 	return true;
+ }
+@@ -69,7 +71,7 @@ static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
+ 		vg = nbp_vlan_group(v->port);
+ 
+ 	if (flags & BRIDGE_VLAN_INFO_PVID)
+-		ret = __vlan_add_pvid(vg, v->vid);
++		ret = __vlan_add_pvid(vg, v);
+ 	else
+ 		ret = __vlan_delete_pvid(vg, v->vid);
+ 
+@@ -257,6 +259,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
+ 					  &changed, extack);
+ 			if (err)
+ 				goto out_filt;
++
++			if (changed)
++				br_vlan_notify(br, NULL, v->vid, 0,
++					       RTM_NEWVLAN);
+ 		}
+ 
+ 		masterv = br_vlan_get_master(br, v->vid, extack);
+@@ -266,7 +272,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
+ 		}
+ 		v->brvlan = masterv;
+ 		if (br_opt_get(br, BROPT_VLAN_STATS_PER_PORT)) {
+-			v->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
++			v->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ 			if (!v->stats) {
+ 				err = -ENOMEM;
+ 				goto out_filt;
+@@ -382,13 +388,31 @@ static void __vlan_group_free(struct net_bridge_vlan_group *vg)
+ 	kfree(vg);
+ }
+ 
+-static void __vlan_flush(struct net_bridge_vlan_group *vg)
++static void __vlan_flush(const struct net_bridge *br,
++			 const struct net_bridge_port *p,
++			 struct net_bridge_vlan_group *vg)
+ {
+ 	struct net_bridge_vlan *vlan, *tmp;
++	u16 v_start = 0, v_end = 0;
+ 
+ 	__vlan_delete_pvid(vg, vg->pvid);
+-	list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist)
++	list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) {
++		/* take care of disjoint ranges */
++		if (!v_start) {
++			v_start = vlan->vid;
++		} else if (vlan->vid - v_end != 1) {
++			/* found range end, notify and start next one */
++			br_vlan_notify(br, p, v_start, v_end, RTM_DELVLAN);
++			v_start = vlan->vid;
++		}
++		v_end = vlan->vid;
++
+ 		__vlan_del(vlan);
++	}
++
++	/* notify about the last/whole vlan range */
++	if (v_start)
++		br_vlan_notify(br, p, v_start, v_end, RTM_DELVLAN);
+ }
+ 
+ struct sk_buff *br_handle_vlan(struct net_bridge *br,
+@@ -396,7 +420,7 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
+ 			       struct net_bridge_vlan_group *vg,
+ 			       struct sk_buff *skb)
+ {
+-	struct br_vlan_stats *stats;
++	struct pcpu_sw_netstats *stats;
+ 	struct net_bridge_vlan *v;
+ 	u16 vid;
+ 
+@@ -448,7 +472,7 @@ static bool __allowed_ingress(const struct net_bridge *br,
+ 			      struct net_bridge_vlan_group *vg,
+ 			      struct sk_buff *skb, u16 *vid)
+ {
+-	struct br_vlan_stats *stats;
++	struct pcpu_sw_netstats *stats;
+ 	struct net_bridge_vlan *v;
+ 	bool tagged;
+ 
+@@ -666,7 +690,7 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,
+ 	if (!vlan)
+ 		return -ENOMEM;
+ 
+-	vlan->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
++	vlan->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ 	if (!vlan->stats) {
+ 		kfree(vlan);
+ 		return -ENOMEM;
+@@ -718,7 +742,7 @@ void br_vlan_flush(struct net_bridge *br)
+ 	ASSERT_RTNL();
+ 
+ 	vg = br_vlan_group(br);
+-	__vlan_flush(vg);
++	__vlan_flush(br, NULL, vg);
+ 	RCU_INIT_POINTER(br->vlgrp, NULL);
+ 	synchronize_rcu();
+ 	__vlan_group_free(vg);
+@@ -927,12 +951,15 @@ static void br_vlan_disable_default_pvid(struct net_bridge *br)
+ 	/* Disable default_pvid on all ports where it is still
+ 	 * configured.
+ 	 */
+-	if (vlan_default_pvid(br_vlan_group(br), pvid))
+-		br_vlan_delete(br, pvid);
++	if (vlan_default_pvid(br_vlan_group(br), pvid)) {
++		if (!br_vlan_delete(br, pvid))
++			br_vlan_notify(br, NULL, pvid, 0, RTM_DELVLAN);
++	}
+ 
+ 	list_for_each_entry(p, &br->port_list, list) {
+-		if (vlan_default_pvid(nbp_vlan_group(p), pvid))
+-			nbp_vlan_delete(p, pvid);
++		if (vlan_default_pvid(nbp_vlan_group(p), pvid) &&
++		    !nbp_vlan_delete(p, pvid))
++			br_vlan_notify(br, p, pvid, 0, RTM_DELVLAN);
+ 	}
+ 
+ 	br->default_pvid = 0;
+@@ -974,7 +1001,10 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ 				  &vlchange, extack);
+ 		if (err)
+ 			goto out;
+-		br_vlan_delete(br, old_pvid);
++
++		if (br_vlan_delete(br, old_pvid))
++			br_vlan_notify(br, NULL, old_pvid, 0, RTM_DELVLAN);
++		br_vlan_notify(br, NULL, pvid, 0, RTM_NEWVLAN);
+ 		set_bit(0, changed);
+ 	}
+ 
+@@ -994,7 +1024,9 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ 				   &vlchange, extack);
+ 		if (err)
+ 			goto err_port;
+-		nbp_vlan_delete(p, old_pvid);
++		if (nbp_vlan_delete(p, old_pvid))
++			br_vlan_notify(br, p, old_pvid, 0, RTM_DELVLAN);
++		br_vlan_notify(p->br, p, pvid, 0, RTM_NEWVLAN);
+ 		set_bit(p->port_no, changed);
+ 	}
+ 
+@@ -1009,22 +1041,28 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ 		if (!test_bit(p->port_no, changed))
+ 			continue;
+ 
+-		if (old_pvid)
++		if (old_pvid) {
+ 			nbp_vlan_add(p, old_pvid,
+ 				     BRIDGE_VLAN_INFO_PVID |
+ 				     BRIDGE_VLAN_INFO_UNTAGGED,
+ 				     &vlchange, NULL);
++			br_vlan_notify(p->br, p, old_pvid, 0, RTM_NEWVLAN);
++		}
+ 		nbp_vlan_delete(p, pvid);
++		br_vlan_notify(br, p, pvid, 0, RTM_DELVLAN);
+ 	}
+ 
+ 	if (test_bit(0, changed)) {
+-		if (old_pvid)
++		if (old_pvid) {
+ 			br_vlan_add(br, old_pvid,
+ 				    BRIDGE_VLAN_INFO_PVID |
+ 				    BRIDGE_VLAN_INFO_UNTAGGED |
+ 				    BRIDGE_VLAN_INFO_BRENTRY,
+ 				    &vlchange, NULL);
++			br_vlan_notify(br, NULL, old_pvid, 0, RTM_NEWVLAN);
++		}
+ 		br_vlan_delete(br, pvid);
++		br_vlan_notify(br, NULL, pvid, 0, RTM_DELVLAN);
+ 	}
+ 	goto out;
+ }
+@@ -1117,6 +1155,7 @@ int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)
+ 				   &changed, extack);
+ 		if (ret)
+ 			goto err_vlan_add;
++		br_vlan_notify(p->br, p, p->br->default_pvid, 0, RTM_NEWVLAN);
+ 	}
+ out:
+ 	return ret;
+@@ -1198,21 +1237,21 @@ void nbp_vlan_flush(struct net_bridge_port *port)
+ 	ASSERT_RTNL();
+ 
+ 	vg = nbp_vlan_group(port);
+-	__vlan_flush(vg);
++	__vlan_flush(port->br, port, vg);
+ 	RCU_INIT_POINTER(port->vlgrp, NULL);
+ 	synchronize_rcu();
+ 	__vlan_group_free(vg);
+ }
+ 
+ void br_vlan_get_stats(const struct net_bridge_vlan *v,
+-		       struct br_vlan_stats *stats)
++		       struct pcpu_sw_netstats *stats)
+ {
+ 	int i;
+ 
+ 	memset(stats, 0, sizeof(*stats));
+ 	for_each_possible_cpu(i) {
+ 		u64 rxpackets, rxbytes, txpackets, txbytes;
+-		struct br_vlan_stats *cpu_stats;
++		struct pcpu_sw_netstats *cpu_stats;
+ 		unsigned int start;
+ 
+ 		cpu_stats = per_cpu_ptr(v->stats, i);
+@@ -1526,8 +1565,8 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ {
+ 	struct netdev_notifier_changeupper_info *info;
+ 	struct net_bridge *br = netdev_priv(dev);
+-	bool changed;
+-	int ret = 0;
++	int vlcmd = 0, ret = 0;
++	bool changed = false;
+ 
+ 	switch (event) {
+ 	case NETDEV_REGISTER:
+@@ -1535,9 +1574,11 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ 				  BRIDGE_VLAN_INFO_PVID |
+ 				  BRIDGE_VLAN_INFO_UNTAGGED |
+ 				  BRIDGE_VLAN_INFO_BRENTRY, &changed, NULL);
++		vlcmd = RTM_NEWVLAN;
+ 		break;
+ 	case NETDEV_UNREGISTER:
+-		br_vlan_delete(br, br->default_pvid);
++		changed = !br_vlan_delete(br, br->default_pvid);
++		vlcmd = RTM_DELVLAN;
+ 		break;
+ 	case NETDEV_CHANGEUPPER:
+ 		info = ptr;
+@@ -1551,6 +1592,8 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ 		br_vlan_link_state_change(dev, br);
+ 		break;
+ 	}
++	if (changed)
++		br_vlan_notify(br, NULL, br->default_pvid, 0, vlcmd);
+ 
+ 	return ret;
+ }
+@@ -1569,3 +1612,608 @@ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
+ 		break;
+ 	}
+ }
++
++static bool br_vlan_stats_fill(struct sk_buff *skb,
++			       const struct net_bridge_vlan *v)
++{
++	struct pcpu_sw_netstats stats;
++	struct nlattr *nest;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_STATS);
++	if (!nest)
++		return false;
++
++	br_vlan_get_stats(v, &stats);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_RX_BYTES, stats.rx_bytes,
++			      BRIDGE_VLANDB_STATS_PAD) ||
++	    nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_RX_PACKETS,
++			      stats.rx_packets, BRIDGE_VLANDB_STATS_PAD) ||
++	    nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_TX_BYTES, stats.tx_bytes,
++			      BRIDGE_VLANDB_STATS_PAD) ||
++	    nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_TX_PACKETS,
++			      stats.tx_packets, BRIDGE_VLANDB_STATS_PAD))
++		goto out_err;
++
++	nla_nest_end(skb, nest);
++
++	return true;
++
++out_err:
++	nla_nest_cancel(skb, nest);
++	return false;
++}
++
++/* v_opts is used to dump the options which must be equal in the whole range */
++static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 vid_range,
++			      const struct net_bridge_vlan *v_opts,
++			      u16 flags,
++			      bool dump_stats)
++{
++	struct bridge_vlan_info info;
++	struct nlattr *nest;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY);
++	if (!nest)
++		return false;
++
++	memset(&info, 0, sizeof(info));
++	info.vid = vid;
++	if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
++		info.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
++	if (flags & BRIDGE_VLAN_INFO_PVID)
++		info.flags |= BRIDGE_VLAN_INFO_PVID;
++
++	if (nla_put(skb, BRIDGE_VLANDB_ENTRY_INFO, sizeof(info), &info))
++		goto out_err;
++
++	if (vid_range && vid < vid_range &&
++	    !(flags & BRIDGE_VLAN_INFO_PVID) &&
++	    nla_put_u16(skb, BRIDGE_VLANDB_ENTRY_RANGE, vid_range))
++		goto out_err;
++
++	if (v_opts) {
++		if (!br_vlan_opts_fill(skb, v_opts))
++			goto out_err;
++
++		if (dump_stats && !br_vlan_stats_fill(skb, v_opts))
++			goto out_err;
++	}
++
++	nla_nest_end(skb, nest);
++
++	return true;
++
++out_err:
++	nla_nest_cancel(skb, nest);
++	return false;
++}
++
++static size_t rtnl_vlan_nlmsg_size(void)
++{
++	return NLMSG_ALIGN(sizeof(struct br_vlan_msg))
++		+ nla_total_size(0) /* BRIDGE_VLANDB_ENTRY */
++		+ nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_ENTRY_RANGE */
++		+ nla_total_size(sizeof(struct bridge_vlan_info)) /* BRIDGE_VLANDB_ENTRY_INFO */
++		+ br_vlan_opts_nl_size(); /* bridge vlan options */
++}
++
++void br_vlan_notify(const struct net_bridge *br,
++		    const struct net_bridge_port *p,
++		    u16 vid, u16 vid_range,
++		    int cmd)
++{
++	struct net_bridge_vlan_group *vg;
++	struct net_bridge_vlan *v = NULL;
++	struct br_vlan_msg *bvm;
++	struct nlmsghdr *nlh;
++	struct sk_buff *skb;
++	int err = -ENOBUFS;
++	struct net *net;
++	u16 flags = 0;
++	int ifindex;
++
++	/* right now notifications are done only with rtnl held */
++	ASSERT_RTNL();
++
++	if (p) {
++		ifindex = p->dev->ifindex;
++		vg = nbp_vlan_group(p);
++		net = dev_net(p->dev);
++	} else {
++		ifindex = br->dev->ifindex;
++		vg = br_vlan_group(br);
++		net = dev_net(br->dev);
++	}
++
++	skb = nlmsg_new(rtnl_vlan_nlmsg_size(), GFP_KERNEL);
++	if (!skb)
++		goto out_err;
++
++	err = -EMSGSIZE;
++	nlh = nlmsg_put(skb, 0, 0, cmd, sizeof(*bvm), 0);
++	if (!nlh)
++		goto out_err;
++	bvm = nlmsg_data(nlh);
++	memset(bvm, 0, sizeof(*bvm));
++	bvm->family = AF_BRIDGE;
++	bvm->ifindex = ifindex;
++
++	switch (cmd) {
++	case RTM_NEWVLAN:
++		/* need to find the vlan due to flags/options */
++		v = br_vlan_find(vg, vid);
++		if (!v || !br_vlan_should_use(v))
++			goto out_kfree;
++
++		flags = v->flags;
++		if (br_get_pvid(vg) == v->vid)
++			flags |= BRIDGE_VLAN_INFO_PVID;
++		break;
++	case RTM_DELVLAN:
++		break;
++	default:
++		goto out_kfree;
++	}
++
++	if (!br_vlan_fill_vids(skb, vid, vid_range, v, flags, false))
++		goto out_err;
++
++	nlmsg_end(skb, nlh);
++	rtnl_notify(skb, net, 0, RTNLGRP_BRVLAN, NULL, GFP_KERNEL);
++	return;
++
++out_err:
++	rtnl_set_sk_err(net, RTNLGRP_BRVLAN, err);
++out_kfree:
++	kfree_skb(skb);
++}
++
++static int br_vlan_replay_one(struct notifier_block *nb,
++			      struct net_device *dev,
++			      struct switchdev_obj_port_vlan *vlan,
++			      const void *ctx, unsigned long action,
++			      struct netlink_ext_ack *extack)
++{
++	struct switchdev_notifier_port_obj_info obj_info = {
++		.info = {
++			.dev = dev,
++			.extack = extack,
++			.ctx = ctx,
++		},
++		.obj = &vlan->obj,
++	};
++	int err;
++
++	err = nb->notifier_call(nb, action, &obj_info);
++	return notifier_to_errno(err);
++}
++
++int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
++		   const void *ctx, bool adding, struct notifier_block *nb,
++		   struct netlink_ext_ack *extack)
++{
++	struct net_bridge_vlan_group *vg;
++	struct net_bridge_vlan *v;
++	struct net_bridge_port *p;
++	struct net_bridge *br;
++	unsigned long action;
++	int err = 0;
++	u16 pvid;
++
++	ASSERT_RTNL();
++
++	if (!nb)
++		return 0;
++
++	if (!netif_is_bridge_master(br_dev))
++		return -EINVAL;
++
++	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
++		return -EINVAL;
++
++	if (netif_is_bridge_master(dev)) {
++		br = netdev_priv(dev);
++		vg = br_vlan_group(br);
++		p = NULL;
++	} else {
++		p = br_port_get_rtnl(dev);
++		if (WARN_ON(!p))
++			return -EINVAL;
++		vg = nbp_vlan_group(p);
++		br = p->br;
++	}
++
++	if (!vg)
++		return 0;
++
++	if (adding)
++		action = SWITCHDEV_PORT_OBJ_ADD;
++	else
++		action = SWITCHDEV_PORT_OBJ_DEL;
++
++	pvid = br_get_pvid(vg);
++
++	list_for_each_entry(v, &vg->vlan_list, vlist) {
++		struct switchdev_obj_port_vlan vlan = {
++			.obj.orig_dev = dev,
++			.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
++			.flags = br_vlan_flags(v, pvid),
++			.vid = v->vid,
++		};
++
++		if (!br_vlan_should_use(v))
++			continue;
++
++		err = br_vlan_replay_one(nb, dev, &vlan, ctx, action, extack);
++		if (err)
++			return err;
++	}
++
++	return err;
++}
++
++/* check if v_curr can enter a range ending in range_end */
++bool br_vlan_can_enter_range(struct net_bridge_vlan *v_curr,
++			     struct net_bridge_vlan *range_end)
++{
++	return v_curr->vid - range_end->vid == 1 &&
++	       range_end->flags == v_curr->flags &&
++	       br_vlan_opts_eq_range(v_curr, range_end);
++}
++
++static int br_vlan_dump_dev(const struct net_device *dev,
++			    struct sk_buff *skb,
++			    struct netlink_callback *cb,
++			    u32 dump_flags)
++{
++	struct net_bridge_vlan *v, *range_start = NULL, *range_end = NULL;
++	bool dump_global = !!(dump_flags & BRIDGE_VLANDB_DUMPF_GLOBAL);
++	bool dump_stats = !!(dump_flags & BRIDGE_VLANDB_DUMPF_STATS);
++	struct net_bridge_vlan_group *vg;
++	int idx = 0, s_idx = cb->args[1];
++	struct nlmsghdr *nlh = NULL;
++	struct net_bridge_port *p;
++	struct br_vlan_msg *bvm;
++	struct net_bridge *br;
++	int err = 0;
++	u16 pvid;
++
++	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
++		return -EINVAL;
++
++	if (netif_is_bridge_master(dev)) {
++		br = netdev_priv(dev);
++		vg = br_vlan_group_rcu(br);
++		p = NULL;
++	} else {
++		/* global options are dumped only for bridge devices */
++		if (dump_global)
++			return 0;
++
++		p = br_port_get_rcu(dev);
++		if (WARN_ON(!p))
++			return -EINVAL;
++		vg = nbp_vlan_group_rcu(p);
++		br = p->br;
++	}
++
++	if (!vg)
++		return 0;
++
++	nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
++			RTM_NEWVLAN, sizeof(*bvm), NLM_F_MULTI);
++	if (!nlh)
++		return -EMSGSIZE;
++	bvm = nlmsg_data(nlh);
++	memset(bvm, 0, sizeof(*bvm));
++	bvm->family = PF_BRIDGE;
++	bvm->ifindex = dev->ifindex;
++	pvid = br_get_pvid(vg);
++
++	/* idx must stay at range's beginning until it is filled in */
++	list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
++		if (!dump_global && !br_vlan_should_use(v))
++			continue;
++		if (idx < s_idx) {
++			idx++;
++			continue;
++		}
++
++		if (!range_start) {
++			range_start = v;
++			range_end = v;
++			continue;
++		}
++
++		if (dump_global) {
++			if (br_vlan_global_opts_can_enter_range(v, range_end))
++				goto update_end;
++			if (!br_vlan_global_opts_fill(skb, range_start->vid,
++						      range_end->vid,
++						      range_start)) {
++				err = -EMSGSIZE;
++				break;
++			}
++			/* advance number of filled vlans */
++			idx += range_end->vid - range_start->vid + 1;
++
++			range_start = v;
++		} else if (dump_stats || v->vid == pvid ||
++			   !br_vlan_can_enter_range(v, range_end)) {
++			u16 vlan_flags = br_vlan_flags(range_start, pvid);
++
++			if (!br_vlan_fill_vids(skb, range_start->vid,
++					       range_end->vid, range_start,
++					       vlan_flags, dump_stats)) {
++				err = -EMSGSIZE;
++				break;
++			}
++			/* advance number of filled vlans */
++			idx += range_end->vid - range_start->vid + 1;
++
++			range_start = v;
++		}
++update_end:
++		range_end = v;
++	}
++
++	/* err will be 0 and range_start will be set in 3 cases here:
++	 * - first vlan (range_start == range_end)
++	 * - last vlan (range_start == range_end, not in range)
++	 * - last vlan range (range_start != range_end, in range)
++	 */
++	if (!err && range_start) {
++		if (dump_global &&
++		    !br_vlan_global_opts_fill(skb, range_start->vid,
++					      range_end->vid, range_start))
++			err = -EMSGSIZE;
++		else if (!dump_global &&
++			 !br_vlan_fill_vids(skb, range_start->vid,
++					    range_end->vid, range_start,
++					    br_vlan_flags(range_start, pvid),
++					    dump_stats))
++			err = -EMSGSIZE;
++	}
++
++	cb->args[1] = err ? idx : 0;
++
++	nlmsg_end(skb, nlh);
++
++	return err;
++}
++
++static const struct nla_policy br_vlan_db_dump_pol[BRIDGE_VLANDB_DUMP_MAX + 1] = {
++	[BRIDGE_VLANDB_DUMP_FLAGS] = { .type = NLA_U32 },
++};
++
++static int br_vlan_rtm_dump(struct sk_buff *skb, struct netlink_callback *cb)
++{
++	struct nlattr *dtb[BRIDGE_VLANDB_DUMP_MAX + 1];
++	int idx = 0, err = 0, s_idx = cb->args[0];
++	struct net *net = sock_net(skb->sk);
++	struct br_vlan_msg *bvm;
++	struct net_device *dev;
++	u32 dump_flags = 0;
++
++	err = nlmsg_parse(cb->nlh, sizeof(*bvm), dtb, BRIDGE_VLANDB_DUMP_MAX,
++			  br_vlan_db_dump_pol, cb->extack);
++	if (err < 0)
++		return err;
++
++	bvm = nlmsg_data(cb->nlh);
++	if (dtb[BRIDGE_VLANDB_DUMP_FLAGS])
++		dump_flags = nla_get_u32(dtb[BRIDGE_VLANDB_DUMP_FLAGS]);
++
++	rcu_read_lock();
++	if (bvm->ifindex) {
++		dev = dev_get_by_index_rcu(net, bvm->ifindex);
++		if (!dev) {
++			err = -ENODEV;
++			goto out_err;
++		}
++		err = br_vlan_dump_dev(dev, skb, cb, dump_flags);
++		/* if the dump completed without an error we return 0 here */
++		if (err != -EMSGSIZE)
++			goto out_err;
++	} else {
++		for_each_netdev_rcu(net, dev) {
++			if (idx < s_idx)
++				goto skip;
++
++			err = br_vlan_dump_dev(dev, skb, cb, dump_flags);
++			if (err == -EMSGSIZE)
++				break;
++skip:
++			idx++;
++		}
++	}
++	cb->args[0] = idx;
++	rcu_read_unlock();
++
++	return skb->len;
++
++out_err:
++	rcu_read_unlock();
++
++	return err;
++}
++
++static const struct nla_policy br_vlan_db_policy[BRIDGE_VLANDB_ENTRY_MAX + 1] = {
++	[BRIDGE_VLANDB_ENTRY_INFO]	=
++		NLA_POLICY_EXACT_LEN(sizeof(struct bridge_vlan_info)),
++	[BRIDGE_VLANDB_ENTRY_RANGE]	= { .type = NLA_U16 },
++	[BRIDGE_VLANDB_ENTRY_STATE]	= { .type = NLA_U8 },
++	[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO] = { .type = NLA_NESTED },
++	[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]	= { .type = NLA_U8 },
++};
++
++static int br_vlan_rtm_process_one(struct net_device *dev,
++				   const struct nlattr *attr,
++				   int cmd, struct netlink_ext_ack *extack)
++{
++	struct bridge_vlan_info *vinfo, vrange_end, *vinfo_last = NULL;
++	struct nlattr *tb[BRIDGE_VLANDB_ENTRY_MAX + 1];
++	bool changed = false, skip_processing = false;
++	struct net_bridge_vlan_group *vg;
++	struct net_bridge_port *p = NULL;
++	int err = 0, cmdmap = 0;
++	struct net_bridge *br;
++
++	if (netif_is_bridge_master(dev)) {
++		br = netdev_priv(dev);
++		vg = br_vlan_group(br);
++	} else {
++		p = br_port_get_rtnl(dev);
++		if (WARN_ON(!p))
++			return -ENODEV;
++		br = p->br;
++		vg = nbp_vlan_group(p);
++	}
++
++	if (WARN_ON(!vg))
++		return -ENODEV;
++
++	err = nla_parse_nested(tb, BRIDGE_VLANDB_ENTRY_MAX, attr,
++			       br_vlan_db_policy, extack);
++	if (err)
++		return err;
++
++	if (!tb[BRIDGE_VLANDB_ENTRY_INFO]) {
++		NL_SET_ERR_MSG_MOD(extack, "Missing vlan entry info");
++		return -EINVAL;
++	}
++	memset(&vrange_end, 0, sizeof(vrange_end));
++
++	vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
++	if (vinfo->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
++			    BRIDGE_VLAN_INFO_RANGE_END)) {
++		NL_SET_ERR_MSG_MOD(extack, "Old-style vlan ranges are not allowed when using RTM vlan calls");
++		return -EINVAL;
++	}
++	if (!br_vlan_valid_id(vinfo->vid, extack))
++		return -EINVAL;
++
++	if (tb[BRIDGE_VLANDB_ENTRY_RANGE]) {
++		vrange_end.vid = nla_get_u16(tb[BRIDGE_VLANDB_ENTRY_RANGE]);
++		/* validate user-provided flags without RANGE_BEGIN */
++		vrange_end.flags = BRIDGE_VLAN_INFO_RANGE_END | vinfo->flags;
++		vinfo->flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
++
++		/* vinfo_last is the range start, vinfo the range end */
++		vinfo_last = vinfo;
++		vinfo = &vrange_end;
++
++		if (!br_vlan_valid_id(vinfo->vid, extack) ||
++		    !br_vlan_valid_range(vinfo, vinfo_last, extack))
++			return -EINVAL;
++	}
++
++	switch (cmd) {
++	case RTM_NEWVLAN:
++		cmdmap = RTM_SETLINK;
++		skip_processing = !!(vinfo->flags & BRIDGE_VLAN_INFO_ONLY_OPTS);
++		break;
++	case RTM_DELVLAN:
++		cmdmap = RTM_DELLINK;
++		break;
++	}
++
++	if (!skip_processing) {
++		struct bridge_vlan_info *tmp_last = vinfo_last;
++
++		/* br_process_vlan_info may overwrite vinfo_last */
++		err = br_process_vlan_info(br, p, cmdmap, vinfo, &tmp_last,
++					   &changed, extack);
++
++		/* notify first if anything changed */
++		if (changed)
++			br_ifinfo_notify(cmdmap, br, p);
++
++		if (err)
++			return err;
++	}
++
++	/* deal with options */
++	if (cmd == RTM_NEWVLAN) {
++		struct net_bridge_vlan *range_start, *range_end;
++
++		if (vinfo_last) {
++			range_start = br_vlan_find(vg, vinfo_last->vid);
++			range_end = br_vlan_find(vg, vinfo->vid);
++		} else {
++			range_start = br_vlan_find(vg, vinfo->vid);
++			range_end = range_start;
++		}
++
++		err = br_vlan_process_options(br, p, range_start, range_end,
++					      tb, extack);
++	}
++
++	return err;
++}
++
++static int br_vlan_rtm_process(struct sk_buff *skb, struct nlmsghdr *nlh,
++			       struct netlink_ext_ack *extack)
++{
++	struct net *net = sock_net(skb->sk);
++	struct br_vlan_msg *bvm;
++	struct net_device *dev;
++	struct nlattr *attr;
++	int err, vlans = 0;
++	int rem;
++
++	/* this should validate the header and check for remaining bytes */
++	err = nlmsg_parse(nlh, sizeof(*bvm), NULL, BRIDGE_VLANDB_MAX, NULL,
++			  extack);
++	if (err < 0)
++		return err;
++
++	bvm = nlmsg_data(nlh);
++	dev = __dev_get_by_index(net, bvm->ifindex);
++	if (!dev)
++		return -ENODEV;
++
++	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev)) {
++		NL_SET_ERR_MSG_MOD(extack, "The device is not a valid bridge or bridge port");
++		return -EINVAL;
++	}
++
++	nlmsg_for_each_attr(attr, nlh, sizeof(*bvm), rem) {
++		switch (nla_type(attr)) {
++		case BRIDGE_VLANDB_ENTRY:
++			err = br_vlan_rtm_process_one(dev, attr,
++						      nlh->nlmsg_type,
++						      extack);
++			break;
++		default:
++			continue;
++		}
++
++		vlans++;
++		if (err)
++			break;
++	}
++	if (!vlans) {
++		NL_SET_ERR_MSG_MOD(extack, "No vlans found to process");
++		err = -EINVAL;
++	}
++
++	return err;
++}
++
++void br_vlan_rtnl_init(void)
++{
++	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_GETVLAN, NULL,
++			     br_vlan_rtm_dump, 0);
++	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_NEWVLAN,
++			     br_vlan_rtm_process, NULL, 0);
++	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_DELVLAN,
++			     br_vlan_rtm_process, NULL, 0);
++}
++
++void br_vlan_rtnl_uninit(void)
++{
++	rtnl_unregister(PF_BRIDGE, RTM_GETVLAN);
++	rtnl_unregister(PF_BRIDGE, RTM_NEWVLAN);
++	rtnl_unregister(PF_BRIDGE, RTM_DELVLAN);
++}
+diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
+new file mode 100644
+index 0000000..5e48c29
+--- /dev/null
++++ b/net/bridge/br_vlan_options.c
+@@ -0,0 +1,346 @@
++// SPDX-License-Identifier: GPL-2.0-only
++// Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/rtnetlink.h>
++#include <linux/slab.h>
++#include <net/ip_tunnels.h>
++
++#include "br_private.h"
++#include "br_private_tunnel.h"
++
++static bool __vlan_tun_put(struct sk_buff *skb, const struct net_bridge_vlan *v)
++{
++	__be32 tid = tunnel_id_to_key32(v->tinfo.tunnel_id);
++	struct nlattr *nest;
++
++	if (!v->tinfo.tunnel_dst)
++		return true;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_TUNNEL_INFO);
++	if (!nest)
++		return false;
++	if (nla_put_u32(skb, BRIDGE_VLANDB_TINFO_ID, be32_to_cpu(tid))) {
++		nla_nest_cancel(skb, nest);
++		return false;
++	}
++	nla_nest_end(skb, nest);
++
++	return true;
++}
++
++static bool __vlan_tun_can_enter_range(struct net_bridge_vlan *v_curr,
++				       struct net_bridge_vlan *range_end)
++{
++	return (!v_curr->tinfo.tunnel_dst && !range_end->tinfo.tunnel_dst) ||
++	       vlan_tunid_inrange(v_curr, range_end);
++}
++
++/* check if the options' state of v_curr allow it to enter the range */
++bool br_vlan_opts_eq_range(struct net_bridge_vlan *v_curr,
++			   struct net_bridge_vlan *range_end)
++{
++	u8 range_mc_rtr = br_vlan_multicast_router(range_end);
++	u8 curr_mc_rtr = br_vlan_multicast_router(v_curr);
++
++	return v_curr->state == range_end->state &&
++	       __vlan_tun_can_enter_range(v_curr, range_end) &&
++	       curr_mc_rtr == range_mc_rtr;
++}
++
++bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
++{
++	if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE, br_vlan_get_state(v)) ||
++	    !__vlan_tun_put(skb, v))
++		return false;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
++		       br_vlan_multicast_router(v)))
++		return false;
++#endif
++
++	return true;
++}
++
++size_t br_vlan_opts_nl_size(void)
++{
++	return nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_STATE */
++	       + nla_total_size(0) /* BRIDGE_VLANDB_ENTRY_TUNNEL_INFO */
++	       + nla_total_size(sizeof(u32)) /* BRIDGE_VLANDB_TINFO_ID */
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	       + nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_MCAST_ROUTER */
++#endif
++	       + 0;
++}
++
++static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
++				struct net_bridge_vlan *v,
++				u8 state,
++				bool *changed,
++				struct netlink_ext_ack *extack)
++{
++	struct net_bridge *br;
++
++	ASSERT_RTNL();
++
++	if (state > BR_STATE_BLOCKING) {
++		NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state");
++		return -EINVAL;
++	}
++
++	if (br_vlan_is_brentry(v))
++		br = v->br;
++	else
++		br = v->port->br;
++
++	if (br->stp_enabled == BR_KERNEL_STP) {
++		NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP");
++		return -EBUSY;
++	}
++
++	if (v->state == state)
++		return 0;
++
++	if (v->vid == br_get_pvid(vg))
++		br_vlan_set_pvid_state(vg, state);
++
++	br_vlan_set_state(v, state);
++	*changed = true;
++
++	return 0;
++}
++
++static const struct nla_policy br_vlandb_tinfo_pol[BRIDGE_VLANDB_TINFO_MAX + 1] = {
++	[BRIDGE_VLANDB_TINFO_ID]	= { .type = NLA_U32 },
++	[BRIDGE_VLANDB_TINFO_CMD]	= { .type = NLA_U32 },
++};
++
++static int br_vlan_modify_tunnel(struct net_bridge_port *p,
++				 struct net_bridge_vlan *v,
++				 struct nlattr **tb,
++				 bool *changed,
++				 struct netlink_ext_ack *extack)
++{
++	struct nlattr *tun_tb[BRIDGE_VLANDB_TINFO_MAX + 1], *attr;
++	struct bridge_vlan_info *vinfo;
++	u32 tun_id = 0;
++	int cmd, err;
++
++	if (!p) {
++		NL_SET_ERR_MSG_MOD(extack, "Can't modify tunnel mapping of non-port vlans");
++		return -EINVAL;
++	}
++	if (!(p->flags & BR_VLAN_TUNNEL)) {
++		NL_SET_ERR_MSG_MOD(extack, "Port doesn't have tunnel flag set");
++		return -EINVAL;
++	}
++
++	attr = tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO];
++	err = nla_parse_nested(tun_tb, BRIDGE_VLANDB_TINFO_MAX, attr,
++			       br_vlandb_tinfo_pol, extack);
++	if (err)
++		return err;
++
++	if (!tun_tb[BRIDGE_VLANDB_TINFO_CMD]) {
++		NL_SET_ERR_MSG_MOD(extack, "Missing tunnel command attribute");
++		return -ENOENT;
++	}
++	cmd = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_CMD]);
++	switch (cmd) {
++	case RTM_SETLINK:
++		if (!tun_tb[BRIDGE_VLANDB_TINFO_ID]) {
++			NL_SET_ERR_MSG_MOD(extack, "Missing tunnel id attribute");
++			return -ENOENT;
++		}
++		/* when working on vlan ranges this is the starting tunnel id */
++		tun_id = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_ID]);
++		/* vlan info attr is guaranteed by br_vlan_rtm_process_one */
++		vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
++		/* tunnel ids are mapped to each vlan in increasing order,
++		 * the starting vlan is in BRIDGE_VLANDB_ENTRY_INFO and v is the
++		 * current vlan, so we compute: tun_id + v - vinfo->vid
++		 */
++		tun_id += v->vid - vinfo->vid;
++		break;
++	case RTM_DELLINK:
++		break;
++	default:
++		NL_SET_ERR_MSG_MOD(extack, "Unsupported tunnel command");
++		return -EINVAL;
++	}
++
++	return br_vlan_tunnel_info(p, cmd, v->vid, tun_id, changed);
++}
++
++static int br_vlan_process_one_opts(const struct net_bridge *br,
++				    struct net_bridge_port *p,
++				    struct net_bridge_vlan_group *vg,
++				    struct net_bridge_vlan *v,
++				    struct nlattr **tb,
++				    bool *changed,
++				    struct netlink_ext_ack *extack)
++{
++	int err;
++
++	*changed = false;
++	if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
++		u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
++
++		err = br_vlan_modify_state(vg, v, state, changed, extack);
++		if (err)
++			return err;
++	}
++	if (tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO]) {
++		err = br_vlan_modify_tunnel(p, v, tb, changed, extack);
++		if (err)
++			return err;
++	}
++
++	return 0;
++}
++
++int br_vlan_process_options(const struct net_bridge *br,
++			    struct net_bridge_port *p,
++			    struct net_bridge_vlan *range_start,
++			    struct net_bridge_vlan *range_end,
++			    struct nlattr **tb,
++			    struct netlink_ext_ack *extack)
++{
++	struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
++	struct net_bridge_vlan_group *vg;
++	int vid, err = 0;
++	u16 pvid;
++
++	if (p)
++		vg = nbp_vlan_group(p);
++	else
++		vg = br_vlan_group(br);
++
++	if (!range_start || !br_vlan_should_use(range_start)) {
++		NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options");
++		return -ENOENT;
++	}
++	if (!range_end || !br_vlan_should_use(range_end)) {
++		NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options");
++		return -ENOENT;
++	}
++
++	pvid = br_get_pvid(vg);
++	for (vid = range_start->vid; vid <= range_end->vid; vid++) {
++		bool changed = false;
++
++		v = br_vlan_find(vg, vid);
++		if (!v || !br_vlan_should_use(v)) {
++			NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options");
++			err = -ENOENT;
++			break;
++		}
++
++		err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
++					       extack);
++		if (err)
++			break;
++
++		if (changed) {
++			/* vlan options changed, check for range */
++			if (!curr_start) {
++				curr_start = v;
++				curr_end = v;
++				continue;
++			}
++
++			if (v->vid == pvid ||
++			    !br_vlan_can_enter_range(v, curr_end)) {
++				br_vlan_notify(br, p, curr_start->vid,
++					       curr_end->vid, RTM_NEWVLAN);
++				curr_start = v;
++			}
++			curr_end = v;
++		} else {
++			/* nothing changed and nothing to notify yet */
++			if (!curr_start)
++				continue;
++
++			br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
++				       RTM_NEWVLAN);
++			curr_start = NULL;
++			curr_end = NULL;
++		}
++	}
++	if (curr_start)
++		br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
++			       RTM_NEWVLAN);
++
++	return err;
++}
++
++bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
++					 const struct net_bridge_vlan *r_end)
++{
++	return v_curr->vid - r_end->vid == 1 &&
++	       ((v_curr->priv_flags ^ r_end->priv_flags) &
++		BR_VLFLAG_GLOBAL_MCAST_ENABLED) == 0 &&
++		br_multicast_ctx_options_equal(&v_curr->br_mcast_ctx,
++					       &r_end->br_mcast_ctx);
++}
++
++bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
++			      const struct net_bridge_vlan *v_opts)
++{
++	struct nlattr *nest2 __maybe_unused;
++	u64 clockval __maybe_unused;
++	struct nlattr *nest;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_GLOBAL_OPTIONS);
++	if (!nest)
++		return false;
++
++	if (nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_ID, vid))
++		goto out_err;
++
++	if (vid_range && vid < vid_range &&
++	    nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_RANGE, vid_range))
++		goto out_err;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_last_member_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_membership_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_querier_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_query_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_query_response_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_startup_query_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++
++#if IS_ENABLED(CONFIG_IPV6)
++	if (nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
++		       v_opts->br_mcast_ctx.multicast_mld_version))
++		goto out_err;
++#endif
++#endif
++
++	nla_nest_end(skb, nest);
++
++	return true;
++
++out_err:
++	nla_nest_cancel(skb, nest);
++	return false;
++}
+diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
+index dbc9b2f..706b207 100644
+--- a/net/core/rtnetlink.c
++++ b/net/core/rtnetlink.c
+@@ -1996,6 +1996,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
+ 				goto cont;
+ 			if (idx < s_idx)
+ 				goto cont;
++
+ 			err = rtnl_fill_ifinfo(skb, dev, net,
+ 					       RTM_NEWLINK,
+ 					       NETLINK_CB(cb->skb).portid,
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 2dfaa1e..a60a26c 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1495,8 +1495,19 @@ int dsa_slave_create(struct dsa_port *port)
+ 		goto out_phy;
+ 	}
+ 
++	rtnl_lock();
++
++	ret = netdev_upper_dev_link(master, slave_dev, NULL);
++
++	rtnl_unlock();
++
++	if (ret)
++		goto out_unregister;
++
+ 	return 0;
+ 
++out_unregister:
++	unregister_netdev(slave_dev);
+ out_phy:
+ 	rtnl_lock();
+ 	phylink_disconnect_phy(p->dp->pl);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg
index b4eef30..eb9070e 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg
@@ -293,6 +293,7 @@
 CONFIG_LZO_DECOMPRESS=y
 CONFIG_MAGIC_SYSRQ=y
 CONFIG_MAGIC_SYSRQ_SERIAL=y
+CONFIG_MAXLINEAR_GPHY=y
 CONFIG_MD=y
 CONFIG_MDIO_BUS=y
 CONFIG_MDIO_DEVICE=y
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
index 26bef5f..6812f0c 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
@@ -32,7 +32,7 @@
 index 0000000..7304278
 --- /dev/null
 +++ b/drivers/net/phy/mxl-gpy.c
-@@ -0,0 +1,738 @@
+@@ -0,0 +1,749 @@
 +// SPDX-License-Identifier: GPL-2.0+
 +/* Copyright (C) 2021 Maxlinear Corporation
 + * Copyright (C) 2020 Intel Corporation
@@ -98,6 +98,7 @@
 +#define VSPEC1_SGMII_CTRL_ANRS	BIT(9)		/* Restart Aneg */
 +#define VSPEC1_SGMII_ANEN_ANRS	(VSPEC1_SGMII_CTRL_ANEN | \
 +				 VSPEC1_SGMII_CTRL_ANRS)
++#define VSPEC1_SGMII_FIXED_2G5	BIT(5)
 +
 +/* WoL */
 +#define VPSPEC2_WOL_CTL		0x0E06
@@ -300,13 +301,23 @@
 +	case SPEED_2500:
 +		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
 +		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
-+				     VSPEC1_SGMII_CTRL_ANEN, 0);
++				     VSPEC1_SGMII_CTRL_ANEN | VSPEC1_SGMII_FIXED_2G5,
++				     0);
 +		if (ret < 0)
 +			phydev_err(phydev,
 +				   "Error: Disable of SGMII ANEG failed: %d\n",
 +				   ret);
 +		break;
 +	case SPEED_1000:
++		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++				     VSPEC1_SGMII_CTRL_ANEN | VSPEC1_SGMII_FIXED_2G5,
++				     VSPEC1_SGMII_FIXED_2G5);
++		if (ret < 0)
++			phydev_err(phydev,
++				   "Error: Disable of SGMII ANEG failed: %d\n",
++				   ret);
++		break;
 +	case SPEED_100:
 +	case SPEED_10:
 +		phydev->interface = PHY_INTERFACE_MODE_SGMII;
@@ -316,7 +327,7 @@
 +		 * if ANEG is disabled (in 2500-BaseX mode).
 +		 */
 +		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
-+				     VSPEC1_SGMII_ANEN_ANRS,
++				     VSPEC1_SGMII_ANEN_ANRS | VSPEC1_SGMII_FIXED_2G5,
 +				     VSPEC1_SGMII_ANEN_ANRS);
 +		if (ret < 0)
 +			phydev_err(phydev,
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch
index 452b237..0b3bf3c 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch
@@ -1,19 +1,19 @@
-From 9deb29cc86b8fdee6702f8d575f08f9a214cf90a Mon Sep 17 00:00:00 2001
+From 4d19c233a01598a28fdc528ebaeb7a1b4bb6884f Mon Sep 17 00:00:00 2001
 From: Zhanyong Wang <zhanyong.wang@mediatek.com>
-Date: Thu, 27 May 2021 11:44:17 +0800
-Subject: [PATCH 1/2] xHCI: MT7986 USB 2.0 USBIF compliance toolkit
+Date: Mon, 15 Aug 2022 12:40:22 +0800
+Subject: [PATCH 2/3] xHCI: MT79xx USB 2.0 USBIF compliance toolkit
 
-MT7986 USB 2.0 USBIF compliance toolkit
+MT79xx USB 2.0 USBIF compliance toolkit
 
 Signed-off-by: Zhanyong Wang <zhanyong.wang@mediatek.com>
 ---
- drivers/usb/host/Kconfig    | 9 +++++++++
- drivers/usb/host/Makefile   | 9 +++++++++
- drivers/usb/host/xhci-mtk.c | 5 ++++-
- drivers/usb/host/xhci-mtk.h | 7 +++++++
- drivers/usb/host/xhci.c     | 2 +-
- drivers/usb/host/xhci.h     | 1 +
- 6 files changed, 31 insertions(+), 2 deletions(-)
+ drivers/usb/host/Kconfig    |  9 +++++++++
+ drivers/usb/host/Makefile   | 10 ++++++++++
+ drivers/usb/host/xhci-mtk.c |  5 ++++-
+ drivers/usb/host/xhci-mtk.h |  7 +++++++
+ drivers/usb/host/xhci.c     |  2 +-
+ drivers/usb/host/xhci.h     |  1 +
+ 6 files changed, 32 insertions(+), 2 deletions(-)
 
 diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
 index 79b2e79dddd0..12b1bf9aa043 100644
@@ -36,10 +36,10 @@
  	tristate "xHCI support for Marvell Armada 375/38x/37xx"
  	select USB_XHCI_PLATFORM
 diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
-index b191361257cc..612c855adfa1 100644
+index b191361257cc..f064f836db2b 100644
 --- a/drivers/usb/host/Makefile
 +++ b/drivers/usb/host/Makefile
-@@ -21,6 +21,15 @@ endif
+@@ -21,6 +21,16 @@ endif
  
  ifneq ($(CONFIG_USB_XHCI_MTK), )
  	xhci-hcd-y += xhci-mtk-sch.o
@@ -52,14 +52,15 @@
 +	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-discth.o
 +	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-chgdt-en.o
 +	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-reg.o
++	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-preemphasic.o
  endif
  
  xhci-plat-hcd-y := xhci-plat.o
 diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
-index 5c0eb35cd007..8bd4c95a5435 100644
+index 104296fdd03e..d4345657945d 100644
 --- a/drivers/usb/host/xhci-mtk.c
 +++ b/drivers/usb/host/xhci-mtk.c
-@@ -18,9 +18,10 @@
+@@ -19,9 +19,10 @@
  #include <linux/pm_runtime.h>
  #include <linux/regmap.h>
  #include <linux/regulator/consumer.h>
@@ -71,7 +72,7 @@
  
  /* ip_pw_ctrl0 register */
  #define CTRL0_IP_SW_RST	BIT(0)
-@@ -570,6 +571,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
+@@ -581,6 +582,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
  	ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
  	if (ret)
  		goto dealloc_usb2_hcd;
@@ -79,7 +80,7 @@
  
  	return 0;
  
-@@ -604,6 +606,7 @@ static int xhci_mtk_remove(struct platform_device *dev)
+@@ -615,6 +617,7 @@ static int xhci_mtk_remove(struct platform_device *dev)
  	struct usb_hcd	*hcd = mtk->hcd;
  	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
  	struct usb_hcd  *shared_hcd = xhci->shared_hcd;
@@ -106,10 +107,10 @@
  
  static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd)
 diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
-index 1c8070023161..cf004950bc00 100644
+index 9fe35bb67731..4f62eddce6ab 100644
 --- a/drivers/usb/host/xhci.c
 +++ b/drivers/usb/host/xhci.c
-@@ -713,7 +713,7 @@ EXPORT_SYMBOL_GPL(xhci_run);
+@@ -711,7 +711,7 @@ EXPORT_SYMBOL_GPL(xhci_run);
   * Disable device contexts, disable IRQs, and quiesce the HC.
   * Reset the HC, finish any completed transactions, and cleanup memory.
   */
@@ -119,12 +120,12 @@
  	u32 temp;
  	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
 diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
-index 02df309e4409..3af400068324 100644
+index a9031f494984..b54be4833ef7 100644
 --- a/drivers/usb/host/xhci.h
 +++ b/drivers/usb/host/xhci.h
-@@ -2067,6 +2067,7 @@ int xhci_halt(struct xhci_hcd *xhci);
+@@ -2070,6 +2070,7 @@ int xhci_halt(struct xhci_hcd *xhci);
  int xhci_start(struct xhci_hcd *xhci);
- int xhci_reset(struct xhci_hcd *xhci);
+ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us);
  int xhci_run(struct usb_hcd *hcd);
 +void xhci_stop(struct usb_hcd *hcd);
  int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch
index 14c32a8..c4970eb 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch
@@ -1,7 +1,7 @@
-From 5b28b61fb9c88e3d2f7c7057929d55e54bc17966 Mon Sep 17 00:00:00 2001
+From be6839b4144867c7ea6ffbedb6c6a2a42976e16d Mon Sep 17 00:00:00 2001
 From: Zhanyong Wang <zhanyong.wang@mediatek.com>
 Date: Thu, 17 Jun 2021 16:09:04 +0800
-Subject: [PATCH 2/2] usb: add embedded Host feature support
+Subject: [PATCH 3/3] usb: add embedded Host feature support
 
 add EH(Embedded Host) feature for PET authentication
 1. need CONFIG_USB_OTG_WHITELIST enable
@@ -107,10 +107,10 @@
  	}
  
 diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
-index 8bd4c95a5435..876e134a01b4 100644
+index d4345657945d..2a4b73a658f9 100644
 --- a/drivers/usb/host/xhci-mtk.c
 +++ b/drivers/usb/host/xhci-mtk.c
-@@ -560,6 +560,8 @@ static int xhci_mtk_probe(struct platform_device *pdev)
+@@ -571,6 +571,8 @@ static int xhci_mtk_probe(struct platform_device *pdev)
  		goto disable_device_wakeup;
  	}