[][kernel][common][net][Add AN8855 dsa and gsw driver]

[Description]
Add AN8855 dsa and gsw driver.
AN8855 is a 5-port giga switch with 1 HSGMII port5 as the CPU port.

This patch adds the dsa driver for modern OpenWRT support, and gsw
driver to support legacy swconfig.

phy addr of 5 internal gphy is 1 to 5 in default. User can set the
"changesmiaddr" and "airoha,smi-addr" in the dts file to change the
beginning number of the phy addr. For example,
    set changesmiaddr = <6>;   // dsa driver
    or  airoha,smi-addr = <6>; // gsw driver
    then 5 phy addr will be 6 to 10.

[Release-log]
N/A

Change-Id: I7d14afde33bf556e5a7b223258f8a68de36dd0c2
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/8758316
diff --git a/21.02/files/target/linux/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7981-spim-nand-2500wan-an8855-gsw.dts b/21.02/files/target/linux/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7981-spim-nand-2500wan-an8855-gsw.dts
new file mode 100644
index 0000000..3f0bb90
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7981-spim-nand-2500wan-an8855-gsw.dts
@@ -0,0 +1,295 @@
+/dts-v1/;
+#include "mt7981.dtsi"
+/ {
+	model = "MediaTek MT7981 RFB";
+	compatible = "mediatek,mt7981-spim-snand-2500wan-an8855-gsw-rfb";
+	chosen {
+		bootargs = "console=ttyS0,115200n1 loglevel=8  \
+				earlycon=uart8250,mmio32,0x11002000";
+	};
+
+	memory {
+		// fpga ddr2: 128MB*2
+		reg = <0 0x40000000 0 0x10000000>;
+	};
+
+	gpio-keys {
+			compatible = "gpio-keys";
+				reset {
+					label = "reset";
+					linux,code = <KEY_RESTART>;
+					gpios = <&pio 1 GPIO_ACTIVE_LOW>;
+				};
+
+				wps {
+					label = "wps";
+					linux,code = <KEY_WPS_BUTTON>;
+					gpios = <&pio 0 GPIO_ACTIVE_HIGH>;
+				};
+	};
+
+	nmbm_spim_nand {
+		compatible = "generic,nmbm";
+
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		lower-mtd-device = <&spi_nand>;
+		forced-create;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "BL2";
+				reg = <0x00000 0x0100000>;
+				read-only;
+			};
+
+			partition@100000 {
+				label = "u-boot-env";
+				reg = <0x0100000 0x0080000>;
+			};
+
+			factory: partition@180000 {
+				label = "Factory";
+				reg = <0x180000 0x0200000>;
+			};
+
+			partition@380000 {
+				label = "FIP";
+				reg = <0x380000 0x0200000>;
+			};
+
+			partition@580000 {
+				label = "ubi";
+				reg = <0x580000 0x4000000>;
+			};
+		};
+	};
+
+	gsw: gsw@0 {
+		compatible = "airoha,an8855";
+		#mediatek,ethsys = <&ethsys>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&watchdog {
+	status = "okay";
+};
+
+&eth {
+	status = "okay";
+
+	gmac0: mac@0 {
+		compatible = "mediatek,eth-mac";
+		reg = <0>;
+		phy-mode = "2500base-x";
+
+		fixed-link {
+			speed = <2500>;
+			full-duplex;
+			pause;
+		};
+	};
+
+	gmac1: mac@1 {
+		compatible = "mediatek,eth-mac";
+		reg = <1>;
+		phy-mode = "2500base-x";
+		phy-handle = <&phy5>;
+	};
+
+	mdio: mdio-bus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reset-gpios = <&pio 14 1>;
+		reset-delay-us = <600>;
+
+		phy5: phy@5 {
+			compatible = "ethernet-phy-ieee802.3-c45";
+			reg = <5>;
+		};
+	};
+};
+
+&gsw {
+	airoha,mdio = <&mdio>;
+	airoha,portmap = "llllw";
+	airoha,smi-addr = <6>;
+	reset-gpios = <&pio 39 0>;
+	interrupt-parent = <&pio>;
+	interrupts = <38 IRQ_TYPE_LEVEL_HIGH>;
+	status = "okay";
+
+	port5: port@5 {
+		compatible = "airoha,an8855-port";
+		reg = <5>;
+		phy-mode = "2500base-x";
+		/* airoha,stag-on = <1>; */
+
+		fixed-link {
+			speed = <2500>;
+			full-duplex;
+		};
+	};
+};
+
+&hnat {
+	mtketh-wan = "eth1";
+	mtketh-lan = "eth0";
+	mtketh-max-gmac = <2>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_flash_pins>;
+	status = "okay";
+	spi_nand: spi_nand@0 {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		compatible = "spi-nand";
+		spi-cal-enable;
+		spi-cal-mode = "read-data";
+		spi-cal-datalen = <7>;
+		spi-cal-data = /bits/ 8 <0x53 0x50 0x49 0x4E 0x41 0x4E 0x44>;
+		spi-cal-addrlen = <5>;
+		spi-cal-addr = /bits/ 32 <0x0 0x0 0x0 0x0 0x0>;
+		reg = <0>;
+		spi-max-frequency = <52000000>;
+		spi-tx-bus-width = <4>;
+		spi-rx-bus-width = <4>;
+	};
+};
+
+&spi1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spic_pins>;
+	status = "disabled";
+
+	slb9670: slb9670@0 {
+		compatible = "infineon,slb9670";
+		reg = <0>; /* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-cal-enable;
+		spi-cal-mode = "read-data";
+		spi-cal-datalen = <2>;
+		spi-cal-data = /bits/ 8 <0x00 0x1b>;
+		spi-max-frequency = <20000000>;
+	};
+};
+
+&wbsys {
+	mediatek,mtd-eeprom = <&factory 0x0000>;
+	status = "okay";
+	pinctrl-names = "dbdc";
+	pinctrl-0 = <&wf_dbdc_pins>;
+};
+
+&pio {
+
+	i2c_pins: i2c-pins-g0 {
+		mux {
+			function = "i2c";
+			groups = "i2c0_0";
+		};
+	};
+
+	pcm_pins: pcm-pins-g0 {
+		mux {
+			function = "pcm";
+			groups = "pcm";
+		};
+	};
+
+	pwm0_pin: pwm0-pin-g0 {
+		mux {
+			function = "pwm";
+			groups = "pwm0_0";
+		};
+	};
+
+	pwm1_pin: pwm1-pin-g0 {
+		mux {
+			function = "pwm";
+			groups = "pwm1_0";
+		};
+	};
+
+	pwm2_pin: pwm2-pin {
+		mux {
+			function = "pwm";
+			groups = "pwm2";
+		};
+	};
+
+	spi0_flash_pins: spi0-pins {
+		mux {
+			function = "spi";
+			groups = "spi0", "spi0_wp_hold";
+		};
+
+		conf-pu {
+			pins = "SPI0_CS", "SPI0_HOLD", "SPI0_WP";
+			drive-strength = <MTK_DRIVE_8mA>;
+			bias-pull-up = <MTK_PUPD_SET_R1R0_11>;
+		};
+
+		conf-pd {
+			pins = "SPI0_CLK", "SPI0_MOSI", "SPI0_MISO";
+			drive-strength = <MTK_DRIVE_8mA>;
+			bias-pull-down = <MTK_PUPD_SET_R1R0_11>;
+		};
+	};
+
+	spic_pins: spi1-pins {
+		mux {
+			function = "spi";
+			groups = "spi1_1";
+		};
+	};
+
+	uart1_pins: uart1-pins-g1 {
+		mux {
+			function = "uart";
+			groups = "uart1_1";
+		};
+	};
+
+	uart2_pins: uart2-pins-g1 {
+		mux {
+			function = "uart";
+			groups = "uart2_1";
+		};
+	};
+
+	wf_dbdc_pins: wf_dbdc-pins {
+		mux {
+			function = "eth";
+			groups = "wf0_mode1";
+		};
+		conf {
+			pins = "WF_HB1", "WF_HB2", "WF_HB3", "WF_HB4",
+			       "WF_HB0", "WF_HB0_B", "WF_HB5", "WF_HB6",
+			       "WF_HB7", "WF_HB8", "WF_HB9", "WF_HB10",
+			       "WF_TOP_CLK", "WF_TOP_DATA", "WF_XO_REQ",
+			       "WF_CBA_RESETB", "WF_DIG_RESETB";
+			drive-strength = <MTK_DRIVE_4mA>;
+		};
+	};
+};
+
+&xhci {
+	status = "okay";
+};
diff --git a/21.02/files/target/linux/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7981-spim-nand-2500wan-an8855.dts b/21.02/files/target/linux/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7981-spim-nand-2500wan-an8855.dts
new file mode 100644
index 0000000..028c945
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7981-spim-nand-2500wan-an8855.dts
@@ -0,0 +1,310 @@
+/dts-v1/;
+#include "mt7981.dtsi"
+/ {
+	model = "MediaTek MT7981 RFB";
+	compatible = "mediatek,mt7981-spim-snand-2500wan-an8855-rfb";
+	chosen {
+		bootargs = "console=ttyS0,115200n1 loglevel=8  \
+				earlycon=uart8250,mmio32,0x11002000";
+	};
+
+	memory {
+		// fpga ddr2: 128MB*2
+		reg = <0 0x40000000 0 0x10000000>;
+	};
+
+	gpio-keys {
+			compatible = "gpio-keys";
+				reset {
+					label = "reset";
+					linux,code = <KEY_RESTART>;
+					gpios = <&pio 1 GPIO_ACTIVE_LOW>;
+				};
+
+				wps {
+					label = "wps";
+					linux,code = <KEY_WPS_BUTTON>;
+					gpios = <&pio 0 GPIO_ACTIVE_HIGH>;
+				};
+	};
+
+	nmbm_spim_nand {
+		compatible = "generic,nmbm";
+
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		lower-mtd-device = <&spi_nand>;
+		forced-create;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			partition@0 {
+				label = "BL2";
+				reg = <0x00000 0x0100000>;
+				read-only;
+			};
+
+			partition@100000 {
+				label = "u-boot-env";
+				reg = <0x0100000 0x0080000>;
+			};
+
+			factory: partition@180000 {
+				label = "Factory";
+				reg = <0x180000 0x0200000>;
+			};
+
+			partition@380000 {
+				label = "FIP";
+				reg = <0x380000 0x0200000>;
+			};
+
+			partition@580000 {
+				label = "ubi";
+				reg = <0x580000 0x4000000>;
+			};
+		};
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&watchdog {
+	status = "okay";
+};
+
+&eth {
+	status = "okay";
+
+	gmac0: mac@0 {
+		compatible = "mediatek,eth-mac";
+		reg = <0>;
+		phy-mode = "2500base-x";
+
+		fixed-link {
+			speed = <2500>;
+			full-duplex;
+			pause;
+		};
+	;
+
+	gmac1: mac@1 {
+		compatible = "mediatek,eth-mac";
+		reg = <1>;
+		phy-mode = "2500base-x";
+		phy-handle = <&phy5>;
+	};
+
+	mdio: mdio-bus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reset-gpios = <&pio 14 1>;
+		reset-delay-us = <600>;
+
+		phy5: phy@5 {
+			compatible = "ethernet-phy-ieee802.3-c45";
+			reg = <5>;
+		};
+
+		switch@0 {
+			compatible = "airoha,an8855";
+			reg = <1>;
+			reset-gpios = <&pio 39 0>;
+			changesmiaddr = <6>;
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+					label = "lan1";
+				};
+
+				port@1 {
+					reg = <1>;
+					label = "lan2";
+				};
+
+				port@2 {
+					reg = <2>;
+					label = "lan3";
+				};
+
+				port@3 {
+					reg = <3>;
+					label = "lan4";
+				};
+
+				port@5 {
+					reg = <5>;
+					label = "cpu";
+					ethernet = <&gmac0>;
+					phy-mode = "2500base-x";
+
+					fixed-link {
+						speed = <2500>;
+						full-duplex;
+						pause;
+					};
+				};
+			};
+		};
+	};
+};
+
+&hnat {
+	mtketh-wan = "eth1";
+	mtketh-lan = "lan";
+	mtketh-max-gmac = <2>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_flash_pins>;
+	status = "okay";
+	spi_nand: spi_nand@0 {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		compatible = "spi-nand";
+		spi-cal-enable;
+		spi-cal-mode = "read-data";
+		spi-cal-datalen = <7>;
+		spi-cal-data = /bits/ 8 <0x53 0x50 0x49 0x4E 0x41 0x4E 0x44>;
+		spi-cal-addrlen = <5>;
+		spi-cal-addr = /bits/ 32 <0x0 0x0 0x0 0x0 0x0>;
+		reg = <0>;
+		spi-max-frequency = <52000000>;
+		spi-tx-bus-width = <4>;
+		spi-rx-bus-width = <4>;
+	};
+};
+
+&spi1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spic_pins>;
+	status = "disabled";
+
+	slb9670: slb9670@0 {
+		compatible = "infineon,slb9670";
+		reg = <0>; /* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-cal-enable;
+		spi-cal-mode = "read-data";
+		spi-cal-datalen = <2>;
+		spi-cal-data = /bits/ 8 <0x00 0x1b>;
+		spi-max-frequency = <20000000>;
+	};
+};
+
+&wbsys {
+	mediatek,mtd-eeprom = <&factory 0x0000>;
+	status = "okay";
+	pinctrl-names = "dbdc";
+	pinctrl-0 = <&wf_dbdc_pins>;
+};
+
+&pio {
+
+	i2c_pins: i2c-pins-g0 {
+		mux {
+			function = "i2c";
+			groups = "i2c0_0";
+		};
+	};
+
+	pcm_pins: pcm-pins-g0 {
+		mux {
+			function = "pcm";
+			groups = "pcm";
+		};
+	};
+
+	pwm0_pin: pwm0-pin-g0 {
+		mux {
+			function = "pwm";
+			groups = "pwm0_0";
+		};
+	};
+
+	pwm1_pin: pwm1-pin-g0 {
+		mux {
+			function = "pwm";
+			groups = "pwm1_0";
+		};
+	};
+
+	pwm2_pin: pwm2-pin {
+		mux {
+			function = "pwm";
+			groups = "pwm2";
+		};
+	};
+
+	spi0_flash_pins: spi0-pins {
+		mux {
+			function = "spi";
+			groups = "spi0", "spi0_wp_hold";
+		};
+
+		conf-pu {
+			pins = "SPI0_CS", "SPI0_HOLD", "SPI0_WP";
+			drive-strength = <MTK_DRIVE_8mA>;
+			bias-pull-up = <MTK_PUPD_SET_R1R0_11>;
+		};
+
+		conf-pd {
+			pins = "SPI0_CLK", "SPI0_MOSI", "SPI0_MISO";
+			drive-strength = <MTK_DRIVE_8mA>;
+			bias-pull-down = <MTK_PUPD_SET_R1R0_11>;
+		};
+	};
+
+	spic_pins: spi1-pins {
+		mux {
+			function = "spi";
+			groups = "spi1_1";
+		};
+	};
+
+	uart1_pins: uart1-pins-g1 {
+		mux {
+			function = "uart";
+			groups = "uart1_1";
+		};
+	};
+
+	uart2_pins: uart2-pins-g1 {
+		mux {
+			function = "uart";
+			groups = "uart2_1";
+		};
+	};
+
+	wf_dbdc_pins: wf_dbdc-pins {
+		mux {
+			function = "eth";
+			groups = "wf0_mode1";
+		};
+		conf {
+			pins = "WF_HB1", "WF_HB2", "WF_HB3", "WF_HB4",
+			       "WF_HB0", "WF_HB0_B", "WF_HB5", "WF_HB6",
+			       "WF_HB7", "WF_HB8", "WF_HB9", "WF_HB10",
+			       "WF_TOP_CLK", "WF_TOP_DATA", "WF_XO_REQ",
+			       "WF_CBA_RESETB", "WF_DIG_RESETB";
+			drive-strength = <MTK_DRIVE_4mA>;
+		};
+	};
+};
+
+&xhci {
+	status = "okay";
+};
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Kconfig b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Kconfig
new file mode 100644
index 0000000..fc00f75
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Kconfig
@@ -0,0 +1,10 @@
+
+config NET_DSA_AN8855
+	tristate "Airoha AN8855 Ethernet switch support"
+	depends on NET_DSA
+	select NET_DSA_TAG_AIROHA
+	help
+	  AN8855 support 2.5G speed and managed by SMI interface.
+	  This enables support for the Airoha AN8855 Ethernet switch
+	  chip.
+	  To compile this driver as a module, choose M here.
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Makefile b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Makefile
new file mode 100644
index 0000000..99c99bd
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for Airoha AN8855 gigabit switch
+#
+obj-$(CONFIG_NET_DSA_AN8855)	+= an8855-dsa.o
+an8855-dsa-objs			:= an8855.o an8855_nl.o an8855_phy.o
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.c
new file mode 100644
index 0000000..b0ba4be
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.c
@@ -0,0 +1,2358 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Airoha AN8855 DSA Switch driver
+ * Copyright (C) 2023 Min Yao <min.yao@airoha.com>
+ */
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phylink.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/gpio/consumer.h>
+#include <net/dsa.h>
+#include <linux/of_address.h>
+
+#include "an8855.h"
+#include "an8855_nl.h"
+
+/* AN8855 driver version */
+#define ARHT_AN8855_DSA_DRIVER_VER	"1.0.1-L5.4"
+
+/* String, offset, and register size in bytes if different from 4 bytes */
+static const struct an8855_mib_desc an8855_mib[] = {
+	MIB_DESC(1, 0x00, "TxDrop"),
+	MIB_DESC(1, 0x04, "TxCrcErr"),
+	MIB_DESC(1, 0x08, "TxUnicast"),
+	MIB_DESC(1, 0x0c, "TxMulticast"),
+	MIB_DESC(1, 0x10, "TxBroadcast"),
+	MIB_DESC(1, 0x14, "TxCollision"),
+	MIB_DESC(1, 0x18, "TxSingleCollision"),
+	MIB_DESC(1, 0x1c, "TxMultipleCollision"),
+	MIB_DESC(1, 0x20, "TxDeferred"),
+	MIB_DESC(1, 0x24, "TxLateCollision"),
+	MIB_DESC(1, 0x28, "TxExcessiveCollistion"),
+	MIB_DESC(1, 0x2c, "TxPause"),
+	MIB_DESC(1, 0x30, "TxPktSz64"),
+	MIB_DESC(1, 0x34, "TxPktSz65To127"),
+	MIB_DESC(1, 0x38, "TxPktSz128To255"),
+	MIB_DESC(1, 0x3c, "TxPktSz256To511"),
+	MIB_DESC(1, 0x40, "TxPktSz512To1023"),
+	MIB_DESC(1, 0x44, "TxPktSz1024To1518"),
+	MIB_DESC(1, 0x48, "TxPktSz1519ToMax"),
+	MIB_DESC(2, 0x4c, "TxBytes"),
+	MIB_DESC(1, 0x54, "TxOversizeDrop"),
+	MIB_DESC(2, 0x58, "TxBadPktBytes"),
+	MIB_DESC(1, 0x80, "RxDrop"),
+	MIB_DESC(1, 0x84, "RxFiltering"),
+	MIB_DESC(1, 0x88, "RxUnicast"),
+	MIB_DESC(1, 0x8c, "RxMulticast"),
+	MIB_DESC(1, 0x90, "RxBroadcast"),
+	MIB_DESC(1, 0x94, "RxAlignErr"),
+	MIB_DESC(1, 0x98, "RxCrcErr"),
+	MIB_DESC(1, 0x9c, "RxUnderSizeErr"),
+	MIB_DESC(1, 0xa0, "RxFragErr"),
+	MIB_DESC(1, 0xa4, "RxOverSzErr"),
+	MIB_DESC(1, 0xa8, "RxJabberErr"),
+	MIB_DESC(1, 0xac, "RxPause"),
+	MIB_DESC(1, 0xb0, "RxPktSz64"),
+	MIB_DESC(1, 0xb4, "RxPktSz65To127"),
+	MIB_DESC(1, 0xb8, "RxPktSz128To255"),
+	MIB_DESC(1, 0xbc, "RxPktSz256To511"),
+	MIB_DESC(1, 0xc0, "RxPktSz512To1023"),
+	MIB_DESC(1, 0xc4, "RxPktSz1024To1518"),
+	MIB_DESC(1, 0xc8, "RxPktSz1519ToMax"),
+	MIB_DESC(2, 0xcc, "RxBytes"),
+	MIB_DESC(1, 0xd4, "RxCtrlDrop"),
+	MIB_DESC(1, 0xd8, "RxIngressDrop"),
+	MIB_DESC(1, 0xdc, "RxArlDrop"),
+	MIB_DESC(1, 0xe0, "FlowControlDrop"),
+	MIB_DESC(1, 0xe4, "WredDrop"),
+	MIB_DESC(1, 0xe8, "MirrorDrop"),
+	MIB_DESC(2, 0xec, "RxBadPktBytes"),
+	MIB_DESC(1, 0xf4, "RxsFlowSamplingPktDrop"),
+	MIB_DESC(1, 0xf8, "RxsFlowTotalPktDrop"),
+	MIB_DESC(1, 0xfc, "PortControlDrop"),
+};
+
+static int
+an8855_mii_write(struct an8855_priv *priv, u32 reg, u32 val)
+{
+	struct mii_bus *bus = priv->bus;
+	int ret = 0;
+
+	ret = bus->write(bus, priv->phy_base, 0x1f, 0x4);
+	ret = bus->write(bus, priv->phy_base, 0x10, 0);
+
+	ret = bus->write(bus, priv->phy_base, 0x11, ((reg >> 16) & 0xFFFF));
+	ret = bus->write(bus, priv->phy_base, 0x12, (reg & 0xFFFF));
+
+	ret = bus->write(bus, priv->phy_base, 0x13, ((val >> 16) & 0xFFFF));
+	ret = bus->write(bus, priv->phy_base, 0x14, (val & 0xFFFF));
+
+	ret = bus->write(bus, priv->phy_base, 0x1f, 0);
+	ret = bus->write(bus, priv->phy_base, 0x10, 0);
+
+	if (ret < 0) {
+		dev_err(&bus->dev, "failed to write an8855 register\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static u32
+an8855_mii_read(struct an8855_priv *priv, u32 reg)
+{
+	struct mii_bus *bus = priv->bus;
+	u16 lo, hi;
+	int ret;
+
+	ret = bus->write(bus, priv->phy_base, 0x1f, 0x4);
+	ret = bus->write(bus, priv->phy_base, 0x10, 0);
+
+	ret = bus->write(bus, priv->phy_base, 0x15, ((reg >> 16) & 0xFFFF));
+	ret = bus->write(bus, priv->phy_base, 0x16, (reg & 0xFFFF));
+	if (ret < 0) {
+		dev_err(&bus->dev, "failed to read an8855 register\n");
+		return ret;
+	}
+
+	lo = bus->read(bus, priv->phy_base, 0x18);
+	hi = bus->read(bus, priv->phy_base, 0x17);
+
+	ret = bus->write(bus, priv->phy_base, 0x1f, 0);
+	ret = bus->write(bus, priv->phy_base, 0x10, 0);
+	if (ret < 0) {
+		dev_err(&bus->dev, "failed to read an8855 register\n");
+		return ret;
+	}
+
+	return (hi << 16) | (lo & 0xffff);
+}
+
+void
+an8855_write(struct an8855_priv *priv, u32 reg, u32 val)
+{
+	struct mii_bus *bus = priv->bus;
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+	an8855_mii_write(priv, reg, val);
+
+	mutex_unlock(&bus->mdio_lock);
+}
+
+static u32
+_an8855_read(struct an8855_dummy_poll *p)
+{
+	struct mii_bus *bus = p->priv->bus;
+	u32 val;
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+	val = an8855_mii_read(p->priv, p->reg);
+
+	mutex_unlock(&bus->mdio_lock);
+
+	return val;
+}
+
+u32
+an8855_read(struct an8855_priv *priv, u32 reg)
+{
+	struct an8855_dummy_poll p;
+
+	INIT_AN8855_DUMMY_POLL(&p, priv, reg);
+	return _an8855_read(&p);
+}
+
+static void
+an8855_rmw(struct an8855_priv *priv, u32 reg, u32 mask, u32 set)
+{
+	struct mii_bus *bus = priv->bus;
+	u32 val;
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+	val = an8855_mii_read(priv, reg);
+	val &= ~mask;
+	val |= set;
+	an8855_mii_write(priv, reg, val);
+
+	mutex_unlock(&bus->mdio_lock);
+}
+
+static void
+an8855_set(struct an8855_priv *priv, u32 reg, u32 val)
+{
+	an8855_rmw(priv, reg, 0, val);
+}
+
+static void
+an8855_clear(struct an8855_priv *priv, u32 reg, u32 val)
+{
+	an8855_rmw(priv, reg, val, 0);
+}
+
+static int
+an8855_fdb_cmd(struct an8855_priv *priv, u32 cmd, u32 *rsp)
+{
+	u32 val;
+	int ret;
+	struct an8855_dummy_poll p;
+
+	/* Set the command operating upon the MAC address entries */
+	val = ATC_BUSY | cmd;
+	an8855_write(priv, AN8855_ATC, val);
+
+	INIT_AN8855_DUMMY_POLL(&p, priv, AN8855_ATC);
+	ret = readx_poll_timeout(_an8855_read, &p, val,
+				 !(val & ATC_BUSY), 20, 200000);
+	if (ret < 0) {
+		dev_err(priv->dev, "reset timeout\n");
+		return ret;
+	}
+
+	if (rsp)
+		*rsp = val;
+
+	return 0;
+}
+
+static void
+an8855_fdb_read(struct an8855_priv *priv, struct an8855_fdb *fdb)
+{
+	u32 reg[4];
+	int i;
+
+	/* Read from ARL table into an array */
+	for (i = 0; i < 4; i++)
+		reg[i] = an8855_read(priv, AN8855_ATRD0 + (i * 4));
+
+	fdb->live = reg[0] & 0x1;
+	fdb->type = (reg[0] >> 3) & 0x3;
+	fdb->ivl = (reg[0] >> 9) & 0x1;
+	fdb->vid = (reg[0] >> 10) & 0xfff;
+	fdb->fid = (reg[0] >> 25) & 0xf;
+	fdb->aging = (reg[1] >> 3) & 0x1ff;
+	fdb->port_mask = reg[3] & 0xff;
+	fdb->mac[0] = (reg[2] >> MAC_BYTE_0) & MAC_BYTE_MASK;
+	fdb->mac[1] = (reg[2] >> MAC_BYTE_1) & MAC_BYTE_MASK;
+	fdb->mac[2] = (reg[2] >> MAC_BYTE_2) & MAC_BYTE_MASK;
+	fdb->mac[3] = (reg[2] >> MAC_BYTE_3) & MAC_BYTE_MASK;
+	fdb->mac[4] = (reg[1] >> MAC_BYTE_4) & MAC_BYTE_MASK;
+	fdb->mac[5] = (reg[1] >> MAC_BYTE_5) & MAC_BYTE_MASK;
+	fdb->noarp = !!((reg[0] >> 1) & 0x3);
+}
+
+static void
+an8855_fdb_write(struct an8855_priv *priv, u16 vid,
+		 u8 port_mask, const u8 *mac, u8 add)
+{
+	u32 reg = 0;
+
+	reg |= mac[3] << MAC_BYTE_3;
+	reg |= mac[2] << MAC_BYTE_2;
+	reg |= mac[1] << MAC_BYTE_1;
+	reg |= mac[0] << MAC_BYTE_0;
+	an8855_write(priv, AN8855_ATA1, reg);
+	reg = 0;
+	reg |= mac[5] << MAC_BYTE_5;
+	reg |= mac[4] << MAC_BYTE_4;
+	an8855_write(priv, AN8855_ATA2, reg);
+	reg = 0;
+	if (add)
+		reg |= 0x1;
+	reg |= 0x1 << 15;
+	reg |= vid << 16;
+	an8855_write(priv, AN8855_ATWD, reg);
+	an8855_write(priv, AN8855_ATWD2, port_mask);
+}
+
+static int
+an8855_pad_setup(struct dsa_switch *ds, phy_interface_t interface)
+{
+	return 0;
+}
+
+static void
+an8855_mib_reset(struct dsa_switch *ds)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	an8855_write(priv, AN8855_MIB_CCR, CCR_MIB_FLUSH);
+	an8855_write(priv, AN8855_MIB_CCR, CCR_MIB_ACTIVATE);
+}
+
+static int
+an8855_cl22_read(struct an8855_priv *priv, int port, int regnum)
+{
+	return mdiobus_read_nested(priv->bus, port, regnum);
+}
+
+static int
+an8855_cl22_write(struct an8855_priv *priv, int port, int regnum, u16 val)
+{
+	return mdiobus_write_nested(priv->bus, port, regnum, val);
+}
+
+static int
+an8855_phy_read(struct dsa_switch *ds, int port, int regnum)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	port += priv->phy_base;
+	return an8855_cl22_read(ds->priv, port, regnum);
+}
+
+static int
+an8855_phy_write(struct dsa_switch *ds, int port, int regnum,
+			    u16 val)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	port += priv->phy_base;
+	return an8855_cl22_write(ds->priv, port, regnum, val);
+}
+
+static int
+an8855_cl45_read(struct an8855_priv *priv, int port, int devad, int regnum)
+{
+	regnum = MII_ADDR_C45 | (devad << 16) | regnum;
+	return mdiobus_read_nested(priv->bus, port, regnum);
+}
+
+static int
+an8855_cl45_write(struct an8855_priv *priv, int port, int devad, int regnum,
+		      u16 val)
+{
+	regnum = MII_ADDR_C45 | (devad << 16) | regnum;
+	return mdiobus_write_nested(priv->bus, port, regnum, val);
+}
+
+int
+an8855_phy_cl22_read(struct an8855_priv *priv, int port, int regnum)
+{
+	port += priv->phy_base;
+	return an8855_cl22_read(priv, port, regnum);
+}
+
+int
+an8855_phy_cl22_write(struct an8855_priv *priv, int port, int regnum,
+			    u16 val)
+{
+	port += priv->phy_base;
+	return an8855_cl22_write(priv, port, regnum, val);
+}
+
+int
+an8855_phy_cl45_read(struct an8855_priv *priv, int port, int devad, int regnum)
+{
+	port += priv->phy_base;
+	return an8855_cl45_read(priv, port, devad, regnum);
+}
+
+int
+an8855_phy_cl45_write(struct an8855_priv *priv, int port, int devad, int regnum,
+		      u16 val)
+{
+	port += priv->phy_base;
+	return an8855_cl45_write(priv, port, devad, regnum, val);
+}
+
+static void
+an8855_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+		   uint8_t *data)
+{
+	int i;
+
+	if (stringset != ETH_SS_STATS)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(an8855_mib); i++)
+		strncpy(data + i * ETH_GSTRING_LEN, an8855_mib[i].name,
+			ETH_GSTRING_LEN);
+}
+
+static void
+an8855_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
+{
+	struct an8855_priv *priv = ds->priv;
+	const struct an8855_mib_desc *mib;
+	u32 reg, i;
+	u64 hi;
+
+	for (i = 0; i < ARRAY_SIZE(an8855_mib); i++) {
+		mib = &an8855_mib[i];
+		reg = AN8855_PORT_MIB_COUNTER(port) + mib->offset;
+
+		data[i] = an8855_read(priv, reg);
+		if (mib->size == 2) {
+			hi = an8855_read(priv, reg + 4);
+			data[i] |= hi << 32;
+		}
+	}
+}
+
+static int
+an8855_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+	if (sset != ETH_SS_STATS)
+		return 0;
+
+	return ARRAY_SIZE(an8855_mib);
+}
+
+static int
+an8855_cpu_port_enable(struct dsa_switch *ds, int port)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	/* Setup max capability of CPU port at first */
+	if (priv->info->cpu_port_config)
+		priv->info->cpu_port_config(ds, port);
+
+	/* Enable Airoha header mode on the cpu port */
+	an8855_write(priv, AN8855_PVC_P(port),
+		     PORT_SPEC_REPLACE_MODE | PORT_SPEC_TAG);
+
+	/* Unknown multicast frame forwarding to the cpu port */
+	an8855_write(priv, AN8855_UNMF, BIT(port));
+
+	/* Set CPU port number */
+	an8855_rmw(priv, AN8855_MFC, CPU_MASK, CPU_EN | CPU_PORT(port));
+
+	/* CPU port gets connected to all user ports of
+	 * the switch.
+	 */
+	an8855_write(priv, AN8855_PORTMATRIX_P(port),
+		     PORTMATRIX_MATRIX(dsa_user_ports(priv->ds)));
+
+	return 0;
+}
+
+static int
+an8855_port_enable(struct dsa_switch *ds, int port, struct phy_device *phy)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	if (!dsa_is_user_port(ds, port))
+		return 0;
+
+	mutex_lock(&priv->reg_mutex);
+
+	/* Allow the user port gets connected to the cpu port and also
+	 * restore the port matrix if the port is the member of a certain
+	 * bridge.
+	 */
+	priv->ports[port].pm |= PORTMATRIX_MATRIX(BIT(AN8855_CPU_PORT));
+	priv->ports[port].enable = true;
+	an8855_write(priv, AN8855_PORTMATRIX_P(port), priv->ports[port].pm);
+	an8855_clear(priv, AN8855_PMCR_P(port), PMCR_LINK_SETTINGS_MASK);
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+static void
+an8855_port_disable(struct dsa_switch *ds, int port)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	if (!dsa_is_user_port(ds, port))
+		return;
+
+	mutex_lock(&priv->reg_mutex);
+
+	/* Clear up all port matrix which could be restored in the next
+	 * enablement for the port.
+	 */
+	priv->ports[port].enable = false;
+	an8855_write(priv, AN8855_PORTMATRIX_P(port), PORTMATRIX_CLR);
+	an8855_clear(priv, AN8855_PMCR_P(port), PMCR_LINK_SETTINGS_MASK);
+
+	mutex_unlock(&priv->reg_mutex);
+}
+
+static void
+an8855_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 stp_state;
+
+	if (dsa_is_unused_port(ds, port))
+		return;
+
+	switch (state) {
+	case BR_STATE_DISABLED:
+		stp_state = AN8855_STP_DISABLED;
+		break;
+	case BR_STATE_BLOCKING:
+		stp_state = AN8855_STP_BLOCKING;
+		break;
+	case BR_STATE_LISTENING:
+		stp_state = AN8855_STP_LISTENING;
+		break;
+	case BR_STATE_LEARNING:
+		stp_state = AN8855_STP_LEARNING;
+		break;
+	case BR_STATE_FORWARDING:
+	default:
+		stp_state = AN8855_STP_FORWARDING;
+		break;
+	}
+
+	an8855_rmw(priv, AN8855_SSP_P(port), FID_PST_MASK, stp_state);
+}
+
+static int
+an8855_port_bridge_join(struct dsa_switch *ds, int port,
+			struct net_device *bridge)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 port_bitmap = BIT(AN8855_CPU_PORT);
+	int i;
+
+	mutex_lock(&priv->reg_mutex);
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		/* Add this port to the port matrix of the other ports in the
+		 * same bridge. If the port is disabled, port matrix is kept
+		 * and not being setup until the port becomes enabled.
+		 */
+		if (dsa_is_user_port(ds, i) && i != port) {
+			if (dsa_to_port(ds, i)->bridge_dev != bridge)
+				continue;
+			if (priv->ports[i].enable)
+				an8855_set(priv, AN8855_PORTMATRIX_P(i),
+					   PORTMATRIX_MATRIX(BIT(port)));
+			priv->ports[i].pm |= PORTMATRIX_MATRIX(BIT(port));
+
+			port_bitmap |= BIT(i);
+		}
+	}
+
+	/* Add the all other ports to this port matrix. */
+	if (priv->ports[port].enable)
+		an8855_rmw(priv, AN8855_PORTMATRIX_P(port),
+			   PORTMATRIX_MASK, PORTMATRIX_MATRIX(port_bitmap));
+	priv->ports[port].pm |= PORTMATRIX_MATRIX(port_bitmap);
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+static void
+an8855_port_set_vlan_unaware(struct dsa_switch *ds, int port)
+{
+	struct an8855_priv *priv = ds->priv;
+	bool all_user_ports_removed = true;
+	int i;
+
+	/* When a port is removed from the bridge, the port would be set up
+	 * back to the default as is at initial boot which is a VLAN-unaware
+	 * port.
+	 */
+	an8855_rmw(priv, AN8855_PCR_P(port), PCR_PORT_VLAN_MASK,
+		   AN8855_PORT_MATRIX_MODE);
+	an8855_rmw(priv, AN8855_PVC_P(port), VLAN_ATTR_MASK | PVC_EG_TAG_MASK,
+		   VLAN_ATTR(AN8855_VLAN_TRANSPARENT) |
+		   PVC_EG_TAG(AN8855_VLAN_EG_CONSISTENT));
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		if (dsa_is_user_port(ds, i) &&
+		    dsa_port_is_vlan_filtering(&ds->ports[i])) {
+			all_user_ports_removed = false;
+			break;
+		}
+	}
+
+	/* CPU port also does the same thing until all user ports belonging to
+	 * the CPU port get out of VLAN filtering mode.
+	 */
+	if (all_user_ports_removed) {
+		an8855_write(priv, AN8855_PORTMATRIX_P(AN8855_CPU_PORT),
+			     PORTMATRIX_MATRIX(dsa_user_ports(priv->ds)));
+		an8855_write(priv, AN8855_PVC_P(AN8855_CPU_PORT),
+			     PORT_SPEC_REPLACE_MODE | PORT_SPEC_TAG |
+			     PVC_EG_TAG(AN8855_VLAN_EG_CONSISTENT));
+	}
+}
+
+static void
+an8855_port_set_vlan_aware(struct dsa_switch *ds, int port)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	/* Trapped into security mode allows packet forwarding through VLAN
+	 * table lookup. CPU port is set to fallback mode to let untagged
+	 * frames pass through.
+	 */
+	if (dsa_is_cpu_port(ds, port))
+		an8855_rmw(priv, AN8855_PCR_P(port), PCR_PORT_VLAN_MASK,
+			   AN8855_PORT_FALLBACK_MODE);
+	else
+		an8855_rmw(priv, AN8855_PCR_P(port), PCR_PORT_VLAN_MASK,
+			   AN8855_PORT_SECURITY_MODE);
+
+	/* Set the port as a user port which is to be able to recognize VID
+	 * from incoming packets before fetching entry within the VLAN table.
+	 */
+	an8855_rmw(priv, AN8855_PVC_P(port), VLAN_ATTR_MASK | PVC_EG_TAG_MASK,
+		   VLAN_ATTR(AN8855_VLAN_USER) |
+		   PVC_EG_TAG(AN8855_VLAN_EG_DISABLED));
+}
+
+static void
+an8855_port_bridge_leave(struct dsa_switch *ds, int port,
+			 struct net_device *bridge)
+{
+	struct an8855_priv *priv = ds->priv;
+	int i;
+
+	mutex_lock(&priv->reg_mutex);
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		/* Remove this port from the port matrix of the other ports
+		 * in the same bridge. If the port is disabled, port matrix
+		 * is kept and not being setup until the port becomes enabled.
+		 */
+		if (dsa_is_user_port(ds, i) && i != port) {
+			if (dsa_to_port(ds, i)->bridge_dev != bridge)
+				continue;
+			if (priv->ports[i].enable)
+				an8855_clear(priv, AN8855_PORTMATRIX_P(i),
+					     PORTMATRIX_MATRIX(BIT(port)));
+			priv->ports[i].pm &= PORTMATRIX_MATRIX(BIT(port));
+		}
+	}
+
+	/* Set the cpu port to be the only one in the port matrix of
+	 * this port.
+	 */
+	if (priv->ports[port].enable)
+		an8855_rmw(priv, AN8855_PORTMATRIX_P(port), PORTMATRIX_MASK,
+			   PORTMATRIX_MATRIX(BIT(AN8855_CPU_PORT)));
+	priv->ports[port].pm = PORTMATRIX_MATRIX(BIT(AN8855_CPU_PORT));
+
+	mutex_unlock(&priv->reg_mutex);
+}
+
+static int
+an8855_port_fdb_add(struct dsa_switch *ds, int port,
+		    const unsigned char *addr, u16 vid)
+{
+	struct an8855_priv *priv = ds->priv;
+	int ret;
+	u8 port_mask = BIT(port);
+
+	mutex_lock(&priv->reg_mutex);
+	an8855_fdb_write(priv, vid, port_mask, addr, 1);
+	ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
+	mutex_unlock(&priv->reg_mutex);
+
+	return ret;
+}
+
+static int
+an8855_port_fdb_del(struct dsa_switch *ds, int port,
+		    const unsigned char *addr, u16 vid)
+{
+	struct an8855_priv *priv = ds->priv;
+	int ret;
+	u8 port_mask = BIT(port);
+
+	mutex_lock(&priv->reg_mutex);
+	an8855_fdb_write(priv, vid, port_mask, addr, 0);
+	ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
+	mutex_unlock(&priv->reg_mutex);
+
+	return ret;
+}
+
+static int
+an8855_port_fdb_dump(struct dsa_switch *ds, int port,
+		     dsa_fdb_dump_cb_t *cb, void *data)
+{
+	struct an8855_priv *priv = ds->priv;
+	struct an8855_fdb _fdb = { 0 };
+	int cnt = 512;
+	int num = 4;
+	int index = 0;
+	bool flag = false;
+	int banks = 0;
+	int i = 0;
+	int ret = 0;
+	u32 rsp = 0;
+
+	mutex_lock(&priv->reg_mutex);
+
+	an8855_write(priv, AN8855_ATWD2, (0x1 << port));
+	ret = an8855_fdb_cmd(priv, ATC_MAT(0xc) | AN8855_FDB_START, &rsp);
+	if (ret < 0)
+		goto err;
+
+	index = (rsp >> ATC_HASH) & ATC_HASH_MASK;
+	if (index == (cnt - 1))
+		flag = true;
+	else
+		flag = false;
+
+	banks = (rsp >> ATC_HIT) & ATC_HIT_MASK;
+	if (banks == 0) {
+		mutex_unlock(&priv->reg_mutex);
+		return 0;
+	}
+	for (i = 0; i < num; i++) {
+		if ((banks >> i) & 0x1) {
+			an8855_write(priv, AN8855_ATRDS, i);
+			udelay(1000);
+			an8855_fdb_read(priv, &_fdb);
+			if (!_fdb.live)
+				continue;
+			if (_fdb.port_mask & BIT(port)) {
+				ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp, data);
+				if (ret < 0)
+					continue;
+			}
+		}
+	}
+	while (1) {
+		if (flag == true)
+			break;
+
+		ret =
+		    an8855_fdb_cmd(priv, ATC_MAT(0xc) | AN8855_FDB_NEXT, &rsp);
+		index = (rsp >> ATC_HASH) & ATC_HASH_MASK;
+		if (index == (cnt - 1))
+			flag = true;
+		else
+			flag = false;
+
+		banks = (rsp >> ATC_HIT) & ATC_HIT_MASK;
+		if (banks == 0) {
+			mutex_unlock(&priv->reg_mutex);
+			return 0;
+		}
+		for (i = 0; i < num; i++) {
+			if ((banks >> i) & 0x1) {
+				an8855_write(priv, AN8855_ATRDS, i);
+				udelay(1000);
+				an8855_fdb_read(priv, &_fdb);
+				if (!_fdb.live)
+					continue;
+				if (_fdb.port_mask & BIT(port)) {
+					ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp,
+						 data);
+					if (ret < 0)
+						continue;
+				}
+			}
+		}
+	}
+
+err:
+	mutex_unlock(&priv->reg_mutex);
+	return 0;
+}
+
+static int
+an8855_vlan_cmd(struct an8855_priv *priv, enum an8855_vlan_cmd cmd, u16 vid)
+{
+	struct an8855_dummy_poll p;
+	u32 val;
+	int ret;
+
+	if (vid > 0xFFF) {
+		dev_err(priv->dev, "vid number invalid\n");
+		return -EINVAL;
+	}
+
+	val = VTCR_BUSY | VTCR_FUNC(cmd) | vid;
+	an8855_write(priv, AN8855_VTCR, val);
+
+	INIT_AN8855_DUMMY_POLL(&p, priv, AN8855_VTCR);
+	ret = readx_poll_timeout(_an8855_read, &p, val,
+				 !(val & VTCR_BUSY), 20, 200000);
+	if (ret < 0) {
+		dev_err(priv->dev, "poll timeout\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int
+an8855_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering)
+{
+	if (vlan_filtering) {
+		/* The port is being kept as VLAN-unaware port when bridge is
+		 * set up with vlan_filtering not being set, Otherwise, the
+		 * port and the corresponding CPU port is required the setup
+		 * for becoming a VLAN-aware port.
+		 */
+		an8855_port_set_vlan_aware(ds, port);
+		an8855_port_set_vlan_aware(ds, AN8855_CPU_PORT);
+	} else {
+		an8855_port_set_vlan_unaware(ds, port);
+	}
+
+	return 0;
+}
+
+static int
+an8855_port_vlan_prepare(struct dsa_switch *ds, int port,
+			 const struct switchdev_obj_port_vlan *vlan)
+{
+	/* nothing needed */
+	return 0;
+}
+
+static void
+an8855_hw_vlan_add(struct an8855_priv *priv, struct an8855_hw_vlan_entry *entry)
+{
+	u8 new_members;
+	u32 val;
+
+	new_members = entry->old_members | BIT(entry->port) |
+	    BIT(AN8855_CPU_PORT);
+
+	/* Validate the entry with independent learning, create egress tag per
+	 * VLAN and joining the port as one of the port members.
+	 */
+	val =
+	    an8855_read(priv,
+			AN8855_VARD0) & (ETAG_CTRL_MASK << PORT_EG_CTRL_SHIFT);
+	val |= (IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID);
+	an8855_write(priv, AN8855_VAWD0, val);
+	an8855_write(priv, AN8855_VAWD1, 0);
+
+	/* Decide whether adding tag or not for those outgoing packets from the
+	 * port inside the VLAN.
+	 */
+	val =
+	    entry->untagged ? AN8855_VLAN_EGRESS_UNTAG : AN8855_VLAN_EGRESS_TAG;
+	an8855_rmw(priv, AN8855_VAWD0,
+		   ETAG_CTRL_P_MASK(entry->port) << PORT_EG_CTRL_SHIFT,
+		   ETAG_CTRL_P(entry->port, val) << PORT_EG_CTRL_SHIFT);
+
+	/* CPU port is always taken as a tagged port for serving more than one
+	 * VLANs across and also being applied with egress type stack mode for
+	 * that VLAN tags would be appended after hardware special tag used as
+	 * DSA tag.
+	 */
+	an8855_rmw(priv, AN8855_VAWD0,
+		   ETAG_CTRL_P_MASK(AN8855_CPU_PORT) << PORT_EG_CTRL_SHIFT,
+		   ETAG_CTRL_P(AN8855_CPU_PORT,
+			       AN8855_VLAN_EGRESS_STACK) << PORT_EG_CTRL_SHIFT);
+}
+
+static void
+an8855_hw_vlan_del(struct an8855_priv *priv, struct an8855_hw_vlan_entry *entry)
+{
+	u8 new_members;
+	u32 val;
+
+	new_members = entry->old_members & ~BIT(entry->port);
+
+	val = an8855_read(priv, AN8855_VARD0);
+	if (!(val & VLAN_VALID)) {
+		dev_err(priv->dev, "Cannot be deleted due to invalid entry\n");
+		return;
+	}
+
+	/* If certain member apart from CPU port is still alive in the VLAN,
+	 * the entry would be kept valid. Otherwise, the entry is got to be
+	 * disabled.
+	 */
+	if (new_members && new_members != BIT(AN8855_CPU_PORT)) {
+		val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID;
+		an8855_write(priv, AN8855_VAWD0, val);
+	} else {
+		an8855_write(priv, AN8855_VAWD0, 0);
+		an8855_write(priv, AN8855_VAWD1, 0);
+	}
+}
+
+static void
+an8855_hw_vlan_update(struct an8855_priv *priv, u16 vid,
+		      struct an8855_hw_vlan_entry *entry,
+		      an8855_vlan_op vlan_op)
+{
+	u32 val;
+
+	/* Fetch entry */
+	an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
+
+	val = an8855_read(priv, AN8855_VARD0);
+
+	entry->old_members = (val >> PORT_MEM_SHFT) & PORT_MEM_MASK;
+
+	/* Manipulate entry */
+	vlan_op(priv, entry);
+
+	/* Flush result to hardware */
+	an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
+}
+
+static void
+an8855_port_vlan_add(struct dsa_switch *ds, int port,
+		     const struct switchdev_obj_port_vlan *vlan)
+{
+	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+	struct an8855_hw_vlan_entry new_entry;
+	struct an8855_priv *priv = ds->priv;
+	u16 vid;
+
+	mutex_lock(&priv->reg_mutex);
+
+	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
+		an8855_hw_vlan_entry_init(&new_entry, port, untagged);
+		an8855_hw_vlan_update(priv, vid, &new_entry,
+				      an8855_hw_vlan_add);
+	}
+
+	if (pvid) {
+		an8855_rmw(priv, AN8855_PVID_P(port), G0_PORT_VID_MASK,
+			   G0_PORT_VID(vlan->vid_end));
+		priv->ports[port].pvid = vlan->vid_end;
+	}
+
+	mutex_unlock(&priv->reg_mutex);
+}
+
+static int
+an8855_port_vlan_del(struct dsa_switch *ds, int port,
+		     const struct switchdev_obj_port_vlan *vlan)
+{
+	struct an8855_hw_vlan_entry target_entry;
+	struct an8855_priv *priv = ds->priv;
+	u16 vid, pvid;
+
+	mutex_lock(&priv->reg_mutex);
+
+	pvid = priv->ports[port].pvid;
+	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
+		an8855_hw_vlan_entry_init(&target_entry, port, 0);
+		an8855_hw_vlan_update(priv, vid, &target_entry,
+				      an8855_hw_vlan_del);
+
+		/* PVID is being restored to the default whenever the PVID port
+		 * is being removed from the VLAN.
+		 */
+		if (pvid == vid)
+			pvid = G0_PORT_VID_DEF;
+	}
+
+	an8855_rmw(priv, AN8855_PVID_P(port), G0_PORT_VID_MASK, pvid);
+	priv->ports[port].pvid = pvid;
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+static int an8855_port_mirror_add(struct dsa_switch *ds, int port,
+				  struct dsa_mall_mirror_tc_entry *mirror,
+				  bool ingress)
+{
+	struct an8855_priv *priv = ds->priv;
+	int monitor_port;
+	u32 val;
+
+	/* Check for existent entry */
+	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
+		return -EEXIST;
+
+	val = an8855_read(priv, AN8855_MIR);
+
+	/* AN8855 supports 4 monitor port, but only use first group */
+	monitor_port = AN8855_MIRROR_PORT_GET(val);
+	if (val & AN8855_MIRROR_EN && monitor_port != mirror->to_local_port)
+		return -EEXIST;
+
+	val |= AN8855_MIRROR_EN;
+	val &= ~AN8855_MIRROR_MASK;
+	val |= AN8855_MIRROR_PORT_SET(mirror->to_local_port);
+	an8855_write(priv, AN8855_MIR, val);
+
+	val = an8855_read(priv, AN8855_PCR_P(port));
+	if (ingress) {
+		val |= PORT_RX_MIR;
+		priv->mirror_rx |= BIT(port);
+	} else {
+		val |= PORT_TX_MIR;
+		priv->mirror_tx |= BIT(port);
+	}
+	an8855_write(priv, AN8855_PCR_P(port), val);
+
+	return 0;
+}
+
+static void an8855_port_mirror_del(struct dsa_switch *ds, int port,
+				   struct dsa_mall_mirror_tc_entry *mirror)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 val;
+
+	val = an8855_read(priv, AN8855_PCR_P(port));
+	if (mirror->ingress) {
+		val &= ~PORT_RX_MIR;
+		priv->mirror_rx &= ~BIT(port);
+	} else {
+		val &= ~PORT_TX_MIR;
+		priv->mirror_tx &= ~BIT(port);
+	}
+	an8855_write(priv, AN8855_PCR_P(port), val);
+
+	if (!priv->mirror_rx && !priv->mirror_tx) {
+		val = an8855_read(priv, AN8855_MIR);
+		val &= ~AN8855_MIRROR_EN;
+		an8855_write(priv, AN8855_MIR, val);
+	}
+}
+
+static enum dsa_tag_protocol
+air_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	if (port != AN8855_CPU_PORT) {
+		dev_warn(priv->dev, "port not matched with tagging CPU port\n");
+		return DSA_TAG_PROTO_NONE;
+	} else {
+		return DSA_TAG_PROTO_ARHT;
+	}
+}
+
+static int
+setup_unused_ports(struct dsa_switch *ds, u32 pm)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 egtag_mask = 0;
+	u32 egtag_val = 0;
+	int i;
+
+	if (!pm)
+		return 0;
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		if (!dsa_is_unused_port(ds, i))
+			continue;
+
+		/* Setup MAC port with maximum capability. */
+		if (i == 5)
+			if (priv->info->cpu_port_config)
+				priv->info->cpu_port_config(ds, i);
+
+		an8855_rmw(priv, AN8855_PORTMATRIX_P(i), PORTMATRIX_MASK,
+			   AN8855_PORTMATRIX_P(pm));
+		an8855_rmw(priv, AN8855_PCR_P(i), PCR_PORT_VLAN_MASK,
+			   AN8855_PORT_SECURITY_MODE);
+		egtag_mask |= ETAG_CTRL_P_MASK(i);
+		egtag_val |= ETAG_CTRL_P(i, AN8855_VLAN_EGRESS_UNTAG);
+	}
+
+	/* Add unused ports to VLAN2 group for using IVL fdb. */
+	an8855_write(priv, AN8855_VAWD0,
+		     IVL_MAC | VTAG_EN | PORT_MEM(pm) | VLAN_VALID);
+	an8855_rmw(priv, AN8855_VAWD0, egtag_mask << PORT_EG_CTRL_SHIFT,
+		   egtag_val << PORT_EG_CTRL_SHIFT);
+	an8855_write(priv, AN8855_VAWD1, 0);
+	an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, AN8855_RESERVED_VLAN);
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		if (!dsa_is_unused_port(ds, i))
+			continue;
+
+		an8855_rmw(priv, AN8855_PVID_P(i), G0_PORT_VID_MASK,
+			   G0_PORT_VID(AN8855_RESERVED_VLAN));
+		an8855_rmw(priv, AN8855_SSP_P(i), FID_PST_MASK,
+			   AN8855_STP_FORWARDING);
+
+		dev_dbg(ds->dev, "Add unused port%d to reserved VLAN%d group\n",
+			i, AN8855_RESERVED_VLAN);
+	}
+
+	return 0;
+}
+
+static int
+an8855_setup(struct dsa_switch *ds)
+{
+	struct an8855_priv *priv = ds->priv;
+	struct an8855_dummy_poll p;
+	u32 unused_pm = 0;
+	u32 val, id;
+	int ret, i;
+
+	/* Reset whole chip through gpio pin or memory-mapped registers for
+	 * different type of hardware
+	 */
+	gpiod_set_value_cansleep(priv->reset, 0);
+	usleep_range(100000, 150000);
+	gpiod_set_value_cansleep(priv->reset, 1);
+	usleep_range(100000, 150000);
+
+	/* Waiting for AN8855 got to stable */
+	INIT_AN8855_DUMMY_POLL(&p, priv, 0x1000009c);
+	ret = readx_poll_timeout(_an8855_read, &p, val, val != 0, 20, 1000000);
+	if (ret < 0) {
+		dev_err(priv->dev, "reset timeout\n");
+		return ret;
+	}
+
+	id = an8855_read(priv, AN8855_CREV);
+	if (id != AN8855_ID) {
+		dev_err(priv->dev, "chip %x can't be supported\n", id);
+		return -ENODEV;
+	}
+
+	/* Reset the switch through internal reset */
+	an8855_write(priv, AN8855_RST_CTRL, SYS_CTRL_SYS_RST);
+	usleep_range(100000, 110000);
+
+	/* change gphy smi address */
+	if (priv->phy_base_new > 0) {
+		an8855_write(priv, RG_GPHY_SMI_ADDR, priv->phy_base_new);
+		priv->phy_base = priv->phy_base_new;
+	}
+
+	/* Let phylink decide the interface later. */
+	priv->p5_interface = PHY_INTERFACE_MODE_NA;
+
+	/* BPDU to CPU port */
+	//an8855_rmw(priv, AN8855_CFC, AN8855_CPU_PMAP_MASK,
+	//         BIT(AN8855_CPU_PORT));
+	an8855_rmw(priv, AN8855_BPC, AN8855_BPDU_PORT_FW_MASK,
+		   AN8855_BPDU_CPU_ONLY);
+
+	/* Enable and reset MIB counters */
+	an8855_mib_reset(ds);
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		/* Disable forwarding by default on all ports */
+		an8855_rmw(priv, AN8855_PORTMATRIX_P(i), PORTMATRIX_MASK,
+			   PORTMATRIX_CLR);
+		if (dsa_is_unused_port(ds, i))
+			unused_pm |= BIT(i);
+		else if (dsa_is_cpu_port(ds, i))
+			an8855_cpu_port_enable(ds, i);
+		else
+			an8855_port_disable(ds, i);
+		/* Enable consistent egress tag */
+		an8855_rmw(priv, AN8855_PVC_P(i), PVC_EG_TAG_MASK,
+			   PVC_EG_TAG(AN8855_VLAN_EG_CONSISTENT));
+	}
+
+	an8855_phy_setup(ds);
+
+	/* Group and enable unused ports as a standalone dumb switch. */
+	setup_unused_ports(ds, unused_pm);
+
+	ds->configure_vlan_while_not_filtering = true;
+
+	/* Flush the FDB table */
+	ret = an8855_fdb_cmd(priv, AN8855_FDB_FLUSH, NULL);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static bool
+an8855_phy_supported(struct dsa_switch *ds, int port,
+		     const struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	switch (port) {
+	case 0:		/* Internal phy */
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+		if (state->interface != PHY_INTERFACE_MODE_GMII)
+			goto unsupported;
+		break;
+	case 5:
+		if (state->interface != PHY_INTERFACE_MODE_SGMII
+		    && state->interface != PHY_INTERFACE_MODE_RGMII
+			&& state->interface != PHY_INTERFACE_MODE_2500BASEX)
+			goto unsupported;
+		break;
+	default:
+		dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
+			port);
+		goto unsupported;
+	}
+
+	return true;
+
+unsupported:
+	return false;
+}
+
+static bool
+an8855_phy_mode_supported(struct dsa_switch *ds, int port,
+			  const struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->phy_mode_supported(ds, port, state);
+}
+
+static int
+an8855_rgmii_setup(struct an8855_priv *priv, u32 port,
+			      phy_interface_t interface,
+			      struct phy_device *phydev)
+{
+	return 0;
+}
+
+static void
+an8855_sgmii_validate(struct an8855_priv *priv, int port,
+				  unsigned long *supported)
+{
+	switch (port) {
+	case 5:
+		phylink_set(supported, 1000baseX_Full);
+		phylink_set(supported, 2500baseX_Full);
+	}
+}
+
+static void
+an8855_sgmii_link_up_force(struct dsa_switch *ds, int port,
+			   unsigned int mode, phy_interface_t interface,
+			   int speed, int duplex)
+{
+	/* For adjusting speed and duplex of SGMII force mode. */
+	if (interface != PHY_INTERFACE_MODE_SGMII ||
+	    phylink_autoneg_inband(mode))
+		return;
+}
+
+static bool
+an8855_is_mac_port(u32 port)
+{
+	return (port == 5);
+}
+
+static int
+an8855_set_hsgmii_mode(struct an8855_priv *priv)
+{
+	u32 val = 0;
+
+	/* PLL */
+	val = an8855_read(priv, QP_DIG_MODE_CTRL_1);
+	val &= ~(0x3 << 2);
+	val |= (0x1 << 2);
+	an8855_write(priv, QP_DIG_MODE_CTRL_1, val);
+
+	/* PLL - LPF */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~(0x3 << 0);
+	val |= (0x1 << 0);
+	val &= ~(0x7 << 2);
+	val |= (0x5 << 2);
+	val &= ~BITS(6, 7);
+	val &= ~(0x7 << 8);
+	val |= (0x3 << 8);
+	val |= BIT(29);
+	val &= ~BITS(12, 13);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - ICO */
+	val = an8855_read(priv, PLL_CTRL_4);
+	val |= BIT(2);
+	an8855_write(priv, PLL_CTRL_4, val);
+
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~BIT(14);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - CHP */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~(0xf << 16);
+	val |= (0x6 << 16);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - PFD */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~(0x3 << 20);
+	val |= (0x1 << 20);
+	val &= ~(0x3 << 24);
+	val |= (0x1 << 24);
+	val &= ~BIT(26);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - POSTDIV */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val |= BIT(22);
+	val &= ~BIT(27);
+	val &= ~BIT(28);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - SDM */
+	val = an8855_read(priv, PLL_CTRL_4);
+	val &= ~BITS(3, 4);
+	an8855_write(priv, PLL_CTRL_4, val);
+
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~BIT(30);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	val = an8855_read(priv, SS_LCPLL_PWCTL_SETTING_2);
+	val &= ~(0x3 << 16);
+	val |= (0x1 << 16);
+	an8855_write(priv, SS_LCPLL_PWCTL_SETTING_2, val);
+
+	an8855_write(priv, SS_LCPLL_TDC_FLT_2, 0x7a000000);
+	an8855_write(priv, SS_LCPLL_TDC_PCW_1, 0x7a000000);
+
+	val = an8855_read(priv, SS_LCPLL_TDC_FLT_5);
+	val &= ~BIT(24);
+	an8855_write(priv, SS_LCPLL_TDC_FLT_5, val);
+
+	val = an8855_read(priv, PLL_CK_CTRL_0);
+	val &= ~BIT(8);
+	an8855_write(priv, PLL_CK_CTRL_0, val);
+
+	/* PLL - SS */
+	val = an8855_read(priv, PLL_CTRL_3);
+	val &= ~BITS(0, 15);
+	an8855_write(priv, PLL_CTRL_3, val);
+
+	val = an8855_read(priv, PLL_CTRL_4);
+	val &= ~BITS(0, 1);
+	an8855_write(priv, PLL_CTRL_4, val);
+
+	val = an8855_read(priv, PLL_CTRL_3);
+	val &= ~BITS(16, 31);
+	an8855_write(priv, PLL_CTRL_3, val);
+
+	/* PLL - TDC */
+	val = an8855_read(priv, PLL_CK_CTRL_0);
+	val &= ~BIT(9);
+	an8855_write(priv, PLL_CK_CTRL_0, val);
+
+	val = an8855_read(priv, RG_QP_PLL_SDM_ORD);
+	val |= BIT(3);
+	val |= BIT(4);
+	an8855_write(priv, RG_QP_PLL_SDM_ORD, val);
+
+	val = an8855_read(priv, RG_QP_RX_DAC_EN);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	an8855_write(priv, RG_QP_RX_DAC_EN, val);
+
+	/* TCL Disable (only for Co-SIM) */
+	val = an8855_read(priv, PON_RXFEDIG_CTRL_0);
+	val &= ~BIT(12);
+	an8855_write(priv, PON_RXFEDIG_CTRL_0, val);
+
+	/* TX Init */
+	val = an8855_read(priv, RG_QP_TX_MODE_16B_EN);
+	val &= ~BIT(0);
+	val &= ~(0xffff << 16);
+	val |= (0x4 << 16);
+	an8855_write(priv, RG_QP_TX_MODE_16B_EN, val);
+
+	/* RX Control */
+	val = an8855_read(priv, RG_QP_RXAFE_RESERVE);
+	val |= BIT(11);
+	an8855_write(priv, RG_QP_RXAFE_RESERVE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_LPF_MJV_LIM);
+	val &= ~(0x3 << 4);
+	val |= (0x1 << 4);
+	an8855_write(priv, RG_QP_CDR_LPF_MJV_LIM, val);
+
+	val = an8855_read(priv, RG_QP_CDR_LPF_SETVALUE);
+	val &= ~(0xf << 25);
+	val |= (0x1 << 25);
+	val &= ~(0x7 << 29);
+	val |= (0x3 << 29);
+	an8855_write(priv, RG_QP_CDR_LPF_SETVALUE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x1f << 8);
+	val |= (0xf << 8);
+	an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~(0x3f << 0);
+	val |= (0x19 << 0);
+	val &= ~BIT(6);
+	an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF);
+	val &= ~(0x7f << 6);
+	val |= (0x21 << 6);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	val &= ~BIT(13);
+	an8855_write(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~BIT(30);
+	an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x7 << 24);
+	val |= (0x4 << 24);
+	an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_read(priv, PLL_CTRL_0);
+	val |= BIT(0);
+	an8855_write(priv, PLL_CTRL_0, val);
+
+	val = an8855_read(priv, RX_CTRL_26);
+	val &= ~BIT(23);
+	val |= BIT(26);
+	an8855_write(priv, RX_CTRL_26, val);
+
+	val = an8855_read(priv, RX_DLY_0);
+	val &= ~(0xff << 0);
+	val |= (0x6f << 0);
+	val |= BITS(8, 13);
+	an8855_write(priv, RX_DLY_0, val);
+
+	val = an8855_read(priv, RX_CTRL_42);
+	val &= ~(0x1fff << 0);
+	val |= (0x150 << 0);
+	an8855_write(priv, RX_CTRL_42, val);
+
+	val = an8855_read(priv, RX_CTRL_2);
+	val &= ~(0x1fff << 16);
+	val |= (0x150 << 16);
+	an8855_write(priv, RX_CTRL_2, val);
+
+	val = an8855_read(priv, PON_RXFEDIG_CTRL_9);
+	val &= ~(0x7 << 0);
+	val |= (0x1 << 0);
+	an8855_write(priv, PON_RXFEDIG_CTRL_9, val);
+
+	val = an8855_read(priv, RX_CTRL_8);
+	val &= ~(0xfff << 16);
+	val |= (0x200 << 16);
+	val &= ~(0x7fff << 14);
+	val |= (0xfff << 14);
+	an8855_write(priv, RX_CTRL_8, val);
+
+	/* Frequency memter */
+	val = an8855_read(priv, RX_CTRL_5);
+	val &= ~(0xfffff << 10);
+	val |= (0x10 << 10);
+	an8855_write(priv, RX_CTRL_5, val);
+
+	val = an8855_read(priv, RX_CTRL_6);
+	val &= ~(0xfffff << 0);
+	val |= (0x64 << 0);
+	an8855_write(priv, RX_CTRL_6, val);
+
+	val = an8855_read(priv, RX_CTRL_7);
+	val &= ~(0xfffff << 0);
+	val |= (0x2710 << 0);
+	an8855_write(priv, RX_CTRL_7, val);
+
+	/* PCS Init */
+	val = an8855_read(priv, RG_HSGMII_PCS_CTROL_1);
+	val &= ~BIT(30);
+	an8855_write(priv, RG_HSGMII_PCS_CTROL_1, val);
+
+	/* Rate Adaption */
+	val = an8855_read(priv, RATE_ADP_P0_CTRL_0);
+	val &= ~BIT(31);
+	an8855_write(priv, RATE_ADP_P0_CTRL_0, val);
+
+	val = an8855_read(priv, RG_RATE_ADAPT_CTRL_0);
+	val |= BIT(0);
+	val |= BIT(4);
+	val |= BITS(26, 27);
+	an8855_write(priv, RG_RATE_ADAPT_CTRL_0, val);
+
+	/* Disable AN */
+	val = an8855_read(priv, SGMII_REG_AN0);
+	val &= ~BIT(12);
+	an8855_write(priv, SGMII_REG_AN0, val);
+
+	/* Force Speed */
+	val = an8855_read(priv, SGMII_STS_CTRL_0);
+	val |= BIT(2);
+	val |= BITS(4, 5);
+	an8855_write(priv, SGMII_STS_CTRL_0, val);
+
+	/* bypass flow control to MAC */
+	an8855_write(priv, MSG_RX_LIK_STS_0, 0x01010107);
+	an8855_write(priv, MSG_RX_LIK_STS_2, 0x00000EEF);
+
+	return 0;
+}
+
+static int
+an8855_sgmii_setup(struct an8855_priv *priv, int mode)
+{
+	u32 val = 0;
+
+	/* PMA Init */
+	/* PLL */
+	val = an8855_read(priv, QP_DIG_MODE_CTRL_1);
+	val &= ~BITS(2, 3);
+	an8855_write(priv, QP_DIG_MODE_CTRL_1, val);
+
+	/* PLL - LPF */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~(0x3 << 0);
+	val |= (0x1 << 0);
+	val &= ~(0x7 << 2);
+	val |= (0x5 << 2);
+	val &= ~BITS(6, 7);
+	val &= ~(0x7 << 8);
+	val |= (0x3 << 8);
+	val |= BIT(29);
+	val &= ~BITS(12, 13);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - ICO */
+	val = an8855_read(priv, PLL_CTRL_4);
+	val |= BIT(2);
+	an8855_write(priv, PLL_CTRL_4, val);
+
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~BIT(14);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - CHP */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~(0xf << 16);
+	val |= (0x4 << 16);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - PFD */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~(0x3 << 20);
+	val |= (0x1 << 20);
+	val &= ~(0x3 << 24);
+	val |= (0x1 << 24);
+	val &= ~BIT(26);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - POSTDIV */
+	val = an8855_read(priv, PLL_CTRL_2);
+	val |= BIT(22);
+	val &= ~BIT(27);
+	val &= ~BIT(28);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	/* PLL - SDM */
+	val = an8855_read(priv, PLL_CTRL_4);
+	val &= ~BITS(3, 4);
+	an8855_write(priv, PLL_CTRL_4, val);
+
+	val = an8855_read(priv, PLL_CTRL_2);
+	val &= ~BIT(30);
+	an8855_write(priv, PLL_CTRL_2, val);
+
+	val = an8855_read(priv, SS_LCPLL_PWCTL_SETTING_2);
+	val &= ~(0x3 << 16);
+	val |= (0x1 << 16);
+	an8855_write(priv, SS_LCPLL_PWCTL_SETTING_2, val);
+
+	an8855_write(priv, SS_LCPLL_TDC_FLT_2, 0x48000000);
+	an8855_write(priv, SS_LCPLL_TDC_PCW_1, 0x48000000);
+
+	val = an8855_read(priv, SS_LCPLL_TDC_FLT_5);
+	val &= ~BIT(24);
+	an8855_write(priv, SS_LCPLL_TDC_FLT_5, val);
+
+	val = an8855_read(priv, PLL_CK_CTRL_0);
+	val &= ~BIT(8);
+	an8855_write(priv, PLL_CK_CTRL_0, val);
+
+	/* PLL - SS */
+	val = an8855_read(priv, PLL_CTRL_3);
+	val &= ~BITS(0, 15);
+	an8855_write(priv, PLL_CTRL_3, val);
+
+	val = an8855_read(priv, PLL_CTRL_4);
+	val &= ~BITS(0, 1);
+	an8855_write(priv, PLL_CTRL_4, val);
+
+	val = an8855_read(priv, PLL_CTRL_3);
+	val &= ~BITS(16, 31);
+	an8855_write(priv, PLL_CTRL_3, val);
+
+	/* PLL - TDC */
+	val = an8855_read(priv, PLL_CK_CTRL_0);
+	val &= ~BIT(9);
+	an8855_write(priv, PLL_CK_CTRL_0, val);
+
+	val = an8855_read(priv, RG_QP_PLL_SDM_ORD);
+	val |= BIT(3);
+	val |= BIT(4);
+	an8855_write(priv, RG_QP_PLL_SDM_ORD, val);
+
+	val = an8855_read(priv, RG_QP_RX_DAC_EN);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	an8855_write(priv, RG_QP_RX_DAC_EN, val);
+
+	/* PLL - TCL Disable (only for Co-SIM) */
+	val = an8855_read(priv, PON_RXFEDIG_CTRL_0);
+	val &= ~BIT(12);
+	an8855_write(priv, PON_RXFEDIG_CTRL_0, val);
+
+	/* TX Init */
+	val = an8855_read(priv, RG_QP_TX_MODE_16B_EN);
+	val &= ~BIT(0);
+	val &= ~BITS(16, 31);
+	an8855_write(priv, RG_QP_TX_MODE_16B_EN, val);
+
+	/* RX Init */
+	val = an8855_read(priv, RG_QP_RXAFE_RESERVE);
+	val |= BIT(11);
+	an8855_write(priv, RG_QP_RXAFE_RESERVE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_LPF_MJV_LIM);
+	val &= ~(0x3 << 4);
+	val |= (0x2 << 4);
+	an8855_write(priv, RG_QP_CDR_LPF_MJV_LIM, val);
+
+	val = an8855_read(priv, RG_QP_CDR_LPF_SETVALUE);
+	val &= ~(0xf << 25);
+	val |= (0x1 << 25);
+	val &= ~(0x7 << 29);
+	val |= (0x6 << 29);
+	an8855_write(priv, RG_QP_CDR_LPF_SETVALUE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x1f << 8);
+	val |= (0xc << 8);
+	an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~(0x3f << 0);
+	val |= (0x19 << 0);
+	val &= ~BIT(6);
+	an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF);
+	val &= ~(0x7f << 6);
+	val |= (0x21 << 6);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	val &= ~BIT(13);
+	an8855_write(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~BIT(30);
+	an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x7 << 24);
+	val |= (0x4 << 24);
+	an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_read(priv, PLL_CTRL_0);
+	val |= BIT(0);
+	an8855_write(priv, PLL_CTRL_0, val);
+
+	val = an8855_read(priv, RX_CTRL_26);
+	val &= ~BIT(23);
+	if (mode == SGMII_MODE_AN)
+		val |= BIT(26);
+
+	an8855_write(priv, RX_CTRL_26, val);
+
+	val = an8855_read(priv, RX_DLY_0);
+	val &= ~(0xff << 0);
+	val |= (0x6f << 0);
+	val |= BITS(8, 13);
+	an8855_write(priv, RX_DLY_0, val);
+
+	val = an8855_read(priv, RX_CTRL_42);
+	val &= ~(0x1fff << 0);
+	val |= (0x150 << 0);
+	an8855_write(priv, RX_CTRL_42, val);
+
+	val = an8855_read(priv, RX_CTRL_2);
+	val &= ~(0x1fff << 16);
+	val |= (0x150 << 16);
+	an8855_write(priv, RX_CTRL_2, val);
+
+	val = an8855_read(priv, PON_RXFEDIG_CTRL_9);
+	val &= ~(0x7 << 0);
+	val |= (0x1 << 0);
+	an8855_write(priv, PON_RXFEDIG_CTRL_9, val);
+
+	val = an8855_read(priv, RX_CTRL_8);
+	val &= ~(0xfff << 16);
+	val |= (0x200 << 16);
+	val &= ~(0x7fff << 0);
+	val |= (0xfff << 0);
+	an8855_write(priv, RX_CTRL_8, val);
+
+	/* Frequency memter */
+	val = an8855_read(priv, RX_CTRL_5);
+	val &= ~(0xfffff << 10);
+	val |= (0x28 << 10);
+	an8855_write(priv, RX_CTRL_5, val);
+
+	val = an8855_read(priv, RX_CTRL_6);
+	val &= ~(0xfffff << 0);
+	val |= (0x64 << 0);
+	an8855_write(priv, RX_CTRL_6, val);
+
+	val = an8855_read(priv, RX_CTRL_7);
+	val &= ~(0xfffff << 0);
+	val |= (0x2710 << 0);
+	an8855_write(priv, RX_CTRL_7, val);
+
+	if (mode == SGMII_MODE_FORCE) {
+		/* PCS Init */
+		val = an8855_read(priv, QP_DIG_MODE_CTRL_0);
+		val &= ~BIT(0);
+		val &= ~BITS(4, 5);
+		an8855_write(priv, QP_DIG_MODE_CTRL_0, val);
+
+		val = an8855_read(priv, RG_HSGMII_PCS_CTROL_1);
+		val &= ~BIT(30);
+		an8855_write(priv, RG_HSGMII_PCS_CTROL_1, val);
+
+		/* Rate Adaption - GMII path config. */
+		val = an8855_read(priv, RG_AN_SGMII_MODE_FORCE);
+		val |= BIT(0);
+		val &= ~BITS(4, 5);
+		an8855_write(priv, RG_AN_SGMII_MODE_FORCE, val);
+
+		val = an8855_read(priv, SGMII_STS_CTRL_0);
+		val |= BIT(2);
+		val &= ~(0x3 << 4);
+		val |= (0x2 << 4);
+		an8855_write(priv, SGMII_STS_CTRL_0, val);
+
+		val = an8855_read(priv, SGMII_REG_AN0);
+		val &= ~BIT(12);
+		an8855_write(priv, SGMII_REG_AN0, val);
+
+		val = an8855_read(priv, PHY_RX_FORCE_CTRL_0);
+		val |= BIT(4);
+		an8855_write(priv, PHY_RX_FORCE_CTRL_0, val);
+
+		val = an8855_read(priv, RATE_ADP_P0_CTRL_0);
+		val &= ~BITS(0, 3);
+		val |= BIT(28);
+		an8855_write(priv, RATE_ADP_P0_CTRL_0, val);
+
+		val = an8855_read(priv, RG_RATE_ADAPT_CTRL_0);
+		val |= BIT(0);
+		val |= BIT(4);
+		val |= BITS(26, 27);
+		an8855_write(priv, RG_RATE_ADAPT_CTRL_0, val);
+	} else {
+		/* PCS Init */
+		val = an8855_read(priv, RG_HSGMII_PCS_CTROL_1);
+		val &= ~BIT(30);
+		an8855_write(priv, RG_HSGMII_PCS_CTROL_1, val);
+
+		/* Set AN Ability - Interrupt */
+		val = an8855_read(priv, SGMII_REG_AN_FORCE_CL37);
+		val |= BIT(0);
+		an8855_write(priv, SGMII_REG_AN_FORCE_CL37, val);
+
+		val = an8855_read(priv, SGMII_REG_AN_13);
+		val &= ~(0x3f << 0);
+		val |= (0xb << 0);
+		val |= BIT(8);
+		an8855_write(priv, SGMII_REG_AN_13, val);
+
+		/* Rate Adaption - GMII path config. */
+		val = an8855_read(priv, SGMII_REG_AN0);
+		val |= BIT(12);
+		an8855_write(priv, SGMII_REG_AN0, val);
+
+		val = an8855_read(priv, MII_RA_AN_ENABLE);
+		val |= BIT(0);
+		an8855_write(priv, MII_RA_AN_ENABLE, val);
+
+		val = an8855_read(priv, RATE_ADP_P0_CTRL_0);
+		val |= BIT(28);
+		an8855_write(priv, RATE_ADP_P0_CTRL_0, val);
+
+		val = an8855_read(priv, RG_RATE_ADAPT_CTRL_0);
+		val |= BIT(0);
+		val |= BIT(4);
+		val |= BITS(26, 27);
+		an8855_write(priv, RG_RATE_ADAPT_CTRL_0, val);
+
+		/* Only for Co-SIM */
+
+		/* AN Speed up (Only for Co-SIM) */
+
+		/* Restart AN */
+		val = an8855_read(priv, SGMII_REG_AN0);
+		val |= BIT(9);
+		val |= BIT(15);
+		an8855_write(priv, SGMII_REG_AN0, val);
+	}
+
+	/* bypass flow control to MAC */
+	an8855_write(priv, MSG_RX_LIK_STS_0, 0x01010107);
+	an8855_write(priv, MSG_RX_LIK_STS_2, 0x00000EEF);
+
+	return 0;
+}
+
+static int
+an8855_sgmii_setup_mode_force(struct an8855_priv *priv, u32 port,
+					 phy_interface_t interface)
+{
+	return an8855_sgmii_setup(priv, SGMII_MODE_FORCE);
+}
+
+static int
+an8855_sgmii_setup_mode_an(struct an8855_priv *priv, int port,
+				      phy_interface_t interface)
+{
+	return an8855_sgmii_setup(priv, SGMII_MODE_AN);
+}
+
+static int
+an8855_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+		  phy_interface_t interface)
+{
+	struct an8855_priv *priv = ds->priv;
+	struct phy_device *phydev;
+	const struct dsa_port *dp;
+
+	if (!an8855_is_mac_port(port)) {
+		dev_err(priv->dev, "port %d is not a MAC port\n", port);
+		return -EINVAL;
+	}
+
+	switch (interface) {
+	case PHY_INTERFACE_MODE_RGMII:
+		dp = dsa_to_port(ds, port);
+		phydev = (dp->slave) ? dp->slave->phydev : NULL;
+		return an8855_rgmii_setup(priv, port, interface, phydev);
+	case PHY_INTERFACE_MODE_SGMII:
+		return an8855_sgmii_setup_mode_an(priv, port, interface);
+	case PHY_INTERFACE_MODE_2500BASEX:
+		if (phylink_autoneg_inband(mode))
+			return -EINVAL;
+		return an8855_set_hsgmii_mode(priv);
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static int
+an8855_sw_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+		     const struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->mac_port_config(ds, port, mode, state->interface);
+}
+
+static void
+an8855_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
+			  const struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 mcr_cur, mcr_new;
+
+	if (!an8855_phy_mode_supported(ds, port, state))
+		goto unsupported;
+
+	switch (port) {
+	case 0:		/* Internal phy */
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+		if (state->interface != PHY_INTERFACE_MODE_GMII)
+			goto unsupported;
+		break;
+	case 5:
+		if (priv->p5_interface == state->interface)
+			break;
+
+		if (an8855_sw_mac_config(ds, port, mode, state) < 0)
+			goto unsupported;
+
+		priv->p5_interface = state->interface;
+		break;
+	default:
+unsupported:
+		dev_err(ds->dev, "%s: unsupported %s port: %i\n",
+			__func__, phy_modes(state->interface), port);
+		return;
+	}
+
+	if (phylink_autoneg_inband(mode) &&
+	    state->interface != PHY_INTERFACE_MODE_SGMII) {
+		dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
+			__func__);
+		return;
+	}
+
+	mcr_cur = an8855_read(priv, AN8855_PMCR_P(port));
+	mcr_new = mcr_cur;
+	mcr_new &= ~PMCR_LINK_SETTINGS_MASK;
+	mcr_new |= PMCR_IFG_XMIT(1) | PMCR_MAC_MODE | PMCR_BACKOFF_EN |
+	    PMCR_BACKPR_EN | AN8855_FORCE_MODE;
+
+	if (mcr_new != mcr_cur)
+		an8855_write(priv, AN8855_PMCR_P(port), mcr_new);
+}
+
+static void
+an8855_phylink_mac_link_down(struct dsa_switch *ds, int port,
+					 unsigned int mode,
+					 phy_interface_t interface)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	an8855_clear(priv, AN8855_PMCR_P(port), PMCR_LINK_SETTINGS_MASK);
+}
+
+static void
+an8855_phylink_mac_link_up(struct dsa_switch *ds, int port,
+				       unsigned int mode,
+				       phy_interface_t interface,
+				       struct phy_device *phydev,
+				       int speed, int duplex,
+				       bool tx_pause, bool rx_pause)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 mcr;
+
+	mcr = an8855_read(priv, AN8855_PMCR_P(port));
+	mcr |= PMCR_RX_EN | PMCR_TX_EN | PMCR_FORCE_LNK;
+	mcr &=
+	    ~(PMCR_FORCE_FDX | PMCR_SPEED_MASK | PMCR_TX_FC_EN | PMCR_RX_FC_EN);
+
+	if (interface == PHY_INTERFACE_MODE_RGMII
+	    || interface == PHY_INTERFACE_MODE_SGMII) {
+		speed = SPEED_1000;
+		duplex = DUPLEX_FULL;
+	} else if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+		speed = SPEED_2500;
+		duplex = DUPLEX_FULL;
+	}
+
+	switch (speed) {
+	case SPEED_2500:
+		mcr |= PMCR_FORCE_SPEED_2500;
+		break;
+	case SPEED_1000:
+		mcr |= PMCR_FORCE_SPEED_1000;
+		if (priv->eee_enable & BIT(port))
+			mcr |= PMCR_FORCE_EEE1G;
+		break;
+	case SPEED_100:
+		mcr |= PMCR_FORCE_SPEED_100;
+		if (priv->eee_enable & BIT(port))
+			mcr |= PMCR_FORCE_EEE100;
+		break;
+	}
+	if (duplex == DUPLEX_FULL) {
+		mcr |= PMCR_FORCE_FDX;
+		if (tx_pause)
+			mcr |= PMCR_TX_FC_EN;
+		if (rx_pause)
+			mcr |= PMCR_RX_FC_EN;
+	}
+
+	an8855_write(priv, AN8855_PMCR_P(port), mcr);
+}
+
+static int
+an8855_cpu_port_config(struct dsa_switch *ds, int port)
+{
+	struct an8855_priv *priv = ds->priv;
+	phy_interface_t interface = PHY_INTERFACE_MODE_NA;
+	int speed;
+
+	switch (port) {
+	case 5:
+		interface = PHY_INTERFACE_MODE_2500BASEX;
+
+		priv->p5_interface = interface;
+		break;
+	};
+	if (interface == PHY_INTERFACE_MODE_NA)
+		dev_err(priv->dev, "invalid interface\n");
+
+	if (interface == PHY_INTERFACE_MODE_2500BASEX)
+		speed = SPEED_2500;
+	else
+		speed = SPEED_1000;
+
+	an8855_mac_config(ds, port, MLO_AN_FIXED, interface);
+	an8855_write(priv, AN8855_PMCR_P(port),
+		     PMCR_CPU_PORT_SETTING(priv->id));
+	an8855_phylink_mac_link_up(ds, port, MLO_AN_FIXED, interface, NULL,
+				   speed, DUPLEX_FULL, true, true);
+
+	return 0;
+}
+
+static void
+an8855_mac_port_validate(struct dsa_switch *ds, int port,
+				     unsigned long *supported)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	an8855_sgmii_validate(priv, port, supported);
+}
+
+static void
+an8855_phylink_validate(struct dsa_switch *ds, int port,
+			unsigned long *supported,
+			struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = {0,};
+
+	if (state->interface != PHY_INTERFACE_MODE_NA &&
+	    !an8855_phy_mode_supported(ds, port, state)) {
+		linkmode_zero(supported);
+		return;
+	}
+
+	phylink_set_port_modes(mask);
+
+	if (state->interface != PHY_INTERFACE_MODE_TRGMII
+	    || state->interface != PHY_INTERFACE_MODE_USXGMII
+	    || state->interface != PHY_INTERFACE_MODE_10GKR
+	    || !phy_interface_mode_is_8023z(state->interface)) {
+		phylink_set(mask, 10baseT_Half);
+		phylink_set(mask, 10baseT_Full);
+		phylink_set(mask, 100baseT_Half);
+		phylink_set(mask, 100baseT_Full);
+		phylink_set(mask, Autoneg);
+	}
+
+	/* This switch only supports 1G full-duplex. */
+	if (state->interface != PHY_INTERFACE_MODE_MII)
+		phylink_set(mask, 1000baseT_Full);
+
+	priv->info->mac_port_validate(ds, port, mask);
+
+	phylink_set(mask, Pause);
+	phylink_set(mask, Asym_Pause);
+
+	linkmode_and(supported, supported, mask);
+	linkmode_and(state->advertising, state->advertising, mask);
+
+	/* We can only operate at 2500BaseX or 1000BaseX.  If requested
+	 * to advertise both, only report advertising at 2500BaseX.
+	 */
+	phylink_helper_basex_speed(state);
+}
+
+static int
+an8855_get_mac_eee(struct dsa_switch *ds, int port,
+			      struct ethtool_eee *e)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 eeecr, pmsr, ckgcr;
+
+	e->eee_enabled = !!(priv->eee_enable & BIT(port));
+
+	if (e->eee_enabled) {
+		eeecr = an8855_read(priv, AN8855_PMEEECR_P(port));
+		e->tx_lpi_enabled = !(eeecr & LPI_MODE_EN);
+		ckgcr = an8855_read(priv, AN8855_CKGCR);
+		e->tx_lpi_timer =
+		    ((ckgcr & LPI_TXIDLE_THD_MASK) >> LPI_TXIDLE_THD) / 500;
+		pmsr = an8855_read(priv, AN8855_PMSR_P(port));
+		e->eee_active = e->eee_enabled
+		    && !!(pmsr & (PMSR_EEE1G | PMSR_EEE100M));
+	} else {
+		e->tx_lpi_enabled = 0;
+		e->tx_lpi_timer = 0;
+		e->eee_active = 0;
+	}
+	return 0;
+}
+
+static int
+an8855_set_mac_eee(struct dsa_switch *ds, int port,
+			      struct ethtool_eee *e)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 eeecr;
+
+	if (e->eee_enabled) {
+		priv->eee_enable |= BIT(port);
+		eeecr = an8855_read(priv, AN8855_PMEEECR_P(port));
+		eeecr &= ~LPI_MODE_EN;
+		if (e->tx_lpi_enabled)
+			eeecr |= LPI_MODE_EN;
+		an8855_write(priv, AN8855_PMEEECR_P(port), eeecr);
+	} else {
+		priv->eee_enable &= ~(BIT(port));
+		eeecr = an8855_read(priv, AN8855_PMEEECR_P(port));
+		eeecr &= ~LPI_MODE_EN;
+		an8855_write(priv, AN8855_PMEEECR_P(port), eeecr);
+	}
+
+	return 0;
+}
+
+static int
+an8855_phylink_mac_link_state(struct dsa_switch *ds, int port,
+			      struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 pmsr;
+
+	if (port < 0 || port >= AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	pmsr = an8855_read(priv, AN8855_PMSR_P(port));
+
+	state->link = (pmsr & PMSR_LINK);
+	state->an_complete = state->link;
+	state->duplex = !!(pmsr & PMSR_DPX);
+
+	switch (pmsr & PMSR_SPEED_MASK) {
+	case PMSR_SPEED_10:
+		state->speed = SPEED_10;
+		break;
+	case PMSR_SPEED_100:
+		state->speed = SPEED_100;
+		break;
+	case PMSR_SPEED_1000:
+		state->speed = SPEED_1000;
+		break;
+	case PMSR_SPEED_2500:
+		state->speed = SPEED_2500;
+		break;
+	default:
+		state->speed = SPEED_UNKNOWN;
+		break;
+	}
+
+	state->pause &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX);
+	if (pmsr & PMSR_RX_FC)
+		state->pause |= MLO_PAUSE_RX;
+	if (pmsr & PMSR_TX_FC)
+		state->pause |= MLO_PAUSE_TX;
+
+	return 1;
+}
+
+static int
+an8855_sw_phylink_mac_link_state(struct dsa_switch *ds, int port,
+				 struct phylink_link_state *state)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->mac_port_get_state(ds, port, state);
+}
+
+static int
+an8855_sw_setup(struct dsa_switch *ds)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->sw_setup(ds);
+}
+
+static int
+an8855_sw_phy_read(struct dsa_switch *ds, int port, int regnum)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->phy_read(ds, port, regnum);
+}
+
+static int
+an8855_sw_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->phy_write(ds, port, regnum, val);
+}
+
+static const struct dsa_switch_ops an8855_switch_ops = {
+	.get_tag_protocol = air_get_tag_protocol,
+	.setup = an8855_sw_setup,
+	.get_strings = an8855_get_strings,
+	.phy_read = an8855_sw_phy_read,
+	.phy_write = an8855_sw_phy_write,
+	.get_ethtool_stats = an8855_get_ethtool_stats,
+	.get_sset_count = an8855_get_sset_count,
+	.port_enable = an8855_port_enable,
+	.port_disable = an8855_port_disable,
+	.port_stp_state_set = an8855_stp_state_set,
+	.port_bridge_join = an8855_port_bridge_join,
+	.port_bridge_leave = an8855_port_bridge_leave,
+	.port_fdb_add = an8855_port_fdb_add,
+	.port_fdb_del = an8855_port_fdb_del,
+	.port_fdb_dump = an8855_port_fdb_dump,
+	.port_vlan_filtering = an8855_port_vlan_filtering,
+	.port_vlan_prepare = an8855_port_vlan_prepare,
+	.port_vlan_add = an8855_port_vlan_add,
+	.port_vlan_del = an8855_port_vlan_del,
+	.port_mirror_add = an8855_port_mirror_add,
+	.port_mirror_del = an8855_port_mirror_del,
+	.phylink_validate = an8855_phylink_validate,
+	.phylink_mac_link_state = an8855_sw_phylink_mac_link_state,
+	.phylink_mac_config = an8855_phylink_mac_config,
+	.phylink_mac_link_down = an8855_phylink_mac_link_down,
+	.phylink_mac_link_up = an8855_phylink_mac_link_up,
+	.get_mac_eee = an8855_get_mac_eee,
+	.set_mac_eee = an8855_set_mac_eee,
+};
+
+static const struct an8855_dev_info an8855_table[] = {
+	[ID_AN8855] = {
+		.id = ID_AN8855,
+		.sw_setup = an8855_setup,
+		.phy_read = an8855_phy_read,
+		.phy_write = an8855_phy_write,
+		.pad_setup = an8855_pad_setup,
+		.cpu_port_config = an8855_cpu_port_config,
+		.phy_mode_supported = an8855_phy_supported,
+		.mac_port_validate = an8855_mac_port_validate,
+		.mac_port_get_state = an8855_phylink_mac_link_state,
+		.mac_port_config = an8855_mac_config,
+	},
+};
+
+static const struct of_device_id an8855_of_match[] = {
+	{.compatible = "airoha,an8855", .data = &an8855_table[ID_AN8855],
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, an8855_of_match);
+
+static int
+an8855_probe(struct mdio_device *mdiodev)
+{
+	struct an8855_priv *priv;
+	struct device_node *dn;
+	struct device_node *switch_node = NULL;
+	int ret;
+
+	dn = mdiodev->dev.of_node;
+
+	priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->ds = dsa_switch_alloc(&mdiodev->dev, AN8855_NUM_PORTS);
+	if (!priv->ds)
+		return -ENOMEM;
+
+	/* Get the hardware identifier from the devicetree node.
+	 * We will need it for some of the clock and regulator setup.
+	 */
+	priv->info = of_device_get_match_data(&mdiodev->dev);
+	if (!priv->info)
+		return -EINVAL;
+
+	/* Sanity check if these required device operations are filled
+	 * properly.
+	 */
+	if (!priv->info->sw_setup || !priv->info->pad_setup ||
+	    !priv->info->phy_read || !priv->info->phy_write ||
+	    !priv->info->phy_mode_supported ||
+	    !priv->info->mac_port_validate ||
+	    !priv->info->mac_port_get_state || !priv->info->mac_port_config)
+		return -EINVAL;
+
+	dev_info(&mdiodev->dev, "Airoha AN8855 DSA driver, version %s\n",
+			ARHT_AN8855_DSA_DRIVER_VER);
+	priv->phy_base = AN8855_GPHY_SMI_ADDR_DEFAULT;
+	priv->id = priv->info->id;
+
+	priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset",
+					      GPIOD_OUT_LOW);
+	if (IS_ERR(priv->reset)) {
+		dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
+		return PTR_ERR(priv->reset);
+	}
+
+	switch_node = of_find_node_by_name(NULL, "switch0");
+	if (switch_node) {
+		priv->base = of_iomap(switch_node, 0);
+		if (priv->base == NULL) {
+			dev_err(&mdiodev->dev, "of_iomap failed\n");
+			return -ENOMEM;
+		}
+	}
+
+	ret = of_property_read_u32(dn, "changesmiaddr", &priv->phy_base_new);
+	if ((ret < 0) || (priv->phy_base_new > 0x1f))
+		priv->phy_base_new = -1;
+
+	priv->bus = mdiodev->bus;
+	priv->dev = &mdiodev->dev;
+	priv->ds->priv = priv;
+	priv->ds->ops = &an8855_switch_ops;
+	mutex_init(&priv->reg_mutex);
+	dev_set_drvdata(&mdiodev->dev, priv);
+
+	ret = dsa_register_switch(priv->ds);
+	if (ret) {
+		if (priv->base)
+			iounmap(priv->base);
+
+		return ret;
+	}
+	an8855_nl_init(&priv);
+
+	return 0;
+}
+
+static void
+an8855_remove(struct mdio_device *mdiodev)
+{
+	struct an8855_priv *priv = dev_get_drvdata(&mdiodev->dev);
+
+	dsa_unregister_switch(priv->ds);
+	mutex_destroy(&priv->reg_mutex);
+
+	if (priv->base)
+		iounmap(priv->base);
+
+	an8855_nl_exit();
+}
+
+static struct mdio_driver an8855_mdio_driver = {
+	.probe = an8855_probe,
+	.remove = an8855_remove,
+	.mdiodrv.driver = {
+		.name = "an8855",
+		.of_match_table = an8855_of_match,
+	},
+};
+
+mdio_module_driver(an8855_mdio_driver);
+
+MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
+MODULE_DESCRIPTION("Driver for Airoha AN8855 Switch");
+MODULE_VERSION(ARHT_AN8855_DSA_DRIVER_VER);
+MODULE_LICENSE("GPL");
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.h
new file mode 100644
index 0000000..531d9be
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.h
@@ -0,0 +1,580 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef __AN8855_H
+#define __AN8855_H
+
+#define BITS(m, n)	 (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
+
+#define AN8855_NUM_PORTS				6
+#define AN8855_CPU_PORT					5
+#define AN8855_NUM_FDB_RECORDS			2048
+#define AN8855_ALL_MEMBERS				0x3f
+#define AN8855_RESERVED_VLAN			2
+#define AN8855_GPHY_SMI_ADDR_DEFAULT	1
+
+enum an8855_id {
+	ID_AN8855 = 0,
+};
+
+enum sgmii_mode {
+	SGMII_MODE_AN,
+	SGMII_MODE_FORCE,
+};
+
+/* Registers to mac forward control for unknown frames */
+#define AN8855_MFC			0x10200010
+#define	 CPU_EN				BIT(15)
+#define	 CPU_PORT(x)		((x) << 8)
+#define	 CPU_MASK			(0x9f << 8)
+
+#define AN8855_UNUF			0x102000b4
+#define AN8855_UNMF			0x102000b8
+#define AN8855_BCF			0x102000bc
+
+/* Registers for mirror port control */
+#define	AN8855_MIR						  0x102000cc
+#define	 AN8855_MIRROR_EN				  BIT(7)
+#define	 AN8855_MIRROR_MASK				  (0x1f)
+#define	 AN8855_MIRROR_PORT_GET(x)		  ((x) & AN8855_MIRROR_MASK)
+#define	 AN8855_MIRROR_PORT_SET(x)		  ((x) & AN8855_MIRROR_MASK)
+
+/* Registers for BPDU and PAE frame control*/
+#define AN8855_BPC					0x102000D0
+#define	AN8855_BPDU_PORT_FW_MASK	GENMASK(2, 0)
+
+enum an8855_bpdu_port_fw {
+	AN8855_BPDU_FOLLOW_MFC,
+	AN8855_BPDU_CPU_EXCLUDE = 4,
+	AN8855_BPDU_CPU_INCLUDE = 5,
+	AN8855_BPDU_CPU_ONLY = 6,
+	AN8855_BPDU_DROP = 7,
+};
+
+/* Registers for address table access */
+#define AN8855_ATA1			0x10200304
+#define AN8855_ATA2			0x10200308
+
+/* Register for address table write data */
+#define AN8855_ATWD			0x10200324
+#define AN8855_ATWD2		0x10200328
+
+/* Register for address table control */
+#define AN8855_ATC			0x10200300
+#define	 ATC_BUSY			BIT(31)
+#define	 ATC_INVALID		~BIT(30)
+#define	 ATC_HASH			16
+#define	 ATC_HASH_MASK		0x1ff
+#define	 ATC_HIT			12
+#define	 ATC_HIT_MASK		0xf
+#define	 ATC_MAT(x)			(((x) & 0x1f) << 7)
+#define	 ATC_MAT_MACTAB		ATC_MAT(1)
+
+enum an8855_fdb_cmds {
+	AN8855_FDB_READ = 0,
+	AN8855_FDB_WRITE = 1,
+	AN8855_FDB_FLUSH = 2,
+	AN8855_FDB_START = 4,
+	AN8855_FDB_NEXT = 5,
+};
+
+/* Registers for table search read address */
+#define AN8855_ATRDS		0x10200330
+#define AN8855_ATRD0		0x10200334
+#define	 CVID				10
+#define	 CVID_MASK			0xfff
+
+enum an8855_fdb_type {
+	AN8855_MAC_TB_TY_MAC = 0,
+	AN8855_MAC_TB_TY_DIP = 1,
+	AN8855_MAC_TB_TY_DIP_SIP = 2,
+};
+
+#define AN8855_ATRD1		0x10200338
+#define	 MAC_BYTE_4			24
+#define	 MAC_BYTE_5			16
+#define	 AGE_TIMER			3
+#define	 AGE_TIMER_MASK		0x1ff
+
+#define AN8855_ATRD2		0x1020033c
+#define	 MAC_BYTE_0			24
+#define	 MAC_BYTE_1			16
+#define	 MAC_BYTE_2			8
+#define	 MAC_BYTE_3			0
+#define	 MAC_BYTE_MASK		0xff
+
+#define AN8855_ATRD3		0x10200340
+#define	 PORT_MAP			4
+#define	 PORT_MAP_MASK		0xff
+
+/* Register for vlan table control */
+#define AN8855_VTCR			0x10200600
+#define	 VTCR_BUSY			BIT(31)
+#define	 VTCR_FUNC(x)		(((x) & 0xf) << 12)
+#define	 VTCR_VID			((x) & 0xfff)
+
+enum an8855_vlan_cmd {
+	/* Read/Write the specified VID entry from VAWD register based
+	 * on VID.
+	 */
+	AN8855_VTCR_RD_VID = 0,
+	AN8855_VTCR_WR_VID = 1,
+};
+
+/* Register for setup vlan write data */
+#define AN8855_VAWD0			0x10200604
+
+/* Independent VLAN Learning */
+#define	 IVL_MAC			BIT(5)
+/* Per VLAN Egress Tag Control */
+#define	 VTAG_EN			BIT(10)
+/* Egress Tag Control */
+#define PORT_EG_CTRL_SHIFT	12
+/* VLAN Member Control */
+#define	 PORT_MEM_SHFT		26
+#define	 PORT_MEM_MASK		0x7f
+#define	 PORT_MEM(x)		(((x) & PORT_MEM_MASK) << PORT_MEM_SHFT)
+/* VLAN Entry Valid */
+#define	 VLAN_VALID			BIT(0)
+
+#define AN8855_VAWD1			0x10200608
+#define	 PORT_STAG			BIT(1)
+/* Egress Tag Control */
+#define	 ETAG_CTRL_P(p, x)		(((x) & 0x3) << ((p) << 1))
+#define	 ETAG_CTRL_P_MASK(p)	ETAG_CTRL_P(p, 3)
+#define	 ETAG_CTRL_MASK			(0x3FFF)
+
+#define AN8855_VARD0			0x10200618
+
+enum an8855_vlan_egress_attr {
+	AN8855_VLAN_EGRESS_UNTAG = 0,
+	AN8855_VLAN_EGRESS_TAG = 2,
+	AN8855_VLAN_EGRESS_STACK = 3,
+};
+
+/* Register for port STP state control */
+#define AN8855_SSP_P(x)		(0x10208000 + ((x) * 0x200))
+#define	 FID_PST(x)			((x) & 0x3)
+#define	 FID_PST_MASK		FID_PST(0x3)
+
+enum an8855_stp_state {
+	AN8855_STP_DISABLED = 0,
+	AN8855_STP_BLOCKING = 1,
+	AN8855_STP_LISTENING = 1,
+	AN8855_STP_LEARNING = 2,
+	AN8855_STP_FORWARDING = 3
+};
+
+/* Register for port control */
+#define AN8855_PCR_P(x)		(0x10208004 + ((x) * 0x200))
+#define	 PORT_TX_MIR		BIT(20)
+#define	 PORT_RX_MIR		BIT(16)
+#define	 PORT_VLAN(x)		((x) & 0x3)
+
+enum an8855_port_mode {
+	/* Port Matrix Mode: Frames are forwarded by the PCR_MATRIX members. */
+	AN8855_PORT_MATRIX_MODE = PORT_VLAN(0),
+
+	/* Fallback Mode: Forward received frames with ingress ports that do
+	 * not belong to the VLAN member. Frames whose VID is not listed on
+	 * the VLAN table are forwarded by the PCR_MATRIX members.
+	 */
+	AN8855_PORT_FALLBACK_MODE = PORT_VLAN(1),
+
+	/* Security Mode: Discard any frame due to ingress membership
+	 * violation or VID missed on the VLAN table.
+	 */
+	AN8855_PORT_SECURITY_MODE = PORT_VLAN(3),
+};
+
+#define	 PORT_PRI(x)		(((x) & 0x7) << 24)
+#define	 EG_TAG(x)			(((x) & 0x3) << 28)
+#define	 PCR_PORT_VLAN_MASK	PORT_VLAN(3)
+
+/* Register for port security control */
+#define AN8855_PSC_P(x)		(0x1020800c + ((x) * 0x200))
+#define	 SA_DIS				BIT(4)
+
+/* Register for port vlan control */
+#define AN8855_PVC_P(x)			(0x10208010 + ((x) * 0x200))
+#define	 PORT_SPEC_REPLACE_MODE	BIT(11)
+#define	 PORT_SPEC_TAG			BIT(5)
+#define	 PVC_EG_TAG(x)			(((x) & 0x7) << 8)
+#define	 PVC_EG_TAG_MASK		PVC_EG_TAG(7)
+#define	 VLAN_ATTR(x)			(((x) & 0x3) << 6)
+#define	 VLAN_ATTR_MASK			VLAN_ATTR(3)
+
+#define AN8855_PORTMATRIX_P(x)	(0x10208044 + ((x) * 0x200))
+#define PORTMATRIX_MATRIX(x)	((x) & 0x3f)
+#define PORTMATRIX_MASK			PORTMATRIX_MATRIX(0x3f)
+#define PORTMATRIX_CLR			PORTMATRIX_MATRIX(0)
+
+enum an8855_vlan_port_eg_tag {
+	AN8855_VLAN_EG_DISABLED = 0,
+	AN8855_VLAN_EG_CONSISTENT = 1,
+};
+
+enum an8855_vlan_port_attr {
+	AN8855_VLAN_USER = 0,
+	AN8855_VLAN_TRANSPARENT = 3,
+};
+
+/* Register for port PVID */
+#define AN8855_PVID_P(x)		(0x10208048 + ((x) * 0x200))
+#define	 G0_PORT_VID(x)			(((x) & 0xfff) << 0)
+#define	 G0_PORT_VID_MASK		G0_PORT_VID(0xfff)
+#define	 G0_PORT_VID_DEF		G0_PORT_VID(1)
+
+/* Register for port MAC control register */
+#define AN8855_PMCR_P(x)		(0x10210000 + ((x) * 0x200))
+#define	 PMCR_IFG_XMIT(x)		(((x) & 0x3) << 20)
+#define	 PMCR_EXT_PHY			BIT(19)
+#define	 PMCR_MAC_MODE			BIT(18)
+#define	 PMCR_FORCE_MODE		BIT(31)
+#define	 PMCR_TX_EN				BIT(16)
+#define	 PMCR_RX_EN				BIT(15)
+#define	 PMCR_BACKOFF_EN		BIT(12)
+#define	 PMCR_BACKPR_EN			BIT(11)
+#define	 PMCR_FORCE_EEE2P5G		BIT(8)
+#define	 PMCR_FORCE_EEE1G		BIT(7)
+#define	 PMCR_FORCE_EEE100		BIT(6)
+#define	 PMCR_TX_FC_EN			BIT(5)
+#define	 PMCR_RX_FC_EN			BIT(4)
+#define	 PMCR_FORCE_SPEED_2500	(0x3 << 28)
+#define	 PMCR_FORCE_SPEED_1000	(0x2 << 28)
+#define	 PMCR_FORCE_SPEED_100	(0x1 << 28)
+#define	 PMCR_FORCE_FDX			BIT(25)
+#define	 PMCR_FORCE_LNK			BIT(24)
+#define	 PMCR_SPEED_MASK		BITS(28, 30)
+#define	 AN8855_FORCE_LNK		BIT(31)
+#define	 AN8855_FORCE_MODE		(AN8855_FORCE_LNK)
+#define	 PMCR_LINK_SETTINGS_MASK	(PMCR_TX_EN | \
+					 PMCR_RX_EN | PMCR_FORCE_SPEED_2500 | \
+					 PMCR_TX_FC_EN | PMCR_RX_FC_EN | \
+					 PMCR_FORCE_FDX | PMCR_FORCE_LNK)
+#define	 PMCR_CPU_PORT_SETTING(id)	(AN8855_FORCE_MODE | \
+					 PMCR_IFG_XMIT(1) | PMCR_MAC_MODE | \
+					 PMCR_BACKOFF_EN | PMCR_BACKPR_EN | \
+					 PMCR_TX_EN | PMCR_RX_EN | \
+					 PMCR_TX_FC_EN | PMCR_RX_FC_EN | \
+					 PMCR_FORCE_SPEED_2500 | \
+					 PMCR_FORCE_FDX | PMCR_FORCE_LNK)
+
+#define AN8855_PMSR_P(x)		(0x10210010 + (x) * 0x200)
+#define	 PMSR_EEE1G				BIT(7)
+#define	 PMSR_EEE100M			BIT(6)
+#define	 PMSR_RX_FC				BIT(5)
+#define	 PMSR_TX_FC				BIT(4)
+#define	 PMSR_SPEED_2500		(0x3 << 28)
+#define	 PMSR_SPEED_1000		(0x2 << 28)
+#define	 PMSR_SPEED_100			(0x1 << 28)
+#define	 PMSR_SPEED_10			(0x0 << 28)
+#define	 PMSR_SPEED_MASK		BITS(28, 30)
+#define	 PMSR_DPX				BIT(25)
+#define	 PMSR_LINK				BIT(24)
+
+#define AN8855_PMEEECR_P(x)		(0x10210004 + (x) * 0x200)
+#define	 WAKEUP_TIME_2500(x)	((x & 0xFF) << 16)
+#define	 WAKEUP_TIME_1000(x)	((x & 0xFF) << 8)
+#define	 WAKEUP_TIME_100(x)		((x & 0xFF) << 0)
+#define	 LPI_MODE_EN			BIT(31)
+#define AN8855_PMEEECR2_P(x)	(0x10210008 + (x) * 0x200)
+#define	 WAKEUP_TIME_5000(x)	((x & 0xFF) << 0)
+
+#define AN8855_CKGCR			(0x10213e1c)
+#define LPI_TXIDLE_THD			14
+#define LPI_TXIDLE_THD_MASK		BITS(14, 31)
+
+/* Register for MIB */
+#define AN8855_PORT_MIB_COUNTER(x)	(0x10214000 + (x) * 0x200)
+#define AN8855_MIB_CCR			0x10213e30
+#define	 CCR_MIB_ENABLE			BIT(31)
+#define	 CCR_RX_OCT_CNT_GOOD	BIT(7)
+#define	 CCR_RX_OCT_CNT_BAD		BIT(6)
+#define	 CCR_TX_OCT_CNT_GOOD	BIT(5)
+#define	 CCR_TX_OCT_CNT_BAD		BIT(4)
+#define	 CCR_RX_OCT_CNT_GOOD_2	BIT(3)
+#define	 CCR_RX_OCT_CNT_BAD_2	BIT(2)
+#define	 CCR_TX_OCT_CNT_GOOD_2	BIT(1)
+#define	 CCR_TX_OCT_CNT_BAD_2	BIT(0)
+#define	 CCR_MIB_FLUSH			(CCR_RX_OCT_CNT_GOOD | \
+					 CCR_RX_OCT_CNT_BAD | \
+					 CCR_TX_OCT_CNT_GOOD | \
+					 CCR_TX_OCT_CNT_BAD | \
+					 CCR_RX_OCT_CNT_GOOD_2 | \
+					 CCR_RX_OCT_CNT_BAD_2 | \
+					 CCR_TX_OCT_CNT_GOOD_2 | \
+					 CCR_TX_OCT_CNT_BAD_2)
+#define	 CCR_MIB_ACTIVATE		(CCR_MIB_ENABLE | \
+					 CCR_RX_OCT_CNT_GOOD | \
+					 CCR_RX_OCT_CNT_BAD | \
+					 CCR_TX_OCT_CNT_GOOD | \
+					 CCR_TX_OCT_CNT_BAD | \
+					 CCR_RX_OCT_CNT_GOOD_2 | \
+					 CCR_RX_OCT_CNT_BAD_2 | \
+					 CCR_TX_OCT_CNT_GOOD_2 | \
+					 CCR_TX_OCT_CNT_BAD_2)
+
+/* AN8855 SGMII register group */
+#define AN8855_SGMII_REG_BASE		0x10220000
+#define AN8855_SGMII_REG(p, r)		(AN8855_SGMII_REG_BASE + \
+					((p) - 5) * 0x1000 + (r))
+
+/* Register forSGMII PCS_CONTROL_1 */
+#define AN8855_PCS_CONTROL_1(p)		AN8855_SGMII_REG(p, 0x00)
+#define	 AN8855_SGMII_AN_ENABLE		BIT(12)
+#define	 AN8855_SGMII_AN_RESTART	BIT(9)
+
+/* Register for system reset */
+#define AN8855_RST_CTRL			0x100050c0
+#define	 SYS_CTRL_SYS_RST		BIT(31)
+
+/* Register for hw trap status */
+#define AN8855_HWTRAP			0x1000009c
+
+#define AN8855_CREV				0x10005000
+#define	 AN8855_ID				0x8855
+
+#define SCU_BASE				0x10000000
+#define RG_RGMII_TXCK_C			(SCU_BASE + 0x1d0)
+
+#define HSGMII_AN_CSR_BASE		0x10220000
+#define SGMII_REG_AN0			(HSGMII_AN_CSR_BASE + 0x000)
+#define SGMII_REG_AN_13			(HSGMII_AN_CSR_BASE + 0x034)
+#define SGMII_REG_AN_FORCE_CL37	(HSGMII_AN_CSR_BASE + 0x060)
+
+#define HSGMII_CSR_PCS_BASE		0x10220000
+#define RG_HSGMII_PCS_CTROL_1	(HSGMII_CSR_PCS_BASE + 0xa00)
+#define RG_AN_SGMII_MODE_FORCE	(HSGMII_CSR_PCS_BASE + 0xa24)
+
+#define MULTI_SGMII_CSR_BASE	0x10224000
+#define SGMII_STS_CTRL_0		(MULTI_SGMII_CSR_BASE + 0x018)
+#define MSG_RX_CTRL_0			(MULTI_SGMII_CSR_BASE + 0x100)
+#define MSG_RX_LIK_STS_0		(MULTI_SGMII_CSR_BASE + 0x514)
+#define MSG_RX_LIK_STS_2		(MULTI_SGMII_CSR_BASE + 0x51c)
+#define PHY_RX_FORCE_CTRL_0		(MULTI_SGMII_CSR_BASE + 0x520)
+
+#define XFI_CSR_PCS_BASE		0x10225000
+#define RG_USXGMII_AN_CONTROL_0	(XFI_CSR_PCS_BASE + 0xbf8)
+
+#define MULTI_PHY_RA_CSR_BASE	0x10226000
+#define RG_RATE_ADAPT_CTRL_0	(MULTI_PHY_RA_CSR_BASE + 0x000)
+#define RATE_ADP_P0_CTRL_0		(MULTI_PHY_RA_CSR_BASE + 0x100)
+#define MII_RA_AN_ENABLE		(MULTI_PHY_RA_CSR_BASE + 0x300)
+
+#define QP_DIG_CSR_BASE			0x1022a000
+#define QP_CK_RST_CTRL_4		(QP_DIG_CSR_BASE + 0x310)
+#define QP_DIG_MODE_CTRL_0		(QP_DIG_CSR_BASE + 0x324)
+#define QP_DIG_MODE_CTRL_1		(QP_DIG_CSR_BASE + 0x330)
+
+#define SERDES_WRAPPER_BASE		0x1022c000
+#define USGMII_CTRL_0			(SERDES_WRAPPER_BASE + 0x000)
+
+#define QP_PMA_TOP_BASE			0x1022e000
+#define PON_RXFEDIG_CTRL_0		(QP_PMA_TOP_BASE + 0x100)
+#define PON_RXFEDIG_CTRL_9		(QP_PMA_TOP_BASE + 0x124)
+
+#define SS_LCPLL_PWCTL_SETTING_2	(QP_PMA_TOP_BASE + 0x208)
+#define SS_LCPLL_TDC_FLT_2			(QP_PMA_TOP_BASE + 0x230)
+#define SS_LCPLL_TDC_FLT_5			(QP_PMA_TOP_BASE + 0x23c)
+#define SS_LCPLL_TDC_PCW_1			(QP_PMA_TOP_BASE + 0x248)
+#define INTF_CTRL_8			(QP_PMA_TOP_BASE + 0x320)
+#define INTF_CTRL_9			(QP_PMA_TOP_BASE + 0x324)
+#define PLL_CTRL_0			(QP_PMA_TOP_BASE + 0x400)
+#define PLL_CTRL_2			(QP_PMA_TOP_BASE + 0x408)
+#define PLL_CTRL_3			(QP_PMA_TOP_BASE + 0x40c)
+#define PLL_CTRL_4			(QP_PMA_TOP_BASE + 0x410)
+#define PLL_CK_CTRL_0		(QP_PMA_TOP_BASE + 0x414)
+#define RX_DLY_0			(QP_PMA_TOP_BASE + 0x614)
+#define RX_CTRL_2			(QP_PMA_TOP_BASE + 0x630)
+#define RX_CTRL_5			(QP_PMA_TOP_BASE + 0x63c)
+#define RX_CTRL_6			(QP_PMA_TOP_BASE + 0x640)
+#define RX_CTRL_7			(QP_PMA_TOP_BASE + 0x644)
+#define RX_CTRL_8			(QP_PMA_TOP_BASE + 0x648)
+#define RX_CTRL_26			(QP_PMA_TOP_BASE + 0x690)
+#define RX_CTRL_42			(QP_PMA_TOP_BASE + 0x6d0)
+
+#define QP_ANA_CSR_BASE				0x1022f000
+#define RG_QP_RX_DAC_EN				(QP_ANA_CSR_BASE + 0x00)
+#define RG_QP_RXAFE_RESERVE			(QP_ANA_CSR_BASE + 0x04)
+#define RG_QP_CDR_LPF_MJV_LIM		(QP_ANA_CSR_BASE + 0x0c)
+#define RG_QP_CDR_LPF_SETVALUE		(QP_ANA_CSR_BASE + 0x14)
+#define RG_QP_CDR_PR_CKREF_DIV1		(QP_ANA_CSR_BASE + 0x18)
+#define RG_QP_CDR_PR_KBAND_DIV_PCIE	(QP_ANA_CSR_BASE + 0x1c)
+#define RG_QP_CDR_FORCE_IBANDLPF_R_OFF	(QP_ANA_CSR_BASE + 0x20)
+#define RG_QP_TX_MODE_16B_EN		(QP_ANA_CSR_BASE + 0x28)
+#define RG_QP_PLL_IPLL_DIG_PWR_SEL	(QP_ANA_CSR_BASE + 0x3c)
+#define RG_QP_PLL_SDM_ORD			(QP_ANA_CSR_BASE + 0x40)
+
+#define ETHER_SYS_BASE				0x1028c800
+#define RG_GPHY_AFE_PWD				(ETHER_SYS_BASE + 0x40)
+#define RG_GPHY_SMI_ADDR			(ETHER_SYS_BASE + 0x48)
+
+#define MIB_DESC(_s, _o, _n)	\
+	{			\
+		.size = (_s),	\
+		.offset = (_o),	\
+		.name = (_n),	\
+	}
+
+struct an8855_mib_desc {
+	unsigned int size;
+	unsigned int offset;
+	const char *name;
+};
+
+struct an8855_fdb {
+	u16 vid;
+	u8 port_mask;
+	u8 aging;
+	u8 mac[6];
+	bool noarp;
+	u8 live;
+	u8 type;
+	u8 fid;
+	u8 ivl;
+};
+
+/* struct an8855_port -	This is the main data structure for holding the state
+ *			of the port.
+ * @enable:	The status used for show port is enabled or not.
+ * @pm:		The matrix used to show all connections with the port.
+ * @pvid:	The VLAN specified is to be considered a PVID at ingress.  Any
+ *		untagged frames will be assigned to the related VLAN.
+ * @vlan_filtering: The flags indicating whether the port that can recognize
+ *			VLAN-tagged frames.
+ */
+struct an8855_port {
+	bool enable;
+	u32 pm;
+	u16 pvid;
+};
+
+/* struct an8855_info -	This is the main data structure for holding the specific
+ *			part for each supported device
+ * @sw_setup:		Holding the handler to a device initialization
+ * @phy_read:		Holding the way reading PHY port
+ * @phy_write:		Holding the way writing PHY port
+ * @pad_setup:		Holding the way setting up the bus pad for a certain
+ *			MAC port
+ * @phy_mode_supported:	Check if the PHY type is being supported on a certain
+ *			port
+ * @mac_port_validate:	Holding the way to set addition validate type for a
+ *			certan MAC port
+ * @mac_port_get_state: Holding the way getting the MAC/PCS state for a certain
+ *			MAC port
+ * @mac_port_config:	Holding the way setting up the PHY attribute to a
+ *			certain MAC port
+ * @mac_pcs_an_restart	Holding the way restarting PCS autonegotiation for a
+ *			certain MAC port
+ * @mac_pcs_link_up:	Holding the way setting up the PHY attribute to the pcs
+ *			of the certain MAC port
+ */
+struct an8855_dev_info {
+	enum an8855_id id;
+
+	int (*sw_setup)(struct dsa_switch *ds);
+	int (*phy_read)(struct dsa_switch *ds, int port, int regnum);
+	int (*phy_write)(struct dsa_switch *ds, int port, int regnum,
+			  u16 val);
+	int (*pad_setup)(struct dsa_switch *ds, phy_interface_t interface);
+	int (*cpu_port_config)(struct dsa_switch *ds, int port);
+	bool (*phy_mode_supported)(struct dsa_switch *ds, int port,
+					const struct phylink_link_state *state);
+	void (*mac_port_validate)(struct dsa_switch *ds, int port,
+				   unsigned long *supported);
+	int (*mac_port_get_state)(struct dsa_switch *ds, int port,
+				   struct phylink_link_state *state);
+	int (*mac_port_config)(struct dsa_switch *ds, int port,
+				unsigned int mode, phy_interface_t interface);
+	void (*mac_pcs_an_restart)(struct dsa_switch *ds, int port);
+};
+
+/* struct an8855_priv -	This is the main data structure for holding the state
+ *			of the driver
+ * @dev:		The device pointer
+ * @ds:			The pointer to the dsa core structure
+ * @bus:		The bus used for the device and built-in PHY
+ * @rstc:		The pointer to reset control used by MCM
+ * @core_pwr:		The power supplied into the core
+ * @io_pwr:		The power supplied into the I/O
+ * @reset:		The descriptor for GPIO line tied to its reset pin
+ * @mcm:		Flag for distinguishing if standalone IC or module
+ *			coupling
+ * @ports:		Holding the state among ports
+ * @reg_mutex:		The lock for protecting among process accessing
+ *			registers
+ * @p6_interface	Holding the current port 6 interface
+ * @p5_intf_sel:	Holding the current port 5 interface select
+ */
+struct an8855_priv {
+	struct device *dev;
+	struct dsa_switch *ds;
+	struct mii_bus *bus;
+	struct reset_control *rstc;
+	struct regulator *core_pwr;
+	struct regulator *io_pwr;
+	struct gpio_desc *reset;
+	void __iomem *base;
+	const struct an8855_dev_info *info;
+	unsigned int phy_base;
+	int phy_base_new;
+	unsigned int id;
+	phy_interface_t p5_interface;
+	unsigned int p5_intf_sel;
+	u8 mirror_rx;
+	u8 mirror_tx;
+	u8 eee_enable;
+
+	struct an8855_port ports[AN8855_NUM_PORTS];
+	/* protect among processes for registers access */
+	struct mutex reg_mutex;
+};
+
+struct an8855_hw_vlan_entry {
+	int port;
+	u8 old_members;
+	bool untagged;
+};
+
+static inline void an8855_hw_vlan_entry_init(struct an8855_hw_vlan_entry *e,
+						 int port, bool untagged)
+{
+	e->port = port;
+	e->untagged = untagged;
+}
+
+typedef void (*an8855_vlan_op) (struct an8855_priv *,
+				struct an8855_hw_vlan_entry *);
+
+struct an8855_hw_stats {
+	const char *string;
+	u16 reg;
+	u8 sizeof_stat;
+};
+
+struct an8855_dummy_poll {
+	struct an8855_priv *priv;
+	u32 reg;
+};
+
+static inline void INIT_AN8855_DUMMY_POLL(struct an8855_dummy_poll *p,
+					  struct an8855_priv *priv, u32 reg)
+{
+	p->priv = priv;
+	p->reg = reg;
+}
+
+int an8855_phy_setup(struct dsa_switch *ds);
+u32 an8855_read(struct an8855_priv *priv, u32 reg);
+void an8855_write(struct an8855_priv *priv, u32 reg, u32 val);
+int an8855_phy_cl22_read(struct an8855_priv *priv, int port, int regnum);
+int an8855_phy_cl22_write(struct an8855_priv *priv, int port,
+				int regnum, u16 val);
+int an8855_phy_cl45_read(struct an8855_priv *priv, int port, int devad,
+				int regnum);
+int an8855_phy_cl45_write(struct an8855_priv *priv, int port, int devad,
+				int regnum,	u16 val);
+#endif /* __AN8855_H */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.c
new file mode 100644
index 0000000..b80ce6f
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.c
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <net/genetlink.h>
+#include <linux/of_mdio.h>
+#include <linux/phylink.h>
+#include <net/dsa.h>
+
+#include "an8855.h"
+#include "an8855_nl.h"
+
+struct an8855_nl_cmd_item {
+	enum an8855_cmd cmd;
+	bool require_dev;
+	int (*process)(struct genl_info *info);
+	u32 nr_required_attrs;
+	const enum an8855_attr *required_attrs;
+};
+
+struct an8855_priv *an8855_sw_priv;
+
+static DEFINE_MUTEX(an8855_devs_lock);
+
+void
+an8855_put(void)
+{
+	mutex_unlock(&an8855_devs_lock);
+}
+
+void
+an8855_lock(void)
+{
+	mutex_lock(&an8855_devs_lock);
+}
+
+static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info);
+
+static const struct nla_policy an8855_nl_cmd_policy[] = {
+	[AN8855_ATTR_TYPE_MESG] = {.type = NLA_STRING},
+	[AN8855_ATTR_TYPE_PHY] = {.type = NLA_S32},
+	[AN8855_ATTR_TYPE_REG] = {.type = NLA_S32},
+	[AN8855_ATTR_TYPE_VAL] = {.type = NLA_S32},
+	[AN8855_ATTR_TYPE_DEV_NAME] = {.type = NLA_S32},
+	[AN8855_ATTR_TYPE_DEV_ID] = {.type = NLA_S32},
+	[AN8855_ATTR_TYPE_DEVAD] = {.type = NLA_S32},
+};
+
+static const struct genl_ops an8855_nl_ops[] = {
+	{
+		.cmd = AN8855_CMD_REQUEST,
+		.doit = an8855_nl_response,
+		.flags = GENL_ADMIN_PERM,
+	},
+	{
+		.cmd = AN8855_CMD_READ,
+		.doit = an8855_nl_response,
+		.flags = GENL_ADMIN_PERM,
+	},
+	{
+		.cmd = AN8855_CMD_WRITE,
+		.doit = an8855_nl_response,
+		.flags = GENL_ADMIN_PERM,
+	},
+};
+
+static struct genl_family an8855_nl_family = {
+	.name = AN8855_DSA_GENL_NAME,
+	.version = AN8855_GENL_VERSION,
+	.maxattr = AN8855_NR_ATTR_TYPE,
+	.ops = an8855_nl_ops,
+	.n_ops = ARRAY_SIZE(an8855_nl_ops),
+	.policy = an8855_nl_cmd_policy,
+};
+
+static int
+an8855_nl_prepare_reply(struct genl_info *info, u8 cmd,
+				   struct sk_buff **skbp)
+{
+	struct sk_buff *msg;
+	void *reply;
+
+	if (!info)
+		return -EINVAL;
+
+	msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	/* Construct send-back message header */
+	reply = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+				&an8855_nl_family, 0, cmd);
+	if (!reply) {
+		nlmsg_free(msg);
+		return -EINVAL;
+	}
+
+	*skbp = msg;
+	return 0;
+}
+
+static int
+an8855_nl_send_reply(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
+	void *reply = genlmsg_data(genlhdr);
+
+	/* Finalize a generic netlink message (update message header) */
+	genlmsg_end(skb, reply);
+
+	/* reply to a request */
+	return genlmsg_reply(skb, info);
+}
+
+static s32
+an8855_nl_get_s32(struct genl_info *info, enum an8855_attr attr,
+				 s32 defval)
+{
+	struct nlattr *na;
+
+	na = info->attrs[attr];
+	if (na)
+		return nla_get_s32(na);
+
+	return defval;
+}
+
+static int
+an8855_nl_get_u32(struct genl_info *info, enum an8855_attr attr,
+				 u32 *val)
+{
+	struct nlattr *na;
+
+	na = info->attrs[attr];
+	if (na) {
+		*val = nla_get_u32(na);
+		return 0;
+	}
+
+	return -1;
+}
+
+static int
+an8855_nl_reply_read(struct genl_info *info)
+{
+	struct sk_buff *rep_skb = NULL;
+	s32 phy, devad, reg;
+	int value;
+	int ret = 0;
+
+	phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
+	devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
+	reg = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_REG, -1);
+
+	if (reg < 0)
+		goto err;
+
+	ret = an8855_nl_prepare_reply(info, AN8855_CMD_READ, &rep_skb);
+	if (ret < 0)
+		goto err;
+	if (phy >= 0) {
+		if (devad < 0)
+			value = an8855_phy_cl22_read(an8855_sw_priv, phy, reg);
+		else
+			value =	an8855_phy_cl45_read(an8855_sw_priv, phy,
+						devad, reg);
+	} else
+		value = an8855_read(an8855_sw_priv, reg);
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
+	if (ret < 0)
+		goto err;
+
+	return an8855_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static int
+an8855_nl_reply_write(struct genl_info *info)
+{
+	struct sk_buff *rep_skb = NULL;
+	s32 phy, devad, reg;
+	u32 value;
+	int ret = 0;
+
+	phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
+	devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
+	reg = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_REG, -1);
+
+	if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_VAL, &value))
+		goto err;
+
+	if (reg < 0)
+		goto err;
+
+	ret = an8855_nl_prepare_reply(info, AN8855_CMD_WRITE, &rep_skb);
+	if (ret < 0)
+		goto err;
+	if (phy >= 0) {
+		if (devad < 0)
+			an8855_phy_cl22_write(an8855_sw_priv, phy, reg, value);
+		else
+			an8855_phy_cl45_write(an8855_sw_priv, phy, devad, reg,
+					  value);
+	} else
+		an8855_write(an8855_sw_priv, reg, value);
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
+	if (ret < 0)
+		goto err;
+
+	return an8855_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static const enum an8855_attr an8855_nl_cmd_read_attrs[] = {
+	AN8855_ATTR_TYPE_REG
+};
+
+static const enum an8855_attr an8855_nl_cmd_write_attrs[] = {
+	AN8855_ATTR_TYPE_REG,
+	AN8855_ATTR_TYPE_VAL
+};
+
+static const struct an8855_nl_cmd_item an8855_nl_cmds[] = {
+	{
+	 .cmd = AN8855_CMD_READ,
+	 .require_dev = true,
+	 .process = an8855_nl_reply_read,
+	 .required_attrs = an8855_nl_cmd_read_attrs,
+	 .nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_read_attrs),
+	},
+	{
+		 .cmd = AN8855_CMD_WRITE,
+		 .require_dev = true,
+		 .process = an8855_nl_reply_write,
+		 .required_attrs = an8855_nl_cmd_write_attrs,
+		 .nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_write_attrs),
+	}
+};
+
+static int
+an8855_nl_response(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
+	const struct an8855_nl_cmd_item *cmditem = NULL;
+	u32 sat_req_attrs = 0;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(an8855_nl_cmds); i++) {
+		if (hdr->cmd == an8855_nl_cmds[i].cmd) {
+			cmditem = &an8855_nl_cmds[i];
+			break;
+		}
+	}
+
+	if (!cmditem) {
+		pr_info("an8855-nl: unknown cmd %u\n", hdr->cmd);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < cmditem->nr_required_attrs; i++) {
+		if (info->attrs[cmditem->required_attrs[i]])
+			sat_req_attrs++;
+	}
+
+	if (sat_req_attrs != cmditem->nr_required_attrs) {
+		pr_info("an8855-nl: missing required attr(s) for cmd %u\n",
+			hdr->cmd);
+		return -EINVAL;
+	}
+
+	ret = cmditem->process(info);
+
+	an8855_put();
+
+	return ret;
+}
+
+int
+an8855_nl_init(struct an8855_priv **priv)
+{
+	int ret;
+
+	pr_info("an8855-nl: genl_register_family_with_ops\n");
+
+	an8855_sw_priv = *priv;
+	ret = genl_register_family(&an8855_nl_family);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+void
+an8855_nl_exit(void)
+{
+	an8855_sw_priv = NULL;
+	genl_unregister_family(&an8855_nl_family);
+}
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.h
new file mode 100644
index 0000000..f8a462d
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef _AN8855_NL_H_
+#define _AN8855_NL_H_
+
+#define AN8855_DSA_GENL_NAME "an8855_dsa"
+#define AN8855_GENL_VERSION		0x1
+
+enum an8855_cmd {
+	AN8855_CMD_UNSPEC = 0,
+	AN8855_CMD_REQUEST,
+	AN8855_CMD_REPLY,
+	AN8855_CMD_READ,
+	AN8855_CMD_WRITE,
+
+	__AN8855_CMD_MAX,
+};
+
+enum an8855_attr {
+	AN8855_ATTR_TYPE_UNSPEC = 0,
+	AN8855_ATTR_TYPE_MESG,
+	AN8855_ATTR_TYPE_PHY,
+	AN8855_ATTR_TYPE_DEVAD,
+	AN8855_ATTR_TYPE_REG,
+	AN8855_ATTR_TYPE_VAL,
+	AN8855_ATTR_TYPE_DEV_NAME,
+	AN8855_ATTR_TYPE_DEV_ID,
+
+	__AN8855_ATTR_TYPE_MAX,
+};
+
+#define AN8855_NR_ATTR_TYPE		(__AN8855_ATTR_TYPE_MAX - 1)
+
+#ifdef __KERNEL__
+int an8855_nl_init(struct an8855_priv **priv);
+void an8855_nl_exit(void);
+#endif /* __KERNEL__ */
+
+#endif /* _AN8855_NL_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.c
new file mode 100644
index 0000000..5499312
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier:     GPL-2.0+
+/*
+ * Common part for Airoha AN8855 gigabit switch
+ *
+ * Copyright (C) 2023 Airoha Inc. All Rights Reserved.
+ *
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/kernel.h>
+#include <net/dsa.h>
+#include "an8855.h"
+#include "an8855_phy.h"
+
+#define AN8855_NUM_PHYS 5
+
+static u32
+an8855_phy_read_dev_reg(struct dsa_switch *ds, u32 port_num,
+				   u32 dev_addr, u32 reg_addr)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 phy_val;
+	u32 addr;
+
+	addr = MII_ADDR_C45 | (dev_addr << 16) | (reg_addr & 0xffff);
+	phy_val = priv->info->phy_read(ds, port_num, addr);
+
+	return phy_val;
+}
+
+static void
+an8855_phy_write_dev_reg(struct dsa_switch *ds, u32 port_num,
+				     u32 dev_addr, u32 reg_addr, u32 write_data)
+{
+	struct an8855_priv *priv = ds->priv;
+	u32 addr;
+
+	addr = MII_ADDR_C45 | (dev_addr << 16) | (reg_addr & 0xffff);
+
+	priv->info->phy_write(ds, port_num, addr, write_data);
+}
+
+static void
+an8855_switch_phy_write(struct dsa_switch *ds, u32 port_num,
+				    u32 reg_addr, u32 write_data)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	priv->info->phy_write(ds, port_num, reg_addr, write_data);
+}
+
+static u32
+an8855_switch_phy_read(struct dsa_switch *ds, u32 port_num,
+				  u32 reg_addr)
+{
+	struct an8855_priv *priv = ds->priv;
+
+	return priv->info->phy_read(ds, port_num, reg_addr);
+}
+
+static void
+an8855_phy_setting(struct dsa_switch *ds)
+{
+	struct an8855_priv *priv = ds->priv;
+	int i;
+	u32 val;
+
+	/* Release power down */
+	an8855_write(priv, RG_GPHY_AFE_PWD, 0x0);
+	for (i = 0; i < AN8855_NUM_PHYS; i++) {
+		/* Enable HW auto downshift */
+		an8855_switch_phy_write(ds, i, 0x1f, 0x1);
+		val = an8855_switch_phy_read(ds, i, PHY_EXT_REG_14);
+		val |= PHY_EN_DOWN_SHFIT;
+		an8855_switch_phy_write(ds, i, PHY_EXT_REG_14, val);
+		an8855_switch_phy_write(ds, i, 0x1f, 0x0);
+
+		/* Enable Asymmetric Pause Capability */
+		val = an8855_switch_phy_read(ds, i, MII_ADVERTISE);
+		val |= ADVERTISE_PAUSE_ASYM;
+		an8855_switch_phy_write(ds, i, MII_ADVERTISE, val);
+	}
+}
+
+static void
+an8855_low_power_setting(struct dsa_switch *ds)
+{
+	int port, addr;
+
+	for (port = 0; port < AN8855_NUM_PHYS; port++) {
+		an8855_phy_write_dev_reg(ds, port, 0x1e, 0x11, 0x0f00);
+		an8855_phy_write_dev_reg(ds, port, 0x1e, 0x3c, 0x0000);
+		an8855_phy_write_dev_reg(ds, port, 0x1e, 0x3d, 0x0000);
+		an8855_phy_write_dev_reg(ds, port, 0x1e, 0x3e, 0x0000);
+		an8855_phy_write_dev_reg(ds, port, 0x1e, 0xc6, 0x53aa);
+	}
+
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x268, 0x07f1);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x269, 0x2111);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x26a, 0x0000);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x26b, 0x0074);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x26e, 0x00f6);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x26f, 0x6666);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x271, 0x2c02);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x272, 0x0c22);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x700, 0x0001);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x701, 0x0803);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x702, 0x01b6);
+	an8855_phy_write_dev_reg(ds, 0, 0x1f, 0x703, 0x2111);
+
+	an8855_phy_write_dev_reg(ds, 1, 0x1f, 0x700, 0x0001);
+
+	for (addr = 0x200; addr <= 0x230; addr += 2)
+		an8855_phy_write_dev_reg(ds, 0, 0x1f, addr, 0x2020);
+
+	for (addr = 0x201; addr <= 0x231; addr += 2)
+		an8855_phy_write_dev_reg(ds, 0, 0x1f, addr, 0x0020);
+}
+
+static void
+an8855_eee_setting(struct dsa_switch *ds, u32 port)
+{
+	/* Disable EEE */
+	an8855_phy_write_dev_reg(ds, port, PHY_DEV07, PHY_DEV07_REG_03C, 0);
+}
+
+int
+an8855_phy_setup(struct dsa_switch *ds)
+{
+	int ret = 0;
+	int i;
+
+	an8855_phy_setting(ds);
+
+	for (i = 0; i < AN8855_NUM_PHYS; i++)
+		an8855_eee_setting(ds, i);
+
+	return ret;
+}
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.h
new file mode 100644
index 0000000..63a03bb
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.h
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier:     GPL-2.0+
+/*
+ * Common part for Airoha AN8855 gigabit switch
+ *
+ * Copyright (C) 2023 Airoha Inc. All Rights Reserved.
+ *
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef _AN8855_PHY_H_
+#define _AN8855_PHY_H_
+
+#include <linux/bitops.h>
+
+/*phy calibration use*/
+#define DEV_1E				0x1E
+/*global device 0x1f, always set P0*/
+#define DEV_1F				0x1F
+
+/************IEXT/REXT CAL***************/
+/* bits range: for example BITS(16,23) = 0xFF0000*/
+#define BITS(m, n)	 (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
+#define ANACAL_INIT			0x01
+#define ANACAL_ERROR			0xFD
+#define ANACAL_SATURATION		0xFE
+#define	ANACAL_FINISH			0xFF
+#define ANACAL_PAIR_A			0
+#define ANACAL_PAIR_B			1
+#define ANACAL_PAIR_C			2
+#define ANACAL_PAIR_D			3
+#define DAC_IN_0V			0x00
+#define DAC_IN_2V			0xf0
+#define TX_AMP_OFFSET_0MV		0x20
+#define TX_AMP_OFFSET_VALID_BITS	6
+
+#define R0				0
+#define PHY0				0
+#define PHY1				1
+#define PHY2				2
+#define PHY3				3
+#define PHY4				4
+#define ANA_TEST_MODE			BITS(8, 15)
+#define TST_TCLK_SEL			BITs(6, 7)
+#define ANA_TEST_VGA_RG			0x100
+
+#define FORCE_MDI_CROSS_OVER		BITS(3, 4)
+#define T10_TEST_CTL_RG			0x145
+#define RG_185				0x185
+#define RG_TX_SLEW			BIT(0)
+#define ANA_CAL_0			0xdb
+#define RG_CAL_CKINV			BIT(12)
+#define RG_ANA_CALEN			BIT(8)
+#define RG_REXT_CALEN			BIT(4)
+#define RG_ZCALEN_A			BIT(0)
+#define ANA_CAL_1			0xdc
+#define RG_ZCALEN_B			BIT(12)
+#define RG_ZCALEN_C			BIT(8)
+#define RG_ZCALEN_D			BIT(4)
+#define RG_TXVOS_CALEN			BIT(0)
+#define ANA_CAL_6			0xe1
+#define RG_CAL_REFSEL			BIT(4)
+#define RG_CAL_COMP_PWD			BIT(0)
+#define ANA_CAL_5			0xe0
+#define RG_REXT_TRIM			BITs(8, 13)
+#define RG_ZCAL_CTRL			BITs(0, 5)
+#define RG_17A				0x17a
+#define AD_CAL_COMP_OUT			BIT(8)
+#define RG_17B				0x17b
+#define AD_CAL_CLK			bit(0)
+#define RG_17C				0x17c
+#define DA_CALIN_FLAG			bit(0)
+/************R50 CAL****************************/
+#define RG_174				0x174
+#define RG_R50OHM_RSEL_TX_A_EN		BIT[15]
+#define CR_R50OHM_RSEL_TX_A		BITS[8:14]
+#define RG_R50OHM_RSEL_TX_B_EN		BIT[7]
+#define CR_R50OHM_RSEL_TX_B		BITS[6:0]
+#define RG_175				0x175
+#define RG_R50OHM_RSEL_TX_C_EN		BITS[15]
+#define CR_R50OHM_RSEL_TX_C		BITS[8:14]
+#define RG_R50OHM_RSEL_TX_D_EN		BIT[7]
+#define CR_R50OHM_RSEL_TX_D		BITS[0:6]
+/**********TX offset Calibration***************************/
+#define RG_95				0x96
+#define BYPASS_TX_OFFSET_CAL		BIT(15)
+#define RG_3E				0x3e
+#define BYPASS_PD_TXVLD_A		BIT(15)
+#define BYPASS_PD_TXVLD_B		BIT(14)
+#define BYPASS_PD_TXVLD_C		BIT(13)
+#define BYPASS_PD_TXVLD_D		BIT(12)
+#define BYPASS_PD_TX_10M		BIT(11)
+#define POWER_DOWN_TXVLD_A		BIT(7)
+#define POWER_DOWN_TXVLD_B		BIT(6)
+#define POWER_DOWN_TXVLD_C		BIT(5)
+#define POWER_DOWN_TXVLD_D		BIT(4)
+#define POWER_DOWN_TX_10M		BIT(3)
+#define RG_DD				0xdd
+#define RG_TXG_CALEN_A			BIT(12)
+#define RG_TXG_CALEN_B			BIT(8)
+#define RG_TXG_CALEN_C			BIT(4)
+#define RG_TXG_CALEN_D			BIT(0)
+#define RG_17D				0x17D
+#define FORCE_DASN_DAC_IN0_A		BIT(15)
+#define DASN_DAC_IN0_A			BITS(0, 9)
+#define RG_17E				0x17E
+#define FORCE_DASN_DAC_IN0_B		BIT(15)
+#define DASN_DAC_IN0_B			BITS(0, 9)
+#define RG_17F				0x17F
+
+#define FORCE_DASN_DAC_IN0_C		BIT(15)
+#define DASN_DAC_IN0_C			BITS(0, 9)
+#define RG_180				0x180
+#define FORCE_DASN_DAC_IN0_D		BIT(15)
+#define DASN_DAC_IN0_D			BITS(0, 9)
+
+#define RG_181				0x181
+#define FORCE_DASN_DAC_IN1_A		BIT(15)
+#define DASN_DAC_IN1_A			BITS(0, 9)
+#define RG_182				0x182
+#define FORCE_DASN_DAC_IN1_B		BIT(15)
+#define DASN_DAC_IN1_B			BITS(0, 9)
+#define RG_183				0x183
+#define FORCE_DASN_DAC_IN1_C		BIT(15)
+#define DASN_DAC_IN1_C			BITS(0, 9)
+#define RG_184				0x184
+#define FORCE_DASN_DAC_IN1_D		BIT(15)
+#define DASN_DAC_IN1_D			BITS(0, 9)
+#define RG_172				0x172
+#define CR_TX_AMP_OFFSET_A		BITS(8, 13)
+#define CR_TX_AMP_OFFSET_B		BITS(0, 5)
+#define RG_173				0x173
+#define CR_TX_AMP_OFFSET_C		BITS(8, 13)
+#define CR_TX_AMP_OFFSET_D		BITS(0, 5)
+/**********TX Amp Calibration ***************************/
+#define RG_12				0x12
+#define DA_TX_I2MPB_A_GBE		BITS(10, 15)
+#define RG_17				0x17
+#define DA_TX_I2MPB_B_GBE		BITS(8, 13)
+#define RG_19				0x19
+#define DA_TX_I2MPB_C_GBE		BITS(8, 13)
+#define RG_21				0x21
+#define DA_TX_I2MPB_D_GBE		BITS(8, 13)
+#define TX_AMP_MAX			0x3f
+#define TX_AMP_MAX_OFFSET		0xb
+#define TX_AMP_HIGHEST_TS		((TX_AMP_MAX) + 3)
+#define TX_AMP_LOWEST_TS		(0 - 3)
+#define TX_AMP_HIGH_TS			(TX_AMP_MAX)
+#define TX_AMP_LOW_TS			0
+
+/* PHY Extend Register 0x14 bitmap of define */
+#define PHY_EXT_REG_14			0x14
+
+/* Fields of PHY_EXT_REG_14 */
+#define PHY_EN_DOWN_SHFIT		BIT(4)
+
+/* PHY Extend Register 0x17 bitmap of define */
+#define PHY_EXT_REG_17			0x17
+
+/* Fields of PHY_EXT_REG_17 */
+#define PHY_LINKDOWN_POWER_SAVING_EN	BIT(4)
+
+/* PHY PMA Register 0x17 bitmap of define */
+#define SLV_DSP_READY_TIME_S		15
+#define SLV_DSP_READY_TIME_M		(0xff << SLV_DSP_READY_TIME_S)
+
+/* PHY PMA Register 0x18 bitmap of define */
+#define ENABLE_RANDOM_UPDATE_TRIGGER	BIT(8)
+
+/* PHY EEE Register bitmap of define */
+#define PHY_DEV07			0x07
+#define PHY_DEV07_REG_03C		0x3c
+
+/* PHY DEV 0x1e Register bitmap of define */
+#define PHY_DEV1E			0x1e
+#define PHY_DEV1F			0x1f
+
+/* Proprietory Control Register of Internal Phy device 0x1e */
+#define PHY_TX_MLT3_BASE		0x0
+#define PHY_DEV1E_REG_13		0x13
+#define PHY_DEV1E_REG_14		0x14
+#define PHY_DEV1E_REG_41		0x41
+#define PHY_DEV1E_REG_A6		0xa6
+#define RXADC_CONTROL_3			0xc2
+#define PHY_DEV1E_REG_0C6		0xc6
+#define RXADC_LDO_CONTROL_2		0xd3
+#define PHY_DEV1E_REG_0FE		0xfe
+#define PHY_DEV1E_REG_123		0x123
+#define PHY_DEV1E_REG_189		0x189
+#define PHY_DEV1E_REG_234		0x234
+
+/* Proprietory Control Register of Internal Phy device 0x1f */
+#define PHY_DEV1F_REG_44		0x44
+#define PHY_DEV1F_REG_268		0x268
+#define PHY_DEV1F_REG_269		0x269
+#define PHY_DEV1F_REG_26A		0x26A
+#define TXVLD_DA_271			0x271
+#define TXVLD_DA_272			0x272
+#define TXVLD_DA_273			0x273
+
+/* Fields of PHY_DEV1E_REG_0C6 */
+#define PHY_POWER_SAVING_S		8
+#define PHY_POWER_SAVING_M		0x300
+#define PHY_POWER_SAVING_TX		0x0
+
+/* Fields of PHY_DEV1E_REG_189 */
+#define DESCRAMBLER_CLEAR_EN		0x1
+
+/* Fields of PHY_DEV1E_REG_234 */
+#define TR_OPEN_LOOP_EN			BIT(0)
+
+/* Internal GPHY Page Control Register */
+#define PHY_CL22_PAGE_CTRL		0x1f
+#define PHY_TR_PAGE			0x52b5
+
+/* Internal GPHY Token Ring Access Registers */
+#define PHY_TR_CTRL			0x10
+#define PHY_TR_LOW_DATA			0x11
+#define PHY_TR_HIGH_DATA		0x12
+
+/* Fields of PHY_TR_CTRL */
+#define PHY_TR_PKT_XMT_STA		BIT(15)
+#define PHY_TR_WR_S			13
+#define PHY_TR_CH_ADDR_S		11
+#define PHY_TR_NODE_ADDR_S		7
+#define PHY_TR_DATA_ADDR_S		1
+
+enum phy_tr_wr {
+	PHY_TR_WRITE = 0,
+	PHY_TR_READ = 1,
+};
+
+/* Helper macro for GPHY Token Ring Access */
+#define PHY_TR_LOW_VAL(x)		((x) & 0xffff)
+#define PHY_TR_HIGH_VAL(x)		(((x) & 0xff0000) >> 16)
+
+/* Token Ring Channels */
+#define PMA_CH				0x1
+#define DSP_CH				0x2
+
+/* Token Ring Nodes */
+#define PMA_NOD				0xf
+#define DSP_NOD				0xd
+
+/* Token Ring register range */
+enum tr_pma_reg_addr {
+	PMA_MIN = 0x0,
+	PMA_01 = 0x1,
+	PMA_17 = 0x17,
+	PMA_18 = 0x18,
+	PMA_MAX = 0x3d,
+};
+
+enum tr_dsp_reg_addr {
+	DSP_MIN = 0x0,
+	DSP_06 = 0x6,
+	DSP_08 = 0x8,
+	DSP_0f = 0xf,
+	DSP_10 = 0x10,
+	DSP_MAX = 0x3e,
+};
+#endif /* _AN8855_REGS_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Kconfig b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Kconfig
new file mode 100644
index 0000000..654aa37
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Kconfig
@@ -0,0 +1,8 @@
+
+config AN8855_GSW
+	tristate "Driver for the Airoha AN8855 switch"
+	help
+	  Airoha AN8855 devices and swconfig driver code,
+	  Provide swconfig command for basic switch operation.
+	  AN8855 support 2.5G speed and managed by SMI interface.
+	  To compile this driver as a module, choose M here.
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Makefile b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Makefile
new file mode 100644
index 0000000..5c24bda
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for Airoha AN8855 gigabit switch
+#
+
+obj-$(CONFIG_AN8855_GSW)	+= an8855.o
+
+an8855-$(CONFIG_SWCONFIG)	+= an8855_swconfig.o
+
+an8855-y			+= an8855_mdio.o an8855.o \
+					an8855_common.o an8855_vlan.o an8855_nl.o
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.c
new file mode 100644
index 0000000..f3db8b8
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.c
@@ -0,0 +1,913 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/of_address.h>
+
+#include "an8855.h"
+#include "an8855_regs.h"
+
+/* AN8855 registers */
+#define SCU_BASE					0x10000000
+#define RG_RGMII_TXCK_C				(SCU_BASE + 0x1d0)
+
+#define HSGMII_AN_CSR_BASE			0x10220000
+#define SGMII_REG_AN0				(HSGMII_AN_CSR_BASE + 0x000)
+#define SGMII_REG_AN_13				(HSGMII_AN_CSR_BASE + 0x034)
+#define SGMII_REG_AN_FORCE_CL37		(HSGMII_AN_CSR_BASE + 0x060)
+
+#define HSGMII_CSR_PCS_BASE			0x10220000
+#define RG_HSGMII_PCS_CTROL_1		(HSGMII_CSR_PCS_BASE + 0xa00)
+#define RG_AN_SGMII_MODE_FORCE		(HSGMII_CSR_PCS_BASE + 0xa24)
+
+#define MULTI_SGMII_CSR_BASE		0x10224000
+#define SGMII_STS_CTRL_0			(MULTI_SGMII_CSR_BASE + 0x018)
+#define MSG_RX_CTRL_0				(MULTI_SGMII_CSR_BASE + 0x100)
+#define MSG_RX_LIK_STS_0			(MULTI_SGMII_CSR_BASE + 0x514)
+#define MSG_RX_LIK_STS_2			(MULTI_SGMII_CSR_BASE + 0x51c)
+#define PHY_RX_FORCE_CTRL_0			(MULTI_SGMII_CSR_BASE + 0x520)
+
+#define XFI_CSR_PCS_BASE			0x10225000
+#define RG_USXGMII_AN_CONTROL_0		(XFI_CSR_PCS_BASE + 0xbf8)
+
+#define MULTI_PHY_RA_CSR_BASE		0x10226000
+#define RG_RATE_ADAPT_CTRL_0		(MULTI_PHY_RA_CSR_BASE + 0x000)
+#define RATE_ADP_P0_CTRL_0			(MULTI_PHY_RA_CSR_BASE + 0x100)
+#define MII_RA_AN_ENABLE			(MULTI_PHY_RA_CSR_BASE + 0x300)
+
+#define QP_DIG_CSR_BASE				0x1022a000
+#define QP_CK_RST_CTRL_4			(QP_DIG_CSR_BASE + 0x310)
+#define QP_DIG_MODE_CTRL_0			(QP_DIG_CSR_BASE + 0x324)
+#define QP_DIG_MODE_CTRL_1			(QP_DIG_CSR_BASE + 0x330)
+
+#define SERDES_WRAPPER_BASE			0x1022c000
+#define USGMII_CTRL_0				(SERDES_WRAPPER_BASE + 0x000)
+
+#define QP_PMA_TOP_BASE				0x1022e000
+#define PON_RXFEDIG_CTRL_0			(QP_PMA_TOP_BASE + 0x100)
+#define PON_RXFEDIG_CTRL_9			(QP_PMA_TOP_BASE + 0x124)
+
+#define SS_LCPLL_PWCTL_SETTING_2	(QP_PMA_TOP_BASE + 0x208)
+#define SS_LCPLL_TDC_FLT_2			(QP_PMA_TOP_BASE + 0x230)
+#define SS_LCPLL_TDC_FLT_5			(QP_PMA_TOP_BASE + 0x23c)
+#define SS_LCPLL_TDC_PCW_1			(QP_PMA_TOP_BASE + 0x248)
+#define INTF_CTRL_8		(QP_PMA_TOP_BASE + 0x320)
+#define INTF_CTRL_9		(QP_PMA_TOP_BASE + 0x324)
+#define PLL_CTRL_0		(QP_PMA_TOP_BASE + 0x400)
+#define PLL_CTRL_2		(QP_PMA_TOP_BASE + 0x408)
+#define PLL_CTRL_3		(QP_PMA_TOP_BASE + 0x40c)
+#define PLL_CTRL_4		(QP_PMA_TOP_BASE + 0x410)
+#define PLL_CK_CTRL_0	(QP_PMA_TOP_BASE + 0x414)
+#define RX_DLY_0		(QP_PMA_TOP_BASE + 0x614)
+#define RX_CTRL_2		(QP_PMA_TOP_BASE + 0x630)
+#define RX_CTRL_5		(QP_PMA_TOP_BASE + 0x63c)
+#define RX_CTRL_6		(QP_PMA_TOP_BASE + 0x640)
+#define RX_CTRL_7		(QP_PMA_TOP_BASE + 0x644)
+#define RX_CTRL_8		(QP_PMA_TOP_BASE + 0x648)
+#define RX_CTRL_26		(QP_PMA_TOP_BASE + 0x690)
+#define RX_CTRL_42		(QP_PMA_TOP_BASE + 0x6d0)
+
+#define QP_ANA_CSR_BASE				0x1022f000
+#define RG_QP_RX_DAC_EN				(QP_ANA_CSR_BASE + 0x00)
+#define RG_QP_RXAFE_RESERVE			(QP_ANA_CSR_BASE + 0x04)
+#define RG_QP_CDR_LPF_MJV_LIM		(QP_ANA_CSR_BASE + 0x0c)
+#define RG_QP_CDR_LPF_SETVALUE		(QP_ANA_CSR_BASE + 0x14)
+#define RG_QP_CDR_PR_CKREF_DIV1		(QP_ANA_CSR_BASE + 0x18)
+#define RG_QP_CDR_PR_KBAND_DIV_PCIE	(QP_ANA_CSR_BASE + 0x1c)
+#define RG_QP_CDR_FORCE_IBANDLPF_R_OFF	(QP_ANA_CSR_BASE + 0x20)
+#define RG_QP_TX_MODE_16B_EN		(QP_ANA_CSR_BASE + 0x28)
+#define RG_QP_PLL_IPLL_DIG_PWR_SEL	(QP_ANA_CSR_BASE + 0x3c)
+#define RG_QP_PLL_SDM_ORD			(QP_ANA_CSR_BASE + 0x40)
+
+#define ETHER_SYS_BASE				0x1028c800
+#define RG_P5MUX_MODE				(ETHER_SYS_BASE + 0x00)
+#define RG_FORCE_CKDIR_SEL			(ETHER_SYS_BASE + 0x04)
+#define RG_SWITCH_MODE				(ETHER_SYS_BASE + 0x08)
+#define RG_FORCE_MAC5_SB			(ETHER_SYS_BASE + 0x2c)
+#define RG_GPHY_AFE_PWD				(ETHER_SYS_BASE + 0x40)
+#define RG_GPHY_SMI_ADDR			(ETHER_SYS_BASE + 0x48)
+#define CSR_RMII					(ETHER_SYS_BASE + 0x70)
+
+/* PHY EEE Register bitmap of define */
+#define PHY_DEV07				0x07
+#define PHY_DEV07_REG_03C		0x3c
+
+/* PHY Extend Register 0x14 bitmap of define */
+#define PHY_EXT_REG_14			0x14
+
+/* Fields of PHY_EXT_REG_14 */
+#define PHY_EN_DOWN_SHFIT		BIT(4)
+
+/* Unique fields of PMCR for AN8855 */
+#define FORCE_TX_FC		BIT(4)
+#define FORCE_RX_FC		BIT(5)
+#define FORCE_DPX		BIT(25)
+#define FORCE_SPD		BITS(28, 30)
+#define FORCE_LNK		BIT(24)
+#define FORCE_MODE		BIT(31)
+
+#define CHIP_ID			0x10005000
+#define CHIP_REV		0x10005004
+
+static int an8855_set_hsgmii_mode(struct gsw_an8855 *gsw)
+{
+	u32 val = 0;
+
+	/* PLL */
+	val = an8855_reg_read(gsw, QP_DIG_MODE_CTRL_1);
+	val &= ~(0x3 << 2);
+	val |= (0x1 << 2);
+	an8855_reg_write(gsw, QP_DIG_MODE_CTRL_1, val);
+
+	/* PLL - LPF */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~(0x3 << 0);
+	val |= (0x1 << 0);
+	val &= ~(0x7 << 2);
+	val |= (0x5 << 2);
+	val &= ~BITS(6, 7);
+	val &= ~(0x7 << 8);
+	val |= (0x3 << 8);
+	val |= BIT(29);
+	val &= ~BITS(12, 13);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - ICO */
+	val = an8855_reg_read(gsw, PLL_CTRL_4);
+	val |= BIT(2);
+	an8855_reg_write(gsw, PLL_CTRL_4, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~BIT(14);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - CHP */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~(0xf << 16);
+	val |= (0x6 << 16);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - PFD */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~(0x3 << 20);
+	val |= (0x1 << 20);
+	val &= ~(0x3 << 24);
+	val |= (0x1 << 24);
+	val &= ~BIT(26);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - POSTDIV */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val |= BIT(22);
+	val &= ~BIT(27);
+	val &= ~BIT(28);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - SDM */
+	val = an8855_reg_read(gsw, PLL_CTRL_4);
+	val &= ~BITS(3, 4);
+	an8855_reg_write(gsw, PLL_CTRL_4, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~BIT(30);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	val = an8855_reg_read(gsw, SS_LCPLL_PWCTL_SETTING_2);
+	val &= ~(0x3 << 16);
+	val |= (0x1 << 16);
+	an8855_reg_write(gsw, SS_LCPLL_PWCTL_SETTING_2, val);
+
+	an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_2, 0x7a000000);
+	an8855_reg_write(gsw, SS_LCPLL_TDC_PCW_1, 0x7a000000);
+
+	val = an8855_reg_read(gsw, SS_LCPLL_TDC_FLT_5);
+	val &= ~BIT(24);
+	an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_5, val);
+
+	val = an8855_reg_read(gsw, PLL_CK_CTRL_0);
+	val &= ~BIT(8);
+	an8855_reg_write(gsw, PLL_CK_CTRL_0, val);
+
+	/* PLL - SS */
+	val = an8855_reg_read(gsw, PLL_CTRL_3);
+	val &= ~BITS(0, 15);
+	an8855_reg_write(gsw, PLL_CTRL_3, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_4);
+	val &= ~BITS(0, 1);
+	an8855_reg_write(gsw, PLL_CTRL_4, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_3);
+	val &= ~BITS(16, 31);
+	an8855_reg_write(gsw, PLL_CTRL_3, val);
+
+	/* PLL - TDC */
+	val = an8855_reg_read(gsw, PLL_CK_CTRL_0);
+	val &= ~BIT(9);
+	an8855_reg_write(gsw, PLL_CK_CTRL_0, val);
+
+	val = an8855_reg_read(gsw, RG_QP_PLL_SDM_ORD);
+	val |= BIT(3);
+	val |= BIT(4);
+	an8855_reg_write(gsw, RG_QP_PLL_SDM_ORD, val);
+
+	val = an8855_reg_read(gsw, RG_QP_RX_DAC_EN);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	an8855_reg_write(gsw, RG_QP_RX_DAC_EN, val);
+
+	/* TCL Disable (only for Co-SIM) */
+	val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_0);
+	val &= ~BIT(12);
+	an8855_reg_write(gsw, PON_RXFEDIG_CTRL_0, val);
+
+	/* TX Init */
+	val = an8855_reg_read(gsw, RG_QP_TX_MODE_16B_EN);
+	val &= ~BIT(0);
+	val &= ~(0xffff << 16);
+	val |= (0x4 << 16);
+	an8855_reg_write(gsw, RG_QP_TX_MODE_16B_EN, val);
+
+	/* RX Control */
+	val = an8855_reg_read(gsw, RG_QP_RXAFE_RESERVE);
+	val |= BIT(11);
+	an8855_reg_write(gsw, RG_QP_RXAFE_RESERVE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_LPF_MJV_LIM);
+	val &= ~(0x3 << 4);
+	val |= (0x1 << 4);
+	an8855_reg_write(gsw, RG_QP_CDR_LPF_MJV_LIM, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_LPF_SETVALUE);
+	val &= ~(0xf << 25);
+	val |= (0x1 << 25);
+	val &= ~(0x7 << 29);
+	val |= (0x3 << 29);
+	an8855_reg_write(gsw, RG_QP_CDR_LPF_SETVALUE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x1f << 8);
+	val |= (0xf << 8);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~(0x3f << 0);
+	val |= (0x19 << 0);
+	val &= ~BIT(6);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF);
+	val &= ~(0x7f << 6);
+	val |= (0x21 << 6);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	val &= ~BIT(13);
+	an8855_reg_write(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~BIT(30);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x7 << 24);
+	val |= (0x4 << 24);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_0);
+	val |= BIT(0);
+	an8855_reg_write(gsw, PLL_CTRL_0, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_26);
+	val &= ~BIT(23);
+	val |= BIT(26);
+	an8855_reg_write(gsw, RX_CTRL_26, val);
+
+	val = an8855_reg_read(gsw, RX_DLY_0);
+	val &= ~(0xff << 0);
+	val |= (0x6f << 0);
+	val |= BITS(8, 13);
+	an8855_reg_write(gsw, RX_DLY_0, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_42);
+	val &= ~(0x1fff << 0);
+	val |= (0x150 << 0);
+	an8855_reg_write(gsw, RX_CTRL_42, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_2);
+	val &= ~(0x1fff << 16);
+	val |= (0x150 << 16);
+	an8855_reg_write(gsw, RX_CTRL_2, val);
+
+	val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_9);
+	val &= ~(0x7 << 0);
+	val |= (0x1 << 0);
+	an8855_reg_write(gsw, PON_RXFEDIG_CTRL_9, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_8);
+	val &= ~(0xfff << 16);
+	val |= (0x200 << 16);
+	val &= ~(0x7fff << 14);
+	val |= (0xfff << 14);
+	an8855_reg_write(gsw, RX_CTRL_8, val);
+
+	/* Frequency memter */
+	val = an8855_reg_read(gsw, RX_CTRL_5);
+	val &= ~(0xfffff << 10);
+	val |= (0x10 << 10);
+	an8855_reg_write(gsw, RX_CTRL_5, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_6);
+	val &= ~(0xfffff << 0);
+	val |= (0x64 << 0);
+	an8855_reg_write(gsw, RX_CTRL_6, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_7);
+	val &= ~(0xfffff << 0);
+	val |= (0x2710 << 0);
+	an8855_reg_write(gsw, RX_CTRL_7, val);
+
+	/* PCS Init */
+	val = an8855_reg_read(gsw, RG_HSGMII_PCS_CTROL_1);
+	val &= ~BIT(30);
+	an8855_reg_write(gsw, RG_HSGMII_PCS_CTROL_1, val);
+
+	/* Rate Adaption */
+	val = an8855_reg_read(gsw, RATE_ADP_P0_CTRL_0);
+	val &= ~BIT(31);
+	an8855_reg_write(gsw, RATE_ADP_P0_CTRL_0, val);
+
+	val = an8855_reg_read(gsw, RG_RATE_ADAPT_CTRL_0);
+	val |= BIT(0);
+	val |= BIT(4);
+	val |= BITS(26, 27);
+	an8855_reg_write(gsw, RG_RATE_ADAPT_CTRL_0, val);
+
+	/* Disable AN */
+	val = an8855_reg_read(gsw, SGMII_REG_AN0);
+	val &= ~BIT(12);
+	an8855_reg_write(gsw, SGMII_REG_AN0, val);
+
+	/* Force Speed */
+	val = an8855_reg_read(gsw, SGMII_STS_CTRL_0);
+	val |= BIT(2);
+	val |= BITS(4, 5);
+	an8855_reg_write(gsw, SGMII_STS_CTRL_0, val);
+
+	/* bypass flow control to MAC */
+	an8855_reg_write(gsw, MSG_RX_LIK_STS_0, 0x01010107);
+	an8855_reg_write(gsw, MSG_RX_LIK_STS_2, 0x00000EEF);
+
+	return 0;
+}
+
+static int an8855_sgmii_setup(struct gsw_an8855 *gsw, int mode)
+{
+	u32 val = 0;
+
+	/* PMA Init */
+	/* PLL */
+	val = an8855_reg_read(gsw, QP_DIG_MODE_CTRL_1);
+	val &= ~BITS(2, 3);
+	an8855_reg_write(gsw, QP_DIG_MODE_CTRL_1, val);
+
+	/* PLL - LPF */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~(0x3 << 0);
+	val |= (0x1 << 0);
+	val &= ~(0x7 << 2);
+	val |= (0x5 << 2);
+	val &= ~BITS(6, 7);
+	val &= ~(0x7 << 8);
+	val |= (0x3 << 8);
+	val |= BIT(29);
+	val &= ~BITS(12, 13);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - ICO */
+	val = an8855_reg_read(gsw, PLL_CTRL_4);
+	val |= BIT(2);
+	an8855_reg_write(gsw, PLL_CTRL_4, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~BIT(14);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - CHP */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~(0xf << 16);
+	val |= (0x4 << 16);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - PFD */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~(0x3 << 20);
+	val |= (0x1 << 20);
+	val &= ~(0x3 << 24);
+	val |= (0x1 << 24);
+	val &= ~BIT(26);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - POSTDIV */
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val |= BIT(22);
+	val &= ~BIT(27);
+	val &= ~BIT(28);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	/* PLL - SDM */
+	val = an8855_reg_read(gsw, PLL_CTRL_4);
+	val &= ~BITS(3, 4);
+	an8855_reg_write(gsw, PLL_CTRL_4, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_2);
+	val &= ~BIT(30);
+	an8855_reg_write(gsw, PLL_CTRL_2, val);
+
+	val = an8855_reg_read(gsw, SS_LCPLL_PWCTL_SETTING_2);
+	val &= ~(0x3 << 16);
+	val |= (0x1 << 16);
+	an8855_reg_write(gsw, SS_LCPLL_PWCTL_SETTING_2, val);
+
+	an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_2, 0x48000000);
+	an8855_reg_write(gsw, SS_LCPLL_TDC_PCW_1, 0x48000000);
+
+	val = an8855_reg_read(gsw, SS_LCPLL_TDC_FLT_5);
+	val &= ~BIT(24);
+	an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_5, val);
+
+	val = an8855_reg_read(gsw, PLL_CK_CTRL_0);
+	val &= ~BIT(8);
+	an8855_reg_write(gsw, PLL_CK_CTRL_0, val);
+
+	/* PLL - SS */
+	val = an8855_reg_read(gsw, PLL_CTRL_3);
+	val &= ~BITS(0, 15);
+	an8855_reg_write(gsw, PLL_CTRL_3, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_4);
+	val &= ~BITS(0, 1);
+	an8855_reg_write(gsw, PLL_CTRL_4, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_3);
+	val &= ~BITS(16, 31);
+	an8855_reg_write(gsw, PLL_CTRL_3, val);
+
+	/* PLL - TDC */
+	val = an8855_reg_read(gsw, PLL_CK_CTRL_0);
+	val &= ~BIT(9);
+	an8855_reg_write(gsw, PLL_CK_CTRL_0, val);
+
+	val = an8855_reg_read(gsw, RG_QP_PLL_SDM_ORD);
+	val |= BIT(3);
+	val |= BIT(4);
+	an8855_reg_write(gsw, RG_QP_PLL_SDM_ORD, val);
+
+	val = an8855_reg_read(gsw, RG_QP_RX_DAC_EN);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	an8855_reg_write(gsw, RG_QP_RX_DAC_EN, val);
+
+	/* PLL - TCL Disable (only for Co-SIM) */
+	val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_0);
+	val &= ~BIT(12);
+	an8855_reg_write(gsw, PON_RXFEDIG_CTRL_0, val);
+
+	/* TX Init */
+	val = an8855_reg_read(gsw, RG_QP_TX_MODE_16B_EN);
+	val &= ~BIT(0);
+	val &= ~BITS(16, 31);
+	an8855_reg_write(gsw, RG_QP_TX_MODE_16B_EN, val);
+
+	/* RX Init */
+	val = an8855_reg_read(gsw, RG_QP_RXAFE_RESERVE);
+	val |= BIT(11);
+	an8855_reg_write(gsw, RG_QP_RXAFE_RESERVE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_LPF_MJV_LIM);
+	val &= ~(0x3 << 4);
+	val |= (0x2 << 4);
+	an8855_reg_write(gsw, RG_QP_CDR_LPF_MJV_LIM, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_LPF_SETVALUE);
+	val &= ~(0xf << 25);
+	val |= (0x1 << 25);
+	val &= ~(0x7 << 29);
+	val |= (0x6 << 29);
+	an8855_reg_write(gsw, RG_QP_CDR_LPF_SETVALUE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x1f << 8);
+	val |= (0xc << 8);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~(0x3f << 0);
+	val |= (0x19 << 0);
+	val &= ~BIT(6);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF);
+	val &= ~(0x7f << 6);
+	val |= (0x21 << 6);
+	val &= ~(0x3 << 16);
+	val |= (0x2 << 16);
+	val &= ~BIT(13);
+	an8855_reg_write(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE);
+	val &= ~BIT(30);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val);
+
+	val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1);
+	val &= ~(0x7 << 24);
+	val |= (0x4 << 24);
+	an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val);
+
+	val = an8855_reg_read(gsw, PLL_CTRL_0);
+	val |= BIT(0);
+	an8855_reg_write(gsw, PLL_CTRL_0, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_26);
+	val &= ~BIT(23);
+	if (mode == SGMII_MODE_AN)
+		val |= BIT(26);
+
+	an8855_reg_write(gsw, RX_CTRL_26, val);
+
+	val = an8855_reg_read(gsw, RX_DLY_0);
+	val &= ~(0xff << 0);
+	val |= (0x6f << 0);
+	val |= BITS(8, 13);
+	an8855_reg_write(gsw, RX_DLY_0, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_42);
+	val &= ~(0x1fff << 0);
+	val |= (0x150 << 0);
+	an8855_reg_write(gsw, RX_CTRL_42, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_2);
+	val &= ~(0x1fff << 16);
+	val |= (0x150 << 16);
+	an8855_reg_write(gsw, RX_CTRL_2, val);
+
+	val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_9);
+	val &= ~(0x7 << 0);
+	val |= (0x1 << 0);
+	an8855_reg_write(gsw, PON_RXFEDIG_CTRL_9, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_8);
+	val &= ~(0xfff << 16);
+	val |= (0x200 << 16);
+	val &= ~(0x7fff << 0);
+	val |= (0xfff << 0);
+	an8855_reg_write(gsw, RX_CTRL_8, val);
+
+	/* Frequency memter */
+	val = an8855_reg_read(gsw, RX_CTRL_5);
+	val &= ~(0xfffff << 10);
+	val |= (0x28 << 10);
+	an8855_reg_write(gsw, RX_CTRL_5, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_6);
+	val &= ~(0xfffff << 0);
+	val |= (0x64 << 0);
+	an8855_reg_write(gsw, RX_CTRL_6, val);
+
+	val = an8855_reg_read(gsw, RX_CTRL_7);
+	val &= ~(0xfffff << 0);
+	val |= (0x2710 << 0);
+	an8855_reg_write(gsw, RX_CTRL_7, val);
+
+	if (mode == SGMII_MODE_FORCE) {
+		/* PCS Init */
+		val = an8855_reg_read(gsw, QP_DIG_MODE_CTRL_0);
+		val &= ~BIT(0);
+		val &= ~BITS(4, 5);
+		an8855_reg_write(gsw, QP_DIG_MODE_CTRL_0, val);
+
+		val = an8855_reg_read(gsw, RG_HSGMII_PCS_CTROL_1);
+		val &= ~BIT(30);
+		an8855_reg_write(gsw, RG_HSGMII_PCS_CTROL_1, val);
+
+		/* Rate Adaption - GMII path config. */
+		val = an8855_reg_read(gsw, RG_AN_SGMII_MODE_FORCE);
+		val |= BIT(0);
+		val &= ~BITS(4, 5);
+		an8855_reg_write(gsw, RG_AN_SGMII_MODE_FORCE, val);
+
+		val = an8855_reg_read(gsw, SGMII_STS_CTRL_0);
+		val |= BIT(2);
+		val &= ~(0x3 << 4);
+		val |= (0x2 << 4);
+		an8855_reg_write(gsw, SGMII_STS_CTRL_0, val);
+
+		val = an8855_reg_read(gsw, SGMII_REG_AN0);
+		val &= ~BIT(12);
+		an8855_reg_write(gsw, SGMII_REG_AN0, val);
+
+		val = an8855_reg_read(gsw, PHY_RX_FORCE_CTRL_0);
+		val |= BIT(4);
+		an8855_reg_write(gsw, PHY_RX_FORCE_CTRL_0, val);
+
+		val = an8855_reg_read(gsw, RATE_ADP_P0_CTRL_0);
+		val &= ~BITS(0, 3);
+		val |= BIT(28);
+		an8855_reg_write(gsw, RATE_ADP_P0_CTRL_0, val);
+
+		val = an8855_reg_read(gsw, RG_RATE_ADAPT_CTRL_0);
+		val |= BIT(0);
+		val |= BIT(4);
+		val |= BITS(26, 27);
+		an8855_reg_write(gsw, RG_RATE_ADAPT_CTRL_0, val);
+	} else {
+		/* PCS Init */
+		val = an8855_reg_read(gsw, RG_HSGMII_PCS_CTROL_1);
+		val &= ~BIT(30);
+		an8855_reg_write(gsw, RG_HSGMII_PCS_CTROL_1, val);
+
+		/* Set AN Ability - Interrupt */
+		val = an8855_reg_read(gsw, SGMII_REG_AN_FORCE_CL37);
+		val |= BIT(0);
+		an8855_reg_write(gsw, SGMII_REG_AN_FORCE_CL37, val);
+
+		val = an8855_reg_read(gsw, SGMII_REG_AN_13);
+		val &= ~(0x3f << 0);
+		val |= (0xb << 0);
+		val |= BIT(8);
+		an8855_reg_write(gsw, SGMII_REG_AN_13, val);
+
+		/* Rate Adaption - GMII path config. */
+		val = an8855_reg_read(gsw, SGMII_REG_AN0);
+		val |= BIT(12);
+		an8855_reg_write(gsw, SGMII_REG_AN0, val);
+
+		val = an8855_reg_read(gsw, MII_RA_AN_ENABLE);
+		val |= BIT(0);
+		an8855_reg_write(gsw, MII_RA_AN_ENABLE, val);
+
+		val = an8855_reg_read(gsw, RATE_ADP_P0_CTRL_0);
+		val |= BIT(28);
+		an8855_reg_write(gsw, RATE_ADP_P0_CTRL_0, val);
+
+		val = an8855_reg_read(gsw, RG_RATE_ADAPT_CTRL_0);
+		val |= BIT(0);
+		val |= BIT(4);
+		val |= BITS(26, 27);
+		an8855_reg_write(gsw, RG_RATE_ADAPT_CTRL_0, val);
+
+		/* Only for Co-SIM */
+
+		/* AN Speed up (Only for Co-SIM) */
+
+		/* Restart AN */
+		val = an8855_reg_read(gsw, SGMII_REG_AN0);
+		val |= BIT(9);
+		val |= BIT(15);
+		an8855_reg_write(gsw, SGMII_REG_AN0, val);
+	}
+
+	/* bypass flow control to MAC */
+	an8855_reg_write(gsw, MSG_RX_LIK_STS_0, 0x01010107);
+	an8855_reg_write(gsw, MSG_RX_LIK_STS_2, 0x00000EEF);
+
+	return 0;
+}
+
+static int an8855_set_port_rmii(struct gsw_an8855 *gsw)
+{
+	an8855_reg_write(gsw, RG_P5MUX_MODE, 0x301);
+	an8855_reg_write(gsw, RG_FORCE_CKDIR_SEL, 0x101);
+	an8855_reg_write(gsw, RG_SWITCH_MODE, 0x101);
+	an8855_reg_write(gsw, RG_FORCE_MAC5_SB, 0x1010101);
+	an8855_reg_write(gsw, CSR_RMII, 0x420102);
+	an8855_reg_write(gsw, RG_RGMII_TXCK_C, 0x1100910);
+	return 0;
+}
+
+static int an8855_set_port_rgmii(struct gsw_an8855 *gsw)
+{
+	an8855_reg_write(gsw, RG_FORCE_MAC5_SB, 0x20101);
+	return 0;
+}
+
+static int an8855_mac_port_setup(struct gsw_an8855 *gsw, u32 port,
+				 struct an8855_port_cfg *port_cfg)
+{
+	u32 pmcr;
+
+	if (port != 5) {
+		dev_info(gsw->dev, "port %d is not a MAC port\n", port);
+		return -EINVAL;
+	}
+
+	if (port_cfg->enabled) {
+		pmcr = an8855_reg_read(gsw, PMCR(5));
+
+		switch (port_cfg->phy_mode) {
+		case PHY_INTERFACE_MODE_RMII:
+			pmcr &= ~FORCE_SPD;
+			pmcr |= FORCE_MODE | (MAC_SPD_100 << 28) | FORCE_DPX
+				| FORCE_LNK | FORCE_TX_FC | FORCE_RX_FC;
+			an8855_set_port_rmii(gsw);
+			break;
+		case PHY_INTERFACE_MODE_RGMII:
+			pmcr &= ~FORCE_SPD;
+			pmcr |= FORCE_MODE | (MAC_SPD_1000 << 28) | FORCE_DPX
+				| FORCE_LNK | FORCE_TX_FC | FORCE_RX_FC;
+			an8855_set_port_rgmii(gsw);
+			break;
+		case PHY_INTERFACE_MODE_SGMII:
+			if (port_cfg->force_link) {
+				pmcr &= ~FORCE_SPD;
+				pmcr |= FORCE_MODE | (MAC_SPD_1000 << 28)
+					 | FORCE_DPX | FORCE_LNK | FORCE_TX_FC
+					 | FORCE_RX_FC;
+				an8855_sgmii_setup(gsw, SGMII_MODE_FORCE);
+			} else
+				an8855_sgmii_setup(gsw, SGMII_MODE_AN);
+			break;
+		case PHY_INTERFACE_MODE_2500BASEX:
+			pmcr &= ~FORCE_SPD;
+			pmcr |= FORCE_MODE | (MAC_SPD_2500 << 28) | FORCE_DPX
+				| FORCE_LNK | FORCE_TX_FC | FORCE_RX_FC;
+			an8855_set_hsgmii_mode(gsw);
+			break;
+		default:
+			dev_info(gsw->dev, "%s is not supported by port %d\n",
+				 phy_modes(port_cfg->phy_mode), port);
+		}
+
+		if (port_cfg->force_link)
+			an8855_reg_write(gsw, PMCR(port), pmcr);
+	}
+
+	return 0;
+}
+
+static int an8855_sw_detect(struct gsw_an8855 *gsw, struct chip_rev *crev)
+{
+	u32 id, rev;
+
+	id = an8855_reg_read(gsw, CHIP_ID);
+	rev = an8855_reg_read(gsw, CHIP_REV);
+	if (id == AN8855) {
+		if (crev) {
+			crev->rev = rev;
+			crev->name = "AN8855";
+		}
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void an8855_phy_setting(struct gsw_an8855 *gsw)
+{
+	int i;
+	u32 val;
+
+	/* Release power down */
+	an8855_reg_write(gsw, RG_GPHY_AFE_PWD, 0x0);
+
+	for (i = 0; i < AN8855_NUM_PHYS; i++) {
+		/* Enable HW auto downshift */
+		gsw->mii_write(gsw, i, 0x1f, 0x1);
+		val = gsw->mii_read(gsw, i, PHY_EXT_REG_14);
+		val |= PHY_EN_DOWN_SHFIT;
+		gsw->mii_write(gsw, i, PHY_EXT_REG_14, val);
+		gsw->mii_write(gsw, i, 0x1f, 0x0);
+
+		/* Enable Asymmetric Pause Capability */
+		val = gsw->mii_read(gsw, i, MII_ADVERTISE);
+		val |= ADVERTISE_PAUSE_ASYM;
+		gsw->mii_write(gsw, i, MII_ADVERTISE, val);
+	}
+}
+
+static void an8855_low_power_setting(struct gsw_an8855 *gsw)
+{
+	int port, addr;
+
+	for (port = 0; port < AN8855_NUM_PHYS; port++) {
+		gsw->mmd_write(gsw, port, 0x1e, 0x11, 0x0f00);
+		gsw->mmd_write(gsw, port, 0x1e, 0x3c, 0x0000);
+		gsw->mmd_write(gsw, port, 0x1e, 0x3d, 0x0000);
+		gsw->mmd_write(gsw, port, 0x1e, 0x3e, 0x0000);
+		gsw->mmd_write(gsw, port, 0x1e, 0xc6, 0x53aa);
+	}
+
+	gsw->mmd_write(gsw, 0, 0x1f, 0x268, 0x07f1);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x269, 0x2111);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x26a, 0x0000);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x26b, 0x0074);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x26e, 0x00f6);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x26f, 0x6666);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x271, 0x2c02);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x272, 0x0c22);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x700, 0x0001);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x701, 0x0803);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x702, 0x01b6);
+	gsw->mmd_write(gsw, 0, 0x1f, 0x703, 0x2111);
+
+	gsw->mmd_write(gsw, 1, 0x1f, 0x700, 0x0001);
+
+	for (addr = 0x200; addr <= 0x230; addr += 2)
+		gsw->mmd_write(gsw, 0, 0x1f, addr, 0x2020);
+
+	for (addr = 0x201; addr <= 0x231; addr += 2)
+		gsw->mmd_write(gsw, 0, 0x1f, addr, 0x0020);
+}
+
+static void an8855_eee_setting(struct gsw_an8855 *gsw, u32 port)
+{
+	/* Disable EEE */
+	gsw->mmd_write(gsw, port, PHY_DEV07, PHY_DEV07_REG_03C, 0);
+}
+
+static int an8855_sw_init(struct gsw_an8855 *gsw)
+{
+	int i;
+	u32 val;
+
+	gsw->phy_base = gsw->smi_addr & AN8855_SMI_ADDR_MASK;
+
+	gsw->mii_read = an8855_mii_read;
+	gsw->mii_write = an8855_mii_write;
+	gsw->mmd_read = an8855_mmd_read;
+	gsw->mmd_write = an8855_mmd_write;
+
+	/* Force MAC link down before reset */
+	an8855_reg_write(gsw, PMCR(5), FORCE_MODE);
+
+	/* Switch soft reset */
+	an8855_reg_write(gsw, SYS_CTRL, SW_SYS_RST);
+	usleep_range(100000, 110000);
+
+	/* change gphy smi address */
+	if (gsw->new_smi_addr != gsw->smi_addr) {
+		an8855_reg_write(gsw, RG_GPHY_SMI_ADDR, gsw->new_smi_addr);
+		gsw->smi_addr = gsw->new_smi_addr;
+		gsw->phy_base = gsw->new_smi_addr;
+	}
+
+	for (i = 0; i < AN8855_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMCR);
+		val |= BMCR_ISOLATE;
+		gsw->mii_write(gsw, i, MII_BMCR, val);
+	}
+
+	an8855_mac_port_setup(gsw, 5, &gsw->port5_cfg);
+
+	/* Global mac control settings */
+	val = an8855_reg_read(gsw, GMACCR);
+	val |= (15 << MAX_RX_JUMBO_S) | RX_PKT_LEN_MAX_JUMBO;
+	an8855_reg_write(gsw, GMACCR, val);
+
+	val = an8855_reg_read(gsw, CKGCR);
+	val &= ~(CKG_LNKDN_GLB_STOP | CKG_LNKDN_PORT_STOP);
+	an8855_reg_write(gsw, CKGCR, val);
+	return 0;
+}
+
+static int an8855_sw_post_init(struct gsw_an8855 *gsw)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < AN8855_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMCR);
+		val &= ~BMCR_ISOLATE;
+		gsw->mii_write(gsw, i, MII_BMCR, val);
+	}
+
+	an8855_phy_setting(gsw);
+
+	for (i = 0; i < AN8855_NUM_PHYS; i++)
+		an8855_eee_setting(gsw, i);
+
+	/* PHY restart AN*/
+	for (i = 0; i < AN8855_NUM_PHYS; i++)
+		gsw->mii_write(gsw, i, MII_BMCR, 0x1240);
+
+	return 0;
+}
+
+struct an8855_sw_id an8855_id = {
+	.model = AN8855,
+	.detect = an8855_sw_detect,
+	.init = an8855_sw_init,
+	.post_init = an8855_sw_post_init
+};
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
+MODULE_DESCRIPTION("Driver for Airoha AN8855 Gigabit Switch");
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.h
new file mode 100644
index 0000000..883ba6e
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef _AN8855_H_
+#define _AN8855_H_
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of_mdio.h>
+#include <linux/workqueue.h>
+#include <linux/gpio/consumer.h>
+#include <linux/phy.h>
+
+#ifdef CONFIG_SWCONFIG
+#include <linux/switch.h>
+#endif
+
+#include "an8855_vlan.h"
+
+#define AN8855_DFL_CPU_PORT		5
+#define AN8855_NUM_PHYS			5
+#define AN8855_DFL_SMI_ADDR		0x1
+#define AN8855_SMI_ADDR_MASK	0x1f
+
+struct gsw_an8855;
+
+enum an8855_model {
+	AN8855 = 0x8855,
+};
+
+enum sgmii_mode {
+	SGMII_MODE_AN,
+	SGMII_MODE_FORCE,
+};
+
+struct an8855_port_cfg {
+	struct device_node *np;
+	phy_interface_t phy_mode;
+	u32 enabled: 1;
+	u32 force_link: 1;
+	u32 speed: 2;
+	u32 duplex: 1;
+	bool stag_on;
+};
+
+struct gsw_an8855 {
+	u32 id;
+
+	struct device *dev;
+	struct mii_bus *host_bus;
+	u32 smi_addr;
+	u32 new_smi_addr;
+	u32 phy_base;
+
+	enum an8855_model model;
+	const char *name;
+
+	struct an8855_port_cfg port5_cfg;
+
+	int phy_link_sts;
+
+	int irq;
+	int reset_pin;
+	struct work_struct irq_worker;
+
+#ifdef CONFIG_SWCONFIG
+	struct switch_dev swdev;
+	u32 cpu_port;
+#endif
+
+	int global_vlan_enable;
+	struct an8855_vlan_entry vlan_entries[AN8855_NUM_VLANS];
+	struct an8855_port_entry port_entries[AN8855_NUM_PORTS];
+
+	int (*mii_read)(struct gsw_an8855 *gsw, int phy, int reg);
+	void (*mii_write)(struct gsw_an8855 *gsw, int phy, int reg, u16 val);
+
+	int (*mmd_read)(struct gsw_an8855 *gsw, int addr, int devad, u16 reg);
+	void (*mmd_write)(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
+			  u16 val);
+
+	struct list_head list;
+};
+
+struct chip_rev {
+	const char *name;
+	u32 rev;
+};
+
+struct an8855_sw_id {
+	enum an8855_model model;
+	int (*detect)(struct gsw_an8855 *gsw, struct chip_rev *crev);
+	int (*init)(struct gsw_an8855 *gsw);
+	int (*post_init)(struct gsw_an8855 *gsw);
+};
+
+extern struct list_head an8855_devs;
+extern struct an8855_sw_id an8855_id;
+
+struct gsw_an8855 *an8855_get_gsw(u32 id);
+struct gsw_an8855 *an8855_get_first_gsw(void);
+void an8855_put_gsw(void);
+void an8855_lock_gsw(void);
+
+u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg);
+void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val);
+
+int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg);
+void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val);
+
+int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg);
+void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
+			  u16 val);
+
+void an8855_irq_worker(struct work_struct *work);
+void an8855_irq_enable(struct gsw_an8855 *gsw);
+
+#endif /* _AN8855_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_common.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_common.c
new file mode 100644
index 0000000..c01b5c4
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_common.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "an8855.h"
+#include "an8855_regs.h"
+
+void an8855_irq_enable(struct gsw_an8855 *gsw)
+{
+	u32 val;
+	int i;
+
+	/* Record initial PHY link status */
+	for (i = 0; i < AN8855_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMSR);
+		if (val & BMSR_LSTATUS)
+			gsw->phy_link_sts |= BIT(i);
+	}
+
+	val = BIT(AN8855_NUM_PHYS) - 1;
+	an8855_reg_write(gsw, SYS_INT_EN, val);
+
+	val = an8855_reg_read(gsw, INT_MASK);
+	val |= INT_SYS_BIT;
+	an8855_reg_write(gsw, INT_MASK, val);
+}
+
+static void display_port_link_status(struct gsw_an8855 *gsw, u32 port)
+{
+	u32 pmsr, speed_bits;
+	const char *speed;
+
+	pmsr = an8855_reg_read(gsw, PMSR(port));
+
+	speed_bits = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S;
+
+	switch (speed_bits) {
+	case MAC_SPD_10:
+		speed = "10Mbps";
+		break;
+	case MAC_SPD_100:
+		speed = "100Mbps";
+		break;
+	case MAC_SPD_1000:
+		speed = "1Gbps";
+		break;
+	case MAC_SPD_2500:
+		speed = "2.5Gbps";
+		break;
+	default:
+		dev_info(gsw->dev, "Invalid speed\n");
+		return;
+	}
+
+	if (pmsr & MAC_LNK_STS) {
+		dev_info(gsw->dev, "Port %d Link is Up - %s/%s\n",
+			 port, speed, (pmsr & MAC_DPX_STS) ? "Full" : "Half");
+	} else {
+		dev_info(gsw->dev, "Port %d Link is Down\n", port);
+	}
+}
+
+void an8855_irq_worker(struct work_struct *work)
+{
+	struct gsw_an8855 *gsw;
+	u32 sts, physts, laststs;
+	int i;
+
+	gsw = container_of(work, struct gsw_an8855, irq_worker);
+
+	sts = an8855_reg_read(gsw, SYS_INT_STS);
+
+	/* Check for changed PHY link status */
+	for (i = 0; i < AN8855_NUM_PHYS; i++) {
+		if (!(sts & PHY_LC_INT(i)))
+			continue;
+
+		laststs = gsw->phy_link_sts & BIT(i);
+		physts = !!(gsw->mii_read(gsw, i, MII_BMSR) & BMSR_LSTATUS);
+		physts <<= i;
+
+		if (physts ^ laststs) {
+			gsw->phy_link_sts ^= BIT(i);
+			display_port_link_status(gsw, i);
+		}
+	}
+
+	an8855_reg_write(gsw, SYS_INT_STS, sts);
+
+	enable_irq(gsw->irq);
+}
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_mdio.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_mdio.c
new file mode 100644
index 0000000..920240d
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_mdio.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifian8855_gsw_ider: GPL-2.0
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/reset.h>
+#include <linux/hrtimer.h>
+#include <linux/mii.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/of_net.h>
+#include <linux/of_irq.h>
+#include <linux/phy.h>
+
+#include "an8855.h"
+#include "an8855_swconfig.h"
+#include "an8855_regs.h"
+#include "an8855_nl.h"
+
+/* AN8855 driver version */
+#define ARHT_AN8855_SWCFG_DRIVER_VER	"1.0.1-L5.4"
+
+static u32 an8855_gsw_id;
+struct list_head an8855_devs;
+static DEFINE_MUTEX(an8855_devs_lock);
+
+static struct an8855_sw_id *an8855_sw_ids[] = {
+	&an8855_id,
+};
+
+u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg)
+{
+	u32 high, low;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4);
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x15,
+			     ((reg >> 16) & 0xFFFF));
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x16,
+			     (reg & 0xFFFF));
+
+	low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x18);
+	high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x17);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0);
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
+
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+
+	return (high << 16) | (low & 0xffff);
+}
+
+void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val)
+{
+	mutex_lock(&gsw->host_bus->mdio_lock);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4);
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x11,
+			     ((reg >> 16) & 0xFFFF));
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x12,
+			     (reg & 0xFFFF));
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x13,
+			     ((val >> 16) & 0xFFFF));
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x14,
+			     (val & 0xFFFF));
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0);
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
+
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+}
+
+int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg)
+{
+	int val;
+
+	if (phy < AN8855_NUM_PHYS)
+		phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+	val = gsw->host_bus->read(gsw->host_bus, phy, reg);
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+
+	return val;
+}
+
+void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val)
+{
+	if (phy < AN8855_NUM_PHYS)
+		phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+	gsw->host_bus->write(gsw->host_bus, phy, reg, val);
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+}
+
+int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg)
+{
+	int val;
+	u32 regnum = MII_ADDR_C45 | (devad << 16) | reg;
+
+	if (addr < AN8855_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+	val = gsw->host_bus->read(gsw->host_bus, addr, regnum);
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+
+	return val;
+}
+
+void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
+		      u16 val)
+{
+	u32 regnum = MII_ADDR_C45 | (devad << 16) | reg;
+
+	if (addr < AN8855_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+	gsw->host_bus->write(gsw->host_bus, addr, regnum, val);
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+}
+
+static inline int an8855_get_duplex(const struct device_node *np)
+{
+	return of_property_read_bool(np, "full-duplex");
+}
+
+static void an8855_load_port_cfg(struct gsw_an8855 *gsw)
+{
+	struct device_node *port_np;
+	struct device_node *fixed_link_node;
+	struct an8855_port_cfg *port_cfg;
+	u32 port;
+
+	for_each_child_of_node(gsw->dev->of_node, port_np) {
+		if (!of_device_is_compatible(port_np, "airoha,an8855-port"))
+			continue;
+
+		if (!of_device_is_available(port_np))
+			continue;
+
+		if (of_property_read_u32(port_np, "reg", &port))
+			continue;
+
+		switch (port) {
+		case 5:
+			port_cfg = &gsw->port5_cfg;
+			break;
+		default:
+			continue;
+		}
+
+		if (port_cfg->enabled) {
+			dev_info(gsw->dev, "duplicated node for port%d\n",
+				 port_cfg->phy_mode);
+			continue;
+		}
+
+		port_cfg->np = port_np;
+
+		port_cfg->phy_mode = of_get_phy_mode(port_np);
+		if (port_cfg->phy_mode < 0) {
+			dev_info(gsw->dev, "incorrect phy-mode %d\n", port);
+			continue;
+		}
+
+		fixed_link_node = of_get_child_by_name(port_np, "fixed-link");
+		if (fixed_link_node) {
+			u32 speed;
+
+			port_cfg->force_link = 1;
+			port_cfg->duplex = an8855_get_duplex(fixed_link_node);
+
+			if (of_property_read_u32(fixed_link_node, "speed",
+						 &speed)) {
+				speed = 0;
+				continue;
+			}
+
+			of_node_put(fixed_link_node);
+
+			switch (speed) {
+			case 10:
+				port_cfg->speed = MAC_SPD_10;
+				break;
+			case 100:
+				port_cfg->speed = MAC_SPD_100;
+				break;
+			case 1000:
+				port_cfg->speed = MAC_SPD_1000;
+				break;
+			case 2500:
+				port_cfg->speed = MAC_SPD_2500;
+				break;
+
+			default:
+				dev_info(gsw->dev, "incorrect speed %d\n",
+					 speed);
+				continue;
+			}
+		}
+
+		port_cfg->stag_on =
+		    of_property_read_bool(port_cfg->np, "airoha,stag-on");
+		port_cfg->enabled = 1;
+	}
+}
+
+static void an8855_add_gsw(struct gsw_an8855 *gsw)
+{
+	mutex_lock(&an8855_devs_lock);
+	gsw->id = an8855_gsw_id++;
+	INIT_LIST_HEAD(&gsw->list);
+	list_add_tail(&gsw->list, &an8855_devs);
+	mutex_unlock(&an8855_devs_lock);
+}
+
+static void an8855_remove_gsw(struct gsw_an8855 *gsw)
+{
+	mutex_lock(&an8855_devs_lock);
+	list_del(&gsw->list);
+	mutex_unlock(&an8855_devs_lock);
+}
+
+struct gsw_an8855 *an8855_get_gsw(u32 id)
+{
+	struct gsw_an8855 *dev;
+
+	mutex_lock(&an8855_devs_lock);
+
+	list_for_each_entry(dev, &an8855_devs, list) {
+		if (dev->id == id)
+			return dev;
+	}
+
+	mutex_unlock(&an8855_devs_lock);
+
+	return NULL;
+}
+
+struct gsw_an8855 *an8855_get_first_gsw(void)
+{
+	struct gsw_an8855 *dev;
+
+	mutex_lock(&an8855_devs_lock);
+
+	list_for_each_entry(dev, &an8855_devs, list)
+		return dev;
+
+	mutex_unlock(&an8855_devs_lock);
+
+	return NULL;
+}
+
+void an8855_put_gsw(void)
+{
+	mutex_unlock(&an8855_devs_lock);
+}
+
+void an8855_lock_gsw(void)
+{
+	mutex_lock(&an8855_devs_lock);
+}
+
+static int an8855_hw_reset(struct gsw_an8855 *gsw)
+{
+	struct device_node *np = gsw->dev->of_node;
+	int ret;
+
+	gsw->reset_pin = of_get_named_gpio(np, "reset-gpios", 0);
+	if (gsw->reset_pin < 0) {
+		dev_info(gsw->dev, "No reset pin of switch\n");
+		return 0;
+	}
+
+	ret = devm_gpio_request(gsw->dev, gsw->reset_pin, "an8855-reset");
+	if (ret) {
+		dev_info(gsw->dev, "Failed to request gpio %d\n",
+			 gsw->reset_pin);
+		return ret;
+	}
+
+	gpio_direction_output(gsw->reset_pin, 0);
+	usleep_range(100000, 150000);
+	gpio_set_value(gsw->reset_pin, 1);
+	usleep_range(100000, 150000);
+
+	return 0;
+}
+
+static irqreturn_t an8855_irq_handler(int irq, void *dev)
+{
+	struct gsw_an8855 *gsw = dev;
+
+	disable_irq_nosync(gsw->irq);
+
+	schedule_work(&gsw->irq_worker);
+
+	return IRQ_HANDLED;
+}
+
+static int an8855_probe(struct platform_device *pdev)
+{
+	struct gsw_an8855 *gsw;
+	struct an8855_sw_id *sw;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *mdio;
+	struct mii_bus *mdio_bus;
+	int ret = -EINVAL;
+	struct chip_rev rev;
+	struct an8855_mapping *map;
+	int i;
+
+	mdio = of_parse_phandle(np, "airoha,mdio", 0);
+	if (!mdio)
+		return -EINVAL;
+
+	mdio_bus = of_mdio_find_bus(mdio);
+	if (!mdio_bus)
+		return -EPROBE_DEFER;
+
+	gsw = devm_kzalloc(&pdev->dev, sizeof(struct gsw_an8855), GFP_KERNEL);
+	if (!gsw)
+		return -ENOMEM;
+
+	gsw->host_bus = mdio_bus;
+	gsw->dev = &pdev->dev;
+
+	dev_info(gsw->dev, "AN8855 Driver Version=%s\n",
+			ARHT_AN8855_SWCFG_DRIVER_VER);
+
+	/* Switch hard reset */
+	if (an8855_hw_reset(gsw)) {
+		dev_info(&pdev->dev, "reset switch fail.\n");
+		goto fail;
+	}
+
+	/* Fetch the SMI address first */
+	gsw->smi_addr = AN8855_DFL_SMI_ADDR;
+	if (of_property_read_u32(np, "airoha,smi-addr", &gsw->new_smi_addr))
+		gsw->new_smi_addr = AN8855_DFL_SMI_ADDR;
+
+	/* Get LAN/WAN port mapping */
+	map = an8855_find_mapping(np);
+	if (map) {
+		an8855_apply_mapping(gsw, map);
+		gsw->global_vlan_enable = 1;
+		dev_info(gsw->dev, "LAN/WAN VLAN setting=%s\n", map->name);
+	}
+
+	/* Load MAC port configurations */
+	an8855_load_port_cfg(gsw);
+
+	/* Check for valid switch and then initialize */
+	an8855_gsw_id = 0;
+	for (i = 0; i < ARRAY_SIZE(an8855_sw_ids); i++) {
+		if (!an8855_sw_ids[i]->detect(gsw, &rev)) {
+			sw = an8855_sw_ids[i];
+
+			gsw->name = rev.name;
+			gsw->model = sw->model;
+
+			dev_info(gsw->dev, "Switch is Airoha %s rev %d",
+				 gsw->name, rev.rev);
+
+			/* Initialize the switch */
+			ret = sw->init(gsw);
+			if (ret)
+				goto fail;
+
+			break;
+		}
+	}
+
+	if (i >= ARRAY_SIZE(an8855_sw_ids)) {
+		dev_err(gsw->dev, "No an8855 switch found\n");
+		goto fail;
+	}
+
+	gsw->irq = platform_get_irq(pdev, 0);
+	if (gsw->irq >= 0) {
+		ret = devm_request_irq(gsw->dev, gsw->irq, an8855_irq_handler,
+				       0, dev_name(gsw->dev), gsw);
+		if (ret) {
+			dev_err(gsw->dev, "Failed to request irq %d\n",
+				gsw->irq);
+			goto fail;
+		}
+
+		INIT_WORK(&gsw->irq_worker, an8855_irq_worker);
+	}
+
+	platform_set_drvdata(pdev, gsw);
+
+	an8855_add_gsw(gsw);
+
+	an8855_gsw_nl_init();
+
+	an8855_swconfig_init(gsw);
+
+	if (sw->post_init)
+		sw->post_init(gsw);
+
+	if (gsw->irq >= 0)
+		an8855_irq_enable(gsw);
+
+	return 0;
+
+fail:
+	devm_kfree(&pdev->dev, gsw);
+
+	return ret;
+}
+
+static int an8855_remove(struct platform_device *pdev)
+{
+	struct gsw_an8855 *gsw = platform_get_drvdata(pdev);
+
+	if (gsw->irq >= 0)
+		cancel_work_sync(&gsw->irq_worker);
+
+	if (gsw->reset_pin >= 0)
+		devm_gpio_free(&pdev->dev, gsw->reset_pin);
+
+#ifdef CONFIG_SWCONFIG
+	an8855_swconfig_destroy(gsw);
+#endif
+
+	an8855_gsw_nl_exit();
+
+	an8855_remove_gsw(gsw);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static const struct of_device_id an8855_ids[] = {
+	{.compatible = "airoha,an8855"},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, an8855_ids);
+
+static struct platform_driver an8855_driver = {
+	.probe = an8855_probe,
+	.remove = an8855_remove,
+	.driver = {
+		   .name = "an8855",
+		   .of_match_table = an8855_ids,
+		   },
+};
+
+static int __init an8855_init(void)
+{
+	int ret;
+
+	INIT_LIST_HEAD(&an8855_devs);
+	ret = platform_driver_register(&an8855_driver);
+
+	return ret;
+}
+
+module_init(an8855_init);
+
+static void __exit an8855_exit(void)
+{
+	platform_driver_unregister(&an8855_driver);
+}
+
+module_exit(an8855_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
+MODULE_VERSION(ARHT_AN8855_SWCFG_DRIVER_VER);
+MODULE_DESCRIPTION("Driver for Airoha AN8855 Gigabit Switch");
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.c
new file mode 100644
index 0000000..37e3dad
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <net/genetlink.h>
+
+#include "an8855.h"
+#include "an8855_nl.h"
+
+struct an8855_nl_cmd_item {
+	enum an8855_cmd cmd;
+	bool require_dev;
+	int (*process)(struct genl_info *info, struct gsw_an8855 *gsw);
+	u32 nr_required_attrs;
+	const enum an8855_attr *required_attrs;
+};
+
+static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info);
+
+static const struct nla_policy an8855_nl_cmd_policy[] = {
+	[AN8855_ATTR_TYPE_MESG] = { .type = NLA_STRING },
+	[AN8855_ATTR_TYPE_PHY] = { .type = NLA_S32 },
+	[AN8855_ATTR_TYPE_REG] = { .type = NLA_S32 },
+	[AN8855_ATTR_TYPE_VAL] = { .type = NLA_S32 },
+	[AN8855_ATTR_TYPE_DEV_NAME] = { .type = NLA_S32 },
+	[AN8855_ATTR_TYPE_DEV_ID] = { .type = NLA_S32 },
+	[AN8855_ATTR_TYPE_DEVAD] = { .type = NLA_S32 },
+};
+
+static const struct genl_ops an8855_nl_ops[] = {
+	{
+		.cmd = AN8855_CMD_REQUEST,
+		.doit = an8855_nl_response,
+//		.policy = an8855_nl_cmd_policy,
+		.flags = GENL_ADMIN_PERM,
+	}, {
+		.cmd = AN8855_CMD_READ,
+		.doit = an8855_nl_response,
+//		.policy = an8855_nl_cmd_policy,
+		.flags = GENL_ADMIN_PERM,
+	}, {
+		.cmd = AN8855_CMD_WRITE,
+		.doit = an8855_nl_response,
+//		.policy = an8855_nl_cmd_policy,
+		.flags = GENL_ADMIN_PERM,
+	},
+};
+
+static struct genl_family an8855_nl_family = {
+	.name =		AN8855_GENL_NAME,
+	.version =	AN8855_GENL_VERSION,
+	.maxattr =	AN8855_NR_ATTR_TYPE,
+	.ops =		an8855_nl_ops,
+	.n_ops =	ARRAY_SIZE(an8855_nl_ops),
+	.policy =	an8855_nl_cmd_policy,
+};
+
+static int an8855_nl_list_devs(char *buff, int size)
+{
+	struct gsw_an8855 *gsw;
+	int len, total = 0;
+	char buf[80];
+
+	memset(buff, 0, size);
+
+	an8855_lock_gsw();
+
+	list_for_each_entry(gsw, &an8855_devs, list) {
+		len = snprintf(buf, sizeof(buf),
+				   "id: %d, model: %s, node: %s\n",
+				   gsw->id, gsw->name, gsw->dev->of_node->name);
+		if (len == strlen(buf)) {
+			if (size - total > 0)
+				strncat(buff, buf, size - total);
+			total += len;
+		}
+	}
+
+	an8855_put_gsw();
+
+	return total;
+}
+
+static int an8855_nl_prepare_reply(struct genl_info *info, u8 cmd,
+				   struct sk_buff **skbp)
+{
+	struct sk_buff *msg;
+	void *reply;
+
+	if (!info)
+		return -EINVAL;
+
+	msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	/* Construct send-back message header */
+	reply = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+				&an8855_nl_family, 0, cmd);
+	if (!reply) {
+		nlmsg_free(msg);
+		return -EINVAL;
+	}
+
+	*skbp = msg;
+	return 0;
+}
+
+static int an8855_nl_send_reply(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
+	void *reply = genlmsg_data(genlhdr);
+
+	/* Finalize a generic netlink message (update message header) */
+	genlmsg_end(skb, reply);
+
+	/* reply to a request */
+	return genlmsg_reply(skb, info);
+}
+
+static s32 an8855_nl_get_s32(struct genl_info *info, enum an8855_attr attr,
+				 s32 defval)
+{
+	struct nlattr *na;
+
+	na = info->attrs[attr];
+	if (na)
+		return nla_get_s32(na);
+
+	return defval;
+}
+
+static int an8855_nl_get_u32(struct genl_info *info, enum an8855_attr attr,
+				 u32 *val)
+{
+	struct nlattr *na;
+
+	na = info->attrs[attr];
+	if (na) {
+		*val = nla_get_u32(na);
+		return 0;
+	}
+
+	return -1;
+}
+
+static struct gsw_an8855 *an8855_nl_parse_find_gsw(struct genl_info *info)
+{
+	struct gsw_an8855 *gsw;
+	struct nlattr *na;
+	int gsw_id;
+
+	na = info->attrs[AN8855_ATTR_TYPE_DEV_ID];
+	if (na) {
+		gsw_id = nla_get_s32(na);
+		if (gsw_id >= 0)
+			gsw = an8855_get_gsw(gsw_id);
+		else
+			gsw = an8855_get_first_gsw();
+	} else {
+		gsw = an8855_get_first_gsw();
+	}
+
+	return gsw;
+}
+
+static int an8855_nl_get_swdevs(struct genl_info *info, struct gsw_an8855 *gsw)
+{
+	struct sk_buff *rep_skb = NULL;
+	char dev_info[512];
+	int ret;
+
+	ret = an8855_nl_list_devs(dev_info, sizeof(dev_info) - 1);
+	if (!ret) {
+		pr_info("No switch registered\n");
+		return -EINVAL;
+	}
+
+	ret = an8855_nl_prepare_reply(info, AN8855_CMD_REPLY, &rep_skb);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_string(rep_skb, AN8855_ATTR_TYPE_MESG, dev_info);
+	if (ret < 0)
+		goto err;
+
+	return an8855_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static int an8855_nl_reply_read(struct genl_info *info, struct gsw_an8855 *gsw)
+{
+	struct sk_buff *rep_skb = NULL;
+	s32 phy, devad, reg;
+	int value;
+	int ret = 0;
+
+	phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
+	devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
+	reg = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_REG, -1);
+
+	if (reg < 0)
+		goto err;
+
+	ret = an8855_nl_prepare_reply(info, AN8855_CMD_READ, &rep_skb);
+	if (ret < 0)
+		goto err;
+
+	if (phy >= 0) {
+		if (devad < 0)
+			value = gsw->mii_read(gsw, phy, reg);
+		else
+			value = gsw->mmd_read(gsw, phy, devad, reg);
+	} else {
+		value = an8855_reg_read(gsw, reg);
+	}
+
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
+	if (ret < 0)
+		goto err;
+
+	return an8855_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static int an8855_nl_reply_write(struct genl_info *info, struct gsw_an8855 *gsw)
+{
+	struct sk_buff *rep_skb = NULL;
+	s32 phy, devad, reg;
+	u32 value;
+	int ret = 0;
+
+	phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
+	devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
+	reg = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_REG, -1);
+
+	if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_VAL, &value))
+		goto err;
+
+	if (reg < 0)
+		goto err;
+
+	ret = an8855_nl_prepare_reply(info, AN8855_CMD_WRITE, &rep_skb);
+	if (ret < 0)
+		goto err;
+
+	if (phy >= 0) {
+		if (devad < 0)
+			gsw->mii_write(gsw, phy, reg, value);
+		else
+			gsw->mmd_write(gsw, phy, devad, reg, value);
+	} else {
+		an8855_reg_write(gsw, reg, value);
+	}
+
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_s32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
+	if (ret < 0)
+		goto err;
+
+	return an8855_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static const enum an8855_attr an8855_nl_cmd_read_attrs[] = {
+	AN8855_ATTR_TYPE_REG
+};
+
+static const enum an8855_attr an8855_nl_cmd_write_attrs[] = {
+	AN8855_ATTR_TYPE_REG,
+	AN8855_ATTR_TYPE_VAL
+};
+
+static const struct an8855_nl_cmd_item an8855_nl_cmds[] = {
+	{
+		.cmd = AN8855_CMD_REQUEST,
+		.require_dev = false,
+		.process = an8855_nl_get_swdevs
+	}, {
+		.cmd = AN8855_CMD_READ,
+		.require_dev = true,
+		.process = an8855_nl_reply_read,
+		.required_attrs = an8855_nl_cmd_read_attrs,
+		.nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_read_attrs),
+	}, {
+		.cmd = AN8855_CMD_WRITE,
+		.require_dev = true,
+		.process = an8855_nl_reply_write,
+		.required_attrs = an8855_nl_cmd_write_attrs,
+		.nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_write_attrs),
+	}
+};
+
+static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
+	const struct an8855_nl_cmd_item *cmditem = NULL;
+	struct gsw_an8855 *gsw = NULL;
+	u32 sat_req_attrs = 0;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(an8855_nl_cmds); i++) {
+		if (hdr->cmd == an8855_nl_cmds[i].cmd) {
+			cmditem = &an8855_nl_cmds[i];
+			break;
+		}
+	}
+
+	if (!cmditem) {
+		pr_info("an8855-nl: unknown cmd %u\n", hdr->cmd);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < cmditem->nr_required_attrs; i++) {
+		if (info->attrs[cmditem->required_attrs[i]])
+			sat_req_attrs++;
+	}
+
+	if (sat_req_attrs != cmditem->nr_required_attrs) {
+		pr_info("an8855-nl: missing required attr(s) for cmd %u\n",
+			hdr->cmd);
+		return -EINVAL;
+	}
+
+	if (cmditem->require_dev) {
+		gsw = an8855_nl_parse_find_gsw(info);
+		if (!gsw) {
+			pr_info("an8855-nl: failed to find switch dev\n");
+			return -EINVAL;
+		}
+	}
+
+	ret = cmditem->process(info, gsw);
+
+	an8855_put_gsw();
+
+	return ret;
+}
+
+int an8855_gsw_nl_init(void)
+{
+	int ret;
+
+	ret = genl_register_family(&an8855_nl_family);
+	if (ret) {
+		pr_info("an8855-nl: genl_register_family_with_ops failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+void an8855_gsw_nl_exit(void)
+{
+	genl_unregister_family(&an8855_nl_family);
+}
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.h
new file mode 100644
index 0000000..b00df75
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef _AN8855_NL_H_
+#define _AN8855_NL_H_
+
+#define AN8855_GENL_NAME		"an8855"
+#define AN8855_GENL_VERSION		0x1
+
+enum an8855_cmd {
+	AN8855_CMD_UNSPEC = 0,
+	AN8855_CMD_REQUEST,
+	AN8855_CMD_REPLY,
+	AN8855_CMD_READ,
+	AN8855_CMD_WRITE,
+
+	__AN8855_CMD_MAX,
+};
+
+enum an8855_attr {
+	AN8855_ATTR_TYPE_UNSPEC = 0,
+	AN8855_ATTR_TYPE_MESG,
+	AN8855_ATTR_TYPE_PHY,
+	AN8855_ATTR_TYPE_DEVAD,
+	AN8855_ATTR_TYPE_REG,
+	AN8855_ATTR_TYPE_VAL,
+	AN8855_ATTR_TYPE_DEV_NAME,
+	AN8855_ATTR_TYPE_DEV_ID,
+
+	__AN8855_ATTR_TYPE_MAX,
+};
+
+#define AN8855_NR_ATTR_TYPE		(__AN8855_ATTR_TYPE_MAX - 1)
+
+#ifdef __KERNEL__
+int an8855_gsw_nl_init(void);
+void an8855_gsw_nl_exit(void);
+#endif /* __KERNEL__ */
+
+#endif /* _AN8855_NL_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_regs.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_regs.h
new file mode 100644
index 0000000..9dbfaeb
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_regs.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef _AN8855_REGS_H_
+#define _AN8855_REGS_H_
+
+#include <linux/bitops.h>
+
+#define BITS(m, n)	 (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
+
+/* Values of Egress TAG Control */
+#define ETAG_CTRL_UNTAG			0
+#define ETAG_CTRL_TAG			2
+#define ETAG_CTRL_SWAP			1
+#define ETAG_CTRL_STACK			3
+
+#define VTCR					0x10200600
+#define VAWD0					0x10200604
+#define VAWD1					0x10200608
+#define VARD0					0x10200618
+#define VARD1					0x1020061C
+
+/* Fields of VTCR */
+#define VTCR_BUSY				BIT(31)
+#define VTCR_FUNC_S				12
+#define VTCR_FUNC_M				0xf000
+#define VTCR_VID_S				0
+#define VTCR_VID_M				0xfff
+
+/* Values of VTCR_FUNC */
+#define VTCR_READ_VLAN_ENTRY	0
+#define VTCR_WRITE_VLAN_ENTRY	1
+
+/* VLAN entry fields */
+#define IVL_MAC					BIT(5)
+#define EG_CON					BIT(11)
+#define VTAG_EN					BIT(10)
+#define PORT_MEM_S				26
+#define PORT_MEM_M				0xfc000000
+#define VENTRY_VALID			BIT(0)
+#define ETAG_M					0x3fff000
+#define PORT_ETAG_S(p)			(((p) * 2) + 12)
+#define PORT_ETAG_M				0x03
+
+#define PORT_CTRL_BASE			0x10208000
+#define PORT_CTRL_PORT_OFFSET	0x200
+#define PORT_CTRL_REG(p, r)		(PORT_CTRL_BASE + \
+					(p) * PORT_CTRL_PORT_OFFSET +  (r))
+#define PCR(p)					PORT_CTRL_REG(p, 0x04)
+#define PVC(p)					PORT_CTRL_REG(p, 0x10)
+#define PORTMATRIX(p)			PORT_CTRL_REG(p, 0x44)
+#define PVID(p)					PORT_CTRL_REG(p, 0x48)
+
+#define GRP_PORT_VID_M			0xfff
+
+/* Values of PORT_VLAN */
+#define PORT_MATRIX_MODE		0
+#define FALLBACK_MODE			1
+#define CHECK_MODE				2
+#define SECURITY_MODE			3
+
+/* Fields of PVC */
+#define STAG_VPID_S				16
+#define STAG_VPID_M				0xffff0000
+#define VLAN_ATTR_S				6
+#define VLAN_ATTR_M				0xc0
+#define PVC_STAG_REPLACE		BIT(11)
+#define PVC_PORT_STAG			BIT(5)
+
+/* Values of VLAN_ATTR */
+#define VA_USER_PORT			0
+#define VA_STACK_PORT			1
+#define VA_TRANSLATION_PORT		2
+#define VA_TRANSPARENT_PORT		3
+
+#define PORT_MATRIX_M			((1 << AN8855_NUM_PORTS) - 1)
+
+#define PORT_MAC_CTRL_BASE		0x10210000
+#define PORT_MAC_CTRL_PORT_OFFSET	0x200
+#define PORT_MAC_CTRL_REG(p, r)		(PORT_MAC_CTRL_BASE + \
+					(p) * PORT_MAC_CTRL_PORT_OFFSET + (r))
+#define PMCR(p)					PORT_MAC_CTRL_REG(p, 0x00)
+#define PMSR(p)					PORT_MAC_CTRL_REG(p, 0x10)
+
+#define GMACCR					(PORT_MAC_CTRL_BASE + 0x30e0)
+
+#define MAX_RX_JUMBO_S			2
+#define MAX_RX_JUMBO_M			0x3c
+#define MAX_RX_PKT_LEN_S		0
+#define MAX_RX_PKT_LEN_M		0x3
+
+/* Values of MAX_RX_PKT_LEN */
+#define RX_PKT_LEN_1518			0
+#define RX_PKT_LEN_1536			1
+#define RX_PKT_LEN_1522			2
+#define RX_PKT_LEN_MAX_JUMBO	3
+
+/* Fields of PMSR */
+#define EEE1G_STS			BIT(7)
+#define EEE100_STS			BIT(6)
+#define RX_FC_STS			BIT(5)
+#define TX_FC_STS			BIT(4)
+#define MAC_SPD_STS_S		28
+#define MAC_SPD_STS_M		0x70000000
+#define MAC_DPX_STS			BIT(25)
+#define MAC_LNK_STS			BIT(24)
+
+/* Values of MAC_SPD_STS */
+#define MAC_SPD_10			0
+#define MAC_SPD_100			1
+#define MAC_SPD_1000		2
+#define MAC_SPD_2500		3
+
+#define MIB_COUNTER_BASE			0x10214000
+#define MIB_COUNTER_PORT_OFFSET		0x200
+#define MIB_COUNTER_REG(p, r)		(MIB_COUNTER_BASE + \
+					(p) * MIB_COUNTER_PORT_OFFSET + (r))
+#define STATS_TDPC			0x00
+#define STATS_TCRC			0x04
+#define STATS_TUPC			0x08
+#define STATS_TMPC			0x0C
+#define STATS_TBPC			0x10
+#define STATS_TCEC			0x14
+#define STATS_TSCEC			0x18
+#define STATS_TMCEC			0x1C
+#define STATS_TDEC			0x20
+#define STATS_TLCEC			0x24
+#define STATS_TXCEC			0x28
+#define STATS_TPPC			0x2C
+#define STATS_TL64PC		0x30
+#define STATS_TL65PC		0x34
+#define STATS_TL128PC		0x38
+#define STATS_TL256PC		0x3C
+#define STATS_TL512PC		0x40
+#define STATS_TL1024PC		0x44
+#define STATS_TL1519PC		0x48
+#define STATS_TOC			0x4C
+#define STATS_TODPC			0x54
+#define STATS_TOC2			0x58
+
+#define STATS_RDPC			0x80
+#define STATS_RFPC			0x84
+#define STATS_RUPC			0x88
+#define STATS_RMPC			0x8C
+#define STATS_RBPC			0x90
+#define STATS_RAEPC			0x94
+#define STATS_RCEPC			0x98
+#define STATS_RUSPC			0x9C
+#define STATS_RFEPC			0xA0
+#define STATS_ROSPC			0xA4
+#define STATS_RJEPC			0xA8
+#define STATS_RPPC			0xAC
+#define STATS_RL64PC		0xB0
+#define STATS_RL65PC		0xB4
+#define STATS_RL128PC		0xB8
+#define STATS_RL256PC		0xBC
+#define STATS_RL512PC		0xC0
+#define STATS_RL1024PC		0xC4
+#define STATS_RL1519PC		0xC8
+#define STATS_ROC			0xCC
+#define STATS_RDPC_CTRL		0xD4
+#define STATS_RDPC_ING		0xD8
+#define STATS_RDPC_ARL		0xDC
+#define STATS_RDPC_FC		0xE0
+#define STATS_RDPC_WRED		0xE4
+#define STATS_RDPC_MIR		0xE8
+#define STATS_ROC2			0xEC
+#define STATS_RSFSPC		0xF4
+#define STATS_RSFTPC		0xF8
+#define STATS_RXCDPC		0xFC
+
+#define SYS_CTRL			0x100050C0
+#define SW_SYS_RST			BIT(31)
+
+#define INT_MASK			0x100050F0
+#define INT_SYS_BIT			BIT(15)
+
+#define SYS_INT_EN			0x1021C010
+#define SYS_INT_STS			0x1021C014
+#define PHY_LC_INT(p)		BIT(p)
+
+#define CKGCR				(0x10213E1C)
+#define CKG_LNKDN_GLB_STOP	(0x01)
+#define CKG_LNKDN_PORT_STOP	(0x02)
+#endif /* _AN8855_REGS_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.c
new file mode 100644
index 0000000..e796582
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.c
@@ -0,0 +1,527 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/if.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/bitops.h>
+#include <net/genetlink.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/lockdep.h>
+#include <linux/workqueue.h>
+#include <linux/of_device.h>
+
+#include "an8855.h"
+#include "an8855_swconfig.h"
+#include "an8855_regs.h"
+
+#define AN8855_PORT_MIB_TXB_ID	19	/* TxByte */
+#define AN8855_PORT_MIB_RXB_ID	40	/* RxByte */
+
+#define MIB_DESC(_s, _o, _n)   \
+	{						\
+		.size = (_s),	\
+		.offset = (_o), \
+		.name = (_n),	\
+	}
+
+struct an8855_mib_desc {
+	unsigned int size;
+	unsigned int offset;
+	const char *name;
+};
+
+static const struct an8855_mib_desc an8855_mibs[] = {
+	MIB_DESC(1, STATS_TDPC, "TxDrop"),
+	MIB_DESC(1, STATS_TCRC, "TxCRC"),
+	MIB_DESC(1, STATS_TUPC, "TxUni"),
+	MIB_DESC(1, STATS_TMPC, "TxMulti"),
+	MIB_DESC(1, STATS_TBPC, "TxBroad"),
+	MIB_DESC(1, STATS_TCEC, "TxCollision"),
+	MIB_DESC(1, STATS_TSCEC, "TxSingleCol"),
+	MIB_DESC(1, STATS_TMCEC, "TxMultiCol"),
+	MIB_DESC(1, STATS_TDEC, "TxDefer"),
+	MIB_DESC(1, STATS_TLCEC, "TxLateCol"),
+	MIB_DESC(1, STATS_TXCEC, "TxExcCol"),
+	MIB_DESC(1, STATS_TPPC, "TxPause"),
+	MIB_DESC(1, STATS_TL64PC, "Tx64Byte"),
+	MIB_DESC(1, STATS_TL65PC, "Tx65Byte"),
+	MIB_DESC(1, STATS_TL128PC, "Tx128Byte"),
+	MIB_DESC(1, STATS_TL256PC, "Tx256Byte"),
+	MIB_DESC(1, STATS_TL512PC, "Tx512Byte"),
+	MIB_DESC(1, STATS_TL1024PC, "Tx1024Byte"),
+	MIB_DESC(1, STATS_TL1519PC, "Tx1519Byte"),
+	MIB_DESC(2, STATS_TOC, "TxByte"),
+	MIB_DESC(1, STATS_TODPC, "TxOverSize"),
+	MIB_DESC(2, STATS_TOC2, "SecondaryTxByte"),
+	MIB_DESC(1, STATS_RDPC, "RxDrop"),
+	MIB_DESC(1, STATS_RFPC, "RxFiltered"),
+	MIB_DESC(1, STATS_RUPC, "RxUni"),
+	MIB_DESC(1, STATS_RMPC, "RxMulti"),
+	MIB_DESC(1, STATS_RBPC, "RxBroad"),
+	MIB_DESC(1, STATS_RAEPC, "RxAlignErr"),
+	MIB_DESC(1, STATS_RCEPC, "RxCRC"),
+	MIB_DESC(1, STATS_RUSPC, "RxUnderSize"),
+	MIB_DESC(1, STATS_RFEPC, "RxFragment"),
+	MIB_DESC(1, STATS_ROSPC, "RxOverSize"),
+	MIB_DESC(1, STATS_RJEPC, "RxJabber"),
+	MIB_DESC(1, STATS_RPPC, "RxPause"),
+	MIB_DESC(1, STATS_RL64PC, "Rx64Byte"),
+	MIB_DESC(1, STATS_RL65PC, "Rx65Byte"),
+	MIB_DESC(1, STATS_RL128PC, "Rx128Byte"),
+	MIB_DESC(1, STATS_RL256PC, "Rx256Byte"),
+	MIB_DESC(1, STATS_RL512PC, "Rx512Byte"),
+	MIB_DESC(1, STATS_RL1024PC, "Rx1024Byte"),
+	MIB_DESC(2, STATS_ROC, "RxByte"),
+	MIB_DESC(1, STATS_RDPC_CTRL, "RxCtrlDrop"),
+	MIB_DESC(1, STATS_RDPC_ING, "RxIngDrop"),
+	MIB_DESC(1, STATS_RDPC_ARL, "RxARLDrop"),
+	MIB_DESC(1, STATS_RDPC_FC, "RxFCDrop"),
+	MIB_DESC(1, STATS_RDPC_WRED, "RxWREDDrop"),
+	MIB_DESC(1, STATS_RDPC_MIR, "RxMIRDrop"),
+	MIB_DESC(2, STATS_ROC2, "SecondaryRxByte"),
+	MIB_DESC(1, STATS_RSFSPC, "RxsFlowSampling"),
+	MIB_DESC(1, STATS_RSFTPC, "RxsFlowTotal"),
+	MIB_DESC(1, STATS_RXCDPC, "RxPortDrop"),
+};
+
+enum {
+	/* Global attributes. */
+	AN8855_ATTR_ENABLE_VLAN,
+};
+
+static int an8855_get_vlan_enable(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	val->value.i = gsw->global_vlan_enable;
+
+	return 0;
+}
+
+static int an8855_set_vlan_enable(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	gsw->global_vlan_enable = val->value.i != 0;
+
+	return 0;
+}
+
+static int an8855_get_port_pvid(struct switch_dev *dev, int port, int *val)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	if (port >= AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	*val = an8855_reg_read(gsw, PVID(port));
+	*val &= GRP_PORT_VID_M;
+
+	return 0;
+}
+
+static int an8855_set_port_pvid(struct switch_dev *dev, int port, int pvid)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	if (port >= AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	if (pvid < AN8855_MIN_VID || pvid > AN8855_MAX_VID)
+		return -EINVAL;
+
+	gsw->port_entries[port].pvid = pvid;
+
+	return 0;
+}
+
+static int an8855_get_vlan_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+	u32 member;
+	u32 etags;
+	int i;
+
+	val->len = 0;
+
+	if (val->port_vlan >= AN8855_NUM_VLANS)
+		return -EINVAL;
+
+	an8855_vlan_ctrl(gsw, VTCR_READ_VLAN_ENTRY, val->port_vlan);
+
+	member = an8855_reg_read(gsw, VARD0);
+	member &= PORT_MEM_M;
+	member >>= PORT_MEM_S;
+	member |= ((an8855_reg_read(gsw, VARD1) & 0x1) << 6);
+
+	etags = an8855_reg_read(gsw, VARD0) & ETAG_M;
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		struct switch_port *p;
+		int etag;
+
+		if (!(member & BIT(i)))
+			continue;
+
+		p = &val->value.ports[val->len++];
+		p->id = i;
+
+		etag = (etags >> PORT_ETAG_S(i)) & PORT_ETAG_M;
+
+		if (etag == ETAG_CTRL_TAG)
+			p->flags |= BIT(SWITCH_PORT_FLAG_TAGGED);
+		else if (etag != ETAG_CTRL_UNTAG)
+			dev_info(gsw->dev,
+				 "vlan egress tag control neither untag nor tag.\n");
+	}
+
+	return 0;
+}
+
+static int an8855_set_vlan_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+	u8 member = 0;
+	u8 etags = 0;
+	int i;
+
+	if (val->port_vlan >= AN8855_NUM_VLANS ||
+		val->len > AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	for (i = 0; i < val->len; i++) {
+		struct switch_port *p = &val->value.ports[i];
+
+		if (p->id >= AN8855_NUM_PORTS)
+			return -EINVAL;
+
+		member |= BIT(p->id);
+
+		if (p->flags & BIT(SWITCH_PORT_FLAG_TAGGED))
+			etags |= BIT(p->id);
+	}
+
+	gsw->vlan_entries[val->port_vlan].member = member;
+	gsw->vlan_entries[val->port_vlan].etags = etags;
+
+	return 0;
+}
+
+static int an8855_set_vid(struct switch_dev *dev,
+			  const struct switch_attr *attr,
+			  struct switch_val *val)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+	int vlan;
+	u16 vid;
+
+	vlan = val->port_vlan;
+	vid = (u16)val->value.i;
+
+	if (vlan < 0 || vlan >= AN8855_NUM_VLANS)
+		return -EINVAL;
+
+	if (vid > AN8855_MAX_VID)
+		return -EINVAL;
+
+	gsw->vlan_entries[vlan].vid = vid;
+	return 0;
+}
+
+static int an8855_get_vid(struct switch_dev *dev,
+			  const struct switch_attr *attr,
+			  struct switch_val *val)
+{
+	val->value.i = val->port_vlan;
+	return 0;
+}
+
+static int an8855_get_port_link(struct switch_dev *dev, int port,
+				struct switch_port_link *link)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+	u32 speed, pmsr;
+
+	if (port < 0 || port >= AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	pmsr = an8855_reg_read(gsw, PMSR(port));
+
+	link->link = pmsr & MAC_LNK_STS;
+	link->duplex = pmsr & MAC_DPX_STS;
+	speed = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S;
+
+	switch (speed) {
+	case MAC_SPD_10:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case MAC_SPD_100:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case MAC_SPD_1000:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	case MAC_SPD_2500:
+		/* TODO: swconfig has no support for 2500 now */
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int an8855_set_port_link(struct switch_dev *dev, int port,
+				struct switch_port_link *link)
+{
+#ifndef MODULE
+	if (port >= AN8855_NUM_PHYS)
+		return -EINVAL;
+
+	return switch_generic_set_link(dev, port, link);
+#else
+	return -ENOTSUPP;
+#endif
+}
+
+static u64 get_mib_counter(struct gsw_an8855 *gsw, int i, int port)
+{
+	unsigned int offset;
+	u64 lo, hi, hi2;
+
+	offset = an8855_mibs[i].offset;
+
+	if (an8855_mibs[i].size == 1)
+		return an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset));
+
+	do {
+		hi = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4));
+		lo = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset));
+		hi2 = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4));
+	} while (hi2 != hi);
+
+	return (hi << 32) | lo;
+}
+
+static int an8855_get_port_mib(struct switch_dev *dev,
+				   const struct switch_attr *attr,
+				   struct switch_val *val)
+{
+	static char buf[4096];
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+	int i, len = 0;
+
+	if (val->port_vlan >= AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	len += snprintf(buf + len, sizeof(buf) - len,
+			"Port %d MIB counters\n", val->port_vlan);
+
+	for (i = 0; i < ARRAY_SIZE(an8855_mibs); ++i) {
+		u64 counter;
+
+		len += snprintf(buf + len, sizeof(buf) - len,
+				"%-11s: ", an8855_mibs[i].name);
+		counter = get_mib_counter(gsw, i, val->port_vlan);
+		len += snprintf(buf + len, sizeof(buf) - len, "%llu\n",
+				counter);
+	}
+
+	val->value.s = buf;
+	val->len = len;
+	return 0;
+}
+
+static int an8855_get_port_stats(struct switch_dev *dev, int port,
+				 struct switch_port_stats *stats)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	if (port < 0 || port >= AN8855_NUM_PORTS)
+		return -EINVAL;
+
+	stats->tx_bytes = get_mib_counter(gsw, AN8855_PORT_MIB_TXB_ID, port);
+	stats->rx_bytes = get_mib_counter(gsw, AN8855_PORT_MIB_RXB_ID, port);
+
+	return 0;
+}
+
+static void an8855_port_isolation(struct gsw_an8855 *gsw)
+{
+	int i;
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++)
+		an8855_reg_write(gsw, PORTMATRIX(i),
+				 BIT(gsw->cpu_port));
+
+	an8855_reg_write(gsw, PORTMATRIX(gsw->cpu_port), PORT_MATRIX_M);
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		u32 pvc_mode = 0x8100 << STAG_VPID_S;
+
+		if (gsw->port5_cfg.stag_on && i == 5)
+			pvc_mode |= PVC_PORT_STAG | PVC_STAG_REPLACE;
+		else
+			pvc_mode |= (VA_TRANSPARENT_PORT << VLAN_ATTR_S);
+
+		an8855_reg_write(gsw, PVC(i), pvc_mode);
+	}
+}
+
+static int an8855_apply_config(struct switch_dev *dev)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	if (!gsw->global_vlan_enable) {
+		an8855_port_isolation(gsw);
+		return 0;
+	}
+
+	an8855_apply_vlan_config(gsw);
+
+	return 0;
+}
+
+static int an8855_reset_switch(struct switch_dev *dev)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+	int i;
+
+	memset(gsw->port_entries, 0, sizeof(gsw->port_entries));
+	memset(gsw->vlan_entries, 0, sizeof(gsw->vlan_entries));
+
+	/* set default vid of each vlan to the same number of vlan, so the vid
+	 * won't need be set explicitly.
+	 */
+	for (i = 0; i < AN8855_NUM_VLANS; i++)
+		gsw->vlan_entries[i].vid = i;
+
+	return 0;
+}
+
+static int an8855_phy_read16(struct switch_dev *dev, int addr, u8 reg,
+				 u16 *value)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	*value = gsw->mii_read(gsw, addr, reg);
+
+	return 0;
+}
+
+static int an8855_phy_write16(struct switch_dev *dev, int addr, u8 reg,
+				  u16 value)
+{
+	struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
+
+	gsw->mii_write(gsw, addr, reg, value);
+
+	return 0;
+}
+
+static const struct switch_attr an8855_global[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "VLAN mode (1:enabled)",
+		.max = 1,
+		.id = AN8855_ATTR_ENABLE_VLAN,
+		.get = an8855_get_vlan_enable,
+		.set = an8855_set_vlan_enable,
+	}
+};
+
+static const struct switch_attr an8855_port[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get MIB counters for port",
+		.get = an8855_get_port_mib,
+		.set = NULL,
+	},
+};
+
+static const struct switch_attr an8855_vlan[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "vid",
+		.description = "VLAN ID (0-4094)",
+		.set = an8855_set_vid,
+		.get = an8855_get_vid,
+		.max = 4094,
+	},
+};
+
+static const struct switch_dev_ops an8855_swdev_ops = {
+	.attr_global = {
+		.attr = an8855_global,
+		.n_attr = ARRAY_SIZE(an8855_global),
+	},
+	.attr_port = {
+		.attr = an8855_port,
+		.n_attr = ARRAY_SIZE(an8855_port),
+	},
+	.attr_vlan = {
+		.attr = an8855_vlan,
+		.n_attr = ARRAY_SIZE(an8855_vlan),
+	},
+	.get_vlan_ports = an8855_get_vlan_ports,
+	.set_vlan_ports = an8855_set_vlan_ports,
+	.get_port_pvid = an8855_get_port_pvid,
+	.set_port_pvid = an8855_set_port_pvid,
+	.get_port_link = an8855_get_port_link,
+	.set_port_link = an8855_set_port_link,
+	.get_port_stats = an8855_get_port_stats,
+	.apply_config = an8855_apply_config,
+	.reset_switch = an8855_reset_switch,
+	.phy_read16 = an8855_phy_read16,
+	.phy_write16 = an8855_phy_write16,
+};
+
+int an8855_swconfig_init(struct gsw_an8855 *gsw)
+{
+	struct device_node *np = gsw->dev->of_node;
+	struct switch_dev *swdev;
+	int ret;
+
+	if (of_property_read_u32(np, "airoha,cpuport", &gsw->cpu_port))
+		gsw->cpu_port = AN8855_DFL_CPU_PORT;
+
+	swdev = &gsw->swdev;
+
+	swdev->name = gsw->name;
+	swdev->alias = gsw->name;
+	swdev->cpu_port = gsw->cpu_port;
+	swdev->ports = AN8855_NUM_PORTS;
+	swdev->vlans = AN8855_NUM_VLANS;
+	swdev->ops = &an8855_swdev_ops;
+
+	ret = register_switch(swdev, NULL);
+	if (ret) {
+		dev_notice(gsw->dev, "Failed to register switch %s\n",
+			   swdev->name);
+		return ret;
+	}
+
+	an8855_apply_config(swdev);
+
+	return 0;
+}
+
+void an8855_swconfig_destroy(struct gsw_an8855 *gsw)
+{
+	unregister_switch(&gsw->swdev);
+}
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.h
new file mode 100644
index 0000000..4c8f2bf
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ * Author: Min Yao <min.yao@airoha.com>
+ */
+
+#ifndef _AN8855_SWCONFIG_H_
+#define _AN8855_SWCONFIG_H_
+
+#ifdef CONFIG_SWCONFIG
+#include <linux/switch.h>
+#include "an8855.h"
+
+int an8855_swconfig_init(struct gsw_an8855 *gsw);
+void an8855_swconfig_destroy(struct gsw_an8855 *gsw);
+#else
+static inline int an8855_swconfig_init(struct gsw_an8855 *gsw)
+{
+	an8855_apply_vlan_config(gsw);
+
+	return 0;
+}
+
+static inline void an8855_swconfig_destroy(struct gsw_an8855 *gsw)
+{
+}
+#endif
+
+#endif /* _AN8855_SWCONFIG_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.c b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.c
new file mode 100644
index 0000000..6ef8c99
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ */
+
+#include "an8855.h"
+#include "an8855_regs.h"
+
+struct an8855_mapping an8855_def_mapping[] = {
+	{
+		.name = "llllw",
+		.pvids = { 1, 1, 1, 1, 2, 1 },
+		.members = { 0, 0x2f, 0x30 },
+		.etags = { 0, 0, 0x20 },
+		.vids = { 0, 1, 2 },
+	}, {
+		.name = "wllll",
+		.pvids = { 2, 1, 1, 1, 1, 1 },
+		.members = { 0, 0x3e, 0x21 },
+		.etags = { 0, 0, 0x20 },
+		.vids = { 0, 1, 2 },
+	}, {
+		.name = "lwlll",
+		.pvids = { 1, 2, 1, 1, 1, 1 },
+		.members = { 0, 0x3d, 0x22 },
+		.etags = { 0, 0, 0x20 },
+		.vids = { 0, 1, 2 },
+	}, {
+		.name = "lllll",
+		.pvids = { 1, 1, 1, 1, 1, 1 },
+		.members = { 0, 0x3f },
+		.etags = { 0, 0 },
+		.vids = { 0, 1 },
+	},
+};
+
+void an8855_vlan_ctrl(struct gsw_an8855 *gsw, u32 cmd, u32 val)
+{
+	int i;
+
+	an8855_reg_write(gsw, VTCR,
+			 VTCR_BUSY | ((cmd << VTCR_FUNC_S) & VTCR_FUNC_M) |
+			 (val & VTCR_VID_M));
+
+	for (i = 0; i < 300; i++) {
+		u32 val = an8855_reg_read(gsw, VTCR);
+
+		if ((val & VTCR_BUSY) == 0)
+			break;
+
+		usleep_range(1000, 1100);
+	}
+
+	if (i == 300)
+		dev_info(gsw->dev, "vtcr timeout\n");
+}
+
+static void an8855_write_vlan_entry(struct gsw_an8855 *gsw, int vlan, u16 vid,
+					u8 ports, u8 etags)
+{
+	int port;
+	u32 val;
+
+	/* vlan port membership */
+	if (ports) {
+		val = IVL_MAC | VTAG_EN | VENTRY_VALID
+			| ((ports << PORT_MEM_S) & PORT_MEM_M);
+		/* egress mode */
+		for (port = 0; port < AN8855_NUM_PORTS; port++) {
+			if (etags & BIT(port))
+				val |= (ETAG_CTRL_TAG << PORT_ETAG_S(port));
+			else
+				val |= (ETAG_CTRL_UNTAG << PORT_ETAG_S(port));
+		}
+		an8855_reg_write(gsw, VAWD0, val);
+	} else {
+		an8855_reg_write(gsw, VAWD0, 0);
+	}
+
+	if (ports & 0x40)
+		an8855_reg_write(gsw, VAWD1, 0x1);
+	else
+		an8855_reg_write(gsw, VAWD1, 0x0);
+
+	/* write to vlan table */
+	an8855_vlan_ctrl(gsw, VTCR_WRITE_VLAN_ENTRY, vid);
+}
+
+void an8855_apply_vlan_config(struct gsw_an8855 *gsw)
+{
+	int i, j;
+	u8 tag_ports;
+	u8 untag_ports;
+	u32 val;
+
+	/* set all ports as security mode */
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		val = an8855_reg_read(gsw, PCR(i));
+		an8855_reg_write(gsw, PCR(i), val | SECURITY_MODE);
+		an8855_reg_write(gsw, PORTMATRIX(i), PORT_MATRIX_M);
+	}
+
+	/* check if a port is used in tag/untag vlan egress mode */
+	tag_ports = 0;
+	untag_ports = 0;
+
+	for (i = 0; i < AN8855_NUM_VLANS; i++) {
+		u8 member = gsw->vlan_entries[i].member;
+		u8 etags = gsw->vlan_entries[i].etags;
+
+		if (!member)
+			continue;
+
+		for (j = 0; j < AN8855_NUM_PORTS; j++) {
+			if (!(member & BIT(j)))
+				continue;
+
+			if (etags & BIT(j))
+				tag_ports |= 1u << j;
+			else
+				untag_ports |= 1u << j;
+		}
+	}
+
+	/* set all untag-only ports as transparent and the rest as user port */
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		u32 pvc_mode = 0x8100 << STAG_VPID_S;
+
+		if (untag_ports & BIT(i) && !(tag_ports & BIT(i)))
+			pvc_mode = (0x8100 << STAG_VPID_S) |
+				(VA_TRANSPARENT_PORT << VLAN_ATTR_S);
+
+		if (gsw->port5_cfg.stag_on && i == 5)
+			pvc_mode = (u32)((0x8100 << STAG_VPID_S) | PVC_PORT_STAG
+						| PVC_STAG_REPLACE);
+
+		an8855_reg_write(gsw, PVC(i), pvc_mode);
+	}
+
+	/* first clear the switch vlan table */
+	for (i = 0; i < AN8855_NUM_VLANS; i++)
+		an8855_write_vlan_entry(gsw, i, i, 0, 0);
+
+	/* now program only vlans with members to avoid
+	 * clobbering remapped entries in later iterations
+	 */
+	for (i = 0; i < AN8855_NUM_VLANS; i++) {
+		u16 vid = gsw->vlan_entries[i].vid;
+		u8 member = gsw->vlan_entries[i].member;
+		u8 etags = gsw->vlan_entries[i].etags;
+
+		if (member)
+			an8855_write_vlan_entry(gsw, i, vid, member, etags);
+	}
+
+	/* Port Default PVID */
+	for (i = 0; i < AN8855_NUM_PORTS; i++) {
+		int vlan = gsw->port_entries[i].pvid;
+		u16 pvid = 0;
+		u32 val;
+
+		if (vlan < AN8855_NUM_VLANS && gsw->vlan_entries[vlan].member)
+			pvid = gsw->vlan_entries[vlan].vid;
+
+		val = an8855_reg_read(gsw, PVID(i));
+		val &= ~GRP_PORT_VID_M;
+		val |= pvid;
+		an8855_reg_write(gsw, PVID(i), val);
+	}
+}
+
+struct an8855_mapping *an8855_find_mapping(struct device_node *np)
+{
+	const char *map;
+	int i;
+
+	if (of_property_read_string(np, "airoha,portmap", &map))
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(an8855_def_mapping); i++)
+		if (!strcmp(map, an8855_def_mapping[i].name))
+			return &an8855_def_mapping[i];
+
+	return NULL;
+}
+
+void an8855_apply_mapping(struct gsw_an8855 *gsw, struct an8855_mapping *map)
+{
+	int i = 0;
+
+	for (i = 0; i < AN8855_NUM_PORTS; i++)
+		gsw->port_entries[i].pvid = map->pvids[i];
+
+	for (i = 0; i < AN8855_NUM_VLANS; i++) {
+		gsw->vlan_entries[i].member = map->members[i];
+		gsw->vlan_entries[i].etags = map->etags[i];
+		gsw->vlan_entries[i].vid = map->vids[i];
+	}
+}
diff --git a/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.h b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.h
new file mode 100644
index 0000000..d2cd68a
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Airoha Inc.
+ */
+
+#ifndef _AN8855_VLAN_H_
+#define _AN8855_VLAN_H_
+
+#define AN8855_NUM_PORTS	6
+#define AN8855_NUM_VLANS	4095
+#define AN8855_MAX_VID		4095
+#define AN8855_MIN_VID		0
+
+struct gsw_an8855;
+
+struct an8855_port_entry {
+	u16	pvid;
+};
+
+struct an8855_vlan_entry {
+	u16	vid;
+	u8	member;
+	u8	etags;
+};
+
+struct an8855_mapping {
+	char	*name;
+	u16	pvids[AN8855_NUM_PORTS];
+	u8	members[AN8855_NUM_VLANS];
+	u8	etags[AN8855_NUM_VLANS];
+	u16	vids[AN8855_NUM_VLANS];
+};
+
+extern struct an8855_mapping an8855_defaults[];
+
+void an8855_vlan_ctrl(struct gsw_an8855 *gsw, u32 cmd, u32 val);
+void an8855_apply_vlan_config(struct gsw_an8855 *gsw);
+struct an8855_mapping *an8855_find_mapping(struct device_node *np);
+void an8855_apply_mapping(struct gsw_an8855 *gsw, struct an8855_mapping *map);
+#endif /* _AN8855_VLAN_H_ */
diff --git a/21.02/files/target/linux/mediatek/files-5.4/net/dsa/tag_arht.c b/21.02/files/target/linux/mediatek/files-5.4/net/dsa/tag_arht.c
new file mode 100644
index 0000000..60ca986
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/files-5.4/net/dsa/tag_arht.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha DSA Tag support
+ * Copyright (C) 2023 Min Yao <min.yao@airoha.com>
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+
+#include "dsa_priv.h"
+
+#define AIR_HDR_LEN		4
+#define AIR_HDR_XMIT_UNTAGGED		0
+#define AIR_HDR_XMIT_TAGGED_TPID_8100	1
+#define AIR_HDR_XMIT_TAGGED_TPID_88A8	2
+#define AIR_HDR_RECV_SOURCE_PORT_MASK	GENMASK(2, 0)
+#define AIR_HDR_XMIT_DP_BIT_MASK	GENMASK(5, 0)
+
+static struct sk_buff *air_tag_xmit(struct sk_buff *skb,
+				    struct net_device *dev)
+{
+	struct dsa_port *dp = dsa_slave_to_port(dev);
+	u8 xmit_tpid;
+	u8 *air_tag;
+	unsigned char *dest = eth_hdr(skb)->h_dest;
+
+	/* Build the special tag after the MAC Source Address. If VLAN header
+	 * is present, it's required that VLAN header and special tag is
+	 * being combined. Only in this way we can allow the switch can parse
+	 * the both special and VLAN tag at the same time and then look up VLAN
+	 * table with VID.
+	 */
+	switch (skb->protocol) {
+	case htons(ETH_P_8021Q):
+		xmit_tpid = AIR_HDR_XMIT_TAGGED_TPID_8100;
+		break;
+	case htons(ETH_P_8021AD):
+		xmit_tpid = AIR_HDR_XMIT_TAGGED_TPID_88A8;
+		break;
+	default:
+		if (skb_cow_head(skb, AIR_HDR_LEN) < 0)
+			return NULL;
+
+		xmit_tpid = AIR_HDR_XMIT_UNTAGGED;
+		skb_push(skb, AIR_HDR_LEN);
+		memmove(skb->data, skb->data + AIR_HDR_LEN, 2 * ETH_ALEN);
+	}
+
+	air_tag = skb->data + 2 * ETH_ALEN;
+
+	/* Mark tag attribute on special tag insertion to notify hardware
+	 * whether that's a combined special tag with 802.1Q header.
+	 */
+	air_tag[0] = xmit_tpid;
+	air_tag[1] = (1 << dp->index) & AIR_HDR_XMIT_DP_BIT_MASK;
+
+	/* Tag control information is kept for 802.1Q */
+	if (xmit_tpid == AIR_HDR_XMIT_UNTAGGED) {
+		air_tag[2] = 0;
+		air_tag[3] = 0;
+	}
+
+	return skb;
+}
+
+static struct sk_buff *air_tag_rcv(struct sk_buff *skb, struct net_device *dev,
+				   struct packet_type *pt)
+{
+	int port;
+	__be16 *phdr, hdr;
+	unsigned char *dest = eth_hdr(skb)->h_dest;
+	bool is_multicast_skb = is_multicast_ether_addr(dest) &&
+				!is_broadcast_ether_addr(dest);
+
+	if (dev->features & NETIF_F_HW_VLAN_CTAG_RX) {
+		hdr = ntohs(skb->vlan_proto);
+		skb->vlan_proto = 0;
+		skb->vlan_tci = 0;
+	} else {
+		if (unlikely(!pskb_may_pull(skb, AIR_HDR_LEN)))
+			return NULL;
+
+		/* The AIR header is added by the switch between src addr
+		 * and ethertype at this point, skb->data points to 2 bytes
+		 * after src addr so header should be 2 bytes right before.
+		 */
+		phdr = (__be16 *)(skb->data - 2);
+		hdr = ntohs(*phdr);
+
+		/* Remove AIR tag and recalculate checksum. */
+		skb_pull_rcsum(skb, AIR_HDR_LEN);
+
+		memmove(skb->data - ETH_HLEN,
+			skb->data - ETH_HLEN - AIR_HDR_LEN,
+			2 * ETH_ALEN);
+	}
+
+	/* Get source port information */
+	port = (hdr & AIR_HDR_RECV_SOURCE_PORT_MASK);
+
+	skb->dev = dsa_master_find_slave(dev, 0, port);
+	if (!skb->dev)
+		return NULL;
+
+	/* Only unicast or broadcast frames are offloaded */
+	if (likely(!is_multicast_skb))
+		skb->offload_fwd_mark = 1;
+
+	return skb;
+}
+
+static int air_tag_flow_dissect(const struct sk_buff *skb, __be16 *proto,
+				int *offset)
+{
+	*offset = 4;
+	*proto = ((__be16 *)skb->data)[1];
+
+	return 0;
+}
+
+static const struct dsa_device_ops air_netdev_ops = {
+	.name		= "air",
+	.proto		= DSA_TAG_PROTO_ARHT,
+	.xmit		= air_tag_xmit,
+	.rcv		= air_tag_rcv,
+	.flow_dissect	= air_tag_flow_dissect,
+	.overhead	= AIR_HDR_LEN,
+};
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_AIR);
+
+module_dsa_tag_driver(air_netdev_ops);
diff --git a/21.02/files/target/linux/mediatek/image/mt7981.mk b/21.02/files/target/linux/mediatek/image/mt7981.mk
index ab3ed5b..eb745b6 100755
--- a/21.02/files/target/linux/mediatek/image/mt7981.mk
+++ b/21.02/files/target/linux/mediatek/image/mt7981.mk
@@ -26,6 +26,40 @@
 endef
 TARGET_DEVICES += mt7981-spim-nand-2500wan-gmac2
 
+define Device/mt7981-spim-nand-2500wan-an8855
+  DEVICE_VENDOR := MediaTek
+  DEVICE_MODEL := mt7981-spim-nand-2500wan-an8855
+  DEVICE_DTS := mt7981-spim-nand-2500wan-an8855
+  DEVICE_DTS_DIR := $(DTS_DIR)/mediatek
+  SUPPORTED_DEVICES := mediatek,mt7981-spim-snand-2500wan-an8855-rfb
+  UBINIZE_OPTS := -E 5
+  BLOCKSIZE := 128k
+  PAGESIZE := 2048
+  IMAGE_SIZE := 65536k
+  KERNEL_IN_UBI := 1
+  IMAGES += factory.bin
+  IMAGE/factory.bin := append-ubi | check-size $$$$(IMAGE_SIZE)
+  IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
+endef
+TARGET_DEVICES += mt7981-spim-nand-2500wan-an8855
+
+define Device/mt7981-spim-nand-2500wan-an8855-gsw
+  DEVICE_VENDOR := MediaTek
+  DEVICE_MODEL := mt7981-spim-nand-2500wan-an8855-gsw
+  DEVICE_DTS := mt7981-spim-nand-2500wan-an8855-gsw
+  DEVICE_DTS_DIR := $(DTS_DIR)/mediatek
+  SUPPORTED_DEVICES := mediatek,mt7981-spim-snand-2500wan-an8855-gsw-rfb
+  UBINIZE_OPTS := -E 5
+  BLOCKSIZE := 128k
+  PAGESIZE := 2048
+  IMAGE_SIZE := 65536k
+  KERNEL_IN_UBI := 1
+  IMAGES += factory.bin
+  IMAGE/factory.bin := append-ubi | check-size $$$$(IMAGE_SIZE)
+  IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
+endef
+TARGET_DEVICES += mt7981-spim-nand-2500wan-an8855-gsw
+
 define Device/mt7981-spim-nand-rfb
   DEVICE_VENDOR := MediaTek
   DEVICE_MODEL := mt7981-spim-nand-rfb
diff --git a/21.02/files/target/linux/mediatek/mt7981/base-files/etc/board.d/02_network b/21.02/files/target/linux/mediatek/mt7981/base-files/etc/board.d/02_network
index 0d35a22..138d45f 100755
--- a/21.02/files/target/linux/mediatek/mt7981/base-files/etc/board.d/02_network
+++ b/21.02/files/target/linux/mediatek/mt7981/base-files/etc/board.d/02_network
@@ -14,6 +14,11 @@
 		ucidef_add_switch "switch0" \
 			"0:lan" "1:lan" "2:lan" "3:lan" "4:wan" "6u@eth0" "5u@eth1"
 		;;
+	*8855-gsw*)
+		ucidef_set_interfaces_lan_wan "eth0" "eth1"
+		ucidef_add_switch "switch0" \
+			"0:lan" "1:lan" "2:lan" "3:lan" "4:lan" "5u@eth0"
+		;;
 	*gsw*)
 		ucidef_set_interfaces_lan_wan "eth0" "eth1"
 		ucidef_add_switch "switch0" \
diff --git a/21.02/files/target/linux/mediatek/mt7981/config-5.4 b/21.02/files/target/linux/mediatek/mt7981/config-5.4
index df78da7..23fd238 100644
--- a/21.02/files/target/linux/mediatek/mt7981/config-5.4
+++ b/21.02/files/target/linux/mediatek/mt7981/config-5.4
@@ -277,6 +277,7 @@
 CONFIG_MODULES_TREE_LOOKUP=y
 CONFIG_MODULES_USE_ELF_RELA=y
 CONFIG_MT753X_GSW=y
+CONFIG_AN8855_GSW=y
 CONFIG_MTD_NAND_CORE=y
 CONFIG_MTD_NAND_ECC_SW_HAMMING=y
 CONFIG_MTD_NAND_MTK=y
@@ -313,7 +314,9 @@
 CONFIG_NET_DEVLINK=y
 CONFIG_NET_DSA=y
 CONFIG_NET_DSA_MT7530=y
+CONFIG_NET_DSA_AN8855=y
 CONFIG_NET_DSA_TAG_MTK=y
+CONFIG_NET_DSA_TAG_ARHT=y
 CONFIG_NET_FLOW_LIMIT=y
 CONFIG_NET_MEDIATEK_SOC=y
 CONFIG_NET_SWITCHDEV=y
diff --git a/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_dsa_add_an8855.patch b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_dsa_add_an8855.patch
new file mode 100644
index 0000000..d089c96
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_dsa_add_an8855.patch
@@ -0,0 +1,25 @@
+Index: linux-5.4.238/drivers/net/dsa/Kconfig
+===================================================================
+--- linux-5.4.238.orig/drivers/net/dsa/Kconfig	2023-12-26 15:31:49.427259000 +0800
++++ linux-5.4.238/drivers/net/dsa/Kconfig	2023-12-26 15:46:52.655226000 +0800
+@@ -48,6 +48,8 @@
+ 	  This enables support for the Marvell 88E6060 ethernet switch
+ 	  chip.
+ 
++source "drivers/net/dsa/airoha/an8855/Kconfig"
++
+ source "drivers/net/dsa/microchip/Kconfig"
+ 
+ source "drivers/net/dsa/mv88e6xxx/Kconfig"
+Index: linux-5.4.238/drivers/net/dsa/Makefile
+===================================================================
+--- linux-5.4.238.orig/drivers/net/dsa/Makefile	2023-12-26 15:32:08.081306000 +0800
++++ linux-5.4.238/drivers/net/dsa/Makefile	2023-12-26 15:47:59.858217000 +0800
+@@ -18,6 +18,7 @@
+ obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
+ obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
+ obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
++obj-y				+= airoha/an8855/
+ obj-y				+= b53/
+ obj-y				+= microchip/
+ obj-y				+= mv88e6xxx/
diff --git a/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_ethernet_mediatek_hnat.patch b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_ethernet_mediatek_hnat.patch
new file mode 100644
index 0000000..34855a5
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_ethernet_mediatek_hnat.patch
@@ -0,0 +1,13 @@
+Index: linux-5.4.238/drivers/net/ethernet/mediatek/mtk_hnat/Makefile
+===================================================================
+--- linux-5.4.238.orig/drivers/net/ethernet/mediatek/mtk_hnat/Makefile	2023-09-05 21:55:25.000000000 +0800
++++ linux-5.4.238/drivers/net/ethernet/mediatek/mtk_hnat/Makefile	2023-12-18 11:26:54.110684000 +0800
+@@ -2,4 +2,8 @@
+ 
+ obj-$(CONFIG_NET_MEDIATEK_HNAT)         += mtkhnat.o
+ mtkhnat-objs := hnat.o hnat_nf_hook.o hnat_debugfs.o hnat_mcast.o
++ifeq ($(CONFIG_NET_DSA_AN8855), y)
++mtkhnat-y	+= hnat_stag.o
++else
+ mtkhnat-$(CONFIG_NET_DSA_MT7530)	+= hnat_stag.o
++endif
diff --git a/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_phy_add_an8855_gsw.patch b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_phy_add_an8855_gsw.patch
new file mode 100644
index 0000000..58220b3
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-drivers_net_phy_add_an8855_gsw.patch
@@ -0,0 +1,24 @@
+Index: linux-5.4.238/drivers/net/phy/Kconfig
+===================================================================
+--- linux-5.4.238.orig/drivers/net/phy/Kconfig	2023-12-19 12:20:34.714805000 +0800
++++ linux-5.4.238/drivers/net/phy/Kconfig	2023-12-25 10:11:11.328554000 +0800
+@@ -337,6 +337,8 @@
+ 
+ source "drivers/net/phy/mtk/mt753x/Kconfig"
+ 
++source "drivers/net/phy/airoha/an8855/Kconfig"
++
+ comment "MII PHY device drivers"
+ 
+ config SFP
+Index: linux-5.4.238/drivers/net/phy/Makefile
+===================================================================
+--- linux-5.4.238.orig/drivers/net/phy/Makefile	2023-12-19 12:20:34.718809000 +0800
++++ linux-5.4.238/drivers/net/phy/Makefile	2023-12-25 10:13:11.891535000 +0800
+@@ -121,5 +121,6 @@
+ obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
+ obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
+ obj-$(CONFIG_MT753X_GSW)        += mtk/mt753x/
++obj-$(CONFIG_AN8855_GSW)        += airoha/an8855/
+ obj-$(CONFIG_RTL8367S_GSW)	+= rtk/
+ 
diff --git a/21.02/files/target/linux/mediatek/patches-5.4/999-2739-net_dsa_add_tag_arht.patch b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-net_dsa_add_tag_arht.patch
new file mode 100644
index 0000000..ca36a0c
--- /dev/null
+++ b/21.02/files/target/linux/mediatek/patches-5.4/999-2739-net_dsa_add_tag_arht.patch
@@ -0,0 +1,46 @@
+Index: linux-5.4.238/net/dsa/Makefile
+===================================================================
+--- linux-5.4.238.orig/net/dsa/Makefile	2023-12-09 09:43:04.335694000 +0800
++++ linux-5.4.238/net/dsa/Makefile	2023-12-09 10:24:27.672514000 +0800
+@@ -16,3 +16,4 @@
+ obj-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca.o
+ obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o
+ obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
++obj-$(CONFIG_NET_DSA_TAG_AIROHA) += tag_arht.o
+Index: linux-5.4.238/net/dsa/Kconfig
+===================================================================
+--- linux-5.4.238.orig/net/dsa/Kconfig	2023-12-09 09:43:04.332694000 +0800
++++ linux-5.4.238/net/dsa/Kconfig	2023-12-09 10:26:13.596504000 +0800
+@@ -74,6 +74,12 @@
+ 	  Say Y or M if you want to enable support for tagging frames for
+ 	  Mediatek switches.
+ 
++config NET_DSA_TAG_AIROHA
++	tristate "Tag driver for Airoha switches"
++	help
++	  Say Y or M if you want to enable support for tagging frames for
++	  Airoha switches.
++
+ config NET_DSA_TAG_KSZ
+ 	tristate "Tag driver for Microchip 8795/9477/9893 families of switches"
+ 	help
+Index: linux-5.4.238/include/net/dsa.h
+===================================================================
+--- linux-5.4.238.orig/include/net/dsa.h	2023-12-09 09:43:17.940694000 +0800
++++ linux-5.4.238/include/net/dsa.h	2023-12-09 10:30:06.432504000 +0800
+@@ -43,6 +43,7 @@
+ #define DSA_TAG_PROTO_SJA1105_VALUE		13
+ #define DSA_TAG_PROTO_KSZ8795_VALUE		14
+ #define DSA_TAG_PROTO_RTL4_A_VALUE		17
++#define DSA_TAG_PROTO_ARHT_VALUE		28
+ 
+ enum dsa_tag_protocol {
+ 	DSA_TAG_PROTO_NONE		= DSA_TAG_PROTO_NONE_VALUE,
+@@ -61,6 +62,7 @@
+ 	DSA_TAG_PROTO_SJA1105		= DSA_TAG_PROTO_SJA1105_VALUE,
+ 	DSA_TAG_PROTO_KSZ8795		= DSA_TAG_PROTO_KSZ8795_VALUE,
+ 	DSA_TAG_PROTO_RTL4_A		= DSA_TAG_PROTO_RTL4_A_VALUE,
++	DSA_TAG_PROTO_ARHT		= DSA_TAG_PROTO_ARHT_VALUE,
+ };
+ 
+ struct packet_type;