[][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 = <ðsys>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&watchdog {
+ status = "okay";
+};
+
+ð {
+ 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";
+};
+
+ð {
+ 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;