diff -Naur linux-orig/arch/arm64/boot/dts/broadcom/Makefile linux/arch/arm64/boot/dts/broadcom/Makefile
--- linux-orig/arch/arm64/boot/dts/broadcom/Makefile	2021-11-18 15:52:35.000000000 +0000
+++ linux/arch/arm64/boot/dts/broadcom/Makefile	2022-04-15 23:51:16.697724207 +0000
@@ -1,3 +1,4 @@
+dtb-y += interceptor.dtb
 # SPDX-License-Identifier: GPL-2.0
 dtb-$(CONFIG_ARCH_BCM2835) += bcm2837-rpi-3-a-plus.dtb \
 			      bcm2837-rpi-3-b.dtb \
diff -Naur linux-orig/arch/arm64/boot/dts/broadcom/interceptor.dts linux/arch/arm64/boot/dts/broadcom/interceptor.dts
--- linux-orig/arch/arm64/boot/dts/broadcom/interceptor.dts	1970-01-01 00:00:00.000000000 +0000
+++ linux/arch/arm64/boot/dts/broadcom/interceptor.dts	2022-04-15 23:51:16.685724410 +0000
@@ -0,0 +1,21 @@
+#include "arm/bcm2711-rpi-cm4.dts"
+
+/ {
+  rtl8367rb: rtl8367rb {
+    compatible = "realtek,rtl8367c";
+    gpio-sda = <&gpio 44 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+    gpio-sck = <&gpio 45 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+  };
+};
+
+&i2c0 {
+  rv3028@52 {
+    compatible = "microcrystal,rv3028";
+    reg = <0x52>;
+  };
+};
+
+&usb {
+  // dwc2 driver appears to be broken on mainline, using xhci instead
+  status = "disabled";
+};
diff -Naur linux-orig/arch/arm64/configs/interceptor_defconfig linux/arch/arm64/configs/interceptor_defconfig
--- linux-orig/arch/arm64/configs/interceptor_defconfig	1970-01-01 00:00:00.000000000 +0000
+++ linux/arch/arm64/configs/interceptor_defconfig	2022-04-15 23:51:27.669538772 +0000
@@ -0,0 +1,1599 @@
+CONFIG_LOCALVERSION="-v8"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_PREEMPT=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CPUSETS=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM2835=y
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+CONFIG_COMPAT=y
+CONFIG_ARMV8_DEPRECATED=y
+CONFIG_SWP_EMULATION=y
+CONFIG_CP15_BARRIER_EMULATION=y
+CONFIG_SETEND_EMULATION=y
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_CPU_IDLE=y
+CONFIG_ARM_CPUIDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=y
+CONFIG_CRYPTO_AES_ARM64_BS=m
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_CLEANCACHE=y
+CONFIG_FRONTSWAP=y
+CONFIG_CMA=y
+CONFIG_ZSWAP=y
+CONFIG_Z3FOLD=m
+CONFIG_ZSMALLOC=m
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_NET_KEY=m
+CONFIG_INET=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU=m
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_LOG_NETDEV=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_COUNTER=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_OBJREF=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_FLOW_TABLE_IPV4=m
+CONFIG_NF_LOG_ARP=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_CLUSTERIP=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPTABLES=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_NF_FLOW_TABLE_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_NF_LOG_BRIDGE=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_ATM=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_DSMARK=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_TCINDEX=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_RSVP=m
+CONFIG_NET_CLS_RSVP6=m
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_IPT=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_WIMAX=m
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NFC=m
+CONFIG_PCI=y
+CONFIG_PCIE_BRCMSTB=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_CRYPTOLOOP=m
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_CDROM_PKTCDVD=m
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_NVME=y
+CONFIG_EEPROM_AT24=m
+CONFIG_TI_ST=m
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=y
+CONFIG_SATA_AHCI=y
+CONFIG_SATA_MV=m
+CONFIG_MD=y
+CONFIG_MD_LINEAR=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_DELAY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_MACVTAP=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NET_VRF=m
+# CONFIG_NET_VENDOR_3COM is not set
+# CONFIG_NET_VENDOR_ADAPTEC is not set
+# CONFIG_NET_VENDOR_AGERE is not set
+# CONFIG_NET_VENDOR_ALACRITECH is not set
+# CONFIG_NET_VENDOR_ALTEON is not set
+# CONFIG_NET_VENDOR_AMAZON is not set
+# CONFIG_NET_VENDOR_AMD is not set
+# CONFIG_NET_VENDOR_AQUANTIA is not set
+# CONFIG_NET_VENDOR_ARC is not set
+# CONFIG_NET_VENDOR_ATHEROS is not set
+# CONFIG_NET_VENDOR_AURORA is not set
+CONFIG_BCMGENET=y
+# CONFIG_NET_VENDOR_BROCADE is not set
+# CONFIG_NET_VENDOR_CADENCE is not set
+# CONFIG_NET_VENDOR_CAVIUM is not set
+# CONFIG_NET_VENDOR_CHELSIO is not set
+# CONFIG_NET_VENDOR_CISCO is not set
+# CONFIG_NET_VENDOR_CORTINA is not set
+# CONFIG_NET_VENDOR_DEC is not set
+# CONFIG_NET_VENDOR_DLINK is not set
+# CONFIG_NET_VENDOR_EMULEX is not set
+# CONFIG_NET_VENDOR_EZCHIP is not set
+# CONFIG_NET_VENDOR_GOOGLE is not set
+# CONFIG_NET_VENDOR_HISILICON is not set
+# CONFIG_NET_VENDOR_HUAWEI is not set
+# CONFIG_NET_VENDOR_INTEL is not set
+# CONFIG_NET_VENDOR_MARVELL is not set
+# CONFIG_NET_VENDOR_MELLANOX is not set
+# CONFIG_NET_VENDOR_MICREL is not set
+# CONFIG_NET_VENDOR_MICROCHIP is not set
+# CONFIG_NET_VENDOR_MICROSEMI is not set
+# CONFIG_NET_VENDOR_MYRI is not set
+# CONFIG_NET_VENDOR_NATSEMI is not set
+# CONFIG_NET_VENDOR_NETERION is not set
+# CONFIG_NET_VENDOR_NETRONOME is not set
+# CONFIG_NET_VENDOR_NI is not set
+# CONFIG_NET_VENDOR_NVIDIA is not set
+# CONFIG_NET_VENDOR_OKI is not set
+# CONFIG_NET_VENDOR_PACKET_ENGINES is not set
+# CONFIG_NET_VENDOR_PENSANDO is not set
+# CONFIG_NET_VENDOR_QLOGIC is not set
+# CONFIG_NET_VENDOR_QUALCOMM is not set
+# CONFIG_NET_VENDOR_RDC is not set
+# CONFIG_NET_VENDOR_REALTEK is not set
+# CONFIG_NET_VENDOR_RENESAS is not set
+# CONFIG_NET_VENDOR_ROCKER is not set
+# CONFIG_NET_VENDOR_SAMSUNG is not set
+# CONFIG_NET_VENDOR_SEEQ is not set
+# CONFIG_NET_VENDOR_SOLARFLARE is not set
+# CONFIG_NET_VENDOR_SILAN is not set
+# CONFIG_NET_VENDOR_SIS is not set
+# CONFIG_NET_VENDOR_SMSC is not set
+# CONFIG_NET_VENDOR_SOCIONEXT is not set
+# CONFIG_NET_VENDOR_STMICRO is not set
+# CONFIG_NET_VENDOR_SUN is not set
+# CONFIG_NET_VENDOR_SYNOPSYS is not set
+# CONFIG_NET_VENDOR_TEHUTI is not set
+# CONFIG_NET_VENDOR_TI is not set
+# CONFIG_NET_VENDOR_VIA is not set
+# CONFIG_NET_VENDOR_WIZNET is not set
+# CONFIG_NET_VENDOR_XILINX is not set
+CONFIG_RTL8366_SMI=m
+CONFIG_RTL8367B_PHY=m
+CONFIG_MICREL_PHY=y
+CONFIG_MDIO_BITBANG=m
+CONFIG_MDIO_GPIO=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=y
+CONFIG_USB_LAN78XX=y
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_HOSTAP=m
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_USB_ZD1201=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_USB_NET_RNDIS_WLAN=m
+CONFIG_WIMAX_I2400M_USB=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_RPISENSE=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_GAMEPORT_NS558=m
+CONFIG_GAMEPORT_L4=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+CONFIG_BCM2835_DEVGPIOMEM=y
+CONFIG_RPIVID_MEM=m
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=1
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_SC16IS7XX_SPI=y
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_RAW_DRIVER=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_RANDOM_TRUST_BOOTLOADER=y
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MUX=y
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=y
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=y
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS=m
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_BCM_VIRT=y
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_DS1WM=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_RPI_POE_FAN=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_THERMAL=y
+CONFIG_BCM2711_THERMAL=y
+CONFIG_BCM2835_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_RC_CORE=y
+CONFIG_LIRC=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_IR_IMON_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_TOY=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_PWC=m
+CONFIG_VIDEO_CPIA2=m
+CONFIG_USB_ZR364XX=m
+CONFIG_USB_STKWEBCAM=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_STK1160_COMMON=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_VIDEO_TM6000=m
+CONFIG_VIDEO_TM6000_ALSA=m
+CONFIG_VIDEO_TM6000_DVB=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_AS102=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_RADIO_SI4713=m
+CONFIG_I2C_SI4713=m
+CONFIG_USB_MR800=m
+CONFIG_USB_DSBR=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_V4L_TEST_DRIVERS=y
+CONFIG_VIDEO_VIMC=m
+CONFIG_VIDEO_VIVID=m
+CONFIG_VIDEO_VIM2M=m
+CONFIG_VIDEO_VICODEC=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9281=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_V3D=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_UDL=m
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE=y
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_SPDIF=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PLATFORM=y
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=y
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC2=m
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_XIRCOM=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_FTDI_ELAN=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_BCM2835_SDHOST=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_GPIO=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DMA_BCM2708=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_VHOST_NET=m
+CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y
+CONFIG_STAGING=y
+CONFIG_PRISM2_USB=m
+CONFIG_R8712U=m
+CONFIG_R8188EU=m
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_VIDEO_RPIVID=m
+CONFIG_ASHMEM=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_FB_TFT_WATTEROTT=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_EXTCON_ARIZONA=m
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HTU21=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_BH1750=m
+CONFIG_TSL4531=m
+CONFIG_VEML6070=m
+CONFIG_BMP280=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_MAX31856=m
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_PCA9685=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_ANDROID=y
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_NVMEM_RMEM=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=y
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS4_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_FSCACHE_HISTOGRAM=y
+CONFIG_CACHEFILES=y
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS_FS=m
+CONFIG_NTFS_RW=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=y
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFS_FSCACHE=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CIFS=m
+CONFIG_CIFS_WEAK_PW_HASH=y
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_POSIX=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_TGR192=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_CRC_ITU_T=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_IRQSOFF_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
diff -Naur linux-orig/drivers/net/phy/Kconfig linux/drivers/net/phy/Kconfig
--- linux-orig/drivers/net/phy/Kconfig	2021-11-18 15:52:35.000000000 +0000
+++ linux/drivers/net/phy/Kconfig	2022-04-15 23:51:16.673724613 +0000
@@ -61,6 +61,80 @@
 	depends on HWMON || HWMON=n
 	select MDIO_I2C
 
+comment "Switch configuration API + drivers"
+
+config SWCONFIG
+	tristate "Switch configuration API"
+	help
+	  Switch configuration API using netlink. This allows
+	  you to configure the VLAN features of certain switches.
+
+config SWCONFIG_LEDS
+	bool "Switch LED trigger support"
+	depends on (SWCONFIG && LEDS_TRIGGERS)
+
+config ADM6996_PHY
+	tristate "Driver for ADM6996 switches"
+	select SWCONFIG
+	help
+	  Currently supports the ADM6996FC and ADM6996M switches.
+	  Support for FC is very limited.
+
+config AR8216_PHY
+	tristate "Driver for Atheros AR8216 switches"
+	select SWCONFIG
+
+config AR8216_PHY_LEDS
+	bool "Atheros AR8216 switch LED support"
+	depends on (AR8216_PHY && LEDS_CLASS)
+
+source "drivers/net/phy/b53/Kconfig"
+
+config IP17XX_PHY
+	tristate "Driver for IC+ IP17xx switches"
+	select SWCONFIG
+
+config PSB6970_PHY
+	tristate "Lantiq XWAY Tantos (PSB6970) Ethernet switch"
+	select SWCONFIG
+	select ETHERNET_PACKET_MANGLE
+
+config RTL8306_PHY
+	tristate "Driver for Realtek RTL8306S switches"
+	select SWCONFIG
+
+config RTL8366_SMI
+	tristate "Driver for the RTL8366 SMI interface"
+	depends on GPIOLIB
+	help
+	  This module implements the SMI interface protocol which is used
+	  by some RTL8366 ethernet switch devices via the generic GPIO API.
+
+if RTL8366_SMI
+
+config RTL8366_SMI_DEBUG_FS
+	bool "RTL8366 SMI interface debugfs support"
+        depends on DEBUG_FS
+        default n
+
+config RTL8366S_PHY
+	tristate "Driver for the Realtek RTL8366S switch"
+	select SWCONFIG
+
+config RTL8366RB_PHY
+	tristate "Driver for the Realtek RTL8366RB switch"
+	select SWCONFIG
+
+config RTL8367_PHY
+	tristate "Driver for the Realtek RTL8367R/M switches"
+	select SWCONFIG
+
+config RTL8367B_PHY
+	tristate "Driver fot the Realtek RTL8367R-VB switch"
+	select SWCONFIG
+
+endif # RTL8366_SMI
+
 comment "MII PHY device drivers"
 
 config AMD_PHY
diff -Naur linux-orig/drivers/net/phy/Makefile linux/drivers/net/phy/Makefile
--- linux-orig/drivers/net/phy/Makefile	2021-11-18 15:52:35.000000000 +0000
+++ linux/drivers/net/phy/Makefile	2022-04-15 23:51:16.673724613 +0000
@@ -24,6 +24,19 @@
 obj-$(CONFIG_PHYLINK)		+= phylink.o
 obj-$(CONFIG_PHYLIB)		+= libphy.o
 
+obj-$(CONFIG_SWCONFIG)		+= swconfig.o
+obj-$(CONFIG_ADM6996_PHY)	+= adm6996.o
+obj-$(CONFIG_AR8216_PHY)	+= ar8216.o ar8327.o
+obj-$(CONFIG_SWCONFIG_B53)	+= b53/
+obj-$(CONFIG_IP17XX_PHY)	+= ip17xx.o
+obj-$(CONFIG_PSB6970_PHY)	+= psb6970.o
+obj-$(CONFIG_RTL8306_PHY)	+= rtl8306.o
+obj-$(CONFIG_RTL8366_SMI)	+= rtl8366_smi.o
+obj-$(CONFIG_RTL8366S_PHY)	+= rtl8366s.o
+obj-$(CONFIG_RTL8366RB_PHY)	+= rtl8366rb.o
+obj-$(CONFIG_RTL8367_PHY)	+= rtl8367.o
+obj-$(CONFIG_RTL8367B_PHY)	+= rtl8367b.o
+
 obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
 
 obj-$(CONFIG_SFP)		+= sfp.o
diff -Naur linux-orig/drivers/net/phy/adm6996.c linux/drivers/net/phy/adm6996.c
--- linux-orig/drivers/net/phy/adm6996.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/adm6996.c	2022-04-15 23:50:31.718484399 +0000
@@ -0,0 +1,1243 @@
+/*
+ * ADM6996 switch driver
+ *
+ * swconfig interface based on ar8216.c
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
+ * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of the GNU General Public License v2 as published by the
+ * Free Software Foundation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+/*#define DEBUG 1*/
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/adm6996-gpio.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/switch.h>
+#include <linux/version.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include "adm6996.h"
+
+MODULE_DESCRIPTION("Infineon ADM6996 Switch");
+MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>");
+MODULE_LICENSE("GPL");
+
+static const char * const adm6996_model_name[] =
+{
+	NULL,
+	"ADM6996FC",
+	"ADM6996M",
+	"ADM6996L"
+};
+
+struct adm6996_mib_desc {
+	unsigned int offset;
+	const char *name;
+};
+
+struct adm6996_priv {
+	struct switch_dev dev;
+	void *priv;
+
+	u8 eecs;
+	u8 eesk;
+	u8 eedi;
+
+	enum adm6996_model model;
+
+	bool enable_vlan;
+	bool vlan_enabled;	/* Current hardware state */
+
+#ifdef DEBUG
+	u16 addr;		/* Debugging: register address to operate on */
+#endif
+
+	u16 pvid[ADM_NUM_PORTS];	/* Primary VLAN ID */
+	u8 tagged_ports;
+
+	u16 vlan_id[ADM_NUM_VLANS];
+	u8 vlan_table[ADM_NUM_VLANS];	/* bitmap, 1 = port is member */
+	u8 vlan_tagged[ADM_NUM_VLANS];	/* bitmap, 1 = tagged member */
+	
+	struct mutex mib_lock;
+	char buf[2048];
+
+	struct mutex reg_mutex;
+
+	/* use abstraction for regops, we want to add gpio support in the future */
+	u16 (*read)(struct adm6996_priv *priv, enum admreg reg);
+	void (*write)(struct adm6996_priv *priv, enum admreg reg, u16 val);
+};
+
+#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev)
+#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
+
+#define MIB_DESC(_o, _n)	\
+	{			\
+		.offset = (_o),	\
+		.name = (_n),	\
+	}
+
+static const struct adm6996_mib_desc adm6996_mibs[] = {
+	MIB_DESC(ADM_CL0, "RxPacket"),
+	MIB_DESC(ADM_CL6, "RxByte"),
+	MIB_DESC(ADM_CL12, "TxPacket"),
+	MIB_DESC(ADM_CL18, "TxByte"),
+	MIB_DESC(ADM_CL24, "Collision"),
+	MIB_DESC(ADM_CL30, "Error"),
+};
+
+#define ADM6996_MIB_RXB_ID	1
+#define ADM6996_MIB_TXB_ID	3
+
+static inline u16
+r16(struct adm6996_priv *priv, enum admreg reg)
+{
+	return priv->read(priv, reg);
+}
+
+static inline void
+w16(struct adm6996_priv *priv, enum admreg reg, u16 val)
+{
+	priv->write(priv, reg, val);
+}
+
+/* Minimum timing constants */
+#define EECK_EDGE_TIME  3   /* 3us - max(adm 2.5us, 93c 1us) */
+#define EEDI_SETUP_TIME 1   /* 1us - max(adm 10ns, 93c 400ns) */
+#define EECS_SETUP_TIME 1   /* 1us - max(adm no, 93c 200ns) */
+
+static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits)
+{
+	int i, len = (bits + 7) / 8;
+	u8 mask;
+
+	gpio_set_value(priv->eecs, cs);
+	udelay(EECK_EDGE_TIME);
+
+	/* Byte assemble from MSB to LSB */
+	for (i = 0; i < len; i++) {
+		/* Bit bang from MSB to LSB */
+		for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) {
+			/* Clock low */
+			gpio_set_value(priv->eesk, 0);
+			udelay(EECK_EDGE_TIME);
+
+			/* Output on rising edge */
+			gpio_set_value(priv->eedi, (mask & buf[i]));
+			udelay(EEDI_SETUP_TIME);
+
+			/* Clock high */
+			gpio_set_value(priv->eesk, 1);
+			udelay(EECK_EDGE_TIME);
+		}
+	}
+
+	/* Clock low */
+	gpio_set_value(priv->eesk, 0);
+	udelay(EECK_EDGE_TIME);
+
+	if (cs)
+		gpio_set_value(priv->eecs, 0);
+}
+
+static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits)
+{
+	int i, len = (bits + 7) / 8;
+	u8 mask;
+
+	gpio_set_value(priv->eecs, cs);
+	udelay(EECK_EDGE_TIME);
+
+	/* Byte assemble from MSB to LSB */
+	for (i = 0; i < len; i++) {
+		u8 byte;
+
+		/* Bit bang from MSB to LSB */
+		for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) {
+			u8 gp;
+
+			/* Clock low */
+			gpio_set_value(priv->eesk, 0);
+			udelay(EECK_EDGE_TIME);
+
+			/* Input on rising edge */
+			gp = gpio_get_value(priv->eedi);
+			if (gp)
+				byte |= mask;
+
+			/* Clock high */
+			gpio_set_value(priv->eesk, 1);
+			udelay(EECK_EDGE_TIME);
+		}
+
+		*buf++ = byte;
+	}
+
+	/* Clock low */
+	gpio_set_value(priv->eesk, 0);
+	udelay(EECK_EDGE_TIME);
+
+	if (cs)
+		gpio_set_value(priv->eecs, 0);
+}
+
+/* Advance clock(s) */
+static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks)
+{
+	int i;
+	for (i = 0; i < clocks; i++) {
+		/* Clock high */
+		gpio_set_value(priv->eesk, 1);
+		udelay(EECK_EDGE_TIME);
+
+		/* Clock low */
+		gpio_set_value(priv->eesk, 0);
+		udelay(EECK_EDGE_TIME);
+	}
+}
+
+static u16
+adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg)
+{
+	/* cmd: 01 10 T DD R RRRRRR */
+	u8 bits[6] = {
+		0xFF, 0xFF, 0xFF, 0xFF,
+		(0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6),
+		((reg&63)<<2)
+	};
+
+	u8 rbits[4];
+
+	/* Enable GPIO outputs with all pins to 0 */
+	gpio_direction_output(priv->eecs, 0);
+	gpio_direction_output(priv->eesk, 0);
+	gpio_direction_output(priv->eedi, 0);
+
+	adm6996_gpio_write(priv, 0, bits, 46);
+	gpio_direction_input(priv->eedi);
+	adm6996_gpio_adclk(priv, 2);
+	adm6996_gpio_read(priv, 0, rbits, 32);
+
+	/* Extra clock(s) required per datasheet */
+	adm6996_gpio_adclk(priv, 2);
+
+	/* Disable GPIO outputs */
+	gpio_direction_input(priv->eecs);
+	gpio_direction_input(priv->eesk);
+
+	 /* EEPROM has 16-bit registers, but pumps out two registers in one request */
+	return (reg & 0x01 ?  (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3]));
+}
+
+/* Write chip configuration register */
+/* Follow 93c66 timing and chip's min EEPROM timing requirement */
+static void
+adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val)
+{
+	/* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */
+	u8 bits[4] = {
+		(0x05 << 5) | (reg >> 3),
+		(reg << 5) | (u8)(val >> 11),
+		(u8)(val >> 3),
+		(u8)(val << 5)
+	};
+
+	/* Enable GPIO outputs with all pins to 0 */
+	gpio_direction_output(priv->eecs, 0);
+	gpio_direction_output(priv->eesk, 0);
+	gpio_direction_output(priv->eedi, 0);
+
+	/* Write cmd. Total 27 bits */
+	adm6996_gpio_write(priv, 1, bits, 27);
+
+	/* Extra clock(s) required per datasheet */
+	adm6996_gpio_adclk(priv, 2);
+
+	/* Disable GPIO outputs */
+	gpio_direction_input(priv->eecs);
+	gpio_direction_input(priv->eesk);
+	gpio_direction_input(priv->eedi);
+}
+
+static u16
+adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg)
+{
+	struct phy_device *phydev = priv->priv;
+	struct mii_bus *bus = phydev->mdio.bus;
+
+	return bus->read(bus, PHYADDR(reg));
+}
+
+static void
+adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val)
+{
+	struct phy_device *phydev = priv->priv;
+	struct mii_bus *bus = phydev->mdio.bus;
+
+	bus->write(bus, PHYADDR(reg), val);
+}
+
+static int
+adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	if (val->value.i > 1)
+		return -EINVAL;
+
+	priv->enable_vlan = val->value.i;
+
+	return 0;
+};
+
+static int
+adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	val->value.i = priv->enable_vlan;
+
+	return 0;
+};
+
+#ifdef DEBUG
+
+static int
+adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr,
+		 struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	if (val->value.i > 1023)
+		return -EINVAL;
+
+	priv->addr = val->value.i;
+
+	return 0;
+};
+
+static int
+adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr,
+		 struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	val->value.i = priv->addr;
+
+	return 0;
+};
+
+static int
+adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr,
+		 struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	if (val->value.i > 65535)
+		return -EINVAL;
+
+	w16(priv, priv->addr, val->value.i);
+
+	return 0;
+};
+
+static int
+adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr,
+		 struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	val->value.i = r16(priv, priv->addr);
+
+	return 0;
+};
+
+#endif /* def DEBUG */
+
+static int
+adm6996_set_pvid(struct switch_dev *dev, int port, int vlan)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	pr_devel("set_pvid port %d vlan %d\n", port, vlan);
+
+	if (vlan > ADM_VLAN_MAX_ID)
+		return -EINVAL;
+
+	priv->pvid[port] = vlan;
+
+	return 0;
+}
+
+static int
+adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	pr_devel("get_pvid port %d\n", port);
+	*vlan = priv->pvid[port];
+
+	return 0;
+}
+
+static int
+adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
+		struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	pr_devel("set_vid port %d vid %d\n", val->port_vlan, val->value.i);
+
+	if (val->value.i > ADM_VLAN_MAX_ID)
+		return -EINVAL;
+
+	priv->vlan_id[val->port_vlan] = val->value.i;
+
+	return 0;
+};
+
+static int
+adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
+		struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	pr_devel("get_vid port %d\n", val->port_vlan);
+
+	val->value.i = priv->vlan_id[val->port_vlan];
+
+	return 0;
+};
+
+static int
+adm6996_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+	u8 ports = priv->vlan_table[val->port_vlan];
+	u8 tagged = priv->vlan_tagged[val->port_vlan];
+	int i;
+
+	pr_devel("get_ports port_vlan %d\n", val->port_vlan);
+
+	val->len = 0;
+
+	for (i = 0; i < ADM_NUM_PORTS; i++) {
+		struct switch_port *p;
+
+		if (!(ports & (1 << i)))
+			continue;
+
+		p = &val->value.ports[val->len++];
+		p->id = i;
+		if (tagged & (1 << i))
+			p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+		else
+			p->flags = 0;
+	}
+
+	return 0;
+};
+
+static int
+adm6996_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+	u8 *ports = &priv->vlan_table[val->port_vlan];
+	u8 *tagged = &priv->vlan_tagged[val->port_vlan];
+	int i;
+
+	pr_devel("set_ports port_vlan %d ports", val->port_vlan);
+
+	*ports = 0;
+	*tagged = 0;
+
+	for (i = 0; i < val->len; i++) {
+		struct switch_port *p = &val->value.ports[i];
+
+#ifdef DEBUG
+		pr_cont(" %d%s", p->id,
+		       ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" :
+			""));
+#endif
+
+		if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) {
+			*tagged |= (1 << p->id);
+			priv->tagged_ports |= (1 << p->id);
+		}
+
+		*ports |= (1 << p->id);
+	}
+
+#ifdef DEBUG
+	pr_cont("\n");
+#endif
+
+	return 0;
+};
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_enable_vlan(struct adm6996_priv *priv)
+{
+	u16 reg;
+
+	reg = r16(priv, ADM_OTBE_P2_PVID);
+	reg &= ~(ADM_OTBE_MASK);
+	w16(priv, ADM_OTBE_P2_PVID, reg);
+	reg = r16(priv, ADM_IFNTE);
+	reg &= ~(ADM_IFNTE_MASK);
+	w16(priv, ADM_IFNTE, reg);
+	reg = r16(priv, ADM_VID_CHECK);
+	reg |= ADM_VID_CHECK_MASK;
+	w16(priv, ADM_VID_CHECK, reg);
+	reg = r16(priv, ADM_SYSC0);
+	reg |= ADM_NTTE;
+	reg &= ~(ADM_RVID1);
+	w16(priv, ADM_SYSC0, reg);
+	reg = r16(priv, ADM_SYSC3);
+	reg |= ADM_TBV;
+	w16(priv, ADM_SYSC3, reg);
+}
+
+static void
+adm6996_enable_vlan_6996l(struct adm6996_priv *priv)
+{
+	u16 reg;
+
+	reg = r16(priv, ADM_SYSC3);
+	reg |= ADM_TBV;
+	reg |= ADM_MAC_CLONE;
+	w16(priv, ADM_SYSC3, reg);
+}
+
+/*
+ * Disable VLANs
+ *
+ * Sets VLAN mapping for port-based VLAN with all ports connected to
+ * eachother (this is also the power-on default).
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_disable_vlan(struct adm6996_priv *priv)
+{
+	u16 reg;
+	int i;
+
+	for (i = 0; i < ADM_NUM_VLANS; i++) {
+		reg = ADM_VLAN_FILT_MEMBER_MASK;
+		w16(priv, ADM_VLAN_FILT_L(i), reg);
+		reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1);
+		w16(priv, ADM_VLAN_FILT_H(i), reg);
+	}
+
+	reg = r16(priv, ADM_OTBE_P2_PVID);
+	reg |= ADM_OTBE_MASK;
+	w16(priv, ADM_OTBE_P2_PVID, reg);
+	reg = r16(priv, ADM_IFNTE);
+	reg |= ADM_IFNTE_MASK;
+	w16(priv, ADM_IFNTE, reg);
+	reg = r16(priv, ADM_VID_CHECK);
+	reg &= ~(ADM_VID_CHECK_MASK);
+	w16(priv, ADM_VID_CHECK, reg);
+	reg = r16(priv, ADM_SYSC0);
+	reg &= ~(ADM_NTTE);
+	reg |= ADM_RVID1;
+	w16(priv, ADM_SYSC0, reg);
+	reg = r16(priv, ADM_SYSC3);
+	reg &= ~(ADM_TBV);
+	w16(priv, ADM_SYSC3, reg);
+}
+
+/*
+ * Disable VLANs
+ *
+ * Sets VLAN mapping for port-based VLAN with all ports connected to
+ * eachother (this is also the power-on default).
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_disable_vlan_6996l(struct adm6996_priv *priv)
+{
+	u16 reg;
+	int i;
+
+	for (i = 0; i < ADM_NUM_VLANS; i++) {
+		w16(priv, ADM_VLAN_MAP(i), 0);
+	}
+
+	reg = r16(priv, ADM_SYSC3);
+	reg &= ~(ADM_TBV);
+	reg &= ~(ADM_MAC_CLONE);
+	w16(priv, ADM_SYSC3, reg);
+}
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_apply_port_pvids(struct adm6996_priv *priv)
+{
+	u16 reg;
+	int i;
+
+	for (i = 0; i < ADM_NUM_PORTS; i++) {
+		reg = r16(priv, adm_portcfg[i]);
+		reg &= ~(ADM_PORTCFG_PVID_MASK);
+		reg |= ADM_PORTCFG_PVID(priv->pvid[i]);
+		if (priv->model == ADM6996L) {
+			if (priv->tagged_ports & (1 << i))
+				reg |= (1 << 4);
+			else
+				reg &= ~(1 << 4);
+		}
+		w16(priv, adm_portcfg[i], reg);
+	}
+
+	w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0]));
+	w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1]));
+	reg = r16(priv, ADM_OTBE_P2_PVID);
+	reg &= ~(ADM_P2_PVID_MASK);
+	reg |= ADM_P2_PVID_VAL(priv->pvid[2]);
+	w16(priv, ADM_OTBE_P2_PVID, reg);
+	reg = ADM_P3_PVID_VAL(priv->pvid[3]);
+	reg |= ADM_P4_PVID_VAL(priv->pvid[4]);
+	w16(priv, ADM_P3_P4_PVID, reg);
+	reg = r16(priv, ADM_P5_PVID);
+	reg &= ~(ADM_P2_PVID_MASK);
+	reg |= ADM_P5_PVID_VAL(priv->pvid[5]);
+	w16(priv, ADM_P5_PVID, reg);
+}
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_apply_vlan_filters(struct adm6996_priv *priv)
+{
+	u8 ports, tagged;
+	u16 vid, reg;
+	int i;
+
+	for (i = 0; i < ADM_NUM_VLANS; i++) {
+		vid = priv->vlan_id[i];
+		ports = priv->vlan_table[i];
+		tagged = priv->vlan_tagged[i];
+
+		if (ports == 0) {
+			/* Disable VLAN entry */
+			w16(priv, ADM_VLAN_FILT_H(i), 0);
+			w16(priv, ADM_VLAN_FILT_L(i), 0);
+			continue;
+		}
+
+		reg = ADM_VLAN_FILT_MEMBER(ports);
+		reg |= ADM_VLAN_FILT_TAGGED(tagged);
+		w16(priv, ADM_VLAN_FILT_L(i), reg);
+		reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid);
+		w16(priv, ADM_VLAN_FILT_H(i), reg);
+	}
+}
+
+static void
+adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv)
+{
+	u8 ports;
+	u16 reg;
+	int i;
+
+	for (i = 0; i < ADM_NUM_VLANS; i++) {
+		ports = priv->vlan_table[i];
+
+		if (ports == 0) {
+			/* Disable VLAN entry */
+			w16(priv, ADM_VLAN_MAP(i), 0);
+			continue;
+		} else {
+			reg = ADM_VLAN_FILT(ports);
+			w16(priv, ADM_VLAN_MAP(i), reg);
+		}
+	}
+}
+
+static int
+adm6996_hw_apply(struct switch_dev *dev)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	pr_devel("hw_apply\n");
+
+	mutex_lock(&priv->reg_mutex);
+
+	if (!priv->enable_vlan) {
+		if (priv->vlan_enabled) {
+			if (priv->model == ADM6996L)
+				adm6996_disable_vlan_6996l(priv);
+			else
+				adm6996_disable_vlan(priv);
+			priv->vlan_enabled = 0;
+		}
+		goto out;
+	}
+
+	if (!priv->vlan_enabled) {
+		if (priv->model == ADM6996L)
+			adm6996_enable_vlan_6996l(priv);
+		else
+			adm6996_enable_vlan(priv);
+		priv->vlan_enabled = 1;
+	}
+
+	adm6996_apply_port_pvids(priv);
+	if (priv->model == ADM6996L)
+		adm6996_apply_vlan_filters_6996l(priv);
+	else
+		adm6996_apply_vlan_filters(priv);
+
+out:
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+/*
+ * Reset the switch
+ *
+ * The ADM6996 can't do a software-initiated reset, so we just initialise the
+ * registers we support in this driver.
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_perform_reset (struct adm6996_priv *priv)
+{
+	int i;
+
+	/* initialize port and vlan settings */
+	for (i = 0; i < ADM_NUM_PORTS - 1; i++) {
+		w16(priv, adm_portcfg[i], ADM_PORTCFG_INIT |
+			ADM_PORTCFG_PVID(0));
+	}
+	w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU);
+
+	if (priv->model == ADM6996M || priv->model == ADM6996FC) {
+		/* reset all PHY ports */
+		for (i = 0; i < ADM_PHY_PORTS; i++) {
+			w16(priv, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
+		}
+	}
+
+	priv->enable_vlan = 0;
+	priv->vlan_enabled = 0;
+
+	for (i = 0; i < ADM_NUM_PORTS; i++) {
+		priv->pvid[i] = 0;
+	}
+
+	for (i = 0; i < ADM_NUM_VLANS; i++) {
+		priv->vlan_id[i] = i;
+		priv->vlan_table[i] = 0;
+		priv->vlan_tagged[i] = 0;
+	}
+
+	if (priv->model == ADM6996M) {
+		/* Clear VLAN priority map so prio's are unused */
+		w16 (priv, ADM_VLAN_PRIOMAP, 0);
+
+		adm6996_disable_vlan(priv);
+		adm6996_apply_port_pvids(priv);
+	} else if (priv->model == ADM6996L) {
+		/* Clear VLAN priority map so prio's are unused */
+		w16 (priv, ADM_VLAN_PRIOMAP, 0);
+
+		adm6996_disable_vlan_6996l(priv);
+		adm6996_apply_port_pvids(priv);
+	}
+}
+
+static int
+adm6996_reset_switch(struct switch_dev *dev)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+
+	pr_devel("reset\n");
+
+	mutex_lock(&priv->reg_mutex);
+	adm6996_perform_reset (priv);
+	mutex_unlock(&priv->reg_mutex);
+	return 0;
+}
+
+static int
+adm6996_get_port_link(struct switch_dev *dev, int port,
+		struct switch_port_link *link)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+	
+	u16 reg = 0;
+	
+	if (port >= ADM_NUM_PORTS)
+		return -EINVAL;
+	
+	switch (port) {
+	case 0:
+		reg = r16(priv, ADM_PS0);
+		break;
+	case 1:
+		reg = r16(priv, ADM_PS0);
+		reg = reg >> 8;
+		break;
+	case 2:
+		reg = r16(priv, ADM_PS1);
+		break;
+	case 3:
+		reg = r16(priv, ADM_PS1);
+		reg = reg >> 8;
+		break;
+	case 4:
+		reg = r16(priv, ADM_PS1);
+		reg = reg >> 12;
+		break;
+	case 5:
+		reg = r16(priv, ADM_PS2);
+		/* Bits 0, 1, 3 and 4. */
+		reg = (reg & 3) | ((reg & 24) >> 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+	
+	link->link = reg & ADM_PS_LS;
+	if (!link->link)
+		return 0;
+	link->aneg = true;
+	link->duplex = reg & ADM_PS_DS;
+	link->tx_flow = reg & ADM_PS_FCS;
+	link->rx_flow = reg & ADM_PS_FCS;
+	if (reg & ADM_PS_SS)
+		link->speed = SWITCH_PORT_SPEED_100;
+	else
+		link->speed = SWITCH_PORT_SPEED_10;
+
+	return 0;
+}
+
+static int
+adm6996_sw_get_port_mib(struct switch_dev *dev,
+		       const struct switch_attr *attr,
+		       struct switch_val *val)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+	int port;
+	char *buf = priv->buf;
+	int i, len = 0;
+	u32 reg = 0;
+
+	port = val->port_vlan;
+	if (port >= ADM_NUM_PORTS)
+		return -EINVAL;
+
+	mutex_lock(&priv->mib_lock);
+
+	len += snprintf(buf + len, sizeof(priv->buf) - len,
+			"Port %d MIB counters\n",
+			port);
+
+	for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) {
+		reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port));
+		reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16;
+		len += snprintf(buf + len, sizeof(priv->buf) - len,
+				"%-12s: %u\n",
+				adm6996_mibs[i].name,
+				reg);
+	}
+
+	mutex_unlock(&priv->mib_lock);
+
+	val->value.s = buf;
+	val->len = len;
+
+	return 0;
+}
+
+static int
+adm6996_get_port_stats(struct switch_dev *dev, int port,
+			struct switch_port_stats *stats)
+{
+	struct adm6996_priv *priv = to_adm(dev);
+	int id;
+	u32 reg = 0;
+
+	if (port >= ADM_NUM_PORTS)
+		return -EINVAL;
+
+	mutex_lock(&priv->mib_lock);
+
+	id = ADM6996_MIB_TXB_ID;
+	reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port));
+	reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16;
+	stats->tx_bytes = reg;
+
+	id = ADM6996_MIB_RXB_ID;
+	reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port));
+	reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16;
+	stats->rx_bytes = reg;
+
+	mutex_unlock(&priv->mib_lock);
+
+	return 0;
+}
+
+static struct switch_attr adm6996_globals[] = {
+	{
+	 .type = SWITCH_TYPE_INT,
+	 .name = "enable_vlan",
+	 .description = "Enable VLANs",
+	 .set = adm6996_set_enable_vlan,
+	 .get = adm6996_get_enable_vlan,
+	},
+#ifdef DEBUG
+	{
+	 .type = SWITCH_TYPE_INT,
+	 .name = "addr",
+	 .description =
+	 "Direct register access: set register address (0 - 1023)",
+	 .set = adm6996_set_addr,
+	 .get = adm6996_get_addr,
+	 },
+	{
+	 .type = SWITCH_TYPE_INT,
+	 .name = "data",
+	 .description =
+	 "Direct register access: read/write to register (0 - 65535)",
+	 .set = adm6996_set_data,
+	 .get = adm6996_get_data,
+	 },
+#endif /* def DEBUG */
+};
+
+static struct switch_attr adm6996_port[] = {
+	{
+	 .type = SWITCH_TYPE_STRING,
+	 .name = "mib",
+	 .description = "Get port's MIB counters",
+	 .set = NULL,
+	 .get = adm6996_sw_get_port_mib,
+	},
+};
+
+static struct switch_attr adm6996_vlan[] = {
+	{
+	 .type = SWITCH_TYPE_INT,
+	 .name = "vid",
+	 .description = "VLAN ID",
+	 .set = adm6996_set_vid,
+	 .get = adm6996_get_vid,
+	 },
+};
+
+static struct switch_dev_ops adm6996_ops = {
+	.attr_global = {
+			.attr = adm6996_globals,
+			.n_attr = ARRAY_SIZE(adm6996_globals),
+			},
+	.attr_port = {
+		      .attr = adm6996_port,
+		      .n_attr = ARRAY_SIZE(adm6996_port),
+		      },
+	.attr_vlan = {
+		      .attr = adm6996_vlan,
+		      .n_attr = ARRAY_SIZE(adm6996_vlan),
+		      },
+	.get_port_pvid = adm6996_get_pvid,
+	.set_port_pvid = adm6996_set_pvid,
+	.get_vlan_ports = adm6996_get_ports,
+	.set_vlan_ports = adm6996_set_ports,
+	.apply_config = adm6996_hw_apply,
+	.reset_switch = adm6996_reset_switch,
+	.get_port_link = adm6996_get_port_link,
+	.get_port_stats = adm6996_get_port_stats,
+};
+
+static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev)
+{
+	struct switch_dev *swdev;
+	u16 test, old;
+
+	if (!priv->model) {
+		/* Detect type of chip */
+		old = r16(priv, ADM_VID_CHECK);
+		test = old ^ (1 << 12);
+		w16(priv, ADM_VID_CHECK, test);
+		test ^= r16(priv, ADM_VID_CHECK);
+		if (test & (1 << 12)) {
+			/* 
+			 * Bit 12 of this register is read-only. 
+			 * This is the FC model. 
+			 */
+			priv->model = ADM6996FC;
+		} else {
+			/* Bit 12 is read-write. This is the M model. */
+			priv->model = ADM6996M;
+			w16(priv, ADM_VID_CHECK, old);
+		}
+	}
+
+	swdev = &priv->dev;
+	swdev->name = (adm6996_model_name[priv->model]);
+	swdev->cpu_port = ADM_CPU_PORT;
+	swdev->ports = ADM_NUM_PORTS;
+	swdev->vlans = ADM_NUM_VLANS;
+	swdev->ops = &adm6996_ops;
+	swdev->alias = alias;
+
+	/* The ADM6996L connected through GPIOs does not support any switch
+	   status calls */
+	if (priv->model == ADM6996L) {
+		adm6996_ops.attr_port.n_attr = 0;
+		adm6996_ops.get_port_link = NULL;
+	}
+
+	pr_info ("%s: %s model PHY found.\n", alias, swdev->name);
+
+	mutex_lock(&priv->reg_mutex);
+	adm6996_perform_reset (priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	if (priv->model == ADM6996M || priv->model == ADM6996L) {
+		return register_switch(swdev, netdev);
+	}
+
+	return -ENODEV;
+}
+
+static int adm6996_config_init(struct phy_device *pdev)
+{
+	struct adm6996_priv *priv;
+	int ret;
+
+	linkmode_zero(pdev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pdev->supported);
+	linkmode_copy(pdev->advertising, pdev->supported);
+
+	if (pdev->mdio.addr != 0) {
+		pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n"
+				, pdev->attached_dev->name, pdev->mdio.addr);
+		return 0;
+	}
+
+	priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	mutex_init(&priv->reg_mutex);
+	mutex_init(&priv->mib_lock);
+	priv->priv = pdev;
+	priv->read = adm6996_read_mii_reg;
+	priv->write = adm6996_write_mii_reg;
+
+	ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev);
+	if (ret < 0)
+		return ret;
+
+	pdev->priv = priv;
+
+	return 0;
+}
+
+/*
+ * Warning: phydev->priv is NULL if phydev->mdio.addr != 0
+ */
+static int adm6996_read_status(struct phy_device *phydev)
+{
+	phydev->speed = SPEED_100;
+	phydev->duplex = DUPLEX_FULL;
+	phydev->link = 1;
+
+	phydev->state = PHY_RUNNING;
+	netif_carrier_on(phydev->attached_dev);
+	phydev->adjust_link(phydev->attached_dev);
+
+	return 0;
+}
+
+/*
+ * Warning: phydev->priv is NULL if phydev->mdio.addr != 0
+ */
+static int adm6996_config_aneg(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int adm6996_fixup(struct phy_device *dev)
+{
+	struct mii_bus *bus = dev->mdio.bus;
+	u16 reg;
+
+	/* Our custom registers are at PHY addresses 0-10. Claim those. */
+	if (dev->mdio.addr > 10)
+		return 0;
+
+	/* look for the switch on the bus */
+	reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK;
+	if (reg != ADM_SIG0_VAL)
+		return 0;
+
+	reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK;
+	if (reg != ADM_SIG1_VAL)
+		return 0;
+
+	dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL;
+
+	return 0;
+}
+
+static int adm6996_probe(struct phy_device *pdev)
+{
+	return 0;
+}
+
+static void adm6996_remove(struct phy_device *pdev)
+{
+	struct adm6996_priv *priv = phy_to_adm(pdev);
+
+	if (priv && (priv->model == ADM6996M || priv->model == ADM6996L))
+		unregister_switch(&priv->dev);
+}
+
+static int adm6996_soft_reset(struct phy_device *phydev)
+{
+	/* we don't need an extra reset */
+	return 0;
+}
+
+static struct phy_driver adm6996_phy_driver = {
+	.name		= "Infineon ADM6996",
+	.phy_id		= (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL,
+	.phy_id_mask	= 0xffffffff,
+	.features	= PHY_BASIC_FEATURES,
+	.probe		= adm6996_probe,
+	.remove		= adm6996_remove,
+	.config_init	= &adm6996_config_init,
+	.config_aneg	= &adm6996_config_aneg,
+	.read_status	= &adm6996_read_status,
+	.soft_reset	= adm6996_soft_reset,
+};
+
+static int adm6996_gpio_probe(struct platform_device *pdev)
+{
+	struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data;
+	struct adm6996_priv *priv;
+	int ret;
+
+	if (!pdata)
+		return -EINVAL;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	mutex_init(&priv->reg_mutex);
+	mutex_init(&priv->mib_lock);
+
+	priv->eecs = pdata->eecs;
+	priv->eedi = pdata->eedi;
+	priv->eesk = pdata->eesk;
+
+	priv->model = pdata->model;
+	priv->read = adm6996_read_gpio_reg;
+	priv->write = adm6996_write_gpio_reg;
+
+	ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs");
+	if (ret)
+		return ret;
+	ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi");
+	if (ret)
+		return ret;
+	ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk");
+	if (ret)
+		return ret;
+
+	ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL);
+	if (ret < 0)
+		return ret;
+
+	platform_set_drvdata(pdev, priv);
+
+	return 0;
+}
+
+static int adm6996_gpio_remove(struct platform_device *pdev)
+{
+	struct adm6996_priv *priv = platform_get_drvdata(pdev);
+
+	if (priv && (priv->model == ADM6996M || priv->model == ADM6996L))
+		unregister_switch(&priv->dev);
+
+	return 0;
+}
+
+static struct platform_driver adm6996_gpio_driver = {
+	.probe = adm6996_gpio_probe,
+	.remove = adm6996_gpio_remove,
+	.driver = {
+		.name = "adm6996_gpio",
+	},
+};
+
+static int __init adm6996_init(void)
+{
+	int err;
+
+	phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup);
+	err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE);
+	if (err)
+		return err;
+
+	err = platform_driver_register(&adm6996_gpio_driver);
+	if (err)
+		phy_driver_unregister(&adm6996_phy_driver);
+
+	return err;
+}
+
+static void __exit adm6996_exit(void)
+{
+	platform_driver_unregister(&adm6996_gpio_driver);
+	phy_driver_unregister(&adm6996_phy_driver);
+}
+
+module_init(adm6996_init);
+module_exit(adm6996_exit);
diff -Naur linux-orig/drivers/net/phy/adm6996.h linux/drivers/net/phy/adm6996.h
--- linux-orig/drivers/net/phy/adm6996.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/adm6996.h	2022-04-15 23:50:31.718484399 +0000
@@ -0,0 +1,186 @@
+/*
+ * ADM6996 switch driver
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of the GNU General Public License v2 as published by the
+ * Free Software Foundation
+ */
+#ifndef __ADM6996_H
+#define __ADM6996_H
+
+/*
+ * ADM_PHY_PORTS: Number of ports with a PHY.
+ * We only control ports 0 to 3, because if 4 is connected, it is most likely
+ * not connected to the switch but to a separate MII and MAC for the WAN port.
+ */
+#define ADM_PHY_PORTS	4
+#define ADM_NUM_PORTS	6
+#define ADM_CPU_PORT	5
+
+#define ADM_NUM_VLANS 16
+#define ADM_VLAN_MAX_ID 4094
+
+enum admreg {
+	ADM_EEPROM_BASE		= 0x0,
+		ADM_P0_CFG		= ADM_EEPROM_BASE + 1,
+		ADM_P1_CFG		= ADM_EEPROM_BASE + 3,
+		ADM_P2_CFG		= ADM_EEPROM_BASE + 5,
+		ADM_P3_CFG		= ADM_EEPROM_BASE + 7,
+		ADM_P4_CFG		= ADM_EEPROM_BASE + 8,
+		ADM_P5_CFG		= ADM_EEPROM_BASE + 9,
+		ADM_SYSC0		= ADM_EEPROM_BASE + 0xa,
+		ADM_VLAN_PRIOMAP	= ADM_EEPROM_BASE + 0xe,
+		ADM_SYSC3		= ADM_EEPROM_BASE + 0x11,
+		/* Input Force No Tag Enable */
+		ADM_IFNTE		= ADM_EEPROM_BASE + 0x20,
+		ADM_VID_CHECK		= ADM_EEPROM_BASE + 0x26,
+		ADM_P0_PVID		= ADM_EEPROM_BASE + 0x28,
+		ADM_P1_PVID		= ADM_EEPROM_BASE + 0x29,
+		/* Output Tag Bypass Enable and P2 PVID */
+		ADM_OTBE_P2_PVID	= ADM_EEPROM_BASE + 0x2a,
+		ADM_P3_P4_PVID		= ADM_EEPROM_BASE + 0x2b,
+		ADM_P5_PVID		= ADM_EEPROM_BASE + 0x2c,
+	ADM_EEPROM_EXT_BASE	= 0x40,
+#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n))
+#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n))
+#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n)
+	ADM_COUNTER_BASE	= 0xa0,
+		ADM_SIG0		= ADM_COUNTER_BASE + 0,
+		ADM_SIG1		= ADM_COUNTER_BASE + 1,
+		ADM_PS0		= ADM_COUNTER_BASE + 2,
+		ADM_PS1		= ADM_COUNTER_BASE + 3,
+		ADM_PS2		= ADM_COUNTER_BASE + 4,
+		ADM_CL0		= ADM_COUNTER_BASE + 8, /* RxPacket */
+		ADM_CL6		= ADM_COUNTER_BASE + 0x1a, /* RxByte */
+		ADM_CL12		= ADM_COUNTER_BASE + 0x2c, /* TxPacket */
+		ADM_CL18		= ADM_COUNTER_BASE + 0x3e, /* TxByte */
+		ADM_CL24		= ADM_COUNTER_BASE + 0x50, /* Coll */
+		ADM_CL30		= ADM_COUNTER_BASE + 0x62, /* Err */
+#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2)
+	ADM_PHY_BASE		= 0x200,
+#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n))
+};
+
+/* Chip identification patterns */
+#define	ADM_SIG0_MASK	0xffff
+#define ADM_SIG0_VAL	0x1023
+#define ADM_SIG1_MASK	0xffff
+#define ADM_SIG1_VAL	0x0007
+
+enum {
+	ADM_PHYCFG_COLTST     = (1 << 7),	/* Enable collision test */
+	ADM_PHYCFG_DPLX       = (1 << 8),	/* Enable full duplex */
+	ADM_PHYCFG_ANEN_RST   = (1 << 9),	/* Restart auto negotiation (self clear) */
+	ADM_PHYCFG_ISO        = (1 << 10),	/* Isolate PHY */
+	ADM_PHYCFG_PDN        = (1 << 11),	/* Power down PHY */
+	ADM_PHYCFG_ANEN       = (1 << 12),	/* Enable auto negotiation */
+	ADM_PHYCFG_SPEED_100  = (1 << 13),	/* Enable 100 Mbit/s */
+	ADM_PHYCFG_LPBK       = (1 << 14),	/* Enable loopback operation */
+	ADM_PHYCFG_RST        = (1 << 15),	/* Reset the port (self clear) */
+	ADM_PHYCFG_INIT = (
+		ADM_PHYCFG_RST |
+		ADM_PHYCFG_SPEED_100 |
+		ADM_PHYCFG_ANEN |
+		ADM_PHYCFG_ANEN_RST
+	)
+};
+
+enum {
+	ADM_PORTCFG_FC        = (1 << 0),	/* Enable 802.x flow control */
+	ADM_PORTCFG_AN        = (1 << 1),	/* Enable auto-negotiation */
+	ADM_PORTCFG_SPEED_100 = (1 << 2),	/* Enable 100 Mbit/s */
+	ADM_PORTCFG_DPLX      = (1 << 3),	/* Enable full duplex */
+	ADM_PORTCFG_OT        = (1 << 4),	/* Output tagged packets */
+	ADM_PORTCFG_PD        = (1 << 5),	/* Port disable */
+	ADM_PORTCFG_TV_PRIO   = (1 << 6),	/* 0 = VLAN based priority
+	                                 	 * 1 = TOS based priority */
+	ADM_PORTCFG_PPE       = (1 << 7),	/* Port based priority enable */
+	ADM_PORTCFG_PP_S      = (1 << 8),	/* Port based priority, 2 bits */
+	ADM_PORTCFG_PVID_BASE = (1 << 10),	/* Primary VLAN id, 4 bits */
+	ADM_PORTCFG_FSE	      = (1 << 14),	/* Fx select enable */
+	ADM_PORTCFG_CAM       = (1 << 15),	/* Crossover Auto MDIX */
+
+	ADM_PORTCFG_INIT = (
+		ADM_PORTCFG_FC |
+		ADM_PORTCFG_AN |
+		ADM_PORTCFG_SPEED_100 |
+		ADM_PORTCFG_DPLX |
+		ADM_PORTCFG_CAM
+	),
+	ADM_PORTCFG_CPU = (
+		ADM_PORTCFG_FC |
+		ADM_PORTCFG_SPEED_100 |
+		ADM_PORTCFG_OT |
+		ADM_PORTCFG_DPLX
+	),
+};
+
+#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8)
+#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10)
+#define ADM_PORTCFG_PVID_MASK (0xf << 10)
+
+#define ADM_IFNTE_MASK (0x3f << 9)
+#define ADM_VID_CHECK_MASK (0x3f << 6)
+
+#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8)
+#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P2_PVID_MASK 0xff
+
+#define ADM_OTBE(n) (((n) & 0x3f) << 8)
+#define ADM_OTBE_MASK (0x3f << 8)
+
+/* ADM_SYSC0 */
+enum {
+	ADM_NTTE	= (1 << 2),	/* New Tag Transmit Enable */
+	ADM_RVID1	= (1 << 8)	/* Replace VLAN ID 1 */
+};
+
+/* Tag Based VLAN in ADM_SYSC3 */
+#define ADM_MAC_CLONE	BIT(4)
+#define ADM_TBV		BIT(5)
+
+static const u8 adm_portcfg[] = {
+	[0] = ADM_P0_CFG,
+	[1] = ADM_P1_CFG,
+	[2] = ADM_P2_CFG,
+	[3] = ADM_P3_CFG,
+	[4] = ADM_P4_CFG,
+	[5] = ADM_P5_CFG,
+};
+
+/* Fields in ADM_VLAN_FILT_L(x) */
+#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12)
+#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6)
+#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0)
+#define ADM_VLAN_FILT_MEMBER_MASK 0x3f
+/* Fields in ADM_VLAN_FILT_H(x) */
+#define ADM_VLAN_FILT_VALID (1 << 15)
+#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0)
+
+/* Convert ports to a form for ADM6996L VLAN map */
+#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \
+			((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \
+			((ports & 0x10) << 3) | ((ports & 0x20) << 3))
+
+/* Port status register */
+enum {
+	ADM_PS_LS = (1 << 0),	/* Link status */
+	ADM_PS_SS = (1 << 1),	/* Speed status */
+	ADM_PS_DS = (1 << 2),	/* Duplex status */
+	ADM_PS_FCS = (1 << 3)	/* Flow control status */
+};
+
+/*
+ * Split the register address in phy id and register
+ * it will get combined again by the mdio bus op
+ */
+#define PHYADDR(_reg)	((_reg >> 5) & 0xff), (_reg & 0x1f)
+
+#endif
diff -Naur linux-orig/drivers/net/phy/ar8216.c linux/drivers/net/phy/ar8216.c
--- linux-orig/drivers/net/phy/ar8216.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/ar8216.c	2022-04-15 23:50:31.718484399 +0000
@@ -0,0 +1,2917 @@
+/*
+ * ar8216.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/of_device.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/bitops.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/etherdevice.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+#include "ar8216.h"
+
+extern const struct ar8xxx_chip ar8327_chip;
+extern const struct ar8xxx_chip ar8337_chip;
+
+#define MIB_DESC_BASIC(_s , _o, _n)		\
+	{					\
+		.size = (_s),			\
+		.offset = (_o),			\
+		.name = (_n),			\
+		.type = AR8XXX_MIB_BASIC,	\
+	}
+
+#define MIB_DESC_EXT(_s , _o, _n)		\
+	{					\
+		.size = (_s),			\
+		.offset = (_o),			\
+		.name = (_n),			\
+		.type = AR8XXX_MIB_EXTENDED,	\
+	}
+
+static const struct ar8xxx_mib_desc ar8216_mibs[] = {
+	MIB_DESC_EXT(1, AR8216_STATS_RXBROAD, "RxBroad"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXPAUSE, "RxPause"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXMULTI, "RxMulti"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXFCSERR, "RxFcsErr"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXRUNT, "RxRunt"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXFRAGMENT, "RxFragment"),
+	MIB_DESC_EXT(1, AR8216_STATS_RX64BYTE, "Rx64Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RX128BYTE, "Rx128Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RX256BYTE, "Rx256Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RX512BYTE, "Rx512Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXTOOLONG, "RxTooLong"),
+	MIB_DESC_BASIC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"),
+	MIB_DESC_EXT(2, AR8216_STATS_RXBADBYTE, "RxBadByte"),
+	MIB_DESC_EXT(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"),
+	MIB_DESC_EXT(1, AR8216_STATS_FILTERED, "Filtered"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXBROAD, "TxBroad"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXPAUSE, "TxPause"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXMULTI, "TxMulti"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"),
+	MIB_DESC_EXT(1, AR8216_STATS_TX64BYTE, "Tx64Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TX128BYTE, "Tx128Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TX256BYTE, "Tx256Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TX512BYTE, "Tx512Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"),
+	MIB_DESC_BASIC(2, AR8216_STATS_TXBYTE, "TxByte"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXCOLLISION, "TxCollision"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXDEFER, "TxDefer"),
+	MIB_DESC_EXT(1, AR8216_STATS_TXLATECOL, "TxLateCol"),
+};
+
+const struct ar8xxx_mib_desc ar8236_mibs[39] = {
+	MIB_DESC_EXT(1, AR8236_STATS_RXBROAD, "RxBroad"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXPAUSE, "RxPause"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXMULTI, "RxMulti"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXFCSERR, "RxFcsErr"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXRUNT, "RxRunt"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXFRAGMENT, "RxFragment"),
+	MIB_DESC_EXT(1, AR8236_STATS_RX64BYTE, "Rx64Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RX128BYTE, "Rx128Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RX256BYTE, "Rx256Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RX512BYTE, "Rx512Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXTOOLONG, "RxTooLong"),
+	MIB_DESC_BASIC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"),
+	MIB_DESC_EXT(2, AR8236_STATS_RXBADBYTE, "RxBadByte"),
+	MIB_DESC_EXT(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"),
+	MIB_DESC_EXT(1, AR8236_STATS_FILTERED, "Filtered"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXBROAD, "TxBroad"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXPAUSE, "TxPause"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXMULTI, "TxMulti"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"),
+	MIB_DESC_EXT(1, AR8236_STATS_TX64BYTE, "Tx64Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TX128BYTE, "Tx128Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TX256BYTE, "Tx256Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TX512BYTE, "Tx512Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"),
+	MIB_DESC_BASIC(2, AR8236_STATS_TXBYTE, "TxByte"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXCOLLISION, "TxCollision"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXDEFER, "TxDefer"),
+	MIB_DESC_EXT(1, AR8236_STATS_TXLATECOL, "TxLateCol"),
+};
+
+static DEFINE_MUTEX(ar8xxx_dev_list_lock);
+static LIST_HEAD(ar8xxx_dev_list);
+
+static void
+ar8xxx_mib_start(struct ar8xxx_priv *priv);
+static void
+ar8xxx_mib_stop(struct ar8xxx_priv *priv);
+
+/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */
+static int
+ar8xxx_phy_poll_reset(struct mii_bus *bus)
+{
+        unsigned int sleep_msecs = 20;
+        int ret, elapsed, i;
+
+        for (elapsed = sleep_msecs; elapsed <= 600;
+	     elapsed += sleep_msecs) {
+                msleep(sleep_msecs);
+                for (i = 0; i < AR8XXX_NUM_PHYS; i++) {
+                        ret = mdiobus_read(bus, i, MII_BMCR);
+                        if (ret < 0)
+				return ret;
+                        if (ret & BMCR_RESET)
+				break;
+                        if (i == AR8XXX_NUM_PHYS - 1) {
+                                usleep_range(1000, 2000);
+                                return 0;
+                        }
+                }
+        }
+        return -ETIMEDOUT;
+}
+
+static int
+ar8xxx_phy_check_aneg(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->autoneg != AUTONEG_ENABLE)
+		return 0;
+	/*
+	 * BMCR_ANENABLE might have been cleared
+	 * by phy_init_hw in certain kernel versions
+	 * therefore check for it
+	 */
+	ret = phy_read(phydev, MII_BMCR);
+	if (ret < 0)
+		return ret;
+	if (ret & BMCR_ANENABLE)
+		return 0;
+
+	dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n");
+	ret |= BMCR_ANENABLE | BMCR_ANRESTART;
+	return phy_write(phydev, MII_BMCR, ret);
+}
+
+void
+ar8xxx_phy_init(struct ar8xxx_priv *priv)
+{
+	int i;
+	struct mii_bus *bus;
+
+	bus = priv->sw_mii_bus ?: priv->mii_bus;
+	for (i = 0; i < AR8XXX_NUM_PHYS; i++) {
+		if (priv->chip->phy_fixup)
+			priv->chip->phy_fixup(priv, i);
+
+		/* initialize the port itself */
+		mdiobus_write(bus, i, MII_ADVERTISE,
+			ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
+		if (ar8xxx_has_gige(priv))
+			mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL);
+		mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
+	}
+
+	ar8xxx_phy_poll_reset(bus);
+}
+
+u32
+ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 lo, hi;
+
+	lo = bus->read(bus, phy_id, regnum);
+	hi = bus->read(bus, phy_id, regnum + 1);
+
+	return (hi << 16) | lo;
+}
+
+void
+ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 lo, hi;
+
+	lo = val & 0xffff;
+	hi = (u16) (val >> 16);
+
+	if (priv->chip->mii_lo_first)
+	{
+		bus->write(bus, phy_id, regnum, lo);
+		bus->write(bus, phy_id, regnum + 1, hi);
+	} else {
+		bus->write(bus, phy_id, regnum + 1, hi);
+		bus->write(bus, phy_id, regnum, lo);
+	}
+}
+
+u32
+ar8xxx_read(struct ar8xxx_priv *priv, int reg)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 r1, r2, page;
+	u32 val;
+
+	split_addr((u32) reg, &r1, &r2, &page);
+
+	mutex_lock(&bus->mdio_lock);
+
+	bus->write(bus, 0x18, 0, page);
+	wait_for_page_switch();
+	val = ar8xxx_mii_read32(priv, 0x10 | r2, r1);
+
+	mutex_unlock(&bus->mdio_lock);
+
+	return val;
+}
+
+void
+ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 r1, r2, page;
+
+	split_addr((u32) reg, &r1, &r2, &page);
+
+	mutex_lock(&bus->mdio_lock);
+
+	bus->write(bus, 0x18, 0, page);
+	wait_for_page_switch();
+	ar8xxx_mii_write32(priv, 0x10 | r2, r1, val);
+
+	mutex_unlock(&bus->mdio_lock);
+}
+
+u32
+ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 r1, r2, page;
+	u32 ret;
+
+	split_addr((u32) reg, &r1, &r2, &page);
+
+	mutex_lock(&bus->mdio_lock);
+
+	bus->write(bus, 0x18, 0, page);
+	wait_for_page_switch();
+
+	ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1);
+	ret &= ~mask;
+	ret |= val;
+	ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret);
+
+	mutex_unlock(&bus->mdio_lock);
+
+	return ret;
+}
+void
+ar8xxx_phy_dbg_read(struct ar8xxx_priv *priv, int phy_addr,
+           u16 dbg_addr, u16 *dbg_data)
+{
+       struct mii_bus *bus = priv->mii_bus;
+
+       mutex_lock(&bus->mdio_lock);
+       bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr);
+       *dbg_data = bus->read(bus, phy_addr, MII_ATH_DBG_DATA);
+       mutex_unlock(&bus->mdio_lock);
+}
+
+void
+ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr,
+		     u16 dbg_addr, u16 dbg_data)
+{
+	struct mii_bus *bus = priv->mii_bus;
+
+	mutex_lock(&bus->mdio_lock);
+	bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr);
+	bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data);
+	mutex_unlock(&bus->mdio_lock);
+}
+
+static inline void
+ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg)
+{
+	bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr);
+	bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg);
+	bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000);
+}
+
+void
+ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data)
+{
+	struct mii_bus *bus = priv->mii_bus;
+
+	mutex_lock(&bus->mdio_lock);
+	ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg);
+	bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data);
+	mutex_unlock(&bus->mdio_lock);
+}
+
+u16
+ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 data;
+
+	mutex_lock(&bus->mdio_lock);
+	ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg);
+	data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA);
+	mutex_unlock(&bus->mdio_lock);
+
+	return data;
+}
+
+static int
+ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val,
+		unsigned timeout)
+{
+	int i;
+
+	for (i = 0; i < timeout; i++) {
+		u32 t;
+
+		t = ar8xxx_read(priv, reg);
+		if ((t & mask) == val)
+			return 0;
+
+		usleep_range(1000, 2000);
+		cond_resched();
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int
+ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op)
+{
+	unsigned mib_func = priv->chip->mib_func;
+	int ret;
+
+	lockdep_assert_held(&priv->mib_lock);
+
+	/* Capture the hardware statistics for all ports */
+	ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S));
+
+	/* Wait for the capturing to complete. */
+	ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10);
+	if (ret)
+		goto out;
+
+	ret = 0;
+
+out:
+	return ret;
+}
+
+static int
+ar8xxx_mib_capture(struct ar8xxx_priv *priv)
+{
+	return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE);
+}
+
+static int
+ar8xxx_mib_flush(struct ar8xxx_priv *priv)
+{
+	return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH);
+}
+
+static void
+ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush)
+{
+	unsigned int base;
+	u64 *mib_stats;
+	int i;
+
+	WARN_ON(port >= priv->dev.ports);
+
+	lockdep_assert_held(&priv->mib_lock);
+
+	base = priv->chip->reg_port_stats_start +
+	       priv->chip->reg_port_stats_length * port;
+
+	mib_stats = &priv->mib_stats[port * priv->chip->num_mibs];
+	for (i = 0; i < priv->chip->num_mibs; i++) {
+		const struct ar8xxx_mib_desc *mib;
+		u64 t;
+
+		mib = &priv->chip->mib_decs[i];
+		if (mib->type > priv->mib_type)
+			continue;
+		t = ar8xxx_read(priv, base + mib->offset);
+		if (mib->size == 2) {
+			u64 hi;
+
+			hi = ar8xxx_read(priv, base + mib->offset + 4);
+			t |= hi << 32;
+		}
+
+		if (flush)
+			mib_stats[i] = 0;
+		else
+			mib_stats[i] += t;
+		cond_resched();
+	}
+}
+
+static void
+ar8216_read_port_link(struct ar8xxx_priv *priv, int port,
+		      struct switch_port_link *link)
+{
+	u32 status;
+	u32 speed;
+
+	memset(link, '\0', sizeof(*link));
+
+	status = priv->chip->read_port_status(priv, port);
+
+	link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO);
+	if (link->aneg) {
+		link->link = !!(status & AR8216_PORT_STATUS_LINK_UP);
+	} else {
+		link->link = true;
+
+		if (priv->get_port_link) {
+			int err;
+
+			err = priv->get_port_link(port);
+			if (err >= 0)
+				link->link = !!err;
+		}
+	}
+
+	if (!link->link)
+		return;
+
+	link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX);
+	link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW);
+	link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW);
+
+	if (link->aneg && link->duplex && priv->chip->read_port_eee_status)
+		link->eee = priv->chip->read_port_eee_status(priv, port);
+
+	speed = (status & AR8216_PORT_STATUS_SPEED) >>
+		 AR8216_PORT_STATUS_SPEED_S;
+
+	switch (speed) {
+	case AR8216_PORT_SPEED_10M:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case AR8216_PORT_SPEED_100M:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case AR8216_PORT_SPEED_1000M:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	default:
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+}
+
+#ifdef CONFIG_ETHERNET_PACKET_MANGLE
+
+static struct sk_buff *
+ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb)
+{
+	struct ar8xxx_priv *priv = dev->phy_ptr;
+	unsigned char *buf;
+
+	if (unlikely(!priv))
+		goto error;
+
+	if (!priv->vlan)
+		goto send;
+
+	if (unlikely(skb_headroom(skb) < 2)) {
+		if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0)
+			goto error;
+	}
+
+	buf = skb_push(skb, 2);
+	buf[0] = 0x10;
+	buf[1] = 0x80;
+
+send:
+	return skb;
+
+error:
+	dev_kfree_skb_any(skb);
+	return NULL;
+}
+
+static void
+ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb)
+{
+	struct ar8xxx_priv *priv;
+	unsigned char *buf;
+	int port, vlan;
+
+	priv = dev->phy_ptr;
+	if (!priv)
+		return;
+
+	/* don't strip the header if vlan mode is disabled */
+	if (!priv->vlan)
+		return;
+
+	/* strip header, get vlan id */
+	buf = skb->data;
+	skb_pull(skb, 2);
+
+	/* check for vlan header presence */
+	if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00))
+		return;
+
+	port = buf[0] & 0x7;
+
+	/* no need to fix up packets coming from a tagged source */
+	if (priv->vlan_tagged & (1 << port))
+		return;
+
+	/* lookup port vid from local table, the switch passes an invalid vlan id */
+	vlan = priv->vlan_id[priv->pvid[port]];
+
+	buf[14 + 2] &= 0xf0;
+	buf[14 + 2] |= vlan >> 8;
+	buf[15 + 2] = vlan & 0xff;
+}
+
+#endif
+
+int
+ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val)
+{
+	int timeout = 20;
+	u32 t = 0;
+
+	while (1) {
+		t = ar8xxx_read(priv, reg);
+		if ((t & mask) == val)
+			return 0;
+
+		if (timeout-- <= 0)
+			break;
+
+		udelay(10);
+		cond_resched();
+	}
+
+	pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n",
+	       (unsigned int) reg, t, mask, val);
+	return -ETIMEDOUT;
+}
+
+static void
+ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val)
+{
+	if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0))
+		return;
+	if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) {
+		val &= AR8216_VTUDATA_MEMBER;
+		val |= AR8216_VTUDATA_VALID;
+		ar8xxx_write(priv, AR8216_REG_VTU_DATA, val);
+	}
+	op |= AR8216_VTU_ACTIVE;
+	ar8xxx_write(priv, AR8216_REG_VTU, op);
+}
+
+static void
+ar8216_vtu_flush(struct ar8xxx_priv *priv)
+{
+	ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0);
+}
+
+static void
+ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask)
+{
+	u32 op;
+
+	op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S);
+	ar8216_vtu_op(priv, op, port_mask);
+}
+
+static int
+ar8216_atu_flush(struct ar8xxx_priv *priv)
+{
+	int ret;
+
+	ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0);
+	if (!ret)
+		ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH |
+							 AR8216_ATU_ACTIVE);
+
+	return ret;
+}
+
+static int
+ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port)
+{
+	u32 t;
+	int ret;
+
+	ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0);
+	if (!ret) {
+		t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT;
+		t |= AR8216_ATU_ACTIVE;
+		ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t);
+	}
+
+	return ret;
+}
+
+static u32
+ar8216_read_port_status(struct ar8xxx_priv *priv, int port)
+{
+	return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port));
+}
+
+static void
+__ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members,
+		    bool ath_hdr_en)
+{
+	u32 header;
+	u32 egress, ingress;
+	u32 pvid;
+
+	if (priv->vlan) {
+		pvid = priv->vlan_id[priv->pvid[port]];
+		if (priv->vlan_tagged & (1 << port))
+			egress = AR8216_OUT_ADD_VLAN;
+		else
+			egress = AR8216_OUT_STRIP_VLAN;
+		ingress = AR8216_IN_SECURE;
+	} else {
+		pvid = port;
+		egress = AR8216_OUT_KEEP;
+		ingress = AR8216_IN_PORT_ONLY;
+	}
+
+	header = ath_hdr_en ? AR8216_PORT_CTRL_HEADER : 0;
+
+	ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port),
+		   AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE |
+		   AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE |
+		   AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK,
+		   AR8216_PORT_CTRL_LEARN | header |
+		   (egress << AR8216_PORT_CTRL_VLAN_MODE_S) |
+		   (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S));
+
+	ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port),
+		   AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE |
+		   AR8216_PORT_VLAN_DEFAULT_ID,
+		   (members << AR8216_PORT_VLAN_DEST_PORTS_S) |
+		   (ingress << AR8216_PORT_VLAN_MODE_S) |
+		   (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S));
+}
+
+static void
+ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members)
+{
+	return __ar8216_setup_port(priv, port, members,
+				   chip_is_ar8216(priv) && priv->vlan &&
+				   port == AR8216_PORT_CPU);
+}
+
+static int
+ar8216_hw_init(struct ar8xxx_priv *priv)
+{
+	if (priv->initialized)
+		return 0;
+
+	ar8xxx_write(priv, AR8216_REG_CTRL, AR8216_CTRL_RESET);
+	ar8xxx_reg_wait(priv, AR8216_REG_CTRL, AR8216_CTRL_RESET, 0, 1000);
+
+	ar8xxx_phy_init(priv);
+
+	priv->initialized = true;
+	return 0;
+}
+
+static void
+ar8216_init_globals(struct ar8xxx_priv *priv)
+{
+	/* standard atheros magic */
+	ar8xxx_write(priv, 0x38, 0xc000050e);
+
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL,
+		   AR8216_GCTRL_MTU, 1518 + 8 + 2);
+}
+
+static void
+__ar8216_init_port(struct ar8xxx_priv *priv, int port,
+		   bool cpu_ge, bool flow_en)
+{
+	/* Enable port learning and tx */
+	ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port),
+		AR8216_PORT_CTRL_LEARN |
+		(4 << AR8216_PORT_CTRL_STATE_S));
+
+	ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0);
+
+	if (port == AR8216_PORT_CPU) {
+		ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port),
+			AR8216_PORT_STATUS_LINK_UP |
+			(cpu_ge ? AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) |
+			AR8216_PORT_STATUS_TXMAC |
+			AR8216_PORT_STATUS_RXMAC |
+			(flow_en ? AR8216_PORT_STATUS_RXFLOW : 0) |
+			(flow_en ? AR8216_PORT_STATUS_TXFLOW : 0) |
+			AR8216_PORT_STATUS_DUPLEX);
+	} else {
+		ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port),
+			AR8216_PORT_STATUS_LINK_AUTO);
+	}
+}
+
+static void
+ar8216_init_port(struct ar8xxx_priv *priv, int port)
+{
+	__ar8216_init_port(priv, port, ar8xxx_has_gige(priv),
+			   chip_is_ar8316(priv));
+}
+
+static void
+ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1)
+{
+	int timeout = 20;
+
+	while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) {
+		udelay(10);
+		cond_resched();
+	}
+
+	if (!timeout)
+		pr_err("ar8216: timeout waiting for atu to become ready\n");
+}
+
+static void ar8216_get_arl_entry(struct ar8xxx_priv *priv,
+				 struct arl_entry *a, u32 *status, enum arl_op op)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 r2, page;
+	u16 r1_func0, r1_func1, r1_func2;
+	u32 t, val0, val1, val2;
+
+	split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page);
+	r2 |= 0x10;
+
+	r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e;
+	r1_func2 = (AR8216_REG_ATU_FUNC2 >> 1) & 0x1e;
+
+	switch (op) {
+	case AR8XXX_ARL_INITIALIZE:
+		/* all ATU registers are on the same page
+		* therefore set page only once
+		*/
+		bus->write(bus, 0x18, 0, page);
+		wait_for_page_switch();
+
+		ar8216_wait_atu_ready(priv, r2, r1_func0);
+
+		ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT);
+		ar8xxx_mii_write32(priv, r2, r1_func1, 0);
+		ar8xxx_mii_write32(priv, r2, r1_func2, 0);
+		break;
+	case AR8XXX_ARL_GET_NEXT:
+		t = ar8xxx_mii_read32(priv, r2, r1_func0);
+		t |= AR8216_ATU_ACTIVE;
+		ar8xxx_mii_write32(priv, r2, r1_func0, t);
+		ar8216_wait_atu_ready(priv, r2, r1_func0);
+
+		val0 = ar8xxx_mii_read32(priv, r2, r1_func0);
+		val1 = ar8xxx_mii_read32(priv, r2, r1_func1);
+		val2 = ar8xxx_mii_read32(priv, r2, r1_func2);
+
+		*status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S;
+		if (!*status)
+			break;
+
+		a->portmap = (val2 & AR8216_ATU_PORTS) >> AR8216_ATU_PORTS_S;
+		a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S;
+		a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S;
+		a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S;
+		a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S;
+		a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S;
+		a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S;
+		break;
+	}
+}
+
+static int
+ar8216_phy_read(struct ar8xxx_priv *priv, int addr, int regnum)
+{
+	u32 t, val = 0xffff;
+	int err;
+
+	if (addr >= AR8216_NUM_PORTS)
+		return 0xffff;
+	t = (regnum << AR8216_MDIO_CTRL_REG_ADDR_S) |
+	    (addr << AR8216_MDIO_CTRL_PHY_ADDR_S) |
+	    AR8216_MDIO_CTRL_MASTER_EN |
+	    AR8216_MDIO_CTRL_BUSY |
+	    AR8216_MDIO_CTRL_CMD_READ;
+
+	ar8xxx_write(priv, AR8216_REG_MDIO_CTRL, t);
+	err = ar8xxx_reg_wait(priv, AR8216_REG_MDIO_CTRL,
+			      AR8216_MDIO_CTRL_BUSY, 0, 5);
+	if (!err)
+		val = ar8xxx_read(priv, AR8216_REG_MDIO_CTRL);
+
+	return val & AR8216_MDIO_CTRL_DATA_M;
+}
+
+static int
+ar8216_phy_write(struct ar8xxx_priv *priv, int addr, int regnum, u16 val)
+{
+	u32 t;
+	int ret;
+
+	if (addr >= AR8216_NUM_PORTS)
+		return -EINVAL;
+
+	t = (addr << AR8216_MDIO_CTRL_PHY_ADDR_S) |
+	    (regnum << AR8216_MDIO_CTRL_REG_ADDR_S) |
+	    AR8216_MDIO_CTRL_MASTER_EN |
+	    AR8216_MDIO_CTRL_BUSY |
+	    AR8216_MDIO_CTRL_CMD_WRITE |
+	    val;
+
+	ar8xxx_write(priv, AR8216_REG_MDIO_CTRL, t);
+	ret = ar8xxx_reg_wait(priv, AR8216_REG_MDIO_CTRL,
+			      AR8216_MDIO_CTRL_BUSY, 0, 5);
+
+	return ret;
+}
+
+static int
+ar8229_hw_init(struct ar8xxx_priv *priv)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+	phy_interface_t phy_if_mode;
+#else
+	int phy_if_mode;
+#endif
+
+	if (priv->initialized)
+		return 0;
+
+	ar8xxx_write(priv, AR8216_REG_CTRL, AR8216_CTRL_RESET);
+	ar8xxx_reg_wait(priv, AR8216_REG_CTRL, AR8216_CTRL_RESET, 0, 1000);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+	of_get_phy_mode(priv->pdev->of_node, &phy_if_mode);
+#else
+	phy_if_mode = of_get_phy_mode(priv->pdev->of_node);
+#endif
+
+	if (phy_if_mode == PHY_INTERFACE_MODE_GMII) {
+		ar8xxx_write(priv, AR8229_REG_OPER_MODE0,
+				 AR8229_OPER_MODE0_MAC_GMII_EN);
+	} else if (phy_if_mode == PHY_INTERFACE_MODE_MII) {
+		ar8xxx_write(priv, AR8229_REG_OPER_MODE0,
+				 AR8229_OPER_MODE0_PHY_MII_EN);
+	} else {
+		pr_err("ar8229: unsupported mii mode\n");
+		return -EINVAL;
+	}
+
+	if (priv->port4_phy) {
+		ar8xxx_write(priv, AR8229_REG_OPER_MODE1,
+			     AR8229_REG_OPER_MODE1_PHY4_MII_EN);
+		/* disable port5 to prevent mii conflict */
+		ar8xxx_write(priv, AR8216_REG_PORT_STATUS(5), 0);
+	}
+
+	ar8xxx_phy_init(priv);
+
+	priv->initialized = true;
+	return 0;
+}
+
+static void
+ar8229_init_globals(struct ar8xxx_priv *priv)
+{
+
+	/* Enable CPU port, and disable mirror port */
+	ar8xxx_write(priv, AR8216_REG_GLOBAL_CPUPORT,
+		     AR8216_GLOBAL_CPUPORT_EN |
+		     (15 << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S));
+
+	/* Setup TAG priority mapping */
+	ar8xxx_write(priv, AR8216_REG_TAG_PRIORITY, 0xfa50);
+
+	/* Enable aging, MAC replacing */
+	ar8xxx_write(priv, AR8216_REG_ATU_CTRL,
+		     0x2b /* 5 min age time */ |
+		     AR8216_ATU_CTRL_AGE_EN |
+		     AR8216_ATU_CTRL_LEARN_CHANGE);
+
+	/* Enable ARP frame acknowledge */
+	ar8xxx_reg_set(priv, AR8229_REG_QM_CTRL,
+		       AR8229_QM_CTRL_ARP_EN);
+
+	/*
+	 * Enable Broadcast/unknown multicast and unicast frames
+	 * transmitted to the CPU port.
+	 */
+	ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK,
+		       AR8229_FLOOD_MASK_BC_DP(0) |
+		       AR8229_FLOOD_MASK_MC_DP(0) |
+		       AR8229_FLOOD_MASK_UC_DP(0));
+
+	/* setup MTU */
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL,
+		   AR8236_GCTRL_MTU, AR8236_GCTRL_MTU);
+
+	/* Enable MIB counters */
+	ar8xxx_reg_set(priv, AR8216_REG_MIB_FUNC,
+		       AR8236_MIB_EN);
+
+	/* setup Service TAG */
+	ar8xxx_rmw(priv, AR8216_REG_SERVICE_TAG, AR8216_SERVICE_TAG_M, 0);
+}
+
+static void
+ar8229_init_port(struct ar8xxx_priv *priv, int port)
+{
+	__ar8216_init_port(priv, port, true, true);
+}
+
+
+static int
+ar7240sw_hw_init(struct ar8xxx_priv *priv)
+{
+	if (priv->initialized)
+		return 0;
+
+	ar8xxx_write(priv, AR8216_REG_CTRL, AR8216_CTRL_RESET);
+	ar8xxx_reg_wait(priv, AR8216_REG_CTRL, AR8216_CTRL_RESET, 0, 1000);
+
+	priv->port4_phy = 1;
+	/* disable port5 to prevent mii conflict */
+	ar8xxx_write(priv, AR8216_REG_PORT_STATUS(5), 0);
+
+	ar8xxx_phy_init(priv);
+
+	priv->initialized = true;
+	return 0;
+}
+
+static void
+ar7240sw_init_globals(struct ar8xxx_priv *priv)
+{
+
+	/* Enable CPU port, and disable mirror port */
+	ar8xxx_write(priv, AR8216_REG_GLOBAL_CPUPORT,
+		     AR8216_GLOBAL_CPUPORT_EN |
+		     (15 << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S));
+
+	/* Setup TAG priority mapping */
+	ar8xxx_write(priv, AR8216_REG_TAG_PRIORITY, 0xfa50);
+
+	/* Enable ARP frame acknowledge, aging, MAC replacing */
+	ar8xxx_write(priv, AR8216_REG_ATU_CTRL,
+		AR8216_ATU_CTRL_RESERVED |
+		0x2b /* 5 min age time */ |
+		AR8216_ATU_CTRL_AGE_EN |
+		AR8216_ATU_CTRL_ARP_EN |
+		AR8216_ATU_CTRL_LEARN_CHANGE);
+
+	/* Enable Broadcast frames transmitted to the CPU */
+	ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK,
+		       AR8216_FM_CPU_BROADCAST_EN);
+
+	/* setup MTU */
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL,
+		   AR8216_GCTRL_MTU,
+		   AR8216_GCTRL_MTU);
+
+	/* setup Service TAG */
+	ar8xxx_rmw(priv, AR8216_REG_SERVICE_TAG, AR8216_SERVICE_TAG_M, 0);
+}
+
+static void
+ar7240sw_setup_port(struct ar8xxx_priv *priv, int port, u32 members)
+{
+	return __ar8216_setup_port(priv, port, members, false);
+}
+
+static void
+ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members)
+{
+	u32 egress, ingress;
+	u32 pvid;
+
+	if (priv->vlan) {
+		pvid = priv->vlan_id[priv->pvid[port]];
+		if (priv->vlan_tagged & (1 << port))
+			egress = AR8216_OUT_ADD_VLAN;
+		else
+			egress = AR8216_OUT_STRIP_VLAN;
+		ingress = AR8216_IN_SECURE;
+	} else {
+		pvid = port;
+		egress = AR8216_OUT_KEEP;
+		ingress = AR8216_IN_PORT_ONLY;
+	}
+
+	ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port),
+		   AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE |
+		   AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE |
+		   AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK,
+		   AR8216_PORT_CTRL_LEARN |
+		   (egress << AR8216_PORT_CTRL_VLAN_MODE_S) |
+		   (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S));
+
+	ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port),
+		   AR8236_PORT_VLAN_DEFAULT_ID,
+		   (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S));
+
+	ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port),
+		   AR8236_PORT_VLAN2_VLAN_MODE |
+		   AR8236_PORT_VLAN2_MEMBER,
+		   (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) |
+		   (members << AR8236_PORT_VLAN2_MEMBER_S));
+}
+
+static void
+ar8236_init_globals(struct ar8xxx_priv *priv)
+{
+	/* enable jumbo frames */
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL,
+		   AR8316_GCTRL_MTU, 9018 + 8 + 2);
+
+	/* enable cpu port to receive arp frames */
+	ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL,
+		   AR8236_ATU_CTRL_RES);
+
+	/*
+	 * Enable Broadcast/unknown multicast and unicast frames
+	 * transmitted to the CPU port.
+	 */
+	ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK,
+		       AR8229_FLOOD_MASK_BC_DP(0) |
+		       AR8229_FLOOD_MASK_MC_DP(0) |
+		       AR8229_FLOOD_MASK_UC_DP(0));
+
+	/* Enable MIB counters */
+	ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN,
+		   (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) |
+		   AR8236_MIB_EN);
+}
+
+static int
+ar8316_hw_init(struct ar8xxx_priv *priv)
+{
+	u32 val, newval;
+
+	val = ar8xxx_read(priv, AR8316_REG_POSTRIP);
+
+	if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) {
+		if (priv->port4_phy) {
+			/* value taken from Ubiquiti RouterStation Pro */
+			newval = 0x81461bea;
+			pr_info("ar8316: Using port 4 as PHY\n");
+		} else {
+			newval = 0x01261be2;
+			pr_info("ar8316: Using port 4 as switch port\n");
+		}
+	} else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) {
+		/* value taken from AVM Fritz!Box 7390 sources */
+		newval = 0x010e5b71;
+	} else {
+		/* no known value for phy interface */
+		pr_err("ar8316: unsupported mii mode: %d.\n",
+		       priv->phy->interface);
+		return -EINVAL;
+	}
+
+	if (val == newval)
+		goto out;
+
+	ar8xxx_write(priv, AR8316_REG_POSTRIP, newval);
+
+	if (priv->port4_phy &&
+	    priv->phy->interface == PHY_INTERFACE_MODE_RGMII) {
+		/* work around for phy4 rgmii mode */
+		ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c);
+		/* rx delay */
+		ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e);
+		/* tx delay */
+		ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47);
+		msleep(1000);
+	}
+
+	ar8xxx_phy_init(priv);
+
+out:
+	priv->initialized = true;
+	return 0;
+}
+
+static void
+ar8316_init_globals(struct ar8xxx_priv *priv)
+{
+	/* standard atheros magic */
+	ar8xxx_write(priv, 0x38, 0xc000050e);
+
+	/* enable cpu port to receive multicast and broadcast frames */
+	ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f);
+
+	/* enable jumbo frames */
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL,
+		   AR8316_GCTRL_MTU, 9018 + 8 + 2);
+
+	/* Enable MIB counters */
+	ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN,
+		   (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) |
+		   AR8236_MIB_EN);
+}
+
+int
+ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+		   struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	priv->vlan = !!val->value.i;
+	return 0;
+}
+
+int
+ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+		   struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->vlan;
+	return 0;
+}
+
+
+int
+ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	/* make sure no invalid PVIDs get set */
+
+	if (vlan < 0 || vlan >= dev->vlans ||
+	    port < 0 || port >= AR8X16_MAX_PORTS)
+		return -EINVAL;
+
+	priv->pvid[port] = vlan;
+	return 0;
+}
+
+int
+ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	if (port < 0 || port >= AR8X16_MAX_PORTS)
+		return -EINVAL;
+
+	*vlan = priv->pvid[port];
+	return 0;
+}
+
+static int
+ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
+		  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	if (val->port_vlan >= dev->vlans)
+		return -EINVAL;
+
+	priv->vlan_id[val->port_vlan] = val->value.i;
+	return 0;
+}
+
+static int
+ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
+		  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->vlan_id[val->port_vlan];
+	return 0;
+}
+
+int
+ar8xxx_sw_get_port_link(struct switch_dev *dev, int port,
+			struct switch_port_link *link)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	ar8216_read_port_link(priv, port, link);
+	return 0;
+}
+
+static int
+ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	u8 ports;
+	int i;
+
+	if (val->port_vlan >= dev->vlans)
+		return -EINVAL;
+
+	ports = priv->vlan_table[val->port_vlan];
+	val->len = 0;
+	for (i = 0; i < dev->ports; i++) {
+		struct switch_port *p;
+
+		if (!(ports & (1 << i)))
+			continue;
+
+		p = &val->value.ports[val->len++];
+		p->id = i;
+		if (priv->vlan_tagged & (1 << i))
+			p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+		else
+			p->flags = 0;
+	}
+	return 0;
+}
+
+static int
+ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	u8 *vt = &priv->vlan_table[val->port_vlan];
+	int i, j;
+
+	*vt = 0;
+	for (i = 0; i < val->len; i++) {
+		struct switch_port *p = &val->value.ports[i];
+
+		if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) {
+			priv->vlan_tagged |= (1 << p->id);
+		} else {
+			priv->vlan_tagged &= ~(1 << p->id);
+			priv->pvid[p->id] = val->port_vlan;
+
+			/* make sure that an untagged port does not
+			 * appear in other vlans */
+			for (j = 0; j < dev->vlans; j++) {
+				if (j == val->port_vlan)
+					continue;
+				priv->vlan_table[j] &= ~(1 << p->id);
+			}
+		}
+
+		*vt |= 1 << p->id;
+	}
+	return 0;
+}
+
+static void
+ar8216_set_mirror_regs(struct ar8xxx_priv *priv)
+{
+	int port;
+
+	/* reset all mirror registers */
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT,
+		   AR8216_GLOBAL_CPUPORT_MIRROR_PORT,
+		   (0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S));
+	for (port = 0; port < AR8216_NUM_PORTS; port++) {
+		ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port),
+			   AR8216_PORT_CTRL_MIRROR_RX);
+
+		ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port),
+			   AR8216_PORT_CTRL_MIRROR_TX);
+	}
+
+	/* now enable mirroring if necessary */
+	if (priv->source_port >= AR8216_NUM_PORTS ||
+	    priv->monitor_port >= AR8216_NUM_PORTS ||
+	    priv->source_port == priv->monitor_port) {
+		return;
+	}
+
+	ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT,
+		   AR8216_GLOBAL_CPUPORT_MIRROR_PORT,
+		   (priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S));
+
+	if (priv->mirror_rx)
+		ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port),
+			   AR8216_PORT_CTRL_MIRROR_RX);
+
+	if (priv->mirror_tx)
+		ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port),
+			   AR8216_PORT_CTRL_MIRROR_TX);
+}
+
+static inline u32
+ar8xxx_age_time_val(int age_time)
+{
+	return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) /
+	       AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS;
+}
+
+static inline void
+ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg)
+{
+	u32 age_time = ar8xxx_age_time_val(priv->arl_age_time);
+	ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S);
+}
+
+int
+ar8xxx_sw_hw_apply(struct switch_dev *dev)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	const struct ar8xxx_chip *chip = priv->chip;
+	u8 portmask[AR8X16_MAX_PORTS];
+	int i, j;
+
+	mutex_lock(&priv->reg_mutex);
+	/* flush all vlan translation unit entries */
+	priv->chip->vtu_flush(priv);
+
+	memset(portmask, 0, sizeof(portmask));
+	if (!priv->init) {
+		/* calculate the port destination masks and load vlans
+		 * into the vlan translation unit */
+		for (j = 0; j < dev->vlans; j++) {
+			u8 vp = priv->vlan_table[j];
+
+			if (!vp)
+				continue;
+
+			for (i = 0; i < dev->ports; i++) {
+				u8 mask = (1 << i);
+				if (vp & mask)
+					portmask[i] |= vp & ~mask;
+			}
+
+			chip->vtu_load_vlan(priv, priv->vlan_id[j],
+					    priv->vlan_table[j]);
+		}
+	} else {
+		/* vlan disabled:
+		 * isolate all ports, but connect them to the cpu port */
+		for (i = 0; i < dev->ports; i++) {
+			if (i == AR8216_PORT_CPU)
+				continue;
+
+			portmask[i] = 1 << AR8216_PORT_CPU;
+			portmask[AR8216_PORT_CPU] |= (1 << i);
+		}
+	}
+
+	/* update the port destination mask registers and tag settings */
+	for (i = 0; i < dev->ports; i++) {
+		chip->setup_port(priv, i, portmask[i]);
+	}
+
+	chip->set_mirror_regs(priv);
+
+	/* set age time */
+	if (chip->reg_arl_ctrl)
+		ar8xxx_set_age_time(priv, chip->reg_arl_ctrl);
+
+	mutex_unlock(&priv->reg_mutex);
+	return 0;
+}
+
+int
+ar8xxx_sw_reset_switch(struct switch_dev *dev)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	const struct ar8xxx_chip *chip = priv->chip;
+	int i;
+
+	mutex_lock(&priv->reg_mutex);
+	memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) -
+		offsetof(struct ar8xxx_priv, vlan));
+
+	for (i = 0; i < dev->vlans; i++)
+		priv->vlan_id[i] = i;
+
+	/* Configure all ports */
+	for (i = 0; i < dev->ports; i++)
+		chip->init_port(priv, i);
+
+	priv->mirror_rx = false;
+	priv->mirror_tx = false;
+	priv->source_port = 0;
+	priv->monitor_port = 0;
+	priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME;
+
+	chip->init_globals(priv);
+	chip->atu_flush(priv);
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return chip->sw_hw_apply(dev);
+}
+
+int
+ar8xxx_sw_set_reset_mibs(struct switch_dev *dev,
+			 const struct switch_attr *attr,
+			 struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	unsigned int len;
+	int ret;
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return -EOPNOTSUPP;
+
+	mutex_lock(&priv->mib_lock);
+
+	len = priv->dev.ports * priv->chip->num_mibs *
+	      sizeof(*priv->mib_stats);
+	memset(priv->mib_stats, '\0', len);
+	ret = ar8xxx_mib_flush(priv);
+	if (ret)
+		goto unlock;
+
+	ret = 0;
+
+unlock:
+	mutex_unlock(&priv->mib_lock);
+	return ret;
+}
+
+int
+ar8xxx_sw_set_mib_poll_interval(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return -EOPNOTSUPP;
+
+	ar8xxx_mib_stop(priv);
+	priv->mib_poll_interval = val->value.i;
+	ar8xxx_mib_start(priv);
+
+	return 0;
+}
+
+int
+ar8xxx_sw_get_mib_poll_interval(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return -EOPNOTSUPP;
+	val->value.i = priv->mib_poll_interval;
+	return 0;
+}
+
+int
+ar8xxx_sw_set_mib_type(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return -EOPNOTSUPP;
+	priv->mib_type = val->value.i;
+	return 0;
+}
+
+int
+ar8xxx_sw_get_mib_type(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return -EOPNOTSUPP;
+	val->value.i = priv->mib_type;
+	return 0;
+}
+
+int
+ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	priv->mirror_rx = !!val->value.i;
+	priv->chip->set_mirror_regs(priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->mirror_rx;
+	return 0;
+}
+
+int
+ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	priv->mirror_tx = !!val->value.i;
+	priv->chip->set_mirror_regs(priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->mirror_tx;
+	return 0;
+}
+
+int
+ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	priv->monitor_port = val->value.i;
+	priv->chip->set_mirror_regs(priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->monitor_port;
+	return 0;
+}
+
+int
+ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	priv->source_port = val->value.i;
+	priv->chip->set_mirror_regs(priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->source_port;
+	return 0;
+}
+
+int
+ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev,
+			     const struct switch_attr *attr,
+			     struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port;
+	int ret;
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return -EOPNOTSUPP;
+
+	port = val->port_vlan;
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->mib_lock);
+	ret = ar8xxx_mib_capture(priv);
+	if (ret)
+		goto unlock;
+
+	ar8xxx_mib_fetch_port_stat(priv, port, true);
+
+	ret = 0;
+
+unlock:
+	mutex_unlock(&priv->mib_lock);
+	return ret;
+}
+
+static void
+ar8xxx_byte_to_str(char *buf, int len, u64 byte)
+{
+	unsigned long b;
+	const char *unit;
+
+	if (byte >= 0x40000000) { /* 1 GiB */
+		b = byte * 10 / 0x40000000;
+		unit = "GiB";
+	} else if (byte >= 0x100000) { /* 1 MiB */
+		b = byte * 10 / 0x100000;
+		unit = "MiB";
+	} else if (byte >= 0x400) { /* 1 KiB */
+		b = byte * 10 / 0x400;
+		unit = "KiB";
+	} else {
+		b = byte;
+		unit = "Byte";
+	}
+	if (strcmp(unit, "Byte"))
+		snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit);
+	else
+		snprintf(buf, len, "%lu %s", b, unit);
+}
+
+int
+ar8xxx_sw_get_port_mib(struct switch_dev *dev,
+		       const struct switch_attr *attr,
+		       struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	const struct ar8xxx_chip *chip = priv->chip;
+	u64 *mib_stats, mib_data;
+	unsigned int port;
+	int ret;
+	char *buf = priv->buf;
+	char buf1[64];
+	const char *mib_name;
+	int i, len = 0;
+	bool mib_stats_empty = true;
+
+	if (!ar8xxx_has_mib_counters(priv) || !priv->mib_poll_interval)
+		return -EOPNOTSUPP;
+
+	port = val->port_vlan;
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->mib_lock);
+	ret = ar8xxx_mib_capture(priv);
+	if (ret)
+		goto unlock;
+
+	ar8xxx_mib_fetch_port_stat(priv, port, false);
+
+	len += snprintf(buf + len, sizeof(priv->buf) - len,
+			"MIB counters\n");
+
+	mib_stats = &priv->mib_stats[port * chip->num_mibs];
+	for (i = 0; i < chip->num_mibs; i++) {
+		if (chip->mib_decs[i].type > priv->mib_type)
+			continue;
+		mib_name = chip->mib_decs[i].name;
+		mib_data = mib_stats[i];
+		len += snprintf(buf + len, sizeof(priv->buf) - len,
+				"%-12s: %llu\n", mib_name, mib_data);
+		if ((!strcmp(mib_name, "TxByte") ||
+		    !strcmp(mib_name, "RxGoodByte")) &&
+		    mib_data >= 1024) {
+			ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data);
+			--len; /* discard newline at the end of buf */
+			len += snprintf(buf + len, sizeof(priv->buf) - len,
+					" (%s)\n", buf1);
+		}
+		if (mib_stats_empty && mib_data)
+			mib_stats_empty = false;
+	}
+
+	if (mib_stats_empty)
+		len = snprintf(buf, sizeof(priv->buf), "No MIB data");
+
+	val->value.s = buf;
+	val->len = len;
+
+	ret = 0;
+
+unlock:
+	mutex_unlock(&priv->mib_lock);
+	return ret;
+}
+
+int
+ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr,
+			   struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int age_time = val->value.i;
+	u32 age_time_val;
+
+	if (age_time < 0)
+		return -EINVAL;
+
+	age_time_val = ar8xxx_age_time_val(age_time);
+	if (age_time_val == 0 || age_time_val > 0xffff)
+		return -EINVAL;
+
+	priv->arl_age_time = age_time;
+	return 0;
+}
+
+int
+ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr,
+                   struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	val->value.i = priv->arl_age_time;
+	return 0;
+}
+
+int
+ar8xxx_sw_get_arl_table(struct switch_dev *dev,
+			const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	struct mii_bus *bus = priv->mii_bus;
+	const struct ar8xxx_chip *chip = priv->chip;
+	char *buf = priv->arl_buf;
+	int i, j, k, len = 0;
+	struct arl_entry *a, *a1;
+	u32 status;
+
+	if (!chip->get_arl_entry)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&priv->reg_mutex);
+	mutex_lock(&bus->mdio_lock);
+
+	chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE);
+
+	for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) {
+		a = &priv->arl_table[i];
+		duplicate:
+		chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT);
+
+		if (!status)
+			break;
+
+		/* avoid duplicates
+		 * ARL table can include multiple valid entries
+		 * per MAC, just with differing status codes
+		 */
+		for (j = 0; j < i; ++j) {
+			a1 = &priv->arl_table[j];
+			if (!memcmp(a->mac, a1->mac, sizeof(a->mac))) {
+				/* ignore ports already seen in former entry */
+				a->portmap &= ~a1->portmap;
+				if (!a->portmap)
+					goto duplicate;
+			}
+		}
+	}
+
+	mutex_unlock(&bus->mdio_lock);
+
+	len += snprintf(buf + len, sizeof(priv->arl_buf) - len,
+                        "address resolution table\n");
+
+	if (i == AR8XXX_NUM_ARL_RECORDS)
+		len += snprintf(buf + len, sizeof(priv->arl_buf) - len,
+				"Too many entries found, displaying the first %d only!\n",
+				AR8XXX_NUM_ARL_RECORDS);
+
+	for (j = 0; j < priv->dev.ports; ++j) {
+		for (k = 0; k < i; ++k) {
+			a = &priv->arl_table[k];
+			if (!(a->portmap & BIT(j)))
+				continue;
+			len += snprintf(buf + len, sizeof(priv->arl_buf) - len,
+					"Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
+					j,
+					a->mac[5], a->mac[4], a->mac[3],
+					a->mac[2], a->mac[1], a->mac[0]);
+		}
+	}
+
+	val->value.s = buf;
+	val->len = len;
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev,
+			      const struct switch_attr *attr,
+			      struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int ret;
+
+	mutex_lock(&priv->reg_mutex);
+	ret = priv->chip->atu_flush(priv);
+	mutex_unlock(&priv->reg_mutex);
+
+	return ret;
+}
+
+int
+ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev,
+				   const struct switch_attr *attr,
+				   struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port, ret;
+
+	port = val->port_vlan;
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	ret = priv->chip->atu_flush_port(priv, port);
+	mutex_unlock(&priv->reg_mutex);
+
+	return ret;
+}
+
+int
+ar8xxx_sw_get_port_stats(struct switch_dev *dev, int port,
+			struct switch_port_stats *stats)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	u64 *mib_stats;
+
+	if (!ar8xxx_has_mib_counters(priv) || !priv->mib_poll_interval)
+		return -EOPNOTSUPP;
+
+	if (!(priv->chip->mib_rxb_id || priv->chip->mib_txb_id))
+		return -EOPNOTSUPP;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->mib_lock);
+
+	mib_stats = &priv->mib_stats[port * priv->chip->num_mibs];
+
+	stats->tx_bytes = mib_stats[priv->chip->mib_txb_id];
+	stats->rx_bytes = mib_stats[priv->chip->mib_rxb_id];
+
+	mutex_unlock(&priv->mib_lock);
+	return 0;
+}
+
+static int
+ar8xxx_phy_read(struct mii_bus *bus, int phy_addr, int reg_addr)
+{
+	struct ar8xxx_priv *priv = bus->priv;
+	return priv->chip->phy_read(priv, phy_addr, reg_addr);
+}
+
+static int
+ar8xxx_phy_write(struct mii_bus *bus, int phy_addr, int reg_addr,
+		 u16 reg_val)
+{
+	struct ar8xxx_priv *priv = bus->priv;
+	return priv->chip->phy_write(priv, phy_addr, reg_addr, reg_val);
+}
+
+static const struct switch_attr ar8xxx_sw_attr_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = ar8xxx_sw_set_vlan,
+		.get = ar8xxx_sw_get_vlan,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mibs",
+		.description = "Reset all MIB counters",
+		.set = ar8xxx_sw_set_reset_mibs,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "ar8xxx_mib_poll_interval",
+		.description = "MIB polling interval in msecs (0 to disable)",
+		.set = ar8xxx_sw_set_mib_poll_interval,
+		.get = ar8xxx_sw_get_mib_poll_interval
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "ar8xxx_mib_type",
+		.description = "MIB type (0=basic 1=extended)",
+		.set = ar8xxx_sw_set_mib_type,
+		.get = ar8xxx_sw_get_mib_type
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_rx",
+		.description = "Enable mirroring of RX packets",
+		.set = ar8xxx_sw_set_mirror_rx_enable,
+		.get = ar8xxx_sw_get_mirror_rx_enable,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_tx",
+		.description = "Enable mirroring of TX packets",
+		.set = ar8xxx_sw_set_mirror_tx_enable,
+		.get = ar8xxx_sw_get_mirror_tx_enable,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_monitor_port",
+		.description = "Mirror monitor port",
+		.set = ar8xxx_sw_set_mirror_monitor_port,
+		.get = ar8xxx_sw_get_mirror_monitor_port,
+		.max = AR8216_NUM_PORTS - 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_source_port",
+		.description = "Mirror source port",
+		.set = ar8xxx_sw_set_mirror_source_port,
+		.get = ar8xxx_sw_get_mirror_source_port,
+		.max = AR8216_NUM_PORTS - 1
+ 	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "arl_table",
+		.description = "Get ARL table",
+		.set = NULL,
+		.get = ar8xxx_sw_get_arl_table,
+	},
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "flush_arl_table",
+		.description = "Flush ARL table",
+		.set = ar8xxx_sw_set_flush_arl_table,
+	},
+};
+
+const struct switch_attr ar8xxx_sw_attr_port[] = {
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mib",
+		.description = "Reset single port MIB counters",
+		.set = ar8xxx_sw_set_port_reset_mib,
+	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get port's MIB counters",
+		.set = NULL,
+		.get = ar8xxx_sw_get_port_mib,
+	},
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "flush_arl_table",
+		.description = "Flush port's ARL table entries",
+		.set = ar8xxx_sw_set_flush_port_arl_table,
+	},
+};
+
+const struct switch_attr ar8xxx_sw_attr_vlan[1] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "vid",
+		.description = "VLAN ID (0-4094)",
+		.set = ar8xxx_sw_set_vid,
+		.get = ar8xxx_sw_get_vid,
+		.max = 4094,
+	},
+};
+
+static const struct switch_dev_ops ar8xxx_sw_ops = {
+	.attr_global = {
+		.attr = ar8xxx_sw_attr_globals,
+		.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals),
+	},
+	.attr_port = {
+		.attr = ar8xxx_sw_attr_port,
+		.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port),
+	},
+	.attr_vlan = {
+		.attr = ar8xxx_sw_attr_vlan,
+		.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan),
+	},
+	.get_port_pvid = ar8xxx_sw_get_pvid,
+	.set_port_pvid = ar8xxx_sw_set_pvid,
+	.get_vlan_ports = ar8xxx_sw_get_ports,
+	.set_vlan_ports = ar8xxx_sw_set_ports,
+	.apply_config = ar8xxx_sw_hw_apply,
+	.reset_switch = ar8xxx_sw_reset_switch,
+	.get_port_link = ar8xxx_sw_get_port_link,
+	.get_port_stats = ar8xxx_sw_get_port_stats,
+};
+
+static const struct ar8xxx_chip ar7240sw_chip = {
+	.caps = AR8XXX_CAP_MIB_COUNTERS,
+
+	.reg_port_stats_start = 0x20000,
+	.reg_port_stats_length = 0x100,
+	.reg_arl_ctrl = AR8216_REG_ATU_CTRL,
+
+	.name = "Atheros AR724X/AR933X built-in",
+	.ports = AR7240SW_NUM_PORTS,
+	.vlans = AR8216_NUM_VLANS,
+	.swops = &ar8xxx_sw_ops,
+
+	.hw_init = ar7240sw_hw_init,
+	.init_globals = ar7240sw_init_globals,
+	.init_port = ar8229_init_port,
+	.phy_read = ar8216_phy_read,
+	.phy_write = ar8216_phy_write,
+	.setup_port = ar7240sw_setup_port,
+	.read_port_status = ar8216_read_port_status,
+	.atu_flush = ar8216_atu_flush,
+	.atu_flush_port = ar8216_atu_flush_port,
+	.vtu_flush = ar8216_vtu_flush,
+	.vtu_load_vlan = ar8216_vtu_load_vlan,
+	.set_mirror_regs = ar8216_set_mirror_regs,
+	.get_arl_entry = ar8216_get_arl_entry,
+	.sw_hw_apply = ar8xxx_sw_hw_apply,
+
+	.num_mibs = ARRAY_SIZE(ar8236_mibs),
+	.mib_decs = ar8236_mibs,
+	.mib_func = AR8216_REG_MIB_FUNC,
+	.mib_rxb_id = AR8236_MIB_RXB_ID,
+	.mib_txb_id = AR8236_MIB_TXB_ID,
+};
+
+static const struct ar8xxx_chip ar8216_chip = {
+	.caps = AR8XXX_CAP_MIB_COUNTERS,
+
+	.reg_port_stats_start = 0x19000,
+	.reg_port_stats_length = 0xa0,
+	.reg_arl_ctrl = AR8216_REG_ATU_CTRL,
+
+	.name = "Atheros AR8216",
+	.ports = AR8216_NUM_PORTS,
+	.vlans = AR8216_NUM_VLANS,
+	.swops = &ar8xxx_sw_ops,
+
+	.hw_init = ar8216_hw_init,
+	.init_globals = ar8216_init_globals,
+	.init_port = ar8216_init_port,
+	.setup_port = ar8216_setup_port,
+	.read_port_status = ar8216_read_port_status,
+	.atu_flush = ar8216_atu_flush,
+	.atu_flush_port = ar8216_atu_flush_port,
+	.vtu_flush = ar8216_vtu_flush,
+	.vtu_load_vlan = ar8216_vtu_load_vlan,
+	.set_mirror_regs = ar8216_set_mirror_regs,
+	.get_arl_entry = ar8216_get_arl_entry,
+	.sw_hw_apply = ar8xxx_sw_hw_apply,
+
+	.num_mibs = ARRAY_SIZE(ar8216_mibs),
+	.mib_decs = ar8216_mibs,
+	.mib_func = AR8216_REG_MIB_FUNC,
+	.mib_rxb_id = AR8216_MIB_RXB_ID,
+	.mib_txb_id = AR8216_MIB_TXB_ID,
+};
+
+static const struct ar8xxx_chip ar8229_chip = {
+	.caps = AR8XXX_CAP_MIB_COUNTERS,
+
+	.reg_port_stats_start = 0x20000,
+	.reg_port_stats_length = 0x100,
+	.reg_arl_ctrl = AR8216_REG_ATU_CTRL,
+
+	.name = "Atheros AR8229",
+	.ports = AR8216_NUM_PORTS,
+	.vlans = AR8216_NUM_VLANS,
+	.swops = &ar8xxx_sw_ops,
+
+	.hw_init = ar8229_hw_init,
+	.init_globals = ar8229_init_globals,
+	.init_port = ar8229_init_port,
+	.phy_read = ar8216_phy_read,
+	.phy_write = ar8216_phy_write,
+	.setup_port = ar8236_setup_port,
+	.read_port_status = ar8216_read_port_status,
+	.atu_flush = ar8216_atu_flush,
+	.atu_flush_port = ar8216_atu_flush_port,
+	.vtu_flush = ar8216_vtu_flush,
+	.vtu_load_vlan = ar8216_vtu_load_vlan,
+	.set_mirror_regs = ar8216_set_mirror_regs,
+	.get_arl_entry = ar8216_get_arl_entry,
+	.sw_hw_apply = ar8xxx_sw_hw_apply,
+
+	.num_mibs = ARRAY_SIZE(ar8236_mibs),
+	.mib_decs = ar8236_mibs,
+	.mib_func = AR8216_REG_MIB_FUNC,
+	.mib_rxb_id = AR8236_MIB_RXB_ID,
+	.mib_txb_id = AR8236_MIB_TXB_ID,
+};
+
+static const struct ar8xxx_chip ar8236_chip = {
+	.caps = AR8XXX_CAP_MIB_COUNTERS,
+
+	.reg_port_stats_start = 0x20000,
+	.reg_port_stats_length = 0x100,
+	.reg_arl_ctrl = AR8216_REG_ATU_CTRL,
+
+	.name = "Atheros AR8236",
+	.ports = AR8216_NUM_PORTS,
+	.vlans = AR8216_NUM_VLANS,
+	.swops = &ar8xxx_sw_ops,
+
+	.hw_init = ar8216_hw_init,
+	.init_globals = ar8236_init_globals,
+	.init_port = ar8216_init_port,
+	.setup_port = ar8236_setup_port,
+	.read_port_status = ar8216_read_port_status,
+	.atu_flush = ar8216_atu_flush,
+	.atu_flush_port = ar8216_atu_flush_port,
+	.vtu_flush = ar8216_vtu_flush,
+	.vtu_load_vlan = ar8216_vtu_load_vlan,
+	.set_mirror_regs = ar8216_set_mirror_regs,
+	.get_arl_entry = ar8216_get_arl_entry,
+	.sw_hw_apply = ar8xxx_sw_hw_apply,
+
+	.num_mibs = ARRAY_SIZE(ar8236_mibs),
+	.mib_decs = ar8236_mibs,
+	.mib_func = AR8216_REG_MIB_FUNC,
+	.mib_rxb_id = AR8236_MIB_RXB_ID,
+	.mib_txb_id = AR8236_MIB_TXB_ID,
+};
+
+static const struct ar8xxx_chip ar8316_chip = {
+	.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS,
+
+	.reg_port_stats_start = 0x20000,
+	.reg_port_stats_length = 0x100,
+	.reg_arl_ctrl = AR8216_REG_ATU_CTRL,
+
+	.name = "Atheros AR8316",
+	.ports = AR8216_NUM_PORTS,
+	.vlans = AR8X16_MAX_VLANS,
+	.swops = &ar8xxx_sw_ops,
+
+	.hw_init = ar8316_hw_init,
+	.init_globals = ar8316_init_globals,
+	.init_port = ar8216_init_port,
+	.setup_port = ar8216_setup_port,
+	.read_port_status = ar8216_read_port_status,
+	.atu_flush = ar8216_atu_flush,
+	.atu_flush_port = ar8216_atu_flush_port,
+	.vtu_flush = ar8216_vtu_flush,
+	.vtu_load_vlan = ar8216_vtu_load_vlan,
+	.set_mirror_regs = ar8216_set_mirror_regs,
+	.get_arl_entry = ar8216_get_arl_entry,
+	.sw_hw_apply = ar8xxx_sw_hw_apply,
+
+	.num_mibs = ARRAY_SIZE(ar8236_mibs),
+	.mib_decs = ar8236_mibs,
+	.mib_func = AR8216_REG_MIB_FUNC,
+	.mib_rxb_id = AR8236_MIB_RXB_ID,
+	.mib_txb_id = AR8236_MIB_TXB_ID,
+};
+
+static int
+ar8xxx_read_id(struct ar8xxx_priv *priv)
+{
+	u32 val;
+	u16 id;
+	int i;
+
+	val = ar8xxx_read(priv, AR8216_REG_CTRL);
+	if (val == ~0)
+		return -ENODEV;
+
+	id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION);
+	for (i = 0; i < AR8X16_PROBE_RETRIES; i++) {
+		u16 t;
+
+		val = ar8xxx_read(priv, AR8216_REG_CTRL);
+		if (val == ~0)
+			return -ENODEV;
+
+		t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION);
+		if (t != id)
+			return -ENODEV;
+	}
+
+	priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S;
+	priv->chip_rev = (id & AR8216_CTRL_REVISION);
+	return 0;
+}
+
+static int
+ar8xxx_id_chip(struct ar8xxx_priv *priv)
+{
+	int ret;
+
+	ret = ar8xxx_read_id(priv);
+	if(ret)
+		return ret;
+
+	switch (priv->chip_ver) {
+	case AR8XXX_VER_AR8216:
+		priv->chip = &ar8216_chip;
+		break;
+	case AR8XXX_VER_AR8236:
+		priv->chip = &ar8236_chip;
+		break;
+	case AR8XXX_VER_AR8316:
+		priv->chip = &ar8316_chip;
+		break;
+	case AR8XXX_VER_AR8327:
+		priv->chip = &ar8327_chip;
+		break;
+	case AR8XXX_VER_AR8337:
+		priv->chip = &ar8337_chip;
+		break;
+	default:
+		pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n",
+		       priv->chip_ver, priv->chip_rev);
+
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void
+ar8xxx_mib_work_func(struct work_struct *work)
+{
+	struct ar8xxx_priv *priv;
+	int err, i;
+
+	priv = container_of(work, struct ar8xxx_priv, mib_work.work);
+
+	mutex_lock(&priv->mib_lock);
+
+	err = ar8xxx_mib_capture(priv);
+	if (err)
+		goto next_attempt;
+
+	for (i = 0; i < priv->dev.ports; i++)
+		ar8xxx_mib_fetch_port_stat(priv, i, false);
+
+next_attempt:
+	mutex_unlock(&priv->mib_lock);
+	schedule_delayed_work(&priv->mib_work,
+			      msecs_to_jiffies(priv->mib_poll_interval));
+}
+
+static int
+ar8xxx_mib_init(struct ar8xxx_priv *priv)
+{
+	unsigned int len;
+
+	if (!ar8xxx_has_mib_counters(priv))
+		return 0;
+
+	BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs);
+
+	len = priv->dev.ports * priv->chip->num_mibs *
+	      sizeof(*priv->mib_stats);
+	priv->mib_stats = kzalloc(len, GFP_KERNEL);
+
+	if (!priv->mib_stats)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void
+ar8xxx_mib_start(struct ar8xxx_priv *priv)
+{
+	if (!ar8xxx_has_mib_counters(priv) || !priv->mib_poll_interval)
+		return;
+
+	schedule_delayed_work(&priv->mib_work,
+			      msecs_to_jiffies(priv->mib_poll_interval));
+}
+
+static void
+ar8xxx_mib_stop(struct ar8xxx_priv *priv)
+{
+	if (!ar8xxx_has_mib_counters(priv) || !priv->mib_poll_interval)
+		return;
+
+	cancel_delayed_work_sync(&priv->mib_work);
+}
+
+static struct ar8xxx_priv *
+ar8xxx_create(void)
+{
+	struct ar8xxx_priv *priv;
+
+	priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL);
+	if (priv == NULL)
+		return NULL;
+
+	mutex_init(&priv->reg_mutex);
+	mutex_init(&priv->mib_lock);
+	INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func);
+
+	return priv;
+}
+
+static void
+ar8xxx_free(struct ar8xxx_priv *priv)
+{
+	if (priv->chip && priv->chip->cleanup)
+		priv->chip->cleanup(priv);
+
+	kfree(priv->chip_data);
+	kfree(priv->mib_stats);
+	kfree(priv);
+}
+
+static int
+ar8xxx_probe_switch(struct ar8xxx_priv *priv)
+{
+	const struct ar8xxx_chip *chip;
+	struct switch_dev *swdev;
+	int ret;
+
+	chip = priv->chip;
+
+	swdev = &priv->dev;
+	swdev->cpu_port = AR8216_PORT_CPU;
+	swdev->name = chip->name;
+	swdev->vlans = chip->vlans;
+	swdev->ports = chip->ports;
+	swdev->ops = chip->swops;
+
+	ret = ar8xxx_mib_init(priv);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int
+ar8xxx_start(struct ar8xxx_priv *priv)
+{
+	int ret;
+
+	priv->init = true;
+
+	ret = priv->chip->hw_init(priv);
+	if (ret)
+		return ret;
+
+	ret = ar8xxx_sw_reset_switch(&priv->dev);
+	if (ret)
+		return ret;
+
+	priv->init = false;
+
+	ar8xxx_mib_start(priv);
+
+	return 0;
+}
+
+static int
+ar8xxx_phy_config_init(struct phy_device *phydev)
+{
+	struct ar8xxx_priv *priv = phydev->priv;
+	struct net_device *dev = phydev->attached_dev;
+	int ret;
+
+	if (WARN_ON(!priv))
+		return -ENODEV;
+
+	if (priv->chip->config_at_probe)
+		return ar8xxx_phy_check_aneg(phydev);
+
+	priv->phy = phydev;
+
+	if (phydev->mdio.addr != 0) {
+		if (chip_is_ar8316(priv)) {
+			/* switch device has been initialized, reinit */
+			priv->dev.ports = (AR8216_NUM_PORTS - 1);
+			priv->initialized = false;
+			priv->port4_phy = true;
+			ar8316_hw_init(priv);
+			return 0;
+		}
+
+		return 0;
+	}
+
+	ret = ar8xxx_start(priv);
+	if (ret)
+		return ret;
+
+#ifdef CONFIG_ETHERNET_PACKET_MANGLE
+	/* VID fixup only needed on ar8216 */
+	if (chip_is_ar8216(priv)) {
+		dev->phy_ptr = priv;
+		dev->priv_flags |= IFF_NO_IP_ALIGN;
+		dev->eth_mangle_rx = ar8216_mangle_rx;
+		dev->eth_mangle_tx = ar8216_mangle_tx;
+	}
+#endif
+
+	return 0;
+}
+
+static bool
+ar8xxx_check_link_states(struct ar8xxx_priv *priv)
+{
+	bool link_new, changed = false;
+	u32 status;
+	int i;
+
+	mutex_lock(&priv->reg_mutex);
+
+	for (i = 0; i < priv->dev.ports; i++) {
+		status = priv->chip->read_port_status(priv, i);
+		link_new = !!(status & AR8216_PORT_STATUS_LINK_UP);
+		if (link_new == priv->link_up[i])
+			continue;
+
+		priv->link_up[i] = link_new;
+		changed = true;
+		/* flush ARL entries for this port if it went down*/
+		if (!link_new)
+			priv->chip->atu_flush_port(priv, i);
+		dev_info(&priv->phy->mdio.dev, "Port %d is %s\n",
+			 i, link_new ? "up" : "down");
+	}
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return changed;
+}
+
+static int
+ar8xxx_phy_read_status(struct phy_device *phydev)
+{
+	struct ar8xxx_priv *priv = phydev->priv;
+	struct switch_port_link link;
+
+	/* check for switch port link changes */
+	ar8xxx_check_link_states(priv);
+
+	if (phydev->mdio.addr != 0)
+		return genphy_read_status(phydev);
+
+	ar8216_read_port_link(priv, phydev->mdio.addr, &link);
+	phydev->link = !!link.link;
+	if (!phydev->link)
+		return 0;
+
+	switch (link.speed) {
+	case SWITCH_PORT_SPEED_10:
+		phydev->speed = SPEED_10;
+		break;
+	case SWITCH_PORT_SPEED_100:
+		phydev->speed = SPEED_100;
+		break;
+	case SWITCH_PORT_SPEED_1000:
+		phydev->speed = SPEED_1000;
+		break;
+	default:
+		phydev->speed = 0;
+	}
+	phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF;
+
+	phydev->state = PHY_RUNNING;
+	netif_carrier_on(phydev->attached_dev);
+	if (phydev->adjust_link)
+		phydev->adjust_link(phydev->attached_dev);
+
+	return 0;
+}
+
+static int
+ar8xxx_phy_config_aneg(struct phy_device *phydev)
+{
+	if (phydev->mdio.addr == 0)
+		return 0;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int
+ar8xxx_get_features(struct phy_device *phydev)
+{
+	struct ar8xxx_priv *priv = phydev->priv;
+
+	linkmode_copy(phydev->supported, PHY_BASIC_FEATURES);
+	if (ar8xxx_has_gige(priv))
+		linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported);
+
+	return 0;
+}
+
+static const u32 ar8xxx_phy_ids[] = {
+	0x004dd033,
+	0x004dd034, /* AR8327 */
+	0x004dd036, /* AR8337 */
+	0x004dd041,
+	0x004dd042,
+	0x004dd043, /* AR8236 */
+};
+
+static bool
+ar8xxx_phy_match(u32 phy_id)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++)
+		if (phy_id == ar8xxx_phy_ids[i])
+			return true;
+
+	return false;
+}
+
+static bool
+ar8xxx_is_possible(struct mii_bus *bus)
+{
+	unsigned int i, found_phys = 0;
+
+	for (i = 0; i < 5; i++) {
+		u32 phy_id;
+
+		phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16;
+		phy_id |= mdiobus_read(bus, i, MII_PHYSID2);
+		if (ar8xxx_phy_match(phy_id)) {
+			found_phys++;
+		} else if (phy_id) {
+			pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n",
+				 dev_name(&bus->dev), i, phy_id);
+		}
+	}
+	return !!found_phys;
+}
+
+static int
+ar8xxx_phy_probe(struct phy_device *phydev)
+{
+	struct ar8xxx_priv *priv;
+	struct switch_dev *swdev;
+	int ret;
+
+	/* skip PHYs at unused adresses */
+	if (phydev->mdio.addr != 0 && phydev->mdio.addr != 3 && phydev->mdio.addr != 4)
+		return -ENODEV;
+
+	if (!ar8xxx_is_possible(phydev->mdio.bus))
+		return -ENODEV;
+
+	mutex_lock(&ar8xxx_dev_list_lock);
+	list_for_each_entry(priv, &ar8xxx_dev_list, list)
+		if (priv->mii_bus == phydev->mdio.bus)
+			goto found;
+
+	priv = ar8xxx_create();
+	if (priv == NULL) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	priv->mii_bus = phydev->mdio.bus;
+	priv->pdev = &phydev->mdio.dev;
+
+	ret = of_property_read_u32(priv->pdev->of_node, "qca,mib-poll-interval",
+				   &priv->mib_poll_interval);
+	if (ret)
+		priv->mib_poll_interval = 0;
+
+	ret = ar8xxx_id_chip(priv);
+	if (ret)
+		goto free_priv;
+
+	ret = ar8xxx_probe_switch(priv);
+	if (ret)
+		goto free_priv;
+
+	swdev = &priv->dev;
+	swdev->alias = dev_name(&priv->mii_bus->dev);
+	ret = register_switch(swdev, NULL);
+	if (ret)
+		goto free_priv;
+
+	pr_info("%s: %s rev. %u switch registered on %s\n",
+		swdev->devname, swdev->name, priv->chip_rev,
+		dev_name(&priv->mii_bus->dev));
+
+	list_add(&priv->list, &ar8xxx_dev_list);
+
+found:
+	priv->use_count++;
+
+	if (phydev->mdio.addr == 0 && priv->chip->config_at_probe) {
+		priv->phy = phydev;
+
+		ret = ar8xxx_start(priv);
+		if (ret)
+			goto err_unregister_switch;
+	} else if (priv->chip->phy_rgmii_set) {
+		priv->chip->phy_rgmii_set(priv, phydev);
+	}
+
+	phydev->priv = priv;
+
+	mutex_unlock(&ar8xxx_dev_list_lock);
+
+	return 0;
+
+err_unregister_switch:
+	if (--priv->use_count)
+		goto unlock;
+
+	unregister_switch(&priv->dev);
+
+free_priv:
+	ar8xxx_free(priv);
+unlock:
+	mutex_unlock(&ar8xxx_dev_list_lock);
+	return ret;
+}
+
+static void
+ar8xxx_phy_detach(struct phy_device *phydev)
+{
+	struct net_device *dev = phydev->attached_dev;
+
+	if (!dev)
+		return;
+
+#ifdef CONFIG_ETHERNET_PACKET_MANGLE
+	dev->phy_ptr = NULL;
+	dev->priv_flags &= ~IFF_NO_IP_ALIGN;
+	dev->eth_mangle_rx = NULL;
+	dev->eth_mangle_tx = NULL;
+#endif
+}
+
+static void
+ar8xxx_phy_remove(struct phy_device *phydev)
+{
+	struct ar8xxx_priv *priv = phydev->priv;
+
+	if (WARN_ON(!priv))
+		return;
+
+	phydev->priv = NULL;
+
+	mutex_lock(&ar8xxx_dev_list_lock);
+
+	if (--priv->use_count > 0) {
+		mutex_unlock(&ar8xxx_dev_list_lock);
+		return;
+	}
+
+	list_del(&priv->list);
+	mutex_unlock(&ar8xxx_dev_list_lock);
+
+	unregister_switch(&priv->dev);
+	ar8xxx_mib_stop(priv);
+	ar8xxx_free(priv);
+}
+
+static struct phy_driver ar8xxx_phy_driver[] = {
+	{
+		.phy_id		= 0x004d0000,
+		.name		= "Atheros AR8216/AR8236/AR8316",
+		.phy_id_mask	= 0xffff0000,
+		.probe		= ar8xxx_phy_probe,
+		.remove		= ar8xxx_phy_remove,
+		.detach		= ar8xxx_phy_detach,
+		.config_init	= ar8xxx_phy_config_init,
+		.config_aneg	= ar8xxx_phy_config_aneg,
+		.read_status	= ar8xxx_phy_read_status,
+		.get_features	= ar8xxx_get_features,
+	}
+};
+
+static const struct of_device_id ar8xxx_mdiodev_of_match[] = {
+	{
+		.compatible = "qca,ar7240sw",
+		.data = &ar7240sw_chip,
+	}, {
+		.compatible = "qca,ar8229",
+		.data = &ar8229_chip,
+	}, {
+		.compatible = "qca,ar8236",
+		.data = &ar8236_chip,
+	}, {
+		.compatible = "qca,ar8327",
+		.data = &ar8327_chip,
+	},
+	{ /* sentinel */ },
+};
+
+static int
+ar8xxx_mdiodev_probe(struct mdio_device *mdiodev)
+{
+	const struct of_device_id *match;
+	struct ar8xxx_priv *priv;
+	struct switch_dev *swdev;
+	struct device_node *mdio_node;
+	int ret;
+
+	match = of_match_device(ar8xxx_mdiodev_of_match, &mdiodev->dev);
+	if (!match)
+		return -EINVAL;
+
+	priv = ar8xxx_create();
+	if (priv == NULL)
+		return -ENOMEM;
+
+	priv->mii_bus = mdiodev->bus;
+	priv->pdev = &mdiodev->dev;
+	priv->chip = (const struct ar8xxx_chip *) match->data;
+
+	ret = of_property_read_u32(priv->pdev->of_node, "qca,mib-poll-interval",
+				   &priv->mib_poll_interval);
+	if (ret)
+		priv->mib_poll_interval = 0;
+
+	ret = ar8xxx_read_id(priv);
+	if (ret)
+		goto free_priv;
+
+	ret = ar8xxx_probe_switch(priv);
+	if (ret)
+		goto free_priv;
+
+	if (priv->chip->phy_read && priv->chip->phy_write) {
+		priv->sw_mii_bus = devm_mdiobus_alloc(&mdiodev->dev);
+		priv->sw_mii_bus->name = "ar8xxx-mdio";
+		priv->sw_mii_bus->read = ar8xxx_phy_read;
+		priv->sw_mii_bus->write = ar8xxx_phy_write;
+		priv->sw_mii_bus->priv = priv;
+		priv->sw_mii_bus->parent = &mdiodev->dev;
+		snprintf(priv->sw_mii_bus->id, MII_BUS_ID_SIZE, "%s",
+			 dev_name(&mdiodev->dev));
+		mdio_node = of_get_child_by_name(priv->pdev->of_node, "mdio-bus");
+		ret = of_mdiobus_register(priv->sw_mii_bus, mdio_node);
+		if (ret)
+			goto free_priv;
+	}
+
+	swdev = &priv->dev;
+	swdev->alias = dev_name(&mdiodev->dev);
+
+	if (of_property_read_bool(priv->pdev->of_node, "qca,phy4-mii-enable")) {
+		priv->port4_phy = true;
+		swdev->ports--;
+	}
+
+	ret = register_switch(swdev, NULL);
+	if (ret)
+		goto free_priv;
+
+	pr_info("%s: %s rev. %u switch registered on %s\n",
+		swdev->devname, swdev->name, priv->chip_rev,
+		dev_name(&priv->mii_bus->dev));
+
+	mutex_lock(&ar8xxx_dev_list_lock);
+	list_add(&priv->list, &ar8xxx_dev_list);
+	mutex_unlock(&ar8xxx_dev_list_lock);
+
+	priv->use_count++;
+
+	ret = ar8xxx_start(priv);
+	if (ret)
+		goto err_unregister_switch;
+
+	dev_set_drvdata(&mdiodev->dev, priv);
+
+	return 0;
+
+err_unregister_switch:
+	if (--priv->use_count)
+		return ret;
+
+	unregister_switch(&priv->dev);
+
+free_priv:
+	ar8xxx_free(priv);
+	return ret;
+}
+
+static void
+ar8xxx_mdiodev_remove(struct mdio_device *mdiodev)
+{
+	struct ar8xxx_priv *priv = dev_get_drvdata(&mdiodev->dev);
+
+	if (WARN_ON(!priv))
+		return;
+
+	mutex_lock(&ar8xxx_dev_list_lock);
+
+	if (--priv->use_count > 0) {
+		mutex_unlock(&ar8xxx_dev_list_lock);
+		return;
+	}
+
+	list_del(&priv->list);
+	mutex_unlock(&ar8xxx_dev_list_lock);
+
+	unregister_switch(&priv->dev);
+	ar8xxx_mib_stop(priv);
+	if(priv->sw_mii_bus)
+		mdiobus_unregister(priv->sw_mii_bus);
+	ar8xxx_free(priv);
+}
+
+static struct mdio_driver ar8xxx_mdio_driver = {
+	.probe  = ar8xxx_mdiodev_probe,
+	.remove = ar8xxx_mdiodev_remove,
+	.mdiodrv.driver = {
+		.name = "ar8xxx-switch",
+		.of_match_table = ar8xxx_mdiodev_of_match,
+	},
+};
+
+static int __init ar8216_init(void)
+{
+	int ret;
+
+	ret = phy_drivers_register(ar8xxx_phy_driver,
+				   ARRAY_SIZE(ar8xxx_phy_driver),
+				   THIS_MODULE);
+	if (ret)
+		return ret;
+
+	ret = mdio_driver_register(&ar8xxx_mdio_driver);
+	if (ret)
+		phy_drivers_unregister(ar8xxx_phy_driver,
+				       ARRAY_SIZE(ar8xxx_phy_driver));
+
+	return ret;
+}
+module_init(ar8216_init);
+
+static void __exit ar8216_exit(void)
+{
+	mdio_driver_unregister(&ar8xxx_mdio_driver);
+	phy_drivers_unregister(ar8xxx_phy_driver,
+			        ARRAY_SIZE(ar8xxx_phy_driver));
+}
+module_exit(ar8216_exit);
+
+MODULE_LICENSE("GPL");
diff -Naur linux-orig/drivers/net/phy/ar8216.h linux/drivers/net/phy/ar8216.h
--- linux-orig/drivers/net/phy/ar8216.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/ar8216.h	2022-04-15 23:50:31.718484399 +0000
@@ -0,0 +1,723 @@
+/*
+ * ar8216.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __AR8216_H
+#define __AR8216_H
+
+#define BITS(_s, _n)	(((1UL << (_n)) - 1) << _s)
+
+#define AR8XXX_CAP_GIGE			BIT(0)
+#define AR8XXX_CAP_MIB_COUNTERS		BIT(1)
+
+#define AR8XXX_NUM_PHYS 	5
+#define AR8216_PORT_CPU	0
+#define AR8216_NUM_PORTS	6
+#define AR8216_NUM_VLANS	16
+#define AR7240SW_NUM_PORTS	5
+#define AR8316_NUM_VLANS	4096
+
+/* size of the vlan table */
+#define AR8X16_MAX_VLANS	128
+#define AR83X7_MAX_VLANS	4096
+#define AR8XXX_MAX_VLANS	AR83X7_MAX_VLANS
+
+#define AR8X16_PROBE_RETRIES	10
+#define AR8X16_MAX_PORTS	8
+
+#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS	7
+#define AR8XXX_DEFAULT_ARL_AGE_TIME		300
+
+/* Atheros specific MII registers */
+#define MII_ATH_MMD_ADDR		0x0d
+#define MII_ATH_MMD_DATA		0x0e
+#define MII_ATH_DBG_ADDR		0x1d
+#define MII_ATH_DBG_DATA		0x1e
+
+#define AR8216_REG_CTRL			0x0000
+#define   AR8216_CTRL_REVISION		BITS(0, 8)
+#define   AR8216_CTRL_REVISION_S	0
+#define   AR8216_CTRL_VERSION		BITS(8, 8)
+#define   AR8216_CTRL_VERSION_S		8
+#define   AR8216_CTRL_RESET		BIT(31)
+
+#define AR8216_REG_FLOOD_MASK		0x002C
+#define   AR8216_FM_UNI_DEST_PORTS	BITS(0, 6)
+#define   AR8216_FM_MULTI_DEST_PORTS	BITS(16, 6)
+#define   AR8216_FM_CPU_BROADCAST_EN	BIT(26)
+#define   AR8229_FLOOD_MASK_UC_DP(_p)	BIT(_p)
+#define   AR8229_FLOOD_MASK_MC_DP(_p)	BIT(16 + (_p))
+#define   AR8229_FLOOD_MASK_BC_DP(_p)	BIT(25 + (_p))
+
+#define AR8216_REG_GLOBAL_CTRL		0x0030
+#define   AR8216_GCTRL_MTU		BITS(0, 11)
+#define   AR8236_GCTRL_MTU		BITS(0, 14)
+#define   AR8316_GCTRL_MTU		BITS(0, 14)
+
+#define AR8216_REG_VTU			0x0040
+#define   AR8216_VTU_OP			BITS(0, 3)
+#define   AR8216_VTU_OP_NOOP		0x0
+#define   AR8216_VTU_OP_FLUSH		0x1
+#define   AR8216_VTU_OP_LOAD		0x2
+#define   AR8216_VTU_OP_PURGE		0x3
+#define   AR8216_VTU_OP_REMOVE_PORT	0x4
+#define   AR8216_VTU_ACTIVE		BIT(3)
+#define   AR8216_VTU_FULL		BIT(4)
+#define   AR8216_VTU_PORT		BITS(8, 4)
+#define   AR8216_VTU_PORT_S		8
+#define   AR8216_VTU_VID		BITS(16, 12)
+#define   AR8216_VTU_VID_S		16
+#define   AR8216_VTU_PRIO		BITS(28, 3)
+#define   AR8216_VTU_PRIO_S		28
+#define   AR8216_VTU_PRIO_EN		BIT(31)
+
+#define AR8216_REG_VTU_DATA		0x0044
+#define   AR8216_VTUDATA_MEMBER		BITS(0, 10)
+#define   AR8236_VTUDATA_MEMBER		BITS(0, 7)
+#define   AR8216_VTUDATA_VALID		BIT(11)
+
+#define AR8216_REG_ATU_FUNC0		0x0050
+#define   AR8216_ATU_OP			BITS(0, 3)
+#define   AR8216_ATU_OP_NOOP		0x0
+#define   AR8216_ATU_OP_FLUSH		0x1
+#define   AR8216_ATU_OP_LOAD		0x2
+#define   AR8216_ATU_OP_PURGE		0x3
+#define   AR8216_ATU_OP_FLUSH_UNLOCKED	0x4
+#define   AR8216_ATU_OP_FLUSH_PORT	0x5
+#define   AR8216_ATU_OP_GET_NEXT	0x6
+#define   AR8216_ATU_ACTIVE		BIT(3)
+#define   AR8216_ATU_PORT_NUM		BITS(8, 4)
+#define   AR8216_ATU_PORT_NUM_S		8
+#define   AR8216_ATU_FULL_VIO		BIT(12)
+#define   AR8216_ATU_ADDR5		BITS(16, 8)
+#define   AR8216_ATU_ADDR5_S		16
+#define   AR8216_ATU_ADDR4		BITS(24, 8)
+#define   AR8216_ATU_ADDR4_S		24
+
+#define AR8216_REG_ATU_FUNC1		0x0054
+#define   AR8216_ATU_ADDR3		BITS(0, 8)
+#define   AR8216_ATU_ADDR3_S		0
+#define   AR8216_ATU_ADDR2		BITS(8, 8)
+#define   AR8216_ATU_ADDR2_S		8
+#define   AR8216_ATU_ADDR1		BITS(16, 8)
+#define   AR8216_ATU_ADDR1_S		16
+#define   AR8216_ATU_ADDR0		BITS(24, 8)
+#define   AR8216_ATU_ADDR0_S		24
+
+#define AR8216_REG_ATU_FUNC2		0x0058
+#define   AR8216_ATU_PORTS		BITS(0, 6)
+#define   AR8216_ATU_PORTS_S		0
+#define   AR8216_ATU_PORT0		BIT(0)
+#define   AR8216_ATU_PORT1		BIT(1)
+#define   AR8216_ATU_PORT2		BIT(2)
+#define   AR8216_ATU_PORT3		BIT(3)
+#define   AR8216_ATU_PORT4		BIT(4)
+#define   AR8216_ATU_PORT5		BIT(5)
+#define   AR8216_ATU_STATUS		BITS(16, 4)
+#define   AR8216_ATU_STATUS_S		16
+
+#define AR8216_REG_ATU_CTRL		0x005C
+#define   AR8216_ATU_CTRL_AGE_EN	BIT(17)
+#define   AR8216_ATU_CTRL_AGE_TIME	BITS(0, 16)
+#define   AR8216_ATU_CTRL_AGE_TIME_S	0
+#define   AR8236_ATU_CTRL_RES		BIT(20)
+#define   AR8216_ATU_CTRL_LEARN_CHANGE	BIT(18)
+#define   AR8216_ATU_CTRL_RESERVED	BIT(19)
+#define   AR8216_ATU_CTRL_ARP_EN	BIT(20)
+
+#define AR8216_REG_TAG_PRIORITY	0x0070
+
+#define AR8216_REG_SERVICE_TAG		0x0074
+#define  AR8216_SERVICE_TAG_M		BITS(0, 16)
+
+#define AR8216_REG_MIB_FUNC		0x0080
+#define   AR8216_MIB_TIMER		BITS(0, 16)
+#define   AR8216_MIB_AT_HALF_EN		BIT(16)
+#define   AR8216_MIB_BUSY		BIT(17)
+#define   AR8216_MIB_FUNC		BITS(24, 3)
+#define   AR8216_MIB_FUNC_S		24
+#define   AR8216_MIB_FUNC_NO_OP		0x0
+#define   AR8216_MIB_FUNC_FLUSH		0x1
+#define   AR8216_MIB_FUNC_CAPTURE	0x3
+#define   AR8236_MIB_EN			BIT(30)
+
+#define AR8216_REG_GLOBAL_CPUPORT		0x0078
+#define   AR8216_GLOBAL_CPUPORT_MIRROR_PORT	BITS(4, 4)
+#define   AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S	4
+#define   AR8216_GLOBAL_CPUPORT_EN		BIT(8)
+
+#define AR8216_REG_MDIO_CTRL		0x98
+#define   AR8216_MDIO_CTRL_DATA_M	BITS(0, 16)
+#define   AR8216_MDIO_CTRL_REG_ADDR_S	16
+#define   AR8216_MDIO_CTRL_PHY_ADDR_S	21
+#define   AR8216_MDIO_CTRL_CMD_WRITE	0
+#define   AR8216_MDIO_CTRL_CMD_READ	BIT(27)
+#define   AR8216_MDIO_CTRL_MASTER_EN	BIT(30)
+#define   AR8216_MDIO_CTRL_BUSY	BIT(31)
+
+#define AR8216_PORT_OFFSET(_i)		(0x0100 * (_i + 1))
+#define AR8216_REG_PORT_STATUS(_i)	(AR8216_PORT_OFFSET(_i) + 0x0000)
+#define   AR8216_PORT_STATUS_SPEED	BITS(0,2)
+#define   AR8216_PORT_STATUS_SPEED_S	0
+#define   AR8216_PORT_STATUS_TXMAC	BIT(2)
+#define   AR8216_PORT_STATUS_RXMAC	BIT(3)
+#define   AR8216_PORT_STATUS_TXFLOW	BIT(4)
+#define   AR8216_PORT_STATUS_RXFLOW	BIT(5)
+#define   AR8216_PORT_STATUS_DUPLEX	BIT(6)
+#define   AR8216_PORT_STATUS_LINK_UP	BIT(8)
+#define   AR8216_PORT_STATUS_LINK_AUTO	BIT(9)
+#define   AR8216_PORT_STATUS_LINK_PAUSE	BIT(10)
+#define   AR8216_PORT_STATUS_FLOW_CONTROL  BIT(12)
+
+#define AR8216_REG_PORT_CTRL(_i)	(AR8216_PORT_OFFSET(_i) + 0x0004)
+
+/* port forwarding state */
+#define   AR8216_PORT_CTRL_STATE	BITS(0, 3)
+#define   AR8216_PORT_CTRL_STATE_S	0
+
+#define   AR8216_PORT_CTRL_LEARN_LOCK	BIT(7)
+
+/* egress 802.1q mode */
+#define   AR8216_PORT_CTRL_VLAN_MODE	BITS(8, 2)
+#define   AR8216_PORT_CTRL_VLAN_MODE_S	8
+
+#define   AR8216_PORT_CTRL_IGMP_SNOOP	BIT(10)
+#define   AR8216_PORT_CTRL_HEADER	BIT(11)
+#define   AR8216_PORT_CTRL_MAC_LOOP	BIT(12)
+#define   AR8216_PORT_CTRL_SINGLE_VLAN	BIT(13)
+#define   AR8216_PORT_CTRL_LEARN	BIT(14)
+#define   AR8216_PORT_CTRL_MIRROR_TX	BIT(16)
+#define   AR8216_PORT_CTRL_MIRROR_RX	BIT(17)
+
+#define AR8216_REG_PORT_VLAN(_i)	(AR8216_PORT_OFFSET(_i) + 0x0008)
+
+#define   AR8216_PORT_VLAN_DEFAULT_ID	BITS(0, 12)
+#define   AR8216_PORT_VLAN_DEFAULT_ID_S	0
+
+#define   AR8216_PORT_VLAN_DEST_PORTS	BITS(16, 9)
+#define   AR8216_PORT_VLAN_DEST_PORTS_S	16
+
+/* bit0 added to the priority field of egress frames */
+#define   AR8216_PORT_VLAN_TX_PRIO	BIT(27)
+
+/* port default priority */
+#define   AR8216_PORT_VLAN_PRIORITY	BITS(28, 2)
+#define   AR8216_PORT_VLAN_PRIORITY_S	28
+
+/* ingress 802.1q mode */
+#define   AR8216_PORT_VLAN_MODE		BITS(30, 2)
+#define   AR8216_PORT_VLAN_MODE_S	30
+
+#define AR8216_REG_PORT_RATE(_i)	(AR8216_PORT_OFFSET(_i) + 0x000c)
+#define AR8216_REG_PORT_PRIO(_i)	(AR8216_PORT_OFFSET(_i) + 0x0010)
+
+#define AR8216_STATS_RXBROAD		0x00
+#define AR8216_STATS_RXPAUSE		0x04
+#define AR8216_STATS_RXMULTI		0x08
+#define AR8216_STATS_RXFCSERR		0x0c
+#define AR8216_STATS_RXALIGNERR		0x10
+#define AR8216_STATS_RXRUNT		0x14
+#define AR8216_STATS_RXFRAGMENT		0x18
+#define AR8216_STATS_RX64BYTE		0x1c
+#define AR8216_STATS_RX128BYTE		0x20
+#define AR8216_STATS_RX256BYTE		0x24
+#define AR8216_STATS_RX512BYTE		0x28
+#define AR8216_STATS_RX1024BYTE		0x2c
+#define AR8216_STATS_RXMAXBYTE		0x30
+#define AR8216_STATS_RXTOOLONG		0x34
+#define AR8216_STATS_RXGOODBYTE		0x38
+#define AR8216_STATS_RXBADBYTE		0x40
+#define AR8216_STATS_RXOVERFLOW		0x48
+#define AR8216_STATS_FILTERED		0x4c
+#define AR8216_STATS_TXBROAD		0x50
+#define AR8216_STATS_TXPAUSE		0x54
+#define AR8216_STATS_TXMULTI		0x58
+#define AR8216_STATS_TXUNDERRUN		0x5c
+#define AR8216_STATS_TX64BYTE		0x60
+#define AR8216_STATS_TX128BYTE		0x64
+#define AR8216_STATS_TX256BYTE		0x68
+#define AR8216_STATS_TX512BYTE		0x6c
+#define AR8216_STATS_TX1024BYTE		0x70
+#define AR8216_STATS_TXMAXBYTE		0x74
+#define AR8216_STATS_TXOVERSIZE		0x78
+#define AR8216_STATS_TXBYTE		0x7c
+#define AR8216_STATS_TXCOLLISION	0x84
+#define AR8216_STATS_TXABORTCOL		0x88
+#define AR8216_STATS_TXMULTICOL		0x8c
+#define AR8216_STATS_TXSINGLECOL	0x90
+#define AR8216_STATS_TXEXCDEFER		0x94
+#define AR8216_STATS_TXDEFER		0x98
+#define AR8216_STATS_TXLATECOL		0x9c
+
+#define AR8216_MIB_RXB_ID		14	/* RxGoodByte */
+#define AR8216_MIB_TXB_ID		29	/* TxByte */
+
+#define AR8229_REG_OPER_MODE0		0x04
+#define   AR8229_OPER_MODE0_MAC_GMII_EN	BIT(6)
+#define   AR8229_OPER_MODE0_PHY_MII_EN	BIT(10)
+
+#define AR8229_REG_OPER_MODE1		0x08
+#define   AR8229_REG_OPER_MODE1_PHY4_MII_EN	BIT(28)
+
+#define AR8229_REG_QM_CTRL		0x3c
+#define   AR8229_QM_CTRL_ARP_EN		BIT(15)
+
+#define AR8236_REG_PORT_VLAN(_i)	(AR8216_PORT_OFFSET((_i)) + 0x0008)
+#define   AR8236_PORT_VLAN_DEFAULT_ID	BITS(16, 12)
+#define   AR8236_PORT_VLAN_DEFAULT_ID_S	16
+#define   AR8236_PORT_VLAN_PRIORITY	BITS(29, 3)
+#define   AR8236_PORT_VLAN_PRIORITY_S	28
+
+#define AR8236_REG_PORT_VLAN2(_i)	(AR8216_PORT_OFFSET((_i)) + 0x000c)
+#define   AR8236_PORT_VLAN2_MEMBER	BITS(16, 7)
+#define   AR8236_PORT_VLAN2_MEMBER_S	16
+#define   AR8236_PORT_VLAN2_TX_PRIO	BIT(23)
+#define   AR8236_PORT_VLAN2_VLAN_MODE	BITS(30, 2)
+#define   AR8236_PORT_VLAN2_VLAN_MODE_S	30
+
+#define AR8236_STATS_RXBROAD		0x00
+#define AR8236_STATS_RXPAUSE		0x04
+#define AR8236_STATS_RXMULTI		0x08
+#define AR8236_STATS_RXFCSERR		0x0c
+#define AR8236_STATS_RXALIGNERR		0x10
+#define AR8236_STATS_RXRUNT		0x14
+#define AR8236_STATS_RXFRAGMENT		0x18
+#define AR8236_STATS_RX64BYTE		0x1c
+#define AR8236_STATS_RX128BYTE		0x20
+#define AR8236_STATS_RX256BYTE		0x24
+#define AR8236_STATS_RX512BYTE		0x28
+#define AR8236_STATS_RX1024BYTE		0x2c
+#define AR8236_STATS_RX1518BYTE		0x30
+#define AR8236_STATS_RXMAXBYTE		0x34
+#define AR8236_STATS_RXTOOLONG		0x38
+#define AR8236_STATS_RXGOODBYTE		0x3c
+#define AR8236_STATS_RXBADBYTE		0x44
+#define AR8236_STATS_RXOVERFLOW		0x4c
+#define AR8236_STATS_FILTERED		0x50
+#define AR8236_STATS_TXBROAD		0x54
+#define AR8236_STATS_TXPAUSE		0x58
+#define AR8236_STATS_TXMULTI		0x5c
+#define AR8236_STATS_TXUNDERRUN		0x60
+#define AR8236_STATS_TX64BYTE		0x64
+#define AR8236_STATS_TX128BYTE		0x68
+#define AR8236_STATS_TX256BYTE		0x6c
+#define AR8236_STATS_TX512BYTE		0x70
+#define AR8236_STATS_TX1024BYTE		0x74
+#define AR8236_STATS_TX1518BYTE		0x78
+#define AR8236_STATS_TXMAXBYTE		0x7c
+#define AR8236_STATS_TXOVERSIZE		0x80
+#define AR8236_STATS_TXBYTE		0x84
+#define AR8236_STATS_TXCOLLISION	0x8c
+#define AR8236_STATS_TXABORTCOL		0x90
+#define AR8236_STATS_TXMULTICOL		0x94
+#define AR8236_STATS_TXSINGLECOL	0x98
+#define AR8236_STATS_TXEXCDEFER		0x9c
+#define AR8236_STATS_TXDEFER		0xa0
+#define AR8236_STATS_TXLATECOL		0xa4
+
+#define AR8236_MIB_RXB_ID		15	/* RxGoodByte */
+#define AR8236_MIB_TXB_ID		31	/* TxByte */
+
+#define AR8316_REG_POSTRIP			0x0008
+#define   AR8316_POSTRIP_MAC0_GMII_EN		BIT(0)
+#define   AR8316_POSTRIP_MAC0_RGMII_EN		BIT(1)
+#define   AR8316_POSTRIP_PHY4_GMII_EN		BIT(2)
+#define   AR8316_POSTRIP_PHY4_RGMII_EN		BIT(3)
+#define   AR8316_POSTRIP_MAC0_MAC_MODE		BIT(4)
+#define   AR8316_POSTRIP_RTL_MODE		BIT(5)
+#define   AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN	BIT(6)
+#define   AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN	BIT(7)
+#define   AR8316_POSTRIP_SERDES_EN		BIT(8)
+#define   AR8316_POSTRIP_SEL_ANA_RST		BIT(9)
+#define   AR8316_POSTRIP_GATE_25M_EN		BIT(10)
+#define   AR8316_POSTRIP_SEL_CLK25M		BIT(11)
+#define   AR8316_POSTRIP_HIB_PULSE_HW		BIT(12)
+#define   AR8316_POSTRIP_DBG_MODE_I		BIT(13)
+#define   AR8316_POSTRIP_MAC5_MAC_MODE		BIT(14)
+#define   AR8316_POSTRIP_MAC5_PHY_MODE		BIT(15)
+#define   AR8316_POSTRIP_POWER_DOWN_HW		BIT(16)
+#define   AR8316_POSTRIP_LPW_STATE_EN		BIT(17)
+#define   AR8316_POSTRIP_MAN_EN			BIT(18)
+#define   AR8316_POSTRIP_PHY_PLL_ON		BIT(19)
+#define   AR8316_POSTRIP_LPW_EXIT		BIT(20)
+#define   AR8316_POSTRIP_TXDELAY_S0		BIT(21)
+#define   AR8316_POSTRIP_TXDELAY_S1		BIT(22)
+#define   AR8316_POSTRIP_RXDELAY_S0		BIT(23)
+#define   AR8316_POSTRIP_LED_OPEN_EN		BIT(24)
+#define   AR8316_POSTRIP_SPI_EN			BIT(25)
+#define   AR8316_POSTRIP_RXDELAY_S1		BIT(26)
+#define   AR8316_POSTRIP_POWER_ON_SEL		BIT(31)
+
+/* port speed */
+enum {
+        AR8216_PORT_SPEED_10M = 0,
+        AR8216_PORT_SPEED_100M = 1,
+        AR8216_PORT_SPEED_1000M = 2,
+        AR8216_PORT_SPEED_ERR = 3,
+};
+
+/* ingress 802.1q mode */
+enum {
+	AR8216_IN_PORT_ONLY = 0,
+	AR8216_IN_PORT_FALLBACK = 1,
+	AR8216_IN_VLAN_ONLY = 2,
+	AR8216_IN_SECURE = 3
+};
+
+/* egress 802.1q mode */
+enum {
+	AR8216_OUT_KEEP = 0,
+	AR8216_OUT_STRIP_VLAN = 1,
+	AR8216_OUT_ADD_VLAN = 2
+};
+
+/* port forwarding state */
+enum {
+	AR8216_PORT_STATE_DISABLED = 0,
+	AR8216_PORT_STATE_BLOCK = 1,
+	AR8216_PORT_STATE_LISTEN = 2,
+	AR8216_PORT_STATE_LEARN = 3,
+	AR8216_PORT_STATE_FORWARD = 4
+};
+
+/* mib counter type */
+enum {
+	AR8XXX_MIB_BASIC = 0,
+	AR8XXX_MIB_EXTENDED = 1
+};
+
+enum {
+	AR8XXX_VER_AR8216 = 0x01,
+	AR8XXX_VER_AR8236 = 0x03,
+	AR8XXX_VER_AR8316 = 0x10,
+	AR8XXX_VER_AR8327 = 0x12,
+	AR8XXX_VER_AR8337 = 0x13,
+};
+
+#define AR8XXX_NUM_ARL_RECORDS	100
+
+enum arl_op {
+	AR8XXX_ARL_INITIALIZE,
+	AR8XXX_ARL_GET_NEXT
+};
+
+struct arl_entry {
+	u16 portmap;
+	u8 mac[6];
+};
+
+struct ar8xxx_priv;
+
+struct ar8xxx_mib_desc {
+	unsigned int size;
+	unsigned int offset;
+	const char *name;
+	u8 type;
+};
+
+struct ar8xxx_chip {
+	unsigned long caps;
+	bool config_at_probe;
+	bool mii_lo_first;
+
+	/* parameters to calculate REG_PORT_STATS_BASE */
+	unsigned reg_port_stats_start;
+	unsigned reg_port_stats_length;
+
+	unsigned reg_arl_ctrl;
+
+	int (*hw_init)(struct ar8xxx_priv *priv);
+	void (*cleanup)(struct ar8xxx_priv *priv);
+
+	const char *name;
+	int vlans;
+	int ports;
+	const struct switch_dev_ops *swops;
+
+	void (*init_globals)(struct ar8xxx_priv *priv);
+	void (*init_port)(struct ar8xxx_priv *priv, int port);
+	void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members);
+	u32 (*read_port_status)(struct ar8xxx_priv *priv, int port);
+	u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port);
+	int (*atu_flush)(struct ar8xxx_priv *priv);
+	int (*atu_flush_port)(struct ar8xxx_priv *priv, int port);
+	void (*vtu_flush)(struct ar8xxx_priv *priv);
+	void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask);
+	void (*phy_fixup)(struct ar8xxx_priv *priv, int phy);
+	void (*set_mirror_regs)(struct ar8xxx_priv *priv);
+	void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a,
+			      u32 *status, enum arl_op op);
+	int (*sw_hw_apply)(struct switch_dev *dev);
+	void (*phy_rgmii_set)(struct ar8xxx_priv *priv, struct phy_device *phydev);
+	int (*phy_read)(struct ar8xxx_priv *priv, int addr, int regnum);
+	int (*phy_write)(struct ar8xxx_priv *priv, int addr, int regnum, u16 val);
+
+	const struct ar8xxx_mib_desc *mib_decs;
+	unsigned num_mibs;
+	unsigned mib_func;
+	int mib_rxb_id;
+	int mib_txb_id;
+};
+
+struct ar8xxx_priv {
+	struct switch_dev dev;
+	struct mii_bus *mii_bus;
+	struct mii_bus *sw_mii_bus;
+	struct phy_device *phy;
+	struct device *pdev;
+
+	int (*get_port_link)(unsigned port);
+
+	const struct net_device_ops *ndo_old;
+	struct net_device_ops ndo;
+	struct mutex reg_mutex;
+	u8 chip_ver;
+	u8 chip_rev;
+	const struct ar8xxx_chip *chip;
+	void *chip_data;
+	bool initialized;
+	bool port4_phy;
+	char buf[2048];
+	struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS];
+	char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256];
+	bool link_up[AR8X16_MAX_PORTS];
+
+	bool init;
+
+	struct mutex mib_lock;
+	struct delayed_work mib_work;
+	u64 *mib_stats;
+	u32 mib_poll_interval;
+	u8 mib_type;
+
+	struct list_head list;
+	unsigned int use_count;
+
+	/* all fields below are cleared on reset */
+	bool vlan;
+
+	u16 vlan_id[AR8XXX_MAX_VLANS];
+	u8 vlan_table[AR8XXX_MAX_VLANS];
+	u8 vlan_tagged;
+	u16 pvid[AR8X16_MAX_PORTS];
+	int arl_age_time;
+
+	/* mirroring */
+	bool mirror_rx;
+	bool mirror_tx;
+	int source_port;
+	int monitor_port;
+	u8 port_vlan_prio[AR8X16_MAX_PORTS];
+};
+
+u32
+ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum);
+void
+ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val);
+u32
+ar8xxx_read(struct ar8xxx_priv *priv, int reg);
+void
+ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val);
+u32
+ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
+
+void
+ar8xxx_phy_dbg_read(struct ar8xxx_priv *priv, int phy_addr,
+		u16 dbg_addr, u16 *dbg_data);
+void
+ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr,
+		     u16 dbg_addr, u16 dbg_data);
+void
+ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data);
+u16
+ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg);
+void
+ar8xxx_phy_init(struct ar8xxx_priv *priv);
+int
+ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+		   struct switch_val *val);
+int
+ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+		   struct switch_val *val);
+int
+ar8xxx_sw_set_reset_mibs(struct switch_dev *dev,
+			 const struct switch_attr *attr,
+			 struct switch_val *val);
+int
+ar8xxx_sw_set_mib_poll_interval(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_get_mib_poll_interval(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_set_mib_type(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_get_mib_type(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int
+ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val);
+int
+ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val);
+int
+ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val);
+int
+ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val);
+int
+ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan);
+int
+ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan);
+int
+ar8xxx_sw_hw_apply(struct switch_dev *dev);
+int
+ar8xxx_sw_reset_switch(struct switch_dev *dev);
+int
+ar8xxx_sw_get_port_link(struct switch_dev *dev, int port,
+			struct switch_port_link *link);
+int
+ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev,
+                             const struct switch_attr *attr,
+                             struct switch_val *val);
+int
+ar8xxx_sw_get_port_mib(struct switch_dev *dev,
+                       const struct switch_attr *attr,
+                       struct switch_val *val);
+int
+ar8xxx_sw_get_arl_age_time(struct switch_dev *dev,
+			   const struct switch_attr *attr,
+			   struct switch_val *val);
+int
+ar8xxx_sw_set_arl_age_time(struct switch_dev *dev,
+			   const struct switch_attr *attr,
+			   struct switch_val *val);
+int
+ar8xxx_sw_get_arl_table(struct switch_dev *dev,
+			const struct switch_attr *attr,
+			struct switch_val *val);
+int
+ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev,
+			      const struct switch_attr *attr,
+			      struct switch_val *val);
+int
+ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev,
+				   const struct switch_attr *attr,
+				   struct switch_val *val);
+int
+ar8xxx_sw_get_port_stats(struct switch_dev *dev, int port,
+			struct switch_port_stats *stats);
+int
+ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
+
+static inline struct ar8xxx_priv *
+swdev_to_ar8xxx(struct switch_dev *swdev)
+{
+	return container_of(swdev, struct ar8xxx_priv, dev);
+}
+
+static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv)
+{
+	return priv->chip->caps & AR8XXX_CAP_GIGE;
+}
+
+static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv)
+{
+	return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS;
+}
+
+static inline bool chip_is_ar8216(struct ar8xxx_priv *priv)
+{
+	return priv->chip_ver == AR8XXX_VER_AR8216;
+}
+
+static inline bool chip_is_ar8236(struct ar8xxx_priv *priv)
+{
+	return priv->chip_ver == AR8XXX_VER_AR8236;
+}
+
+static inline bool chip_is_ar8316(struct ar8xxx_priv *priv)
+{
+	return priv->chip_ver == AR8XXX_VER_AR8316;
+}
+
+static inline bool chip_is_ar8327(struct ar8xxx_priv *priv)
+{
+	return priv->chip_ver == AR8XXX_VER_AR8327;
+}
+
+static inline bool chip_is_ar8337(struct ar8xxx_priv *priv)
+{
+	return priv->chip_ver == AR8XXX_VER_AR8337;
+}
+
+static inline void
+ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val)
+{
+	ar8xxx_rmw(priv, reg, 0, val);
+}
+
+static inline void
+ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val)
+{
+	ar8xxx_rmw(priv, reg, val, 0);
+}
+
+static inline void
+split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+{
+	regaddr >>= 1;
+	*r1 = regaddr & 0x1e;
+
+	regaddr >>= 5;
+	*r2 = regaddr & 0x7;
+
+	regaddr >>= 3;
+	*page = regaddr & 0x1ff;
+}
+
+static inline void
+wait_for_page_switch(void)
+{
+	udelay(5);
+}
+
+#endif
diff -Naur linux-orig/drivers/net/phy/ar8327.c linux/drivers/net/phy/ar8327.c
--- linux-orig/drivers/net/phy/ar8327.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/ar8327.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1550 @@
+/*
+ * ar8327.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/of_device.h>
+#include <linux/leds.h>
+#include <linux/mdio.h>
+
+#include "ar8216.h"
+#include "ar8327.h"
+
+extern const struct ar8xxx_mib_desc ar8236_mibs[39];
+extern const struct switch_attr ar8xxx_sw_attr_vlan[1];
+
+static u32
+ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg)
+{
+	u32 t;
+
+	if (!cfg)
+		return 0;
+
+	t = 0;
+	switch (cfg->mode) {
+	case AR8327_PAD_NC:
+		break;
+
+	case AR8327_PAD_MAC2MAC_MII:
+		t = AR8327_PAD_MAC_MII_EN;
+		if (cfg->rxclk_sel)
+			t |= AR8327_PAD_MAC_MII_RXCLK_SEL;
+		if (cfg->txclk_sel)
+			t |= AR8327_PAD_MAC_MII_TXCLK_SEL;
+		break;
+
+	case AR8327_PAD_MAC2MAC_GMII:
+		t = AR8327_PAD_MAC_GMII_EN;
+		if (cfg->rxclk_sel)
+			t |= AR8327_PAD_MAC_GMII_RXCLK_SEL;
+		if (cfg->txclk_sel)
+			t |= AR8327_PAD_MAC_GMII_TXCLK_SEL;
+		break;
+
+	case AR8327_PAD_MAC_SGMII:
+		t = AR8327_PAD_SGMII_EN;
+
+		/*
+		 * WAR for the QUalcomm Atheros AP136 board.
+		 * It seems that RGMII TX/RX delay settings needs to be
+		 * applied for SGMII mode as well, The ethernet is not
+		 * reliable without this.
+		 */
+		t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S;
+		t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S;
+		if (cfg->rxclk_delay_en)
+			t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
+		if (cfg->txclk_delay_en)
+			t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN;
+
+		if (cfg->sgmii_delay_en)
+			t |= AR8327_PAD_SGMII_DELAY_EN;
+
+		break;
+
+	case AR8327_PAD_MAC2PHY_MII:
+		t = AR8327_PAD_PHY_MII_EN;
+		if (cfg->rxclk_sel)
+			t |= AR8327_PAD_PHY_MII_RXCLK_SEL;
+		if (cfg->txclk_sel)
+			t |= AR8327_PAD_PHY_MII_TXCLK_SEL;
+		break;
+
+	case AR8327_PAD_MAC2PHY_GMII:
+		t = AR8327_PAD_PHY_GMII_EN;
+		if (cfg->pipe_rxclk_sel)
+			t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL;
+		if (cfg->rxclk_sel)
+			t |= AR8327_PAD_PHY_GMII_RXCLK_SEL;
+		if (cfg->txclk_sel)
+			t |= AR8327_PAD_PHY_GMII_TXCLK_SEL;
+		break;
+
+	case AR8327_PAD_MAC_RGMII:
+		t = AR8327_PAD_RGMII_EN;
+		t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S;
+		t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S;
+		if (cfg->rxclk_delay_en)
+			t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
+		if (cfg->txclk_delay_en)
+			t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN;
+		break;
+
+	case AR8327_PAD_PHY_GMII:
+		t = AR8327_PAD_PHYX_GMII_EN;
+		break;
+
+	case AR8327_PAD_PHY_RGMII:
+		t = AR8327_PAD_PHYX_RGMII_EN;
+		break;
+
+	case AR8327_PAD_PHY_MII:
+		t = AR8327_PAD_PHYX_MII_EN;
+		break;
+	}
+
+	return t;
+}
+
+static void
+ar8327_phy_rgmii_set(struct ar8xxx_priv *priv, struct phy_device *phydev)
+{
+	u16 phy_val = 0;
+	int phyaddr = phydev->mdio.addr;
+	struct device_node *np = phydev->mdio.dev.of_node;
+
+	if (!np)
+		return;
+
+	if (!of_property_read_bool(np, "qca,phy-rgmii-en")) {
+		pr_err("ar8327: qca,phy-rgmii-en is not specified\n");
+		return;
+	}
+	ar8xxx_phy_dbg_read(priv, phyaddr,
+				AR8327_PHY_MODE_SEL, &phy_val);
+	phy_val |= AR8327_PHY_MODE_SEL_RGMII;
+	ar8xxx_phy_dbg_write(priv, phyaddr,
+				AR8327_PHY_MODE_SEL, phy_val);
+
+	/* set rgmii tx clock delay if needed */
+	if (!of_property_read_bool(np, "qca,txclk-delay-en")) {
+		pr_err("ar8327: qca,txclk-delay-en is not specified\n");
+		return;
+	}
+	ar8xxx_phy_dbg_read(priv, phyaddr,
+				AR8327_PHY_SYS_CTRL, &phy_val);
+	phy_val |= AR8327_PHY_SYS_CTRL_RGMII_TX_DELAY;
+	ar8xxx_phy_dbg_write(priv, phyaddr,
+				AR8327_PHY_SYS_CTRL, phy_val);
+
+	/* set rgmii rx clock delay if needed */
+	if (!of_property_read_bool(np, "qca,rxclk-delay-en")) {
+		pr_err("ar8327: qca,rxclk-delay-en is not specified\n");
+		return;
+	}
+	ar8xxx_phy_dbg_read(priv, phyaddr,
+				AR8327_PHY_TEST_CTRL, &phy_val);
+	phy_val |= AR8327_PHY_TEST_CTRL_RGMII_RX_DELAY;
+	ar8xxx_phy_dbg_write(priv, phyaddr,
+				AR8327_PHY_TEST_CTRL, phy_val);
+}
+
+static void
+ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy)
+{
+	switch (priv->chip_rev) {
+	case 1:
+		/* For 100M waveform */
+		ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea);
+		/* Turn on Gigabit clock */
+		ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0);
+		break;
+
+	case 2:
+		ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0);
+		/* fallthrough */
+	case 4:
+		ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f);
+		ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860);
+		ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46);
+		ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000);
+		break;
+	}
+}
+
+static u32
+ar8327_get_port_init_status(struct ar8327_port_cfg *cfg)
+{
+	u32 t;
+
+	if (!cfg->force_link)
+		return AR8216_PORT_STATUS_LINK_AUTO;
+
+	t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC;
+	t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0;
+	t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0;
+	t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0;
+
+	switch (cfg->speed) {
+	case AR8327_PORT_SPEED_10:
+		t |= AR8216_PORT_SPEED_10M;
+		break;
+	case AR8327_PORT_SPEED_100:
+		t |= AR8216_PORT_SPEED_100M;
+		break;
+	case AR8327_PORT_SPEED_1000:
+		t |= AR8216_PORT_SPEED_1000M;
+		break;
+	}
+
+	return t;
+}
+
+#define AR8327_LED_ENTRY(_num, _reg, _shift) \
+	[_num] = { .reg = (_reg), .shift = (_shift) }
+
+static const struct ar8327_led_entry
+ar8327_led_map[AR8327_NUM_LEDS] = {
+	AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14),
+	AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14),
+	AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14),
+
+	AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8),
+	AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10),
+	AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12),
+
+	AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14),
+	AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16),
+	AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18),
+
+	AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20),
+	AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22),
+	AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24),
+
+	AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30),
+	AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30),
+	AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30),
+};
+
+static void
+ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num,
+		       enum ar8327_led_pattern pattern)
+{
+	const struct ar8327_led_entry *entry;
+
+	entry = &ar8327_led_map[led_num];
+	ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg),
+		   (3 << entry->shift), pattern << entry->shift);
+}
+
+static void
+ar8327_led_work_func(struct work_struct *work)
+{
+	struct ar8327_led *aled;
+	u8 pattern;
+
+	aled = container_of(work, struct ar8327_led, led_work);
+
+	pattern = aled->pattern;
+
+	ar8327_set_led_pattern(aled->sw_priv, aled->led_num,
+			       pattern);
+}
+
+static void
+ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern)
+{
+	if (aled->pattern == pattern)
+		return;
+
+	aled->pattern = pattern;
+	schedule_work(&aled->led_work);
+}
+
+static inline struct ar8327_led *
+led_cdev_to_ar8327_led(struct led_classdev *led_cdev)
+{
+	return container_of(led_cdev, struct ar8327_led, cdev);
+}
+
+static int
+ar8327_led_blink_set(struct led_classdev *led_cdev,
+		     unsigned long *delay_on,
+		     unsigned long *delay_off)
+{
+	struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		*delay_on = 125;
+		*delay_off = 125;
+	}
+
+	if (*delay_on != 125 || *delay_off != 125) {
+		/*
+		 * The hardware only supports blinking at 4Hz. Fall back
+		 * to software implementation in other cases.
+		 */
+		return -EINVAL;
+	}
+
+	spin_lock(&aled->lock);
+
+	aled->enable_hw_mode = false;
+	ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK);
+
+	spin_unlock(&aled->lock);
+
+	return 0;
+}
+
+static void
+ar8327_led_set_brightness(struct led_classdev *led_cdev,
+			  enum led_brightness brightness)
+{
+	struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
+	u8 pattern;
+	bool active;
+
+	active = (brightness != LED_OFF);
+	active ^= aled->active_low;
+
+	pattern = (active) ? AR8327_LED_PATTERN_ON :
+			     AR8327_LED_PATTERN_OFF;
+
+	spin_lock(&aled->lock);
+
+	aled->enable_hw_mode = false;
+	ar8327_led_schedule_change(aled, pattern);
+
+	spin_unlock(&aled->lock);
+}
+
+static ssize_t
+ar8327_led_enable_hw_mode_show(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
+	ssize_t ret = 0;
+
+	ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode);
+
+	return ret;
+}
+
+static ssize_t
+ar8327_led_enable_hw_mode_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf,
+				size_t size)
+{
+        struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
+	u8 pattern;
+	u8 value;
+	int ret;
+
+	ret = kstrtou8(buf, 10, &value);
+	if (ret < 0)
+		return -EINVAL;
+
+	spin_lock(&aled->lock);
+
+	aled->enable_hw_mode = !!value;
+	if (aled->enable_hw_mode)
+		pattern = AR8327_LED_PATTERN_RULE;
+	else
+		pattern = AR8327_LED_PATTERN_OFF;
+
+	ar8327_led_schedule_change(aled, pattern);
+
+	spin_unlock(&aled->lock);
+
+	return size;
+}
+
+static DEVICE_ATTR(enable_hw_mode,  S_IRUGO | S_IWUSR,
+		   ar8327_led_enable_hw_mode_show,
+		   ar8327_led_enable_hw_mode_store);
+
+static int
+ar8327_led_register(struct ar8327_led *aled)
+{
+	int ret;
+
+	ret = led_classdev_register(NULL, &aled->cdev);
+	if (ret < 0)
+		return ret;
+
+	if (aled->mode == AR8327_LED_MODE_HW) {
+		ret = device_create_file(aled->cdev.dev,
+					 &dev_attr_enable_hw_mode);
+		if (ret)
+			goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	led_classdev_unregister(&aled->cdev);
+	return ret;
+}
+
+static void
+ar8327_led_unregister(struct ar8327_led *aled)
+{
+	if (aled->mode == AR8327_LED_MODE_HW)
+		device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode);
+
+	led_classdev_unregister(&aled->cdev);
+	cancel_work_sync(&aled->led_work);
+}
+
+static int
+ar8327_led_create(struct ar8xxx_priv *priv,
+		  const struct ar8327_led_info *led_info)
+{
+	struct ar8327_data *data = priv->chip_data;
+	struct ar8327_led *aled;
+	int ret;
+
+	if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS))
+		return 0;
+
+	if (!led_info->name)
+		return -EINVAL;
+
+	if (led_info->led_num >= AR8327_NUM_LEDS)
+		return -EINVAL;
+
+	aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1,
+		       GFP_KERNEL);
+	if (!aled)
+		return -ENOMEM;
+
+	aled->sw_priv = priv;
+	aled->led_num = led_info->led_num;
+	aled->active_low = led_info->active_low;
+	aled->mode = led_info->mode;
+
+	if (aled->mode == AR8327_LED_MODE_HW)
+		aled->enable_hw_mode = true;
+
+	aled->name = (char *)(aled + 1);
+	strcpy(aled->name, led_info->name);
+
+	aled->cdev.name = aled->name;
+	aled->cdev.brightness_set = ar8327_led_set_brightness;
+	aled->cdev.blink_set = ar8327_led_blink_set;
+	aled->cdev.default_trigger = led_info->default_trigger;
+
+	spin_lock_init(&aled->lock);
+	mutex_init(&aled->mutex);
+	INIT_WORK(&aled->led_work, ar8327_led_work_func);
+
+	ret = ar8327_led_register(aled);
+	if (ret)
+		goto err_free;
+
+	data->leds[data->num_leds++] = aled;
+
+	return 0;
+
+err_free:
+	kfree(aled);
+	return ret;
+}
+
+static void
+ar8327_led_destroy(struct ar8327_led *aled)
+{
+	ar8327_led_unregister(aled);
+	kfree(aled);
+}
+
+static void
+ar8327_leds_init(struct ar8xxx_priv *priv)
+{
+	struct ar8327_data *data = priv->chip_data;
+	unsigned i;
+
+	if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS))
+		return;
+
+	for (i = 0; i < data->num_leds; i++) {
+		struct ar8327_led *aled;
+
+		aled = data->leds[i];
+
+		if (aled->enable_hw_mode)
+			aled->pattern = AR8327_LED_PATTERN_RULE;
+		else
+			aled->pattern = AR8327_LED_PATTERN_OFF;
+
+		ar8327_set_led_pattern(priv, aled->led_num, aled->pattern);
+	}
+}
+
+static void
+ar8327_leds_cleanup(struct ar8xxx_priv *priv)
+{
+	struct ar8327_data *data = priv->chip_data;
+	unsigned i;
+
+	if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS))
+		return;
+
+	for (i = 0; i < data->num_leds; i++) {
+		struct ar8327_led *aled;
+
+		aled = data->leds[i];
+		ar8327_led_destroy(aled);
+	}
+
+	kfree(data->leds);
+}
+
+static int
+ar8327_hw_config_pdata(struct ar8xxx_priv *priv,
+		       struct ar8327_platform_data *pdata)
+{
+	struct ar8327_led_cfg *led_cfg;
+	struct ar8327_data *data = priv->chip_data;
+	u32 pos, new_pos;
+	u32 t;
+
+	if (!pdata)
+		return -EINVAL;
+
+	priv->get_port_link = pdata->get_port_link;
+
+	data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg);
+	data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg);
+
+	t = ar8327_get_pad_cfg(pdata->pad0_cfg);
+	if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis)
+	    t |= AR8337_PAD_MAC06_EXCHANGE_EN;
+	ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t);
+
+	t = ar8327_get_pad_cfg(pdata->pad5_cfg);
+	ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t);
+	t = ar8327_get_pad_cfg(pdata->pad6_cfg);
+	ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t);
+
+	pos = ar8xxx_read(priv, AR8327_REG_POWER_ON_STRAP);
+	new_pos = pos;
+
+	led_cfg = pdata->led_cfg;
+	if (led_cfg) {
+		if (led_cfg->open_drain)
+			new_pos |= AR8327_POWER_ON_STRAP_LED_OPEN_EN;
+		else
+			new_pos &= ~AR8327_POWER_ON_STRAP_LED_OPEN_EN;
+
+		ar8xxx_write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0);
+		ar8xxx_write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1);
+		ar8xxx_write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2);
+		ar8xxx_write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3);
+
+		if (new_pos != pos)
+			new_pos |= AR8327_POWER_ON_STRAP_POWER_ON_SEL;
+	}
+
+	if (pdata->sgmii_cfg) {
+		t = pdata->sgmii_cfg->sgmii_ctrl;
+		if (priv->chip_rev == 1)
+			t |= AR8327_SGMII_CTRL_EN_PLL |
+			     AR8327_SGMII_CTRL_EN_RX |
+			     AR8327_SGMII_CTRL_EN_TX;
+		else
+			t &= ~(AR8327_SGMII_CTRL_EN_PLL |
+			       AR8327_SGMII_CTRL_EN_RX |
+			       AR8327_SGMII_CTRL_EN_TX);
+
+		ar8xxx_write(priv, AR8327_REG_SGMII_CTRL, t);
+
+		if (pdata->sgmii_cfg->serdes_aen)
+			new_pos &= ~AR8327_POWER_ON_STRAP_SERDES_AEN;
+		else
+			new_pos |= AR8327_POWER_ON_STRAP_SERDES_AEN;
+	}
+
+	ar8xxx_write(priv, AR8327_REG_POWER_ON_STRAP, new_pos);
+
+	if (pdata->leds && pdata->num_leds) {
+		int i;
+
+		data->leds = kzalloc(pdata->num_leds * sizeof(void *),
+				     GFP_KERNEL);
+		if (!data->leds)
+			return -ENOMEM;
+
+		for (i = 0; i < pdata->num_leds; i++)
+			ar8327_led_create(priv, &pdata->leds[i]);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static int
+ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np)
+{
+	struct ar8327_data *data = priv->chip_data;
+	const __be32 *paddr;
+	int len;
+	int i;
+
+	paddr = of_get_property(np, "qca,ar8327-initvals", &len);
+	if (!paddr || len < (2 * sizeof(*paddr)))
+		return -EINVAL;
+
+	len /= sizeof(*paddr);
+
+	for (i = 0; i < len - 1; i += 2) {
+		u32 reg;
+		u32 val;
+
+		reg = be32_to_cpup(paddr + i);
+		val = be32_to_cpup(paddr + i + 1);
+
+		switch (reg) {
+		case AR8327_REG_PORT_STATUS(0):
+			data->port0_status = val;
+			break;
+		case AR8327_REG_PORT_STATUS(6):
+			data->port6_status = val;
+			break;
+		default:
+			ar8xxx_write(priv, reg, val);
+			break;
+		}
+	}
+
+	return 0;
+}
+#else
+static inline int
+ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np)
+{
+	return -EINVAL;
+}
+#endif
+
+static int
+ar8327_hw_init(struct ar8xxx_priv *priv)
+{
+	int ret;
+
+	priv->chip_data = kzalloc(sizeof(struct ar8327_data), GFP_KERNEL);
+	if (!priv->chip_data)
+		return -ENOMEM;
+
+	if (priv->pdev->of_node)
+		ret = ar8327_hw_config_of(priv, priv->pdev->of_node);
+	else
+		ret = ar8327_hw_config_pdata(priv,
+					     priv->phy->mdio.dev.platform_data);
+
+	if (ret)
+		return ret;
+
+	ar8327_leds_init(priv);
+
+	ar8xxx_phy_init(priv);
+
+	return 0;
+}
+
+static void
+ar8327_cleanup(struct ar8xxx_priv *priv)
+{
+	ar8327_leds_cleanup(priv);
+}
+
+static void
+ar8327_init_globals(struct ar8xxx_priv *priv)
+{
+	struct ar8327_data *data = priv->chip_data;
+	u32 t;
+	int i;
+
+	/* enable CPU port and disable mirror port */
+	t = AR8327_FWD_CTRL0_CPU_PORT_EN |
+	    AR8327_FWD_CTRL0_MIRROR_PORT;
+	ar8xxx_write(priv, AR8327_REG_FWD_CTRL0, t);
+
+	/* forward multicast and broadcast frames to CPU */
+	t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) |
+	    (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) |
+	    (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S);
+	ar8xxx_write(priv, AR8327_REG_FWD_CTRL1, t);
+
+	/* enable jumbo frames */
+	ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE,
+		   AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2);
+
+	/* Enable MIB counters */
+	ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN,
+		       AR8327_MODULE_EN_MIB);
+
+	/* Disable EEE on all phy's due to stability issues */
+	for (i = 0; i < AR8XXX_NUM_PHYS; i++)
+		data->eee[i] = false;
+}
+
+static void
+ar8327_init_port(struct ar8xxx_priv *priv, int port)
+{
+	struct ar8327_data *data = priv->chip_data;
+	u32 t;
+
+	if (port == AR8216_PORT_CPU)
+		t = data->port0_status;
+	else if (port == 6)
+		t = data->port6_status;
+	else
+		t = AR8216_PORT_STATUS_LINK_AUTO;
+
+	if (port != AR8216_PORT_CPU && port != 6) {
+		/*hw limitation:if configure mac when there is traffic,
+		port MAC may work abnormal. Need disable lan&wan mac at fisrt*/
+		ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0);
+		msleep(100);
+		t |= AR8216_PORT_STATUS_FLOW_CONTROL;
+		ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t);
+	} else {
+		ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t);
+	}
+
+	ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0);
+
+	ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0);
+
+	t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S;
+	ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t);
+
+	t = AR8327_PORT_LOOKUP_LEARN;
+	t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S;
+	ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t);
+}
+
+static u32
+ar8327_read_port_status(struct ar8xxx_priv *priv, int port)
+{
+	u32 t;
+
+	t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port));
+	/* map the flow control autoneg result bits to the flow control bits
+	 * used in forced mode to allow ar8216_read_port_link detect
+	 * flow control properly if autoneg is used
+	 */
+	if (t & AR8216_PORT_STATUS_LINK_UP &&
+	    t & AR8216_PORT_STATUS_LINK_AUTO) {
+		t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW);
+		if (t & AR8327_PORT_STATUS_TXFLOW_AUTO)
+			t |= AR8216_PORT_STATUS_TXFLOW;
+		if (t & AR8327_PORT_STATUS_RXFLOW_AUTO)
+			t |= AR8216_PORT_STATUS_RXFLOW;
+	}
+
+	return t;
+}
+
+static u32
+ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port)
+{
+	int phy;
+	u16 t;
+
+	if (port >= priv->dev.ports)
+		return 0;
+
+	if (port == 0 || port == 6)
+		return 0;
+
+	phy = port - 1;
+
+	/* EEE Ability Auto-negotiation Result */
+	t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000);
+
+	return mmd_eee_adv_to_ethtool_adv_t(t);
+}
+
+static int
+ar8327_atu_flush(struct ar8xxx_priv *priv)
+{
+	int ret;
+
+	ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC,
+			      AR8327_ATU_FUNC_BUSY, 0);
+	if (!ret)
+		ar8xxx_write(priv, AR8327_REG_ATU_FUNC,
+			     AR8327_ATU_FUNC_OP_FLUSH |
+			     AR8327_ATU_FUNC_BUSY);
+
+	return ret;
+}
+
+static int
+ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port)
+{
+	u32 t;
+	int ret;
+
+	ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC,
+			      AR8327_ATU_FUNC_BUSY, 0);
+	if (!ret) {
+		t = (port << AR8327_ATU_PORT_NUM_S);
+		t |= AR8327_ATU_FUNC_OP_FLUSH_PORT;
+		t |= AR8327_ATU_FUNC_BUSY;
+		ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t);
+	}
+
+	return ret;
+}
+
+static int
+ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port)
+{
+	u32 fwd_ctrl, frame_ack;
+
+	fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S);
+	frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD |
+		      AR8327_FRAME_ACK_CTRL_IGMP_JOIN |
+		      AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) <<
+		     AR8327_FRAME_ACK_CTRL_S(port));
+
+	return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) &
+			fwd_ctrl) == fwd_ctrl &&
+		(ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) &
+			frame_ack) == frame_ack;
+}
+
+static void
+ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable)
+{
+	int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port);
+	u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD |
+			  AR8327_FRAME_ACK_CTRL_IGMP_JOIN |
+			  AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) <<
+			 AR8327_FRAME_ACK_CTRL_S(port);
+
+	if (enable) {
+		ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1,
+			   BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S,
+			   BIT(port) << AR8327_FWD_CTRL1_IGMP_S);
+		ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack);
+	} else {
+		ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1,
+			   BIT(port) << AR8327_FWD_CTRL1_IGMP_S,
+			   BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S);
+		ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack);
+	}
+}
+
+static void
+ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val)
+{
+	if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1,
+			    AR8327_VTU_FUNC1_BUSY, 0))
+		return;
+
+	if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD)
+		ar8xxx_write(priv, AR8327_REG_VTU_FUNC0, val);
+
+	op |= AR8327_VTU_FUNC1_BUSY;
+	ar8xxx_write(priv, AR8327_REG_VTU_FUNC1, op);
+}
+
+static void
+ar8327_vtu_flush(struct ar8xxx_priv *priv)
+{
+	ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0);
+}
+
+static void
+ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask)
+{
+	u32 op;
+	u32 val;
+	int i;
+
+	op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S);
+	val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL;
+	for (i = 0; i < AR8327_NUM_PORTS; i++) {
+		u32 mode;
+
+		if ((port_mask & BIT(i)) == 0)
+			mode = AR8327_VTU_FUNC0_EG_MODE_NOT;
+		else if (priv->vlan == 0)
+			mode = AR8327_VTU_FUNC0_EG_MODE_KEEP;
+		else if ((priv->vlan_tagged & BIT(i)) || (priv->vlan_id[priv->pvid[i]] != vid))
+			mode = AR8327_VTU_FUNC0_EG_MODE_TAG;
+		else
+			mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG;
+
+		val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i);
+	}
+	ar8327_vtu_op(priv, op, val);
+}
+
+static void
+ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 members)
+{
+	u32 t;
+	u32 egress, ingress;
+	u32 pvid = priv->vlan_id[priv->pvid[port]];
+
+	if (priv->vlan) {
+		egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD;
+		ingress = AR8216_IN_SECURE;
+	} else {
+		egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH;
+		ingress = AR8216_IN_PORT_ONLY;
+	}
+
+	t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S;
+	t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S;
+	if (priv->vlan && priv->port_vlan_prio[port]) {
+		u32 prio = priv->port_vlan_prio[port];
+
+		t |= prio << AR8327_PORT_VLAN0_DEF_SPRI_S;
+		t |= prio << AR8327_PORT_VLAN0_DEF_CPRI_S;
+	}
+	ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t);
+
+	t = AR8327_PORT_VLAN1_PORT_VLAN_PROP;
+	t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S;
+	if (priv->vlan && priv->port_vlan_prio[port])
+		t |= AR8327_PORT_VLAN1_VLAN_PRI_PROP;
+
+	ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t);
+
+	t = members;
+	t |= AR8327_PORT_LOOKUP_LEARN;
+	t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S;
+	t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S;
+	ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t);
+}
+
+static int
+ar8327_sw_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	u8 ports = priv->vlan_table[val->port_vlan];
+	int i;
+
+	val->len = 0;
+	for (i = 0; i < dev->ports; i++) {
+		struct switch_port *p;
+
+		if (!(ports & (1 << i)))
+			continue;
+
+		p = &val->value.ports[val->len++];
+		p->id = i;
+		if ((priv->vlan_tagged & (1 << i)) || (priv->pvid[i] != val->port_vlan))
+			p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+		else
+			p->flags = 0;
+	}
+	return 0;
+}
+
+static int
+ar8327_sw_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	u8 *vt = &priv->vlan_table[val->port_vlan];
+	int i;
+
+	*vt = 0;
+	for (i = 0; i < val->len; i++) {
+		struct switch_port *p = &val->value.ports[i];
+
+		if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) {
+			if (val->port_vlan == priv->pvid[p->id]) {
+				priv->vlan_tagged |= (1 << p->id);
+			}
+		} else {
+			priv->vlan_tagged &= ~(1 << p->id);
+			priv->pvid[p->id] = val->port_vlan;
+		}
+
+		*vt |= 1 << p->id;
+	}
+	return 0;
+}
+
+static void
+ar8327_set_mirror_regs(struct ar8xxx_priv *priv)
+{
+	int port;
+
+	/* reset all mirror registers */
+	ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0,
+		   AR8327_FWD_CTRL0_MIRROR_PORT,
+		   (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S));
+	for (port = 0; port < AR8327_NUM_PORTS; port++) {
+		ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port),
+			   AR8327_PORT_LOOKUP_ING_MIRROR_EN);
+
+		ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port),
+			   AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN);
+	}
+
+	/* now enable mirroring if necessary */
+	if (priv->source_port >= AR8327_NUM_PORTS ||
+	    priv->monitor_port >= AR8327_NUM_PORTS ||
+	    priv->source_port == priv->monitor_port) {
+		return;
+	}
+
+	ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0,
+		   AR8327_FWD_CTRL0_MIRROR_PORT,
+		   (priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S));
+
+	if (priv->mirror_rx)
+		ar8xxx_reg_set(priv, AR8327_REG_PORT_LOOKUP(priv->source_port),
+			   AR8327_PORT_LOOKUP_ING_MIRROR_EN);
+
+	if (priv->mirror_tx)
+		ar8xxx_reg_set(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port),
+			   AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN);
+}
+
+static int
+ar8327_sw_set_eee(struct switch_dev *dev,
+		  const struct switch_attr *attr,
+		  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	struct ar8327_data *data = priv->chip_data;
+	int port = val->port_vlan;
+	int phy;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+	if (port == 0 || port == 6)
+		return -EOPNOTSUPP;
+
+	phy = port - 1;
+
+	data->eee[phy] = !!(val->value.i);
+
+	return 0;
+}
+
+static int
+ar8327_sw_get_eee(struct switch_dev *dev,
+		  const struct switch_attr *attr,
+		  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	const struct ar8327_data *data = priv->chip_data;
+	int port = val->port_vlan;
+	int phy;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+	if (port == 0 || port == 6)
+		return -EOPNOTSUPP;
+
+	phy = port - 1;
+
+	val->value.i = data->eee[phy];
+
+	return 0;
+}
+
+static void
+ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1)
+{
+	int timeout = 20;
+
+	while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) {
+		udelay(10);
+		cond_resched();
+	}
+
+	if (!timeout)
+		pr_err("ar8327: timeout waiting for atu to become ready\n");
+}
+
+static void ar8327_get_arl_entry(struct ar8xxx_priv *priv,
+				 struct arl_entry *a, u32 *status, enum arl_op op)
+{
+	struct mii_bus *bus = priv->mii_bus;
+	u16 r2, page;
+	u16 r1_data0, r1_data1, r1_data2, r1_func;
+	u32 val0, val1, val2;
+
+	split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page);
+	r2 |= 0x10;
+
+	r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e;
+	r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e;
+	r1_func  = (AR8327_REG_ATU_FUNC >> 1) & 0x1e;
+
+	switch (op) {
+	case AR8XXX_ARL_INITIALIZE:
+		/* all ATU registers are on the same page
+		* therefore set page only once
+		*/
+		bus->write(bus, 0x18, 0, page);
+		wait_for_page_switch();
+
+		ar8327_wait_atu_ready(priv, r2, r1_func);
+
+		ar8xxx_mii_write32(priv, r2, r1_data0, 0);
+		ar8xxx_mii_write32(priv, r2, r1_data1, 0);
+		ar8xxx_mii_write32(priv, r2, r1_data2, 0);
+		break;
+	case AR8XXX_ARL_GET_NEXT:
+		ar8xxx_mii_write32(priv, r2, r1_func,
+				   AR8327_ATU_FUNC_OP_GET_NEXT |
+				   AR8327_ATU_FUNC_BUSY);
+		ar8327_wait_atu_ready(priv, r2, r1_func);
+
+		val0 = ar8xxx_mii_read32(priv, r2, r1_data0);
+		val1 = ar8xxx_mii_read32(priv, r2, r1_data1);
+		val2 = ar8xxx_mii_read32(priv, r2, r1_data2);
+
+		*status = val2 & AR8327_ATU_STATUS;
+		if (!*status)
+			break;
+
+		a->portmap = (val1 & AR8327_ATU_PORTS) >> AR8327_ATU_PORTS_S;
+		a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S;
+		a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S;
+		a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S;
+		a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S;
+		a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S;
+		a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S;
+		break;
+	}
+}
+
+static int
+ar8327_sw_hw_apply(struct switch_dev *dev)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	const struct ar8327_data *data = priv->chip_data;
+	int ret, i;
+
+	ret = ar8xxx_sw_hw_apply(dev);
+	if (ret)
+		return ret;
+
+	for (i=0; i < AR8XXX_NUM_PHYS; i++) {
+		if (data->eee[i])
+			ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL,
+			       AR8327_EEE_CTRL_DISABLE_PHY(i));
+		else
+			ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL,
+			       AR8327_EEE_CTRL_DISABLE_PHY(i));
+	}
+
+	return 0;
+}
+
+int
+ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	val->value.i = ar8327_get_port_igmp(priv, port);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+
+	mutex_lock(&priv->reg_mutex);
+	ar8327_set_port_igmp(priv, port, val->value.i);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_get_igmp_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	int port;
+
+	for (port = 0; port < dev->ports; port++) {
+		val->port_vlan = port;
+		if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) ||
+		    !val->value.i)
+			break;
+	}
+
+	return 0;
+}
+
+int
+ar8327_sw_set_igmp_snooping(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	int port;
+
+	for (port = 0; port < dev->ports; port++) {
+		val->port_vlan = port;
+		if (ar8327_sw_set_port_igmp_snooping(dev, attr, val))
+			break;
+	}
+
+	return 0;
+}
+
+int
+ar8327_sw_get_igmp_v3(struct switch_dev *dev,
+		      const struct switch_attr *attr,
+		      struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	u32 val_reg;
+
+	mutex_lock(&priv->reg_mutex);
+	val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1);
+	val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+int
+ar8327_sw_set_igmp_v3(struct switch_dev *dev,
+		      const struct switch_attr *attr,
+		      struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+	mutex_lock(&priv->reg_mutex);
+	if (val->value.i)
+		ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1,
+			       AR8327_FRAME_ACK_CTRL_IGMP_V3_EN);
+	else
+		ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1,
+				 AR8327_FRAME_ACK_CTRL_IGMP_V3_EN);
+	mutex_unlock(&priv->reg_mutex);
+
+	return 0;
+}
+
+static int
+ar8327_sw_set_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr,
+			     struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	if (port >= dev->ports)
+		return -EINVAL;
+	if (port == 0 || port == 6)
+		return -EOPNOTSUPP;
+	if (val->value.i < 0 || val->value.i > 7)
+		return -EINVAL;
+
+	priv->port_vlan_prio[port] = val->value.i;
+
+	return 0;
+}
+
+static int
+ar8327_sw_get_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr,
+                  struct switch_val *val)
+{
+	struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+	int port = val->port_vlan;
+
+	val->value.i = priv->port_vlan_prio[port];
+
+	return 0;
+}
+
+static const struct switch_attr ar8327_sw_attr_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = ar8xxx_sw_set_vlan,
+		.get = ar8xxx_sw_get_vlan,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mibs",
+		.description = "Reset all MIB counters",
+		.set = ar8xxx_sw_set_reset_mibs,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "ar8xxx_mib_poll_interval",
+		.description = "MIB polling interval in msecs (0 to disable)",
+		.set = ar8xxx_sw_set_mib_poll_interval,
+		.get = ar8xxx_sw_get_mib_poll_interval
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "ar8xxx_mib_type",
+		.description = "MIB type (0=basic 1=extended)",
+		.set = ar8xxx_sw_set_mib_type,
+		.get = ar8xxx_sw_get_mib_type
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_rx",
+		.description = "Enable mirroring of RX packets",
+		.set = ar8xxx_sw_set_mirror_rx_enable,
+		.get = ar8xxx_sw_get_mirror_rx_enable,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_tx",
+		.description = "Enable mirroring of TX packets",
+		.set = ar8xxx_sw_set_mirror_tx_enable,
+		.get = ar8xxx_sw_get_mirror_tx_enable,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_monitor_port",
+		.description = "Mirror monitor port",
+		.set = ar8xxx_sw_set_mirror_monitor_port,
+		.get = ar8xxx_sw_get_mirror_monitor_port,
+		.max = AR8327_NUM_PORTS - 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_source_port",
+		.description = "Mirror source port",
+		.set = ar8xxx_sw_set_mirror_source_port,
+		.get = ar8xxx_sw_get_mirror_source_port,
+		.max = AR8327_NUM_PORTS - 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "arl_age_time",
+		.description = "ARL age time (secs)",
+		.set = ar8xxx_sw_set_arl_age_time,
+		.get = ar8xxx_sw_get_arl_age_time,
+	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "arl_table",
+		.description = "Get ARL table",
+		.set = NULL,
+		.get = ar8xxx_sw_get_arl_table,
+	},
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "flush_arl_table",
+		.description = "Flush ARL table",
+		.set = ar8xxx_sw_set_flush_arl_table,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_snooping",
+		.description = "Enable IGMP Snooping",
+		.set = ar8327_sw_set_igmp_snooping,
+		.get = ar8327_sw_get_igmp_snooping,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_v3",
+		.description = "Enable IGMPv3 support",
+		.set = ar8327_sw_set_igmp_v3,
+		.get = ar8327_sw_get_igmp_v3,
+		.max = 1
+	},
+};
+
+static const struct switch_attr ar8327_sw_attr_port[] = {
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mib",
+		.description = "Reset single port MIB counters",
+		.set = ar8xxx_sw_set_port_reset_mib,
+	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get port's MIB counters",
+		.set = NULL,
+		.get = ar8xxx_sw_get_port_mib,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_eee",
+		.description = "Enable EEE PHY sleep mode",
+		.set = ar8327_sw_set_eee,
+		.get = ar8327_sw_get_eee,
+		.max = 1,
+	},
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "flush_arl_table",
+		.description = "Flush port's ARL table entries",
+		.set = ar8xxx_sw_set_flush_port_arl_table,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "igmp_snooping",
+		.description = "Enable port's IGMP Snooping",
+		.set = ar8327_sw_set_port_igmp_snooping,
+		.get = ar8327_sw_get_port_igmp_snooping,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "vlan_prio",
+		.description = "Port VLAN default priority (VLAN PCP) (0-7)",
+		.set = ar8327_sw_set_port_vlan_prio,
+		.get = ar8327_sw_get_port_vlan_prio,
+		.max = 7,
+	},
+};
+
+static const struct switch_dev_ops ar8327_sw_ops = {
+	.attr_global = {
+		.attr = ar8327_sw_attr_globals,
+		.n_attr = ARRAY_SIZE(ar8327_sw_attr_globals),
+	},
+	.attr_port = {
+		.attr = ar8327_sw_attr_port,
+		.n_attr = ARRAY_SIZE(ar8327_sw_attr_port),
+	},
+	.attr_vlan = {
+		.attr = ar8xxx_sw_attr_vlan,
+		.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan),
+	},
+	.get_port_pvid = ar8xxx_sw_get_pvid,
+	.set_port_pvid = ar8xxx_sw_set_pvid,
+	.get_vlan_ports = ar8327_sw_get_ports,
+	.set_vlan_ports = ar8327_sw_set_ports,
+	.apply_config = ar8327_sw_hw_apply,
+	.reset_switch = ar8xxx_sw_reset_switch,
+	.get_port_link = ar8xxx_sw_get_port_link,
+	.get_port_stats = ar8xxx_sw_get_port_stats,
+};
+
+const struct ar8xxx_chip ar8327_chip = {
+	.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS,
+	.config_at_probe = true,
+	.mii_lo_first = true,
+
+	.name = "Atheros AR8327",
+	.ports = AR8327_NUM_PORTS,
+	.vlans = AR83X7_MAX_VLANS,
+	.swops = &ar8327_sw_ops,
+
+	.reg_port_stats_start = 0x1000,
+	.reg_port_stats_length = 0x100,
+	.reg_arl_ctrl = AR8327_REG_ARL_CTRL,
+
+	.hw_init = ar8327_hw_init,
+	.cleanup = ar8327_cleanup,
+	.init_globals = ar8327_init_globals,
+	.init_port = ar8327_init_port,
+	.setup_port = ar8327_setup_port,
+	.read_port_status = ar8327_read_port_status,
+	.read_port_eee_status = ar8327_read_port_eee_status,
+	.atu_flush = ar8327_atu_flush,
+	.atu_flush_port = ar8327_atu_flush_port,
+	.vtu_flush = ar8327_vtu_flush,
+	.vtu_load_vlan = ar8327_vtu_load_vlan,
+	.phy_fixup = ar8327_phy_fixup,
+	.set_mirror_regs = ar8327_set_mirror_regs,
+	.get_arl_entry = ar8327_get_arl_entry,
+	.sw_hw_apply = ar8327_sw_hw_apply,
+
+	.num_mibs = ARRAY_SIZE(ar8236_mibs),
+	.mib_decs = ar8236_mibs,
+	.mib_func = AR8327_REG_MIB_FUNC,
+	.mib_rxb_id = AR8236_MIB_RXB_ID,
+	.mib_txb_id = AR8236_MIB_TXB_ID,
+};
+
+const struct ar8xxx_chip ar8337_chip = {
+	.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS,
+	.config_at_probe = true,
+	.mii_lo_first = true,
+
+	.name = "Atheros AR8337",
+	.ports = AR8327_NUM_PORTS,
+	.vlans = AR83X7_MAX_VLANS,
+	.swops = &ar8327_sw_ops,
+
+	.reg_port_stats_start = 0x1000,
+	.reg_port_stats_length = 0x100,
+	.reg_arl_ctrl = AR8327_REG_ARL_CTRL,
+
+	.hw_init = ar8327_hw_init,
+	.cleanup = ar8327_cleanup,
+	.init_globals = ar8327_init_globals,
+	.init_port = ar8327_init_port,
+	.setup_port = ar8327_setup_port,
+	.read_port_status = ar8327_read_port_status,
+	.read_port_eee_status = ar8327_read_port_eee_status,
+	.atu_flush = ar8327_atu_flush,
+	.atu_flush_port = ar8327_atu_flush_port,
+	.vtu_flush = ar8327_vtu_flush,
+	.vtu_load_vlan = ar8327_vtu_load_vlan,
+	.phy_fixup = ar8327_phy_fixup,
+	.set_mirror_regs = ar8327_set_mirror_regs,
+	.get_arl_entry = ar8327_get_arl_entry,
+	.sw_hw_apply = ar8327_sw_hw_apply,
+	.phy_rgmii_set = ar8327_phy_rgmii_set,
+
+	.num_mibs = ARRAY_SIZE(ar8236_mibs),
+	.mib_decs = ar8236_mibs,
+	.mib_func = AR8327_REG_MIB_FUNC,
+	.mib_rxb_id = AR8236_MIB_RXB_ID,
+	.mib_txb_id = AR8236_MIB_TXB_ID,
+};
diff -Naur linux-orig/drivers/net/phy/ar8327.h linux/drivers/net/phy/ar8327.h
--- linux-orig/drivers/net/phy/ar8327.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/ar8327.h	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,333 @@
+/*
+ * ar8327.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __AR8327_H
+#define __AR8327_H
+
+#define AR8327_NUM_PORTS	7
+#define AR8327_NUM_LEDS		15
+#define AR8327_PORTS_ALL	0x7f
+#define AR8327_NUM_LED_CTRL_REGS	4
+
+#define AR8327_REG_MASK				0x000
+
+#define AR8327_REG_PAD0_MODE			0x004
+#define AR8327_REG_PAD5_MODE			0x008
+#define AR8327_REG_PAD6_MODE			0x00c
+#define   AR8327_PAD_MAC_MII_RXCLK_SEL		BIT(0)
+#define   AR8327_PAD_MAC_MII_TXCLK_SEL		BIT(1)
+#define   AR8327_PAD_MAC_MII_EN			BIT(2)
+#define   AR8327_PAD_MAC_GMII_RXCLK_SEL		BIT(4)
+#define   AR8327_PAD_MAC_GMII_TXCLK_SEL		BIT(5)
+#define   AR8327_PAD_MAC_GMII_EN		BIT(6)
+#define   AR8327_PAD_SGMII_EN			BIT(7)
+#define   AR8327_PAD_PHY_MII_RXCLK_SEL		BIT(8)
+#define   AR8327_PAD_PHY_MII_TXCLK_SEL		BIT(9)
+#define   AR8327_PAD_PHY_MII_EN			BIT(10)
+#define   AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL	BIT(11)
+#define   AR8327_PAD_PHY_GMII_RXCLK_SEL		BIT(12)
+#define   AR8327_PAD_PHY_GMII_TXCLK_SEL		BIT(13)
+#define   AR8327_PAD_PHY_GMII_EN		BIT(14)
+#define   AR8327_PAD_PHYX_GMII_EN		BIT(16)
+#define   AR8327_PAD_PHYX_RGMII_EN		BIT(17)
+#define   AR8327_PAD_PHYX_MII_EN		BIT(18)
+#define   AR8327_PAD_SGMII_DELAY_EN		BIT(19)
+#define   AR8327_PAD_RGMII_RXCLK_DELAY_SEL	BITS(20, 2)
+#define   AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S	20
+#define   AR8327_PAD_RGMII_TXCLK_DELAY_SEL	BITS(22, 2)
+#define   AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S	22
+#define   AR8327_PAD_RGMII_RXCLK_DELAY_EN	BIT(24)
+#define   AR8327_PAD_RGMII_TXCLK_DELAY_EN	BIT(25)
+#define   AR8327_PAD_RGMII_EN			BIT(26)
+
+#define AR8327_REG_POWER_ON_STRAP		0x010
+#define   AR8327_POWER_ON_STRAP_POWER_ON_SEL	BIT(31)
+#define   AR8327_POWER_ON_STRAP_LED_OPEN_EN	BIT(24)
+#define   AR8327_POWER_ON_STRAP_SERDES_AEN	BIT(7)
+
+#define AR8327_REG_INT_STATUS0			0x020
+#define   AR8327_INT0_VT_DONE			BIT(20)
+
+#define AR8327_REG_INT_STATUS1			0x024
+#define AR8327_REG_INT_MASK0			0x028
+#define AR8327_REG_INT_MASK1			0x02c
+
+#define AR8327_REG_MODULE_EN			0x030
+#define   AR8327_MODULE_EN_MIB			BIT(0)
+
+#define AR8327_REG_MIB_FUNC			0x034
+#define   AR8327_MIB_CPU_KEEP			BIT(20)
+
+#define AR8327_REG_SERVICE_TAG			0x048
+#define AR8327_REG_LED_CTRL(_i)			(0x050 + (_i) * 4)
+#define AR8327_REG_LED_CTRL0			0x050
+#define AR8327_REG_LED_CTRL1			0x054
+#define AR8327_REG_LED_CTRL2			0x058
+#define AR8327_REG_LED_CTRL3			0x05c
+#define AR8327_REG_MAC_ADDR0			0x060
+#define AR8327_REG_MAC_ADDR1			0x064
+
+#define AR8327_REG_MAX_FRAME_SIZE		0x078
+#define   AR8327_MAX_FRAME_SIZE_MTU		BITS(0, 14)
+
+#define AR8327_REG_PORT_STATUS(_i)		(0x07c + (_i) * 4)
+#define   AR8327_PORT_STATUS_TXFLOW_AUTO	BIT(10)
+#define   AR8327_PORT_STATUS_RXFLOW_AUTO	BIT(11)
+
+#define AR8327_REG_HEADER_CTRL			0x098
+#define AR8327_REG_PORT_HEADER(_i)		(0x09c + (_i) * 4)
+
+#define AR8327_REG_SGMII_CTRL			0x0e0
+#define   AR8327_SGMII_CTRL_EN_PLL		BIT(1)
+#define   AR8327_SGMII_CTRL_EN_RX		BIT(2)
+#define   AR8327_SGMII_CTRL_EN_TX		BIT(3)
+
+#define AR8327_REG_EEE_CTRL			0x100
+#define   AR8327_EEE_CTRL_DISABLE_PHY(_i)	BIT(4 + (_i) * 2)
+
+#define AR8327_REG_FRAME_ACK_CTRL0		0x210
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0	BIT(0)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0	BIT(1)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0	BIT(2)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN0	BIT(3)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN0	BIT(4)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0	BIT(5)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0	BIT(6)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1	BIT(8)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1	BIT(9)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1	BIT(10)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN1	BIT(11)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN1	BIT(12)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1	BIT(13)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1	BIT(14)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2	BIT(16)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2	BIT(17)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2	BIT(18)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN2	BIT(19)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN2	BIT(20)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2	BIT(21)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2	BIT(22)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3	BIT(24)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3	BIT(25)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3	BIT(26)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN3	BIT(27)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN3	BIT(28)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3	BIT(29)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3	BIT(30)
+
+#define AR8327_REG_FRAME_ACK_CTRL1		0x214
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4	BIT(0)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4	BIT(1)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4	BIT(2)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN4	BIT(3)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN4	BIT(4)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4	BIT(5)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4	BIT(6)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5	BIT(8)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5	BIT(9)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5	BIT(10)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN5	BIT(11)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN5	BIT(12)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5	BIT(13)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5	BIT(14)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6	BIT(16)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6	BIT(17)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6	BIT(18)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL_EN6	BIT(19)
+#define   AR8327_FRAME_ACK_CTRL_DHCP_EN6	BIT(20)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6	BIT(21)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6	BIT(22)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_V3_EN	BIT(24)
+#define   AR8327_FRAME_ACK_CTRL_PPPOE_EN	BIT(25)
+
+#define AR8327_REG_FRAME_ACK_CTRL(_i)		(0x210 + ((_i) / 4) * 0x4)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_MLD	BIT(0)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_JOIN	BIT(1)
+#define   AR8327_FRAME_ACK_CTRL_IGMP_LEAVE	BIT(2)
+#define   AR8327_FRAME_ACK_CTRL_EAPOL		BIT(3)
+#define   AR8327_FRAME_ACK_CTRL_DHCP		BIT(4)
+#define   AR8327_FRAME_ACK_CTRL_ARP_ACK		BIT(5)
+#define   AR8327_FRAME_ACK_CTRL_ARP_REQ		BIT(6)
+#define   AR8327_FRAME_ACK_CTRL_S(_i)		(((_i) % 4) * 8)
+
+#define AR8327_REG_PORT_VLAN0(_i)		(0x420 + (_i) * 0x8)
+#define   AR8327_PORT_VLAN0_DEF_PRI_MASK	BITS(0, 3)
+#define   AR8327_PORT_VLAN0_DEF_SVID		BITS(0, 12)
+#define   AR8327_PORT_VLAN0_DEF_SVID_S		0
+#define   AR8327_PORT_VLAN0_DEF_SPRI		BITS(13, 3)
+#define   AR8327_PORT_VLAN0_DEF_SPRI_S		13
+#define   AR8327_PORT_VLAN0_DEF_CVID		BITS(16, 12)
+#define   AR8327_PORT_VLAN0_DEF_CVID_S		16
+#define   AR8327_PORT_VLAN0_DEF_CPRI		BITS(29, 3)
+#define   AR8327_PORT_VLAN0_DEF_CPRI_S		29
+
+#define AR8327_REG_PORT_VLAN1(_i)		(0x424 + (_i) * 0x8)
+#define   AR8327_PORT_VLAN1_VLAN_PRI_PROP	BIT(4)
+#define   AR8327_PORT_VLAN1_PORT_VLAN_PROP	BIT(6)
+#define   AR8327_PORT_VLAN1_OUT_MODE		BITS(12, 2)
+#define   AR8327_PORT_VLAN1_OUT_MODE_S		12
+#define   AR8327_PORT_VLAN1_OUT_MODE_UNMOD	0
+#define   AR8327_PORT_VLAN1_OUT_MODE_UNTAG	1
+#define   AR8327_PORT_VLAN1_OUT_MODE_TAG	2
+#define   AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH	3
+
+#define AR8327_REG_ATU_DATA0			0x600
+#define   AR8327_ATU_ADDR0			BITS(0, 8)
+#define   AR8327_ATU_ADDR0_S			0
+#define   AR8327_ATU_ADDR1			BITS(8, 8)
+#define   AR8327_ATU_ADDR1_S			8
+#define   AR8327_ATU_ADDR2			BITS(16, 8)
+#define   AR8327_ATU_ADDR2_S			16
+#define   AR8327_ATU_ADDR3			BITS(24, 8)
+#define   AR8327_ATU_ADDR3_S			24
+#define AR8327_REG_ATU_DATA1			0x604
+#define   AR8327_ATU_ADDR4			BITS(0, 8)
+#define   AR8327_ATU_ADDR4_S			0
+#define   AR8327_ATU_ADDR5			BITS(8, 8)
+#define   AR8327_ATU_ADDR5_S			8
+#define   AR8327_ATU_PORTS			BITS(16, 7)
+#define   AR8327_ATU_PORTS_S			16
+#define   AR8327_ATU_PORT0			BIT(16)
+#define   AR8327_ATU_PORT1			BIT(17)
+#define   AR8327_ATU_PORT2			BIT(18)
+#define   AR8327_ATU_PORT3			BIT(19)
+#define   AR8327_ATU_PORT4			BIT(20)
+#define   AR8327_ATU_PORT5			BIT(21)
+#define   AR8327_ATU_PORT6			BIT(22)
+#define AR8327_REG_ATU_DATA2			0x608
+#define   AR8327_ATU_STATUS			BITS(0, 4)
+
+#define AR8327_REG_ATU_FUNC			0x60c
+#define   AR8327_ATU_FUNC_OP			BITS(0, 4)
+#define   AR8327_ATU_FUNC_OP_NOOP		0x0
+#define   AR8327_ATU_FUNC_OP_FLUSH		0x1
+#define   AR8327_ATU_FUNC_OP_LOAD		0x2
+#define   AR8327_ATU_FUNC_OP_PURGE		0x3
+#define   AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED	0x4
+#define   AR8327_ATU_FUNC_OP_FLUSH_PORT		0x5
+#define   AR8327_ATU_FUNC_OP_GET_NEXT		0x6
+#define   AR8327_ATU_FUNC_OP_SEARCH_MAC		0x7
+#define   AR8327_ATU_FUNC_OP_CHANGE_TRUNK	0x8
+#define   AR8327_ATU_PORT_NUM			BITS(8, 4)
+#define   AR8327_ATU_PORT_NUM_S			8
+#define   AR8327_ATU_FUNC_BUSY			BIT(31)
+
+#define AR8327_REG_VTU_FUNC0			0x0610
+#define   AR8327_VTU_FUNC0_EG_MODE		BITS(4, 14)
+#define   AR8327_VTU_FUNC0_EG_MODE_S(_i)	(4 + (_i) * 2)
+#define   AR8327_VTU_FUNC0_EG_MODE_KEEP		0
+#define   AR8327_VTU_FUNC0_EG_MODE_UNTAG	1
+#define   AR8327_VTU_FUNC0_EG_MODE_TAG		2
+#define   AR8327_VTU_FUNC0_EG_MODE_NOT		3
+#define   AR8327_VTU_FUNC0_IVL			BIT(19)
+#define   AR8327_VTU_FUNC0_VALID		BIT(20)
+
+#define AR8327_REG_VTU_FUNC1			0x0614
+#define   AR8327_VTU_FUNC1_OP			BITS(0, 3)
+#define   AR8327_VTU_FUNC1_OP_NOOP		0
+#define   AR8327_VTU_FUNC1_OP_FLUSH		1
+#define   AR8327_VTU_FUNC1_OP_LOAD		2
+#define   AR8327_VTU_FUNC1_OP_PURGE		3
+#define   AR8327_VTU_FUNC1_OP_REMOVE_PORT	4
+#define   AR8327_VTU_FUNC1_OP_GET_NEXT		5
+#define   AR8327_VTU_FUNC1_OP_GET_ONE		6
+#define   AR8327_VTU_FUNC1_FULL			BIT(4)
+#define   AR8327_VTU_FUNC1_PORT			BIT(8, 4)
+#define   AR8327_VTU_FUNC1_PORT_S		8
+#define   AR8327_VTU_FUNC1_VID			BIT(16, 12)
+#define   AR8327_VTU_FUNC1_VID_S		16
+#define   AR8327_VTU_FUNC1_BUSY			BIT(31)
+
+#define AR8327_REG_ARL_CTRL			0x0618
+
+#define AR8327_REG_FWD_CTRL0			0x620
+#define   AR8327_FWD_CTRL0_CPU_PORT_EN		BIT(10)
+#define   AR8327_FWD_CTRL0_MIRROR_PORT		BITS(4, 4)
+#define   AR8327_FWD_CTRL0_MIRROR_PORT_S	4
+
+#define AR8327_REG_FWD_CTRL1			0x624
+#define   AR8327_FWD_CTRL1_UC_FLOOD		BITS(0, 7)
+#define   AR8327_FWD_CTRL1_UC_FLOOD_S		0
+#define   AR8327_FWD_CTRL1_MC_FLOOD		BITS(8, 7)
+#define   AR8327_FWD_CTRL1_MC_FLOOD_S		8
+#define   AR8327_FWD_CTRL1_BC_FLOOD		BITS(16, 7)
+#define   AR8327_FWD_CTRL1_BC_FLOOD_S		16
+#define   AR8327_FWD_CTRL1_IGMP			BITS(24, 7)
+#define   AR8327_FWD_CTRL1_IGMP_S		24
+
+#define AR8327_REG_PORT_LOOKUP(_i)		(0x660 + (_i) * 0xc)
+#define   AR8327_PORT_LOOKUP_MEMBER		BITS(0, 7)
+#define   AR8327_PORT_LOOKUP_IN_MODE		BITS(8, 2)
+#define   AR8327_PORT_LOOKUP_IN_MODE_S		8
+#define   AR8327_PORT_LOOKUP_STATE		BITS(16, 3)
+#define   AR8327_PORT_LOOKUP_STATE_S		16
+#define   AR8327_PORT_LOOKUP_LEARN		BIT(20)
+#define   AR8327_PORT_LOOKUP_ING_MIRROR_EN	BIT(25)
+
+#define AR8327_REG_PORT_PRIO(_i)		(0x664 + (_i) * 0xc)
+
+#define AR8327_REG_PORT_HOL_CTRL1(_i)		(0x974 + (_i) * 0x8)
+#define   AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN	BIT(16)
+
+#define AR8337_PAD_MAC06_EXCHANGE_EN		BIT(31)
+
+#define AR8327_PHY_MODE_SEL			0x12
+#define   AR8327_PHY_MODE_SEL_RGMII		BIT(3)
+#define AR8327_PHY_TEST_CTRL			0x0
+#define   AR8327_PHY_TEST_CTRL_RGMII_RX_DELAY	BIT(15)
+#define AR8327_PHY_SYS_CTRL			0x5
+#define   AR8327_PHY_SYS_CTRL_RGMII_TX_DELAY	BIT(8)
+
+enum ar8327_led_pattern {
+	AR8327_LED_PATTERN_OFF = 0,
+	AR8327_LED_PATTERN_BLINK,
+	AR8327_LED_PATTERN_ON,
+	AR8327_LED_PATTERN_RULE,
+};
+
+struct ar8327_led_entry {
+	unsigned reg;
+	unsigned shift;
+};
+
+struct ar8327_led {
+	struct led_classdev cdev;
+	struct ar8xxx_priv *sw_priv;
+
+	char *name;
+	bool active_low;
+	u8 led_num;
+	enum ar8327_led_mode mode;
+
+	struct mutex mutex;
+	spinlock_t lock;
+	struct work_struct led_work;
+	bool enable_hw_mode;
+	enum ar8327_led_pattern pattern;
+};
+
+struct ar8327_data {
+	u32 port0_status;
+	u32 port6_status;
+
+	struct ar8327_led **leds;
+	unsigned int num_leds;
+
+	/* all fields below are cleared on reset */
+	bool eee[AR8XXX_NUM_PHYS];
+};
+
+#endif
diff -Naur linux-orig/drivers/net/phy/b53/Kconfig linux/drivers/net/phy/b53/Kconfig
--- linux-orig/drivers/net/phy/b53/Kconfig	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/Kconfig	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,37 @@
+menuconfig SWCONFIG_B53
+	tristate "Broadcom bcm53xx managed switch support"
+	depends on SWCONFIG
+	help
+	  This driver adds support for Broadcom managed switch chips. It supports
+	  BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX
+	  integrated switches.
+
+config SWCONFIG_B53_SPI_DRIVER
+	tristate "B53 SPI connected switch driver"
+	depends on SWCONFIG_B53 && SPI
+	help
+	  Select to enable support for registering switches configured through SPI.
+
+config SWCONFIG_B53_PHY_DRIVER
+	tristate "B53 MDIO connected switch driver"
+	depends on SWCONFIG_B53
+	select SWCONFIG_B53_PHY_FIXUP
+	help
+	  Select to enable support for registering switches configured through MDIO.
+
+config SWCONFIG_B53_MMAP_DRIVER
+	tristate "B53 MMAP connected switch driver"
+	depends on SWCONFIG_B53
+	help
+	  Select to enable support for memory-mapped switches like the BCM63XX
+	  integrated switches.
+
+config SWCONFIG_B53_SRAB_DRIVER
+	tristate "B53 SRAB connected switch driver"
+	depends on SWCONFIG_B53
+	help
+	  Select to enable support for memory-mapped Switch Register Access
+	  Bridge Registers (SRAB) like it is found on the BCM53010
+
+config SWCONFIG_B53_PHY_FIXUP
+	bool
diff -Naur linux-orig/drivers/net/phy/b53/Makefile linux/drivers/net/phy/b53/Makefile
--- linux-orig/drivers/net/phy/b53/Makefile	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/Makefile	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,10 @@
+obj-$(CONFIG_SWCONFIG_B53)		+= b53_common.o
+
+obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP)	+= b53_phy_fixup.o
+
+obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER)	+= b53_mmap.o
+obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER)	+= b53_srab.o
+obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER)	+= b53_mdio.o
+obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER)	+= b53_spi.o
+
+ccflags-y				+= -Werror
diff -Naur linux-orig/drivers/net/phy/b53/b53_common.c linux/drivers/net/phy/b53/b53_common.c
--- linux-orig/drivers/net/phy/b53/b53_common.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_common.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1730 @@
+/*
+ * B53 switch driver main logic
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_data/b53.h>
+
+#include "b53_regs.h"
+#include "b53_priv.h"
+
+/* buffer size needed for displaying all MIBs with max'd values */
+#define B53_BUF_SIZE	1188
+
+struct b53_mib_desc {
+	u8 size;
+	u8 offset;
+	const char *name;
+};
+
+/* BCM5365 MIB counters */
+static const struct b53_mib_desc b53_mibs_65[] = {
+	{ 8, 0x00, "TxOctets" },
+	{ 4, 0x08, "TxDropPkts" },
+	{ 4, 0x10, "TxBroadcastPkts" },
+	{ 4, 0x14, "TxMulticastPkts" },
+	{ 4, 0x18, "TxUnicastPkts" },
+	{ 4, 0x1c, "TxCollisions" },
+	{ 4, 0x20, "TxSingleCollision" },
+	{ 4, 0x24, "TxMultipleCollision" },
+	{ 4, 0x28, "TxDeferredTransmit" },
+	{ 4, 0x2c, "TxLateCollision" },
+	{ 4, 0x30, "TxExcessiveCollision" },
+	{ 4, 0x38, "TxPausePkts" },
+	{ 8, 0x44, "RxOctets" },
+	{ 4, 0x4c, "RxUndersizePkts" },
+	{ 4, 0x50, "RxPausePkts" },
+	{ 4, 0x54, "Pkts64Octets" },
+	{ 4, 0x58, "Pkts65to127Octets" },
+	{ 4, 0x5c, "Pkts128to255Octets" },
+	{ 4, 0x60, "Pkts256to511Octets" },
+	{ 4, 0x64, "Pkts512to1023Octets" },
+	{ 4, 0x68, "Pkts1024to1522Octets" },
+	{ 4, 0x6c, "RxOversizePkts" },
+	{ 4, 0x70, "RxJabbers" },
+	{ 4, 0x74, "RxAlignmentErrors" },
+	{ 4, 0x78, "RxFCSErrors" },
+	{ 8, 0x7c, "RxGoodOctets" },
+	{ 4, 0x84, "RxDropPkts" },
+	{ 4, 0x88, "RxUnicastPkts" },
+	{ 4, 0x8c, "RxMulticastPkts" },
+	{ 4, 0x90, "RxBroadcastPkts" },
+	{ 4, 0x94, "RxSAChanges" },
+	{ 4, 0x98, "RxFragments" },
+	{ },
+};
+
+#define B63XX_MIB_TXB_ID	0	/* TxOctets */
+#define B63XX_MIB_RXB_ID	14	/* RxOctets */
+
+/* BCM63xx MIB counters */
+static const struct b53_mib_desc b53_mibs_63xx[] = {
+	{ 8, 0x00, "TxOctets" },
+	{ 4, 0x08, "TxDropPkts" },
+	{ 4, 0x0c, "TxQoSPkts" },
+	{ 4, 0x10, "TxBroadcastPkts" },
+	{ 4, 0x14, "TxMulticastPkts" },
+	{ 4, 0x18, "TxUnicastPkts" },
+	{ 4, 0x1c, "TxCollisions" },
+	{ 4, 0x20, "TxSingleCollision" },
+	{ 4, 0x24, "TxMultipleCollision" },
+	{ 4, 0x28, "TxDeferredTransmit" },
+	{ 4, 0x2c, "TxLateCollision" },
+	{ 4, 0x30, "TxExcessiveCollision" },
+	{ 4, 0x38, "TxPausePkts" },
+	{ 8, 0x3c, "TxQoSOctets" },
+	{ 8, 0x44, "RxOctets" },
+	{ 4, 0x4c, "RxUndersizePkts" },
+	{ 4, 0x50, "RxPausePkts" },
+	{ 4, 0x54, "Pkts64Octets" },
+	{ 4, 0x58, "Pkts65to127Octets" },
+	{ 4, 0x5c, "Pkts128to255Octets" },
+	{ 4, 0x60, "Pkts256to511Octets" },
+	{ 4, 0x64, "Pkts512to1023Octets" },
+	{ 4, 0x68, "Pkts1024to1522Octets" },
+	{ 4, 0x6c, "RxOversizePkts" },
+	{ 4, 0x70, "RxJabbers" },
+	{ 4, 0x74, "RxAlignmentErrors" },
+	{ 4, 0x78, "RxFCSErrors" },
+	{ 8, 0x7c, "RxGoodOctets" },
+	{ 4, 0x84, "RxDropPkts" },
+	{ 4, 0x88, "RxUnicastPkts" },
+	{ 4, 0x8c, "RxMulticastPkts" },
+	{ 4, 0x90, "RxBroadcastPkts" },
+	{ 4, 0x94, "RxSAChanges" },
+	{ 4, 0x98, "RxFragments" },
+	{ 4, 0xa0, "RxSymbolErrors" },
+	{ 4, 0xa4, "RxQoSPkts" },
+	{ 8, 0xa8, "RxQoSOctets" },
+	{ 4, 0xb0, "Pkts1523to2047Octets" },
+	{ 4, 0xb4, "Pkts2048to4095Octets" },
+	{ 4, 0xb8, "Pkts4096to8191Octets" },
+	{ 4, 0xbc, "Pkts8192to9728Octets" },
+	{ 4, 0xc0, "RxDiscarded" },
+	{ }
+};
+
+#define B53XX_MIB_TXB_ID	0	/* TxOctets */
+#define B53XX_MIB_RXB_ID	12	/* RxOctets */
+
+/* MIB counters */
+static const struct b53_mib_desc b53_mibs[] = {
+	{ 8, 0x00, "TxOctets" },
+	{ 4, 0x08, "TxDropPkts" },
+	{ 4, 0x10, "TxBroadcastPkts" },
+	{ 4, 0x14, "TxMulticastPkts" },
+	{ 4, 0x18, "TxUnicastPkts" },
+	{ 4, 0x1c, "TxCollisions" },
+	{ 4, 0x20, "TxSingleCollision" },
+	{ 4, 0x24, "TxMultipleCollision" },
+	{ 4, 0x28, "TxDeferredTransmit" },
+	{ 4, 0x2c, "TxLateCollision" },
+	{ 4, 0x30, "TxExcessiveCollision" },
+	{ 4, 0x38, "TxPausePkts" },
+	{ 8, 0x50, "RxOctets" },
+	{ 4, 0x58, "RxUndersizePkts" },
+	{ 4, 0x5c, "RxPausePkts" },
+	{ 4, 0x60, "Pkts64Octets" },
+	{ 4, 0x64, "Pkts65to127Octets" },
+	{ 4, 0x68, "Pkts128to255Octets" },
+	{ 4, 0x6c, "Pkts256to511Octets" },
+	{ 4, 0x70, "Pkts512to1023Octets" },
+	{ 4, 0x74, "Pkts1024to1522Octets" },
+	{ 4, 0x78, "RxOversizePkts" },
+	{ 4, 0x7c, "RxJabbers" },
+	{ 4, 0x80, "RxAlignmentErrors" },
+	{ 4, 0x84, "RxFCSErrors" },
+	{ 8, 0x88, "RxGoodOctets" },
+	{ 4, 0x90, "RxDropPkts" },
+	{ 4, 0x94, "RxUnicastPkts" },
+	{ 4, 0x98, "RxMulticastPkts" },
+	{ 4, 0x9c, "RxBroadcastPkts" },
+	{ 4, 0xa0, "RxSAChanges" },
+	{ 4, 0xa4, "RxFragments" },
+	{ 4, 0xa8, "RxJumboPkts" },
+	{ 4, 0xac, "RxSymbolErrors" },
+	{ 4, 0xc0, "RxDiscarded" },
+	{ }
+};
+
+static int b53_do_vlan_op(struct b53_device *dev, u8 op)
+{
+	unsigned int i;
+
+	b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op);
+
+	for (i = 0; i < 10; i++) {
+		u8 vta;
+
+		b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta);
+		if (!(vta & VTA_START_CMD))
+			return 0;
+
+		usleep_range(100, 200);
+	}
+
+	return -EIO;
+}
+
+static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members,
+			       u16 untag)
+{
+	if (is5325(dev)) {
+		u32 entry = 0;
+
+		if (members) {
+			entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) |
+				members;
+			if (dev->core_rev >= 3)
+				entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S;
+			else
+				entry |= VA_VALID_25;
+		}
+
+		b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry);
+		b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid |
+			    VTA_RW_STATE_WR | VTA_RW_OP_EN);
+	} else if (is5365(dev)) {
+		u16 entry = 0;
+
+		if (members)
+			entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) |
+				members | VA_VALID_65;
+
+		b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry);
+		b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid |
+			    VTA_RW_STATE_WR | VTA_RW_OP_EN);
+	} else {
+		b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid);
+		b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2],
+			    (untag << VTE_UNTAG_S) | members);
+
+		b53_do_vlan_op(dev, VTA_CMD_WRITE);
+	}
+}
+
+void b53_set_forwarding(struct b53_device *dev, int enable)
+{
+	u8 mgmt;
+
+	b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
+
+	if (enable)
+		mgmt |= SM_SW_FWD_EN;
+	else
+		mgmt &= ~SM_SW_FWD_EN;
+
+	b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt);
+}
+
+static void b53_enable_vlan(struct b53_device *dev, int enable)
+{
+	u8 mgmt, vc0, vc1, vc4 = 0, vc5;
+
+	b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
+	b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0);
+	b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1);
+
+	if (is5325(dev) || is5365(dev)) {
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4);
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5);
+	} else if (is63xx(dev)) {
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4);
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5);
+	} else {
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4);
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5);
+	}
+
+	mgmt &= ~SM_SW_FWD_MODE;
+
+	if (enable) {
+		vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID;
+		vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN;
+		vc4 &= ~VC4_ING_VID_CHECK_MASK;
+		vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S;
+		vc5 |= VC5_DROP_VTABLE_MISS;
+
+		if (is5325(dev))
+			vc0 &= ~VC0_RESERVED_1;
+
+		if (is5325(dev) || is5365(dev))
+			vc1 |= VC1_RX_MCST_TAG_EN;
+
+		if (!is5325(dev) && !is5365(dev)) {
+			if (dev->allow_vid_4095)
+				vc5 |= VC5_VID_FFF_EN;
+			else
+				vc5 &= ~VC5_VID_FFF_EN;
+		}
+	} else {
+		vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID);
+		vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN);
+		vc4 &= ~VC4_ING_VID_CHECK_MASK;
+		vc5 &= ~VC5_DROP_VTABLE_MISS;
+
+		if (is5325(dev) || is5365(dev))
+			vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S;
+		else
+			vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S;
+
+		if (is5325(dev) || is5365(dev))
+			vc1 &= ~VC1_RX_MCST_TAG_EN;
+
+		if (!is5325(dev) && !is5365(dev))
+			vc5 &= ~VC5_VID_FFF_EN;
+	}
+
+	b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0);
+	b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1);
+
+	if (is5325(dev) || is5365(dev)) {
+		/* enable the high 8 bit vid check on 5325 */
+		if (is5325(dev) && enable)
+			b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3,
+				   VC3_HIGH_8BIT_EN);
+		else
+			b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0);
+
+		b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4);
+		b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5);
+	} else if (is63xx(dev)) {
+		b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0);
+		b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4);
+		b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5);
+	} else {
+		b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0);
+		b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4);
+		b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5);
+	}
+
+	b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt);
+}
+
+static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100)
+{
+	u32 port_mask = 0;
+	u16 max_size = JMS_MIN_SIZE;
+
+	if (is5325(dev) || is5365(dev))
+		return -EINVAL;
+
+	if (enable) {
+		port_mask = dev->enabled_ports;
+		max_size = JMS_MAX_SIZE;
+		if (allow_10_100)
+			port_mask |= JPM_10_100_JUMBO_EN;
+	}
+
+	b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask);
+	return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size);
+}
+
+static int b53_flush_arl(struct b53_device *dev)
+{
+	unsigned int i;
+
+	b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL,
+		   FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC);
+
+	for (i = 0; i < 10; i++) {
+		u8 fast_age_ctrl;
+
+		b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL,
+			  &fast_age_ctrl);
+
+		if (!(fast_age_ctrl & FAST_AGE_DONE))
+			return 0;
+
+		mdelay(1);
+	}
+
+	pr_warn("time out while flushing ARL\n");
+
+	return -EINVAL;
+}
+
+static void b53_enable_ports(struct b53_device *dev)
+{
+	unsigned i;
+
+	b53_for_each_port(dev, i) {
+		u8 port_ctrl;
+		u16 pvlan_mask;
+
+		/*
+		 * prevent leaking packets between wan and lan in unmanaged
+		 * mode through port vlans.
+		 */
+		if (dev->enable_vlan || is_cpu_port(dev, i))
+			pvlan_mask = 0x1ff;
+		else if (is531x5(dev) || is5301x(dev))
+			/* BCM53115 may use a different port as cpu port */
+			pvlan_mask = BIT(dev->sw_dev.cpu_port);
+		else
+			pvlan_mask = BIT(B53_CPU_PORT);
+
+		/* BCM5325 CPU port is at 8 */
+		if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25)
+			i = B53_CPU_PORT;
+
+		if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7))
+			/* disable unused ports 6 & 7 */
+			port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE;
+		else if (i == B53_CPU_PORT)
+			port_ctrl = PORT_CTRL_RX_BCST_EN |
+				    PORT_CTRL_RX_MCST_EN |
+				    PORT_CTRL_RX_UCST_EN;
+		else
+			port_ctrl = 0;
+
+		b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i),
+			    pvlan_mask);
+
+		/* port state is handled by bcm63xx_enet driver */
+		if (!is63xx(dev) && !(is5301x(dev) && i == 6))
+			b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i),
+				   port_ctrl);
+	}
+}
+
+static void b53_enable_mib(struct b53_device *dev)
+{
+	u8 gc;
+
+	b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc);
+
+	gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN);
+
+	b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc);
+}
+
+static int b53_apply(struct b53_device *dev)
+{
+	int i;
+
+	/* clear all vlan entries */
+	if (is5325(dev) || is5365(dev)) {
+		for (i = 1; i < dev->sw_dev.vlans; i++)
+			b53_set_vlan_entry(dev, i, 0, 0);
+	} else {
+		b53_do_vlan_op(dev, VTA_CMD_CLEAR);
+	}
+
+	b53_enable_vlan(dev, dev->enable_vlan);
+
+	/* fill VLAN table */
+	if (dev->enable_vlan) {
+		for (i = 0; i < dev->sw_dev.vlans; i++) {
+			struct b53_vlan *vlan = &dev->vlans[i];
+
+			if (!vlan->members)
+				continue;
+
+			b53_set_vlan_entry(dev, i, vlan->members, vlan->untag);
+		}
+
+		b53_for_each_port(dev, i)
+			b53_write16(dev, B53_VLAN_PAGE,
+				    B53_VLAN_PORT_DEF_TAG(i),
+				    dev->ports[i].pvid);
+	} else {
+		b53_for_each_port(dev, i)
+			b53_write16(dev, B53_VLAN_PAGE,
+				    B53_VLAN_PORT_DEF_TAG(i), 1);
+
+	}
+
+	b53_enable_ports(dev);
+
+	if (!is5325(dev) && !is5365(dev))
+		b53_set_jumbo(dev, dev->enable_jumbo, 1);
+
+	return 0;
+}
+
+static void b53_switch_reset_gpio(struct b53_device *dev)
+{
+	int gpio = dev->reset_gpio;
+
+	if (gpio < 0)
+		return;
+
+	/*
+	 * Reset sequence: RESET low(50ms)->high(20ms)
+	 */
+	gpio_set_value(gpio, 0);
+	mdelay(50);
+
+	gpio_set_value(gpio, 1);
+	mdelay(20);
+
+	dev->current_page = 0xff;
+}
+
+static int b53_configure_ports_of(struct b53_device *dev)
+{
+	struct device_node *dn, *pn;
+	u32 port_num;
+
+	dn = of_get_child_by_name(dev_of_node(dev->dev), "ports");
+
+	for_each_available_child_of_node(dn, pn) {
+		struct device_node *fixed_link;
+
+		if (of_property_read_u32(pn, "reg", &port_num))
+			continue;
+
+		if (port_num > B53_CPU_PORT)
+			continue;
+
+		fixed_link = of_get_child_by_name(pn, "fixed-link");
+		if (fixed_link) {
+			u32 spd;
+			u8 po = GMII_PO_LINK;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+			phy_interface_t mode;
+#else
+			int mode = of_get_phy_mode(pn);
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+			of_get_phy_mode(pn, &mode);
+#endif
+
+			if (!of_property_read_u32(fixed_link, "speed", &spd)) {
+				switch (spd) {
+				case 10:
+					po |= GMII_PO_SPEED_10M;
+					break;
+				case 100:
+					po |= GMII_PO_SPEED_100M;
+					break;
+				case 2000:
+					if (is_imp_port(dev, port_num))
+						po |= PORT_OVERRIDE_SPEED_2000M;
+					else
+						po |= GMII_PO_SPEED_2000M;
+					/* fall through */
+				case 1000:
+					po |= GMII_PO_SPEED_1000M;
+					break;
+				}
+			}
+
+			if (of_property_read_bool(fixed_link, "full-duplex"))
+				po |= PORT_OVERRIDE_FULL_DUPLEX;
+			if (of_property_read_bool(fixed_link, "pause"))
+				po |= GMII_PO_RX_FLOW;
+			if (of_property_read_bool(fixed_link, "asym-pause"))
+				po |= GMII_PO_TX_FLOW;
+
+			if (is_imp_port(dev, port_num)) {
+				po |= PORT_OVERRIDE_EN;
+
+				if (is5325(dev) &&
+				    mode == PHY_INTERFACE_MODE_REVMII)
+					po |= PORT_OVERRIDE_RV_MII_25;
+
+				b53_write8(dev, B53_CTRL_PAGE,
+					   B53_PORT_OVERRIDE_CTRL, po);
+
+				if (is5325(dev) &&
+				    mode == PHY_INTERFACE_MODE_REVMII) {
+					b53_read8(dev, B53_CTRL_PAGE,
+						  B53_PORT_OVERRIDE_CTRL, &po);
+					if (!(po & PORT_OVERRIDE_RV_MII_25))
+					pr_err("Failed to enable reverse MII mode\n");
+					return -EINVAL;
+				}
+			} else {
+				po |= GMII_PO_EN;
+				b53_write8(dev, B53_CTRL_PAGE,
+					   B53_GMII_PORT_OVERRIDE_CTRL(port_num),
+					   po);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int b53_configure_ports(struct b53_device *dev)
+{
+	u8 cpu_port = dev->sw_dev.cpu_port;
+
+	/* configure MII port if necessary */
+	if (is5325(dev)) {
+		u8 mii_port_override;
+
+		b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+			  &mii_port_override);
+		/* reverse mii needs to be enabled */
+		if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) {
+			b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+				   mii_port_override | PORT_OVERRIDE_RV_MII_25);
+			b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+				  &mii_port_override);
+
+			if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) {
+				pr_err("Failed to enable reverse MII mode\n");
+				return -EINVAL;
+			}
+		}
+	} else if (is531x5(dev) && cpu_port == B53_CPU_PORT) {
+		u8 mii_port_override;
+
+		b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+			  &mii_port_override);
+		b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+			   mii_port_override | PORT_OVERRIDE_EN |
+			   PORT_OVERRIDE_LINK);
+
+		/* BCM47189 has another interface connected to the port 5 */
+		if (dev->enabled_ports & BIT(5)) {
+			u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(5);
+			u8 gmii_po;
+
+			b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po);
+			gmii_po |= GMII_PO_LINK |
+				   GMII_PO_RX_FLOW |
+				   GMII_PO_TX_FLOW |
+				   GMII_PO_EN;
+			b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po);
+		}
+	} else if (is5301x(dev)) {
+		if (cpu_port == 8) {
+			u8 mii_port_override;
+
+			b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+				  &mii_port_override);
+			mii_port_override |= PORT_OVERRIDE_LINK |
+					     PORT_OVERRIDE_RX_FLOW |
+					     PORT_OVERRIDE_TX_FLOW |
+					     PORT_OVERRIDE_SPEED_2000M |
+					     PORT_OVERRIDE_EN;
+			b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL,
+				   mii_port_override);
+
+			/* TODO: Ports 5 & 7 require some extra handling */
+		} else {
+			u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(cpu_port);
+			u8 gmii_po;
+
+			b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po);
+			gmii_po |= GMII_PO_LINK |
+				   GMII_PO_RX_FLOW |
+				   GMII_PO_TX_FLOW |
+				   GMII_PO_EN |
+				   GMII_PO_SPEED_2000M;
+			b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po);
+		}
+	}
+
+	return 0;
+}
+
+static int b53_switch_reset(struct b53_device *dev)
+{
+	int ret = 0;
+	u8 mgmt;
+
+	b53_switch_reset_gpio(dev);
+
+	if (is539x(dev)) {
+		b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83);
+		b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00);
+	}
+
+	b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
+
+	if (!(mgmt & SM_SW_FWD_EN)) {
+		mgmt &= ~SM_SW_FWD_MODE;
+		mgmt |= SM_SW_FWD_EN;
+
+		b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt);
+		b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt);
+
+		if (!(mgmt & SM_SW_FWD_EN)) {
+			pr_err("Failed to enable switch!\n");
+			return -EINVAL;
+		}
+	}
+
+	/* enable all ports */
+	b53_enable_ports(dev);
+
+	if (dev->dev->of_node)
+		ret = b53_configure_ports_of(dev);
+	else
+		ret = b53_configure_ports(dev);
+
+	if (ret)
+		return ret;
+
+	b53_enable_mib(dev);
+
+	return b53_flush_arl(dev);
+}
+
+/*
+ * Swconfig glue functions
+ */
+
+static int b53_global_get_vlan_enable(struct switch_dev *dev,
+				      const struct switch_attr *attr,
+				      struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	val->value.i = priv->enable_vlan;
+
+	return 0;
+}
+
+static int b53_global_set_vlan_enable(struct switch_dev *dev,
+				      const struct switch_attr *attr,
+				      struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	priv->enable_vlan = val->value.i;
+
+	return 0;
+}
+
+static int b53_global_get_jumbo_enable(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	val->value.i = priv->enable_jumbo;
+
+	return 0;
+}
+
+static int b53_global_set_jumbo_enable(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	priv->enable_jumbo = val->value.i;
+
+	return 0;
+}
+
+static int b53_global_get_4095_enable(struct switch_dev *dev,
+				      const struct switch_attr *attr,
+				      struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	val->value.i = priv->allow_vid_4095;
+
+	return 0;
+}
+
+static int b53_global_set_4095_enable(struct switch_dev *dev,
+				      const struct switch_attr *attr,
+				      struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	priv->allow_vid_4095 = val->value.i;
+
+	return 0;
+}
+
+static int b53_global_get_ports(struct switch_dev *dev,
+				const struct switch_attr *attr,
+				struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x",
+			    priv->enabled_ports);
+	val->value.s = priv->buf;
+
+	return 0;
+}
+
+static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	*val = priv->ports[port].pvid;
+
+	return 0;
+}
+
+static int b53_port_set_pvid(struct switch_dev *dev, int port, int val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	if (val > 15 && is5325(priv))
+		return -EINVAL;
+	if (val == 4095 && !priv->allow_vid_4095)
+		return -EINVAL;
+
+	priv->ports[port].pvid = val;
+
+	return 0;
+}
+
+static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+	struct switch_port *port = &val->value.ports[0];
+	struct b53_vlan *vlan = &priv->vlans[val->port_vlan];
+	int i;
+
+	val->len = 0;
+
+	if (!vlan->members)
+		return 0;
+
+	for (i = 0; i < dev->ports; i++) {
+		if (!(vlan->members & BIT(i)))
+			continue;
+
+
+		if (!(vlan->untag & BIT(i)))
+			port->flags = BIT(SWITCH_PORT_FLAG_TAGGED);
+		else
+			port->flags = 0;
+
+		port->id = i;
+		val->len++;
+		port++;
+	}
+
+	return 0;
+}
+
+static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+	struct switch_port *port;
+	struct b53_vlan *vlan = &priv->vlans[val->port_vlan];
+	int i;
+
+	/* only BCM5325 and BCM5365 supports VID 0 */
+	if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv))
+		return -EINVAL;
+
+	/* VLAN 4095 needs special handling */
+	if (val->port_vlan == 4095 && !priv->allow_vid_4095)
+		return -EINVAL;
+
+	port = &val->value.ports[0];
+	vlan->members = 0;
+	vlan->untag = 0;
+	for (i = 0; i < val->len; i++, port++) {
+		vlan->members |= BIT(port->id);
+
+		if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) {
+			vlan->untag |= BIT(port->id);
+			priv->ports[port->id].pvid = val->port_vlan;
+		};
+	}
+
+	/* ignore disabled ports */
+	vlan->members &= priv->enabled_ports;
+	vlan->untag &= priv->enabled_ports;
+
+	return 0;
+}
+
+static int b53_port_get_link(struct switch_dev *dev, int port,
+			     struct switch_port_link *link)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	if (is_cpu_port(priv, port)) {
+		link->link = 1;
+		link->duplex = 1;
+		link->speed = is5325(priv) || is5365(priv) ?
+				SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000;
+		link->aneg = 0;
+	} else if (priv->enabled_ports & BIT(port)) {
+		u32 speed;
+		u16 lnk, duplex;
+
+		b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk);
+		b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex);
+
+		lnk = (lnk >> port) & 1;
+		duplex = (duplex >> port) & 1;
+
+		if (is5325(priv) || is5365(priv)) {
+			u16 tmp;
+
+			b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp);
+			speed = SPEED_PORT_FE(tmp, port);
+		} else {
+			b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed);
+			speed = SPEED_PORT_GE(speed, port);
+		}
+
+		link->link = lnk;
+		if (lnk) {
+			link->duplex = duplex;
+			switch (speed) {
+			case SPEED_STAT_10M:
+				link->speed = SWITCH_PORT_SPEED_10;
+				break;
+			case SPEED_STAT_100M:
+				link->speed = SWITCH_PORT_SPEED_100;
+				break;
+			case SPEED_STAT_1000M:
+				link->speed = SWITCH_PORT_SPEED_1000;
+				break;
+			}
+		}
+
+		link->aneg = 1;
+	} else {
+		link->link = 0;
+	}
+
+	return 0;
+
+}
+
+static int b53_port_set_link(struct switch_dev *sw_dev, int port,
+			     struct switch_port_link *link)
+{
+	struct b53_device *dev = sw_to_b53(sw_dev);
+
+	/*
+	 * TODO: BCM63XX requires special handling as it can have external phys
+	 * and ports might be GE or only FE
+	 */
+	if (is63xx(dev))
+		return -ENOTSUPP;
+
+	if (port == sw_dev->cpu_port)
+		return -EINVAL;
+
+	if (!(BIT(port) & dev->enabled_ports))
+		return -EINVAL;
+
+	if (link->speed == SWITCH_PORT_SPEED_1000 &&
+	    (is5325(dev) || is5365(dev)))
+		return -EINVAL;
+
+	if (link->speed == SWITCH_PORT_SPEED_1000 && !link->duplex)
+		return -EINVAL;
+
+	return switch_generic_set_link(sw_dev, port, link);
+}
+
+static int b53_phy_read16(struct switch_dev *dev, int addr, u8 reg, u16 *value)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	if (priv->ops->phy_read16)
+		return priv->ops->phy_read16(priv, addr, reg, value);
+
+	return b53_read16(priv, B53_PORT_MII_PAGE(addr), reg, value);
+}
+
+static int b53_phy_write16(struct switch_dev *dev, int addr, u8 reg, u16 value)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	if (priv->ops->phy_write16)
+		return priv->ops->phy_write16(priv, addr, reg, value);
+
+	return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg, value);
+}
+
+static int b53_global_reset_switch(struct switch_dev *dev)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	/* reset vlans */
+	priv->enable_vlan = 0;
+	priv->enable_jumbo = 0;
+	priv->allow_vid_4095 = 0;
+
+	memset(priv->vlans, 0, sizeof(*priv->vlans) * dev->vlans);
+	memset(priv->ports, 0, sizeof(*priv->ports) * dev->ports);
+
+	return b53_switch_reset(priv);
+}
+
+static int b53_global_apply_config(struct switch_dev *dev)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+
+	/* disable switching */
+	b53_set_forwarding(priv, 0);
+
+	b53_apply(priv);
+
+	/* enable switching */
+	b53_set_forwarding(priv, 1);
+
+	return 0;
+}
+
+
+static int b53_global_reset_mib(struct switch_dev *dev,
+				const struct switch_attr *attr,
+				struct switch_val *val)
+{
+	struct b53_device *priv = sw_to_b53(dev);
+	u8 gc;
+
+	b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc);
+
+	b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB);
+	mdelay(1);
+	b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB);
+	mdelay(1);
+
+	return 0;
+}
+
+static int b53_port_get_mib(struct switch_dev *sw_dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct b53_device *dev = sw_to_b53(sw_dev);
+	const struct b53_mib_desc *mibs;
+	int port = val->port_vlan;
+	int len = 0;
+
+	if (!(BIT(port) & dev->enabled_ports))
+		return -1;
+
+	if (is5365(dev)) {
+		if (port == 5)
+			port = 8;
+
+		mibs = b53_mibs_65;
+	} else if (is63xx(dev)) {
+		mibs = b53_mibs_63xx;
+	} else {
+		mibs = b53_mibs;
+	}
+
+	dev->buf[0] = 0;
+
+	for (; mibs->size > 0; mibs++) {
+		u64 val;
+
+		if (mibs->size == 8) {
+			b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val);
+		} else {
+			u32 val32;
+
+			b53_read32(dev, B53_MIB_PAGE(port), mibs->offset,
+				   &val32);
+			val = val32;
+		}
+
+		len += snprintf(dev->buf + len, B53_BUF_SIZE - len,
+				"%-20s: %llu\n", mibs->name, val);
+	}
+
+	val->len = len;
+	val->value.s = dev->buf;
+
+	return 0;
+}
+
+static int b53_port_get_stats(struct switch_dev *sw_dev, int port,
+				struct switch_port_stats *stats)
+{
+	struct b53_device *dev = sw_to_b53(sw_dev);
+	const struct b53_mib_desc *mibs;
+	int txb_id, rxb_id;
+	u64 rxb, txb;
+
+	if (!(BIT(port) & dev->enabled_ports))
+		return -EINVAL;
+
+	txb_id = B53XX_MIB_TXB_ID;
+	rxb_id = B53XX_MIB_RXB_ID;
+
+	if (is5365(dev)) {
+		if (port == 5)
+			port = 8;
+
+		mibs = b53_mibs_65;
+	} else if (is63xx(dev)) {
+		mibs = b53_mibs_63xx;
+		txb_id = B63XX_MIB_TXB_ID;
+		rxb_id = B63XX_MIB_RXB_ID;
+	} else {
+		mibs = b53_mibs;
+	}
+
+	dev->buf[0] = 0;
+
+	if (mibs->size == 8) {
+		b53_read64(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &txb);
+		b53_read64(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &rxb);
+	} else {
+		u32 val32;
+
+		b53_read32(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &val32);
+		txb = val32;
+
+		b53_read32(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &val32);
+		rxb = val32;
+	}
+
+	stats->tx_bytes = txb;
+	stats->rx_bytes = rxb;
+
+	return 0;
+}
+
+static struct switch_attr b53_global_ops_25[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = b53_global_set_vlan_enable,
+		.get = b53_global_get_vlan_enable,
+		.max = 1,
+	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "ports",
+		.description = "Available ports (as bitmask)",
+		.get = b53_global_get_ports,
+	},
+};
+
+static struct switch_attr b53_global_ops_65[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = b53_global_set_vlan_enable,
+		.get = b53_global_get_vlan_enable,
+		.max = 1,
+	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "ports",
+		.description = "Available ports (as bitmask)",
+		.get = b53_global_get_ports,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "reset_mib",
+		.description = "Reset MIB counters",
+		.set = b53_global_reset_mib,
+	},
+};
+
+static struct switch_attr b53_global_ops[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = b53_global_set_vlan_enable,
+		.get = b53_global_get_vlan_enable,
+		.max = 1,
+	},
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "ports",
+		.description = "Available Ports (as bitmask)",
+		.get = b53_global_get_ports,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "reset_mib",
+		.description = "Reset MIB counters",
+		.set = b53_global_reset_mib,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_jumbo",
+		.description = "Enable Jumbo Frames",
+		.set = b53_global_set_jumbo_enable,
+		.get = b53_global_get_jumbo_enable,
+		.max = 1,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "allow_vid_4095",
+		.description = "Allow VID 4095",
+		.set = b53_global_set_4095_enable,
+		.get = b53_global_get_4095_enable,
+		.max = 1,
+	},
+};
+
+static struct switch_attr b53_port_ops[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get port's MIB counters",
+		.get = b53_port_get_mib,
+	},
+};
+
+static struct switch_attr b53_no_ops[] = {
+};
+
+static const struct switch_dev_ops b53_switch_ops_25 = {
+	.attr_global = {
+		.attr = b53_global_ops_25,
+		.n_attr = ARRAY_SIZE(b53_global_ops_25),
+	},
+	.attr_port = {
+		.attr = b53_no_ops,
+		.n_attr = ARRAY_SIZE(b53_no_ops),
+	},
+	.attr_vlan = {
+		.attr = b53_no_ops,
+		.n_attr = ARRAY_SIZE(b53_no_ops),
+	},
+
+	.get_vlan_ports = b53_vlan_get_ports,
+	.set_vlan_ports = b53_vlan_set_ports,
+	.get_port_pvid = b53_port_get_pvid,
+	.set_port_pvid = b53_port_set_pvid,
+	.apply_config = b53_global_apply_config,
+	.reset_switch = b53_global_reset_switch,
+	.get_port_link = b53_port_get_link,
+	.set_port_link = b53_port_set_link,
+	.get_port_stats = b53_port_get_stats,
+	.phy_read16 = b53_phy_read16,
+	.phy_write16 = b53_phy_write16,
+};
+
+static const struct switch_dev_ops b53_switch_ops_65 = {
+	.attr_global = {
+		.attr = b53_global_ops_65,
+		.n_attr = ARRAY_SIZE(b53_global_ops_65),
+	},
+	.attr_port = {
+		.attr = b53_port_ops,
+		.n_attr = ARRAY_SIZE(b53_port_ops),
+	},
+	.attr_vlan = {
+		.attr = b53_no_ops,
+		.n_attr = ARRAY_SIZE(b53_no_ops),
+	},
+
+	.get_vlan_ports = b53_vlan_get_ports,
+	.set_vlan_ports = b53_vlan_set_ports,
+	.get_port_pvid = b53_port_get_pvid,
+	.set_port_pvid = b53_port_set_pvid,
+	.apply_config = b53_global_apply_config,
+	.reset_switch = b53_global_reset_switch,
+	.get_port_link = b53_port_get_link,
+	.set_port_link = b53_port_set_link,
+	.get_port_stats = b53_port_get_stats,
+	.phy_read16 = b53_phy_read16,
+	.phy_write16 = b53_phy_write16,
+};
+
+static const struct switch_dev_ops b53_switch_ops = {
+	.attr_global = {
+		.attr = b53_global_ops,
+		.n_attr = ARRAY_SIZE(b53_global_ops),
+	},
+	.attr_port = {
+		.attr = b53_port_ops,
+		.n_attr = ARRAY_SIZE(b53_port_ops),
+	},
+	.attr_vlan = {
+		.attr = b53_no_ops,
+		.n_attr = ARRAY_SIZE(b53_no_ops),
+	},
+
+	.get_vlan_ports = b53_vlan_get_ports,
+	.set_vlan_ports = b53_vlan_set_ports,
+	.get_port_pvid = b53_port_get_pvid,
+	.set_port_pvid = b53_port_set_pvid,
+	.apply_config = b53_global_apply_config,
+	.reset_switch = b53_global_reset_switch,
+	.get_port_link = b53_port_get_link,
+	.set_port_link = b53_port_set_link,
+	.get_port_stats = b53_port_get_stats,
+	.phy_read16 = b53_phy_read16,
+	.phy_write16 = b53_phy_write16,
+};
+
+struct b53_chip_data {
+	u32 chip_id;
+	const char *dev_name;
+	const char *alias;
+	u16 vlans;
+	u16 enabled_ports;
+	u8 cpu_port;
+	u8 vta_regs[3];
+	u8 duplex_reg;
+	u8 jumbo_pm_reg;
+	u8 jumbo_size_reg;
+	const struct switch_dev_ops *sw_ops;
+};
+
+#define B53_VTA_REGS	\
+	{ B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY }
+#define B53_VTA_REGS_9798 \
+	{ B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 }
+#define B53_VTA_REGS_63XX \
+	{ B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX }
+
+static const struct b53_chip_data b53_switch_chips[] = {
+	{
+		.chip_id = BCM5325_DEVICE_ID,
+		.dev_name = "BCM5325",
+		.alias = "bcm5325",
+		.vlans = 16,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT_25,
+		.duplex_reg = B53_DUPLEX_STAT_FE,
+		.sw_ops = &b53_switch_ops_25,
+	},
+	{
+		.chip_id = BCM5365_DEVICE_ID,
+		.dev_name = "BCM5365",
+		.alias = "bcm5365",
+		.vlans = 256,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT_25,
+		.duplex_reg = B53_DUPLEX_STAT_FE,
+		.sw_ops = &b53_switch_ops_65,
+	},
+	{
+		.chip_id = BCM5395_DEVICE_ID,
+		.dev_name = "BCM5395",
+		.alias = "bcm5395",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT,
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM5397_DEVICE_ID,
+		.dev_name = "BCM5397",
+		.alias = "bcm5397",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT,
+		.vta_regs = B53_VTA_REGS_9798,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM5398_DEVICE_ID,
+		.dev_name = "BCM5398",
+		.alias = "bcm5398",
+		.vlans = 4096,
+		.enabled_ports = 0x7f,
+		.cpu_port = B53_CPU_PORT,
+		.vta_regs = B53_VTA_REGS_9798,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53115_DEVICE_ID,
+		.dev_name = "BCM53115",
+		.alias = "bcm53115",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.vta_regs = B53_VTA_REGS,
+		.cpu_port = B53_CPU_PORT,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53125_DEVICE_ID,
+		.dev_name = "BCM53125",
+		.alias = "bcm53125",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT,
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53128_DEVICE_ID,
+		.dev_name = "BCM53128",
+		.alias = "bcm53128",
+		.vlans = 4096,
+		.enabled_ports = 0x1ff,
+		.cpu_port = B53_CPU_PORT,
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM63XX_DEVICE_ID,
+		.dev_name = "BCM63xx",
+		.alias = "bcm63xx",
+		.vlans = 4096,
+		.enabled_ports = 0, /* pdata must provide them */
+		.cpu_port = B53_CPU_PORT,
+		.vta_regs = B53_VTA_REGS_63XX,
+		.duplex_reg = B53_DUPLEX_STAT_63XX,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53010_DEVICE_ID,
+		.dev_name = "BCM53010",
+		.alias = "bcm53011",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53011_DEVICE_ID,
+		.dev_name = "BCM53011",
+		.alias = "bcm53011",
+		.vlans = 4096,
+		.enabled_ports = 0x1bf,
+		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53012_DEVICE_ID,
+		.dev_name = "BCM53012",
+		.alias = "bcm53011",
+		.vlans = 4096,
+		.enabled_ports = 0x1bf,
+		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53018_DEVICE_ID,
+		.dev_name = "BCM53018",
+		.alias = "bcm53018",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+	{
+		.chip_id = BCM53019_DEVICE_ID,
+		.dev_name = "BCM53019",
+		.alias = "bcm53019",
+		.vlans = 4096,
+		.enabled_ports = 0x1f,
+		.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */
+		.vta_regs = B53_VTA_REGS,
+		.duplex_reg = B53_DUPLEX_STAT_GE,
+		.jumbo_pm_reg = B53_JUMBO_PORT_MASK,
+		.jumbo_size_reg = B53_JUMBO_MAX_SIZE,
+		.sw_ops = &b53_switch_ops,
+	},
+};
+
+static int b53_switch_init_of(struct b53_device *dev)
+{
+	struct device_node *dn, *pn;
+	const char *alias;
+	u32 port_num;
+	u16 ports = 0;
+
+	dn = of_get_child_by_name(dev_of_node(dev->dev), "ports");
+	if (!dn)
+		return -EINVAL;
+
+	for_each_available_child_of_node(dn, pn) {
+		const char *label;
+		int len;
+
+		if (of_property_read_u32(pn, "reg", &port_num))
+			continue;
+
+		if (port_num > B53_CPU_PORT)
+			continue;
+
+		ports |= BIT(port_num);
+
+		label = of_get_property(pn, "label", &len);
+		if (label && !strcmp(label, "cpu"))
+			dev->sw_dev.cpu_port = port_num;
+	}
+
+	dev->enabled_ports = ports;
+
+	if (!of_property_read_string(dev_of_node(dev->dev), "lede,alias",
+						 &alias))
+		dev->sw_dev.alias = devm_kstrdup(dev->dev, alias, GFP_KERNEL);
+
+	return 0;
+}
+
+static int b53_switch_init(struct b53_device *dev)
+{
+	struct switch_dev *sw_dev = &dev->sw_dev;
+	unsigned i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) {
+		const struct b53_chip_data *chip = &b53_switch_chips[i];
+
+		if (chip->chip_id == dev->chip_id) {
+			sw_dev->name = chip->dev_name;
+			if (!sw_dev->alias)
+				sw_dev->alias = chip->alias;
+			if (!dev->enabled_ports)
+				dev->enabled_ports = chip->enabled_ports;
+			dev->duplex_reg = chip->duplex_reg;
+			dev->vta_regs[0] = chip->vta_regs[0];
+			dev->vta_regs[1] = chip->vta_regs[1];
+			dev->vta_regs[2] = chip->vta_regs[2];
+			dev->jumbo_pm_reg = chip->jumbo_pm_reg;
+			sw_dev->ops = chip->sw_ops;
+			sw_dev->cpu_port = chip->cpu_port;
+			sw_dev->vlans = chip->vlans;
+			break;
+		}
+	}
+
+	if (!sw_dev->name)
+		return -EINVAL;
+
+	/* check which BCM5325x version we have */
+	if (is5325(dev)) {
+		u8 vc4;
+
+		b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4);
+
+		/* check reserved bits */
+		switch (vc4 & 3) {
+		case 1:
+			/* BCM5325E */
+			break;
+		case 3:
+			/* BCM5325F - do not use port 4 */
+			dev->enabled_ports &= ~BIT(4);
+			break;
+		default:
+/* On the BCM47XX SoCs this is the supported internal switch.*/
+#ifndef CONFIG_BCM47XX
+			/* BCM5325M */
+			return -EINVAL;
+#else
+			break;
+#endif
+		}
+	} else if (dev->chip_id == BCM53115_DEVICE_ID) {
+		u64 strap_value;
+
+		b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value);
+		/* use second IMP port if GMII is enabled */
+		if (strap_value & SV_GMII_CTRL_115)
+			sw_dev->cpu_port = 5;
+	}
+
+	if (dev_of_node(dev->dev)) {
+		ret = b53_switch_init_of(dev);
+		if (ret)
+			return ret;
+	}
+
+	dev->enabled_ports |= BIT(sw_dev->cpu_port);
+	sw_dev->ports = fls(dev->enabled_ports);
+
+	dev->ports = devm_kzalloc(dev->dev,
+				  sizeof(struct b53_port) * sw_dev->ports,
+				  GFP_KERNEL);
+	if (!dev->ports)
+		return -ENOMEM;
+
+	dev->vlans = devm_kzalloc(dev->dev,
+				  sizeof(struct b53_vlan) * sw_dev->vlans,
+				  GFP_KERNEL);
+	if (!dev->vlans)
+		return -ENOMEM;
+
+	dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL);
+	if (!dev->buf)
+		return -ENOMEM;
+
+	dev->reset_gpio = b53_switch_get_reset_gpio(dev);
+	if (dev->reset_gpio >= 0) {
+		ret = devm_gpio_request_one(dev->dev, dev->reset_gpio,
+					    GPIOF_OUT_INIT_HIGH, "robo_reset");
+		if (ret)
+			return ret;
+	}
+
+	return b53_switch_reset(dev);
+}
+
+struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops,
+				    void *priv)
+{
+	struct b53_device *dev;
+
+	dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return NULL;
+
+	dev->dev = base;
+	dev->ops = ops;
+	dev->priv = priv;
+	mutex_init(&dev->reg_mutex);
+
+	return dev;
+}
+EXPORT_SYMBOL(b53_switch_alloc);
+
+int b53_switch_detect(struct b53_device *dev)
+{
+	u32 id32;
+	u16 tmp;
+	u8 id8;
+	int ret;
+
+	ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8);
+	if (ret)
+		return ret;
+
+	switch (id8) {
+	case 0:
+		/*
+		 * BCM5325 and BCM5365 do not have this register so reads
+		 * return 0. But the read operation did succeed, so assume
+		 * this is one of them.
+		 *
+		 * Next check if we can write to the 5325's VTA register; for
+		 * 5365 it is read only.
+		 */
+
+		b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf);
+		b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp);
+
+		if (tmp == 0xf)
+			dev->chip_id = BCM5325_DEVICE_ID;
+		else
+			dev->chip_id = BCM5365_DEVICE_ID;
+		break;
+	case BCM5395_DEVICE_ID:
+	case BCM5397_DEVICE_ID:
+	case BCM5398_DEVICE_ID:
+		dev->chip_id = id8;
+		break;
+	default:
+		ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32);
+		if (ret)
+			return ret;
+
+		switch (id32) {
+		case BCM53115_DEVICE_ID:
+		case BCM53125_DEVICE_ID:
+		case BCM53128_DEVICE_ID:
+		case BCM53010_DEVICE_ID:
+		case BCM53011_DEVICE_ID:
+		case BCM53012_DEVICE_ID:
+		case BCM53018_DEVICE_ID:
+		case BCM53019_DEVICE_ID:
+			dev->chip_id = id32;
+			break;
+		default:
+			pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n",
+			       id8, id32);
+			return -ENODEV;
+		}
+	}
+
+	if (dev->chip_id == BCM5325_DEVICE_ID)
+		return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25,
+				 &dev->core_rev);
+	else
+		return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID,
+				 &dev->core_rev);
+}
+EXPORT_SYMBOL(b53_switch_detect);
+
+int b53_switch_register(struct b53_device *dev)
+{
+	int ret;
+
+	if (dev->pdata) {
+		dev->chip_id = dev->pdata->chip_id;
+		dev->enabled_ports = dev->pdata->enabled_ports;
+		dev->sw_dev.alias = dev->pdata->alias;
+	}
+
+	if (!dev->chip_id && b53_switch_detect(dev))
+		return -EINVAL;
+
+	ret = b53_switch_init(dev);
+	if (ret)
+		return ret;
+
+	pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev);
+
+	return register_switch(&dev->sw_dev, NULL);
+}
+EXPORT_SYMBOL(b53_switch_register);
+
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 switch library");
+MODULE_LICENSE("Dual BSD/GPL");
diff -Naur linux-orig/drivers/net/phy/b53/b53_mdio.c linux/drivers/net/phy/b53/b53_mdio.c
--- linux-orig/drivers/net/phy/b53/b53_mdio.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_mdio.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,468 @@
+/*
+ * B53 register access through MII registers
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+
+#include "b53_priv.h"
+
+#define B53_PSEUDO_PHY	0x1e /* Register Access Pseudo PHY */
+
+/* MII registers */
+#define REG_MII_PAGE    0x10    /* MII Page register */
+#define REG_MII_ADDR    0x11    /* MII Address register */
+#define REG_MII_DATA0   0x18    /* MII Data register 0 */
+#define REG_MII_DATA1   0x19    /* MII Data register 1 */
+#define REG_MII_DATA2   0x1a    /* MII Data register 2 */
+#define REG_MII_DATA3   0x1b    /* MII Data register 3 */
+
+#define REG_MII_PAGE_ENABLE     BIT(0)
+#define REG_MII_ADDR_WRITE      BIT(0)
+#define REG_MII_ADDR_READ       BIT(1)
+
+static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op)
+{
+	int i;
+	u16 v;
+	int ret;
+	struct mii_bus *bus = dev->priv;
+
+	if (dev->current_page != page) {
+		/* set page number */
+		v = (page << 8) | REG_MII_PAGE_ENABLE;
+		ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v);
+		if (ret)
+			return ret;
+		dev->current_page = page;
+	}
+
+	/* set register address */
+	v = (reg << 8) | op;
+	ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v);
+	if (ret)
+		return ret;
+
+	/* check if operation completed */
+	for (i = 0; i < 5; ++i) {
+		v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR);
+		if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ)))
+			break;
+		usleep_range(10, 100);
+	}
+
+	if (WARN_ON(i == 5))
+		return -EIO;
+
+	return 0;
+}
+
+static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
+{
+	struct mii_bus *bus = dev->priv;
+	int ret;
+
+	ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
+	if (ret)
+		return ret;
+
+	*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff;
+
+	return 0;
+}
+
+static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
+{
+	struct mii_bus *bus = dev->priv;
+	int ret;
+
+	ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
+	if (ret)
+		return ret;
+
+	*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
+
+	return 0;
+}
+
+static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
+{
+	struct mii_bus *bus = dev->priv;
+	int ret;
+
+	ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
+	if (ret)
+		return ret;
+
+	*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
+	*val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16;
+
+	return 0;
+}
+
+static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	struct mii_bus *bus = dev->priv;
+	u64 temp = 0;
+	int i;
+	int ret;
+
+	ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
+	if (ret)
+		return ret;
+
+	for (i = 2; i >= 0; i--) {
+		temp <<= 16;
+		temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
+	}
+
+	*val = temp;
+
+	return 0;
+}
+
+static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	struct mii_bus *bus = dev->priv;
+	u64 temp = 0;
+	int i;
+	int ret;
+
+	ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
+	if (ret)
+		return ret;
+
+	for (i = 3; i >= 0; i--) {
+		temp <<= 16;
+		temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
+	}
+
+	*val = temp;
+
+	return 0;
+}
+
+static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
+{
+	struct mii_bus *bus = dev->priv;
+	int ret;
+
+	ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
+	if (ret)
+		return ret;
+
+	return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
+}
+
+static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg,
+			     u16 value)
+{
+	struct mii_bus *bus = dev->priv;
+	int ret;
+
+	ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
+	if (ret)
+		return ret;
+
+	return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
+}
+
+static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg,
+				    u32 value)
+{
+	struct mii_bus *bus = dev->priv;
+	unsigned int i;
+	u32 temp = value;
+
+	for (i = 0; i < 2; i++) {
+		int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
+				    temp & 0xffff);
+		if (ret)
+			return ret;
+		temp >>= 16;
+	}
+
+	return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
+
+}
+
+static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg,
+				    u64 value)
+{
+	struct mii_bus *bus = dev->priv;
+	unsigned i;
+	u64 temp = value;
+
+	for (i = 0; i < 3; i++) {
+		int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
+				    temp & 0xffff);
+		if (ret)
+			return ret;
+		temp >>= 16;
+	}
+
+	return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
+
+}
+
+static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg,
+			     u64 value)
+{
+	struct mii_bus *bus = dev->priv;
+	unsigned i;
+	u64 temp = value;
+
+	for (i = 0; i < 4; i++) {
+		int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
+				    temp & 0xffff);
+		if (ret)
+			return ret;
+		temp >>= 16;
+	}
+
+	return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
+}
+
+static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg,
+			       u16 *value)
+{
+	struct mii_bus *bus = dev->priv;
+
+	*value = mdiobus_read(bus, addr, reg);
+
+	return 0;
+}
+
+static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg,
+				u16 value)
+{
+	struct mii_bus *bus = dev->priv;
+
+	return mdiobus_write(bus, addr, reg, value);
+}
+
+static struct b53_io_ops b53_mdio_ops = {
+	.read8 = b53_mdio_read8,
+	.read16 = b53_mdio_read16,
+	.read32 = b53_mdio_read32,
+	.read48 = b53_mdio_read48,
+	.read64 = b53_mdio_read64,
+	.write8 = b53_mdio_write8,
+	.write16 = b53_mdio_write16,
+	.write32 = b53_mdio_write32,
+	.write48 = b53_mdio_write48,
+	.write64 = b53_mdio_write64,
+	.phy_read16 = b53_mdio_phy_read16,
+	.phy_write16 = b53_mdio_phy_write16,
+};
+
+static int b53_phy_probe(struct phy_device *phydev)
+{
+	struct b53_device *dev;
+	int ret;
+
+	/* allow the generic phy driver to take over */
+	if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0)
+		return -ENODEV;
+
+	dev = b53_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->current_page = 0xff;
+	dev->priv = phydev->mdio.bus;
+	dev->ops = &b53_mdio_ops;
+	dev->pdata = NULL;
+	mutex_init(&dev->reg_mutex);
+
+	ret = b53_switch_detect(dev);
+	if (ret)
+		return ret;
+
+	linkmode_zero(phydev->supported);
+	if (is5325(dev) || is5365(dev))
+		linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported);
+	else
+		linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported);
+
+	linkmode_copy(phydev->advertising, phydev->supported);
+
+	ret = b53_switch_register(dev);
+	if (ret) {
+		dev_err(dev->dev, "failed to register switch: %i\n", ret);
+		return ret;
+	}
+
+	phydev->priv = dev;
+
+	return 0;
+}
+
+static int b53_phy_config_init(struct phy_device *phydev)
+{
+	struct b53_device *dev = phydev->priv;
+
+	/* we don't use page 0xff, so force a page set */
+	dev->current_page = 0xff;
+	/* force the ethX as alias */
+	dev->sw_dev.alias = phydev->attached_dev->name;
+
+	return 0;
+}
+
+static void b53_phy_remove(struct phy_device *phydev)
+{
+	struct b53_device *priv = phydev->priv;
+
+	if (!priv)
+		return;
+
+	b53_switch_remove(priv);
+
+	phydev->priv = NULL;
+}
+
+static int b53_phy_config_aneg(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int b53_phy_read_status(struct phy_device *phydev)
+{
+	struct b53_device *priv = phydev->priv;
+
+	if (is5325(priv) || is5365(priv))
+		phydev->speed = 100;
+	else
+		phydev->speed = 1000;
+
+	phydev->duplex = DUPLEX_FULL;
+	phydev->link = 1;
+	phydev->state = PHY_RUNNING;
+
+	netif_carrier_on(phydev->attached_dev);
+	phydev->adjust_link(phydev->attached_dev);
+
+	return 0;
+}
+
+static const struct of_device_id b53_of_match_1[] = {
+	{ .compatible = "brcm,bcm5325" },
+	{ .compatible = "brcm,bcm5395" },
+	{ .compatible = "brcm,bcm5397" },
+	{ .compatible = "brcm,bcm5398" },
+	{ /* sentinel */ },
+};
+
+static const struct of_device_id b53_of_match_2[] = {
+	{ .compatible = "brcm,bcm53115" },
+	{ .compatible = "brcm,bcm53125" },
+	{ .compatible = "brcm,bcm53128" },
+	{ /* sentinel */ },
+};
+
+static const struct of_device_id b53_of_match_3[] = {
+	{ .compatible = "brcm,bcm5365" },
+	{ /* sentinel */ },
+};
+
+/* BCM5325, BCM539x */
+static struct phy_driver b53_phy_driver_id1 = {
+	.phy_id		= 0x0143bc00,
+	.name		= "Broadcom B53 (1)",
+	.phy_id_mask	= 0x1ffffc00,
+	.features	= 0,
+	.probe		= b53_phy_probe,
+	.remove		= b53_phy_remove,
+	.config_aneg	= b53_phy_config_aneg,
+	.config_init	= b53_phy_config_init,
+	.read_status	= b53_phy_read_status,
+	.mdiodrv.driver = {
+		.name = "bcm539x",
+		.of_match_table = b53_of_match_1,
+	},
+};
+
+/* BCM53125, BCM53128 */
+static struct phy_driver b53_phy_driver_id2 = {
+	.phy_id		= 0x03625c00,
+	.name		= "Broadcom B53 (2)",
+	.phy_id_mask	= 0x1ffffc00,
+	.features	= 0,
+	.probe		= b53_phy_probe,
+	.remove		= b53_phy_remove,
+	.config_aneg	= b53_phy_config_aneg,
+	.config_init	= b53_phy_config_init,
+	.read_status	= b53_phy_read_status,
+	.mdiodrv.driver = {
+		.name = "bcm531xx",
+		.of_match_table = b53_of_match_2,
+	},
+};
+
+/* BCM5365 */
+static struct phy_driver b53_phy_driver_id3 = {
+	.phy_id		= 0x00406300,
+	.name		= "Broadcom B53 (3)",
+	.phy_id_mask	= 0x1fffff00,
+	.features	= 0,
+	.probe		= b53_phy_probe,
+	.remove		= b53_phy_remove,
+	.config_aneg	= b53_phy_config_aneg,
+	.config_init	= b53_phy_config_init,
+	.read_status	= b53_phy_read_status,
+	.mdiodrv.driver = {
+		.name = "bcm5365",
+		.of_match_table = b53_of_match_3,
+	},
+};
+
+int __init b53_phy_driver_register(void)
+{
+	int ret;
+
+	ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE);
+	if (ret)
+		return ret;
+
+	ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE);
+	if (ret)
+		goto err1;
+
+	ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE);
+	if (!ret)
+		return 0;
+
+	phy_driver_unregister(&b53_phy_driver_id2);
+err1:
+	phy_driver_unregister(&b53_phy_driver_id1);
+	return ret;
+}
+
+void __exit b53_phy_driver_unregister(void)
+{
+	phy_driver_unregister(&b53_phy_driver_id3);
+	phy_driver_unregister(&b53_phy_driver_id2);
+	phy_driver_unregister(&b53_phy_driver_id1);
+}
+
+module_init(b53_phy_driver_register);
+module_exit(b53_phy_driver_unregister);
+
+MODULE_DESCRIPTION("B53 MDIO access driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff -Naur linux-orig/drivers/net/phy/b53/b53_mmap.c linux/drivers/net/phy/b53/b53_mmap.c
--- linux-orig/drivers/net/phy/b53/b53_mmap.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_mmap.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,241 @@
+/*
+ * B53 register access through memory mapped registers
+ *
+ * Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#include "b53_priv.h"
+
+static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
+{
+	u8 __iomem *regs = dev->priv;
+
+	*val = readb(regs + (page << 8) + reg);
+
+	return 0;
+}
+
+static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
+{
+	u8 __iomem *regs = dev->priv;
+
+	if (WARN_ON(reg % 2))
+		return -EINVAL;
+
+	if (dev->pdata && dev->pdata->big_endian)
+		*val = readw_be(regs + (page << 8) + reg);
+	else
+		*val = readw(regs + (page << 8) + reg);
+
+	return 0;
+}
+
+static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
+{
+	u8 __iomem *regs = dev->priv;
+
+	if (WARN_ON(reg % 4))
+		return -EINVAL;
+
+	if (dev->pdata && dev->pdata->big_endian)
+		*val = readl_be(regs + (page << 8) + reg);
+	else
+		*val = readl(regs + (page << 8) + reg);
+
+	return 0;
+}
+
+static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	if (WARN_ON(reg % 2))
+		return -EINVAL;
+
+	if (reg % 4) {
+		u16 lo;
+		u32 hi;
+
+		b53_mmap_read16(dev, page, reg, &lo);
+		b53_mmap_read32(dev, page, reg + 2, &hi);
+
+		*val = ((u64)hi << 16) | lo;
+	} else {
+		u32 lo;
+		u16 hi;
+
+		b53_mmap_read32(dev, page, reg, &lo);
+		b53_mmap_read16(dev, page, reg + 4, &hi);
+
+		*val = ((u64)hi << 32) | lo;
+	}
+
+	return 0;
+}
+
+static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	u32 hi, lo;
+
+	if (WARN_ON(reg % 4))
+		return -EINVAL;
+
+	b53_mmap_read32(dev, page, reg, &lo);
+	b53_mmap_read32(dev, page, reg + 4, &hi);
+
+	*val = ((u64)hi << 32) | lo;
+
+	return 0;
+}
+
+static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
+{
+	u8 __iomem *regs = dev->priv;
+
+	writeb(value, regs + (page << 8) + reg);
+
+	return 0;
+}
+
+static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg,
+			     u16 value)
+{
+	u8 __iomem *regs = dev->priv;
+
+	if (WARN_ON(reg % 2))
+		return -EINVAL;
+
+	if (dev->pdata && dev->pdata->big_endian)
+		writew_be(value, regs + (page << 8) + reg);
+	else
+		writew(value, regs + (page << 8) + reg);
+
+	return 0;
+}
+
+static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg,
+				    u32 value)
+{
+	u8 __iomem *regs = dev->priv;
+
+	if (WARN_ON(reg % 4))
+		return -EINVAL;
+
+	if (dev->pdata && dev->pdata->big_endian)
+		writel_be(value, regs + (page << 8) + reg);
+	else
+		writel(value, regs + (page << 8) + reg);
+
+	return 0;
+}
+
+static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg,
+				    u64 value)
+{
+	if (WARN_ON(reg % 2))
+		return -EINVAL;
+
+	if (reg % 4) {
+		u32 hi = (u32)(value >> 16);
+		u16 lo = (u16)value;
+
+		b53_mmap_write16(dev, page, reg, lo);
+		b53_mmap_write32(dev, page, reg + 2, hi);
+	} else {
+		u16 hi = (u16)(value >> 32);
+		u32 lo = (u32)value;
+
+		b53_mmap_write32(dev, page, reg, lo);
+		b53_mmap_write16(dev, page, reg + 4, hi);
+	}
+
+	return 0;
+}
+
+static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg,
+			     u64 value)
+{
+	u32 hi, lo;
+
+	hi = (u32)(value >> 32);
+	lo = (u32)value;
+
+	if (WARN_ON(reg % 4))
+		return -EINVAL;
+
+	b53_mmap_write32(dev, page, reg, lo);
+	b53_mmap_write32(dev, page, reg + 4, hi);
+
+	return 0;
+}
+
+static struct b53_io_ops b53_mmap_ops = {
+	.read8 = b53_mmap_read8,
+	.read16 = b53_mmap_read16,
+	.read32 = b53_mmap_read32,
+	.read48 = b53_mmap_read48,
+	.read64 = b53_mmap_read64,
+	.write8 = b53_mmap_write8,
+	.write16 = b53_mmap_write16,
+	.write32 = b53_mmap_write32,
+	.write48 = b53_mmap_write48,
+	.write64 = b53_mmap_write64,
+};
+
+static int b53_mmap_probe(struct platform_device *pdev)
+{
+	struct b53_platform_data *pdata = pdev->dev.platform_data;
+	struct b53_device *dev;
+
+	if (!pdata)
+		return -EINVAL;
+
+	dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs);
+	if (!dev)
+		return -ENOMEM;
+
+	if (pdata)
+		dev->pdata = pdata;
+
+	platform_set_drvdata(pdev, dev);
+
+	return b53_switch_register(dev);
+}
+
+static int b53_mmap_remove(struct platform_device *pdev)
+{
+	struct b53_device *dev = platform_get_drvdata(pdev);
+
+	if (dev)
+		b53_switch_remove(dev);
+
+	return 0;
+}
+
+static struct platform_driver b53_mmap_driver = {
+	.probe = b53_mmap_probe,
+	.remove = b53_mmap_remove,
+	.driver = {
+		.name = "b53-switch",
+	},
+};
+
+module_platform_driver(b53_mmap_driver);
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 MMAP access driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff -Naur linux-orig/drivers/net/phy/b53/b53_phy_fixup.c linux/drivers/net/phy/b53/b53_phy_fixup.c
--- linux-orig/drivers/net/phy/b53/b53_phy_fixup.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_phy_fixup.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,55 @@
+/*
+ * B53 PHY Fixup call
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/phy.h>
+
+#define B53_PSEUDO_PHY	0x1e /* Register Access Pseudo PHY */
+
+#define B53_BRCM_OUI_1	0x0143bc00
+#define B53_BRCM_OUI_2	0x03625c00
+#define B53_BRCM_OUI_3	0x00406300
+
+static int b53_phy_fixup(struct phy_device *dev)
+{
+	struct mii_bus *bus = dev->mdio.bus;
+	u32 phy_id;
+
+	if (dev->mdio.addr != B53_PSEUDO_PHY)
+		return 0;
+
+	/* read the first port's id */
+	phy_id = mdiobus_read(bus, 0, 2) << 16;
+	phy_id |= mdiobus_read(bus, 0, 3);
+
+	if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 ||
+	    (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 ||
+	    (phy_id & 0xffffff00) == B53_BRCM_OUI_3) {
+		dev->phy_id = phy_id;
+	}
+
+	return 0;
+}
+
+int __init b53_phy_fixup_register(void)
+{
+	return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup);
+}
+
+subsys_initcall(b53_phy_fixup_register);
diff -Naur linux-orig/drivers/net/phy/b53/b53_priv.h linux/drivers/net/phy/b53/b53_priv.h
--- linux-orig/drivers/net/phy/b53/b53_priv.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_priv.h	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,336 @@
+/*
+ * B53 common definitions
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __B53_PRIV_H
+#define __B53_PRIV_H
+
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/switch.h>
+
+struct b53_device;
+
+struct b53_io_ops {
+	int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value);
+	int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value);
+	int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value);
+	int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
+	int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
+	int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value);
+	int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value);
+	int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value);
+	int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value);
+	int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value);
+	int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value);
+	int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value);
+};
+
+enum {
+	BCM5325_DEVICE_ID = 0x25,
+	BCM5365_DEVICE_ID = 0x65,
+	BCM5395_DEVICE_ID = 0x95,
+	BCM5397_DEVICE_ID = 0x97,
+	BCM5398_DEVICE_ID = 0x98,
+	BCM53115_DEVICE_ID = 0x53115,
+	BCM53125_DEVICE_ID = 0x53125,
+	BCM53128_DEVICE_ID = 0x53128,
+	BCM63XX_DEVICE_ID = 0x6300,
+	BCM53010_DEVICE_ID = 0x53010,
+	BCM53011_DEVICE_ID = 0x53011,
+	BCM53012_DEVICE_ID = 0x53012,
+	BCM53018_DEVICE_ID = 0x53018,
+	BCM53019_DEVICE_ID = 0x53019,
+};
+
+#define B53_N_PORTS	9
+#define B53_N_PORTS_25	6
+
+struct b53_vlan {
+	unsigned int	members:B53_N_PORTS;
+	unsigned int	untag:B53_N_PORTS;
+};
+
+struct b53_port {
+	unsigned int	pvid:12;
+};
+
+struct b53_device {
+	struct switch_dev sw_dev;
+	struct b53_platform_data *pdata;
+
+	struct mutex reg_mutex;
+	const struct b53_io_ops *ops;
+
+	/* chip specific data */
+	u32 chip_id;
+	u8 core_rev;
+	u8 vta_regs[3];
+	u8 duplex_reg;
+	u8 jumbo_pm_reg;
+	u8 jumbo_size_reg;
+	int reset_gpio;
+
+	/* used ports mask */
+	u16 enabled_ports;
+
+	/* connect specific data */
+	u8 current_page;
+	struct device *dev;
+	void *priv;
+
+	/* run time configuration */
+	unsigned enable_vlan:1;
+	unsigned enable_jumbo:1;
+	unsigned allow_vid_4095:1;
+
+	struct b53_port *ports;
+	struct b53_vlan *vlans;
+
+	char *buf;
+};
+
+#define b53_for_each_port(dev, i) \
+	for (i = 0; i < B53_N_PORTS; i++) \
+		if (dev->enabled_ports & BIT(i))
+
+
+
+static inline int is5325(struct b53_device *dev)
+{
+	return dev->chip_id == BCM5325_DEVICE_ID;
+}
+
+static inline int is5365(struct b53_device *dev)
+{
+#ifdef CONFIG_BCM47XX
+	return dev->chip_id == BCM5365_DEVICE_ID;
+#else
+	return 0;
+#endif
+}
+
+static inline int is5397_98(struct b53_device *dev)
+{
+	return dev->chip_id == BCM5397_DEVICE_ID ||
+		dev->chip_id == BCM5398_DEVICE_ID;
+}
+
+static inline int is539x(struct b53_device *dev)
+{
+	return dev->chip_id == BCM5395_DEVICE_ID ||
+		dev->chip_id == BCM5397_DEVICE_ID ||
+		dev->chip_id == BCM5398_DEVICE_ID;
+}
+
+static inline int is531x5(struct b53_device *dev)
+{
+	return dev->chip_id == BCM53115_DEVICE_ID ||
+		dev->chip_id == BCM53125_DEVICE_ID ||
+		dev->chip_id == BCM53128_DEVICE_ID;
+}
+
+static inline int is63xx(struct b53_device *dev)
+{
+#ifdef CONFIG_BCM63XX
+	return dev->chip_id == BCM63XX_DEVICE_ID;
+#else
+	return 0;
+#endif
+}
+
+static inline int is5301x(struct b53_device *dev)
+{
+	return dev->chip_id == BCM53010_DEVICE_ID ||
+		dev->chip_id == BCM53011_DEVICE_ID ||
+		dev->chip_id == BCM53012_DEVICE_ID ||
+		dev->chip_id == BCM53018_DEVICE_ID ||
+		dev->chip_id == BCM53019_DEVICE_ID;
+}
+
+#define B53_CPU_PORT_25	5
+#define B53_CPU_PORT	8
+
+static inline int is_cpu_port(struct b53_device *dev, int port)
+{
+	return dev->sw_dev.cpu_port == port;
+}
+
+static inline int is_imp_port(struct b53_device *dev, int port)
+{
+	if (is5325(dev) || is5365(dev))
+		return port == B53_CPU_PORT_25;
+	else
+		return port == B53_CPU_PORT;
+}
+
+static inline struct b53_device *sw_to_b53(struct switch_dev *sw)
+{
+	return container_of(sw, struct b53_device, sw_dev);
+}
+
+struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops,
+				    void *priv);
+
+int b53_switch_detect(struct b53_device *dev);
+
+int b53_switch_register(struct b53_device *dev);
+
+static inline void b53_switch_remove(struct b53_device *dev)
+{
+	unregister_switch(&dev->sw_dev);
+}
+
+static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->read8(dev, page, reg, val);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->read16(dev, page, reg, val);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->read32(dev, page, reg, val);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->read48(dev, page, reg, val);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->read64(dev, page, reg, val);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->write8(dev, page, reg, value);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg,
+			      u16 value)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->write16(dev, page, reg, value);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg,
+			      u32 value)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->write32(dev, page, reg, value);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg,
+			      u64 value)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->write48(dev, page, reg, value);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg,
+			       u64 value)
+{
+	int ret;
+
+	mutex_lock(&dev->reg_mutex);
+	ret = dev->ops->write64(dev, page, reg, value);
+	mutex_unlock(&dev->reg_mutex);
+
+	return ret;
+}
+
+#ifdef CONFIG_BCM47XX
+#include <bcm47xx_board.h>
+#endif
+
+#include <linux/version.h>
+#include <linux/bcm47xx_nvram.h>
+
+static inline int b53_switch_get_reset_gpio(struct b53_device *dev)
+{
+#ifdef CONFIG_BCM47XX
+	enum bcm47xx_board board = bcm47xx_board_get();
+
+	switch (board) {
+	case BCM47XX_BOARD_LINKSYS_WRT300NV11:
+	case BCM47XX_BOARD_LINKSYS_WRT310NV1:
+		return 8;
+	default:
+		break;
+	}
+#endif
+
+	return bcm47xx_nvram_gpio_pin("robo_reset");
+}
+
+#endif
diff -Naur linux-orig/drivers/net/phy/b53/b53_regs.h linux/drivers/net/phy/b53/b53_regs.h
--- linux-orig/drivers/net/phy/b53/b53_regs.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_regs.h	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,348 @@
+/*
+ * B53 register definitions
+ *
+ * Copyright (C) 2004 Broadcom Corporation
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __B53_REGS_H
+#define __B53_REGS_H
+
+/* Management Port (SMP) Page offsets */
+#define B53_CTRL_PAGE			0x00 /* Control */
+#define B53_STAT_PAGE			0x01 /* Status */
+#define B53_MGMT_PAGE			0x02 /* Management Mode */
+#define B53_MIB_AC_PAGE			0x03 /* MIB Autocast */
+#define B53_ARLCTRL_PAGE		0x04 /* ARL Control */
+#define B53_ARLIO_PAGE			0x05 /* ARL Access */
+#define B53_FRAMEBUF_PAGE		0x06 /* Management frame access */
+#define B53_MEM_ACCESS_PAGE		0x08 /* Memory access */
+
+/* PHY Registers */
+#define B53_PORT_MII_PAGE(i)		(0x10 + (i)) /* Port i MII Registers */
+#define B53_IM_PORT_PAGE		0x18 /* Inverse MII Port (to EMAC) */
+#define B53_ALL_PORT_PAGE		0x19 /* All ports MII (broadcast) */
+
+/* MIB registers */
+#define B53_MIB_PAGE(i)			(0x20 + (i))
+
+/* Quality of Service (QoS) Registers */
+#define B53_QOS_PAGE			0x30
+
+/* Port VLAN Page */
+#define B53_PVLAN_PAGE			0x31
+
+/* VLAN Registers */
+#define B53_VLAN_PAGE			0x34
+
+/* Jumbo Frame Registers */
+#define B53_JUMBO_PAGE			0x40
+
+/* CFP Configuration Registers Page */
+#define B53_CFP_PAGE			0xa1
+
+/*************************************************************************
+ * Control Page registers
+ *************************************************************************/
+
+/* Port Control Register (8 bit) */
+#define B53_PORT_CTRL(i)		(0x00 + (i))
+#define   PORT_CTRL_RX_DISABLE		BIT(0)
+#define   PORT_CTRL_TX_DISABLE		BIT(1)
+#define   PORT_CTRL_RX_BCST_EN		BIT(2) /* Broadcast RX (P8 only) */
+#define   PORT_CTRL_RX_MCST_EN		BIT(3) /* Multicast RX (P8 only) */
+#define   PORT_CTRL_RX_UCST_EN		BIT(4) /* Unicast RX (P8 only) */
+#define	  PORT_CTRL_STP_STATE_S		5
+#define   PORT_CTRL_STP_STATE_MASK	(0x7 << PORT_CTRL_STP_STATE_S)
+
+/* SMP Control Register (8 bit) */
+#define B53_SMP_CTRL			0x0a
+
+/* Switch Mode Control Register (8 bit) */
+#define B53_SWITCH_MODE			0x0b
+#define   SM_SW_FWD_MODE		BIT(0)	/* 1 = Managed Mode */
+#define   SM_SW_FWD_EN			BIT(1)	/* Forwarding Enable */
+
+/* IMP Port state override register (8 bit) */
+#define B53_PORT_OVERRIDE_CTRL		0x0e
+#define   PORT_OVERRIDE_LINK		BIT(0)
+#define   PORT_OVERRIDE_FULL_DUPLEX	BIT(1) /* 0 = Half Duplex */
+#define   PORT_OVERRIDE_SPEED_S		2
+#define   PORT_OVERRIDE_SPEED_10M	(0 << PORT_OVERRIDE_SPEED_S)
+#define   PORT_OVERRIDE_SPEED_100M	(1 << PORT_OVERRIDE_SPEED_S)
+#define   PORT_OVERRIDE_SPEED_1000M	(2 << PORT_OVERRIDE_SPEED_S)
+#define   PORT_OVERRIDE_RV_MII_25	BIT(4) /* BCM5325 only */
+#define   PORT_OVERRIDE_RX_FLOW		BIT(4)
+#define   PORT_OVERRIDE_TX_FLOW		BIT(5)
+#define   PORT_OVERRIDE_SPEED_2000M	BIT(6) /* BCM5301X only, requires setting 1000M */
+#define   PORT_OVERRIDE_EN		BIT(7) /* Use the register contents */
+
+/* Power-down mode control */
+#define B53_PD_MODE_CTRL_25		0x0f
+
+/* IP Multicast control (8 bit) */
+#define B53_IP_MULTICAST_CTRL		0x21
+#define  B53_IPMC_FWD_EN		BIT(1)
+#define  B53_UC_FWD_EN			BIT(6)
+#define  B53_MC_FWD_EN			BIT(7)
+
+/* (16 bit) */
+#define B53_UC_FLOOD_MASK		0x32
+#define B53_MC_FLOOD_MASK		0x34
+#define B53_IPMC_FLOOD_MASK		0x36
+
+/*
+ * Override Ports 0-7 State on devices with xMII interfaces (8 bit)
+ *
+ * For port 8 still use B53_PORT_OVERRIDE_CTRL
+ * Please note that not all ports are available on every hardware, e.g. BCM5301X
+ * don't include overriding port 6, BCM63xx also have some limitations.
+ */
+#define B53_GMII_PORT_OVERRIDE_CTRL(i)	(0x58 + (i))
+#define   GMII_PO_LINK			BIT(0)
+#define   GMII_PO_FULL_DUPLEX		BIT(1) /* 0 = Half Duplex */
+#define   GMII_PO_SPEED_S		2
+#define   GMII_PO_SPEED_10M		(0 << GMII_PO_SPEED_S)
+#define   GMII_PO_SPEED_100M		(1 << GMII_PO_SPEED_S)
+#define   GMII_PO_SPEED_1000M		(2 << GMII_PO_SPEED_S)
+#define   GMII_PO_RX_FLOW		BIT(4)
+#define   GMII_PO_TX_FLOW		BIT(5)
+#define   GMII_PO_EN			BIT(6) /* Use the register contents */
+#define   GMII_PO_SPEED_2000M		BIT(7) /* BCM5301X only, requires setting 1000M */
+
+/* Software reset register (8 bit) */
+#define B53_SOFTRESET			0x79
+
+/* Fast Aging Control register (8 bit) */
+#define B53_FAST_AGE_CTRL		0x88
+#define   FAST_AGE_STATIC		BIT(0)
+#define   FAST_AGE_DYNAMIC		BIT(1)
+#define   FAST_AGE_PORT			BIT(2)
+#define   FAST_AGE_VLAN			BIT(3)
+#define   FAST_AGE_STP			BIT(4)
+#define   FAST_AGE_MC			BIT(5)
+#define   FAST_AGE_DONE			BIT(7)
+
+/*************************************************************************
+ * Status Page registers
+ *************************************************************************/
+
+/* Link Status Summary Register (16bit) */
+#define B53_LINK_STAT			0x00
+
+/* Link Status Change Register (16 bit) */
+#define B53_LINK_STAT_CHANGE		0x02
+
+/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */
+#define B53_SPEED_STAT			0x04
+#define  SPEED_PORT_FE(reg, port)	(((reg) >> (port)) & 1)
+#define  SPEED_PORT_GE(reg, port)	(((reg) >> 2 * (port)) & 3)
+#define  SPEED_STAT_10M			0
+#define  SPEED_STAT_100M		1
+#define  SPEED_STAT_1000M		2
+
+/* Duplex Status Summary (16 bit) */
+#define B53_DUPLEX_STAT_FE		0x06
+#define B53_DUPLEX_STAT_GE		0x08
+#define B53_DUPLEX_STAT_63XX		0x0c
+
+/* Revision ID register for BCM5325 */
+#define B53_REV_ID_25			0x50
+
+/* Strap Value (48 bit) */
+#define B53_STRAP_VALUE			0x70
+#define   SV_GMII_CTRL_115		BIT(27)
+
+/*************************************************************************
+ * Management Mode Page Registers
+ *************************************************************************/
+
+/* Global Management Config Register (8 bit) */
+#define B53_GLOBAL_CONFIG		0x00
+#define   GC_RESET_MIB			0x01
+#define   GC_RX_BPDU_EN			0x02
+#define   GC_MIB_AC_HDR_EN		0x10
+#define   GC_MIB_AC_EN			0x20
+#define   GC_FRM_MGMT_PORT_M		0xC0
+#define   GC_FRM_MGMT_PORT_04		0x00
+#define   GC_FRM_MGMT_PORT_MII		0x80
+
+/* Broadcom Header control register (8 bit) */
+#define B53_BRCM_HDR			0x03
+#define   BRCM_HDR_P8_EN		BIT(0) /* Enable tagging on port 8 */
+#define   BRCM_HDR_P5_EN		BIT(1) /* Enable tagging on port 5 */
+
+/* Device ID register (8 or 32 bit) */
+#define B53_DEVICE_ID			0x30
+
+/* Revision ID register (8 bit) */
+#define B53_REV_ID			0x40
+
+/*************************************************************************
+ * ARL Access Page Registers
+ *************************************************************************/
+
+/* VLAN Table Access Register (8 bit) */
+#define B53_VT_ACCESS			0x80
+#define B53_VT_ACCESS_9798		0x60 /* for BCM5397/BCM5398 */
+#define B53_VT_ACCESS_63XX		0x60 /* for BCM6328/62/68 */
+#define   VTA_CMD_WRITE			0
+#define   VTA_CMD_READ			1
+#define   VTA_CMD_CLEAR			2
+#define   VTA_START_CMD			BIT(7)
+
+/* VLAN Table Index Register (16 bit) */
+#define B53_VT_INDEX			0x81
+#define B53_VT_INDEX_9798		0x61
+#define B53_VT_INDEX_63XX		0x62
+
+/* VLAN Table Entry Register (32 bit) */
+#define B53_VT_ENTRY			0x83
+#define B53_VT_ENTRY_9798		0x63
+#define B53_VT_ENTRY_63XX		0x64
+#define   VTE_MEMBERS			0x1ff
+#define   VTE_UNTAG_S			9
+#define   VTE_UNTAG			(0x1ff << 9)
+
+/*************************************************************************
+ * Port VLAN Registers
+ *************************************************************************/
+
+/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */
+#define B53_PVLAN_PORT_MASK(i)		((i) * 2)
+
+/*************************************************************************
+ * 802.1Q Page Registers
+ *************************************************************************/
+
+/* Global QoS Control (8 bit) */
+#define B53_QOS_GLOBAL_CTL		0x00
+
+/* Enable 802.1Q for individual Ports (16 bit) */
+#define B53_802_1P_EN			0x04
+
+/*************************************************************************
+ * VLAN Page Registers
+ *************************************************************************/
+
+/* VLAN Control 0 (8 bit) */
+#define B53_VLAN_CTRL0			0x00
+#define   VC0_8021PF_CTRL_MASK		0x3
+#define   VC0_8021PF_CTRL_NONE		0x0
+#define   VC0_8021PF_CTRL_CHANGE_PRI	0x1
+#define   VC0_8021PF_CTRL_CHANGE_VID	0x2
+#define   VC0_8021PF_CTRL_CHANGE_BOTH	0x3
+#define   VC0_8021QF_CTRL_MASK		0xc
+#define   VC0_8021QF_CTRL_CHANGE_PRI	0x1
+#define   VC0_8021QF_CTRL_CHANGE_VID	0x2
+#define   VC0_8021QF_CTRL_CHANGE_BOTH	0x3
+#define   VC0_RESERVED_1		BIT(1)
+#define   VC0_DROP_VID_MISS		BIT(4)
+#define   VC0_VID_HASH_VID		BIT(5)
+#define   VC0_VID_CHK_EN		BIT(6)	/* Use VID,DA or VID,SA */
+#define   VC0_VLAN_EN			BIT(7)	/* 802.1Q VLAN Enabled */
+
+/* VLAN Control 1 (8 bit) */
+#define B53_VLAN_CTRL1			0x01
+#define   VC1_RX_MCST_TAG_EN		BIT(1)
+#define   VC1_RX_MCST_FWD_EN		BIT(2)
+#define   VC1_RX_MCST_UNTAG_EN		BIT(3)
+
+/* VLAN Control 2 (8 bit) */
+#define B53_VLAN_CTRL2			0x02
+
+/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */
+#define B53_VLAN_CTRL3			0x03
+#define B53_VLAN_CTRL3_63XX		0x04
+#define   VC3_MAXSIZE_1532		BIT(6) /* 5325 only */
+#define   VC3_HIGH_8BIT_EN		BIT(7) /* 5325 only */
+
+/* VLAN Control 4 (8 bit) */
+#define B53_VLAN_CTRL4			0x05
+#define B53_VLAN_CTRL4_25		0x04
+#define B53_VLAN_CTRL4_63XX		0x06
+#define   VC4_ING_VID_CHECK_S		6
+#define   VC4_ING_VID_CHECK_MASK	(0x3 << VC4_ING_VID_CHECK_S)
+#define   VC4_ING_VID_VIO_FWD		0 /* forward, but do not learn */
+#define   VC4_ING_VID_VIO_DROP		1 /* drop VID violations */
+#define   VC4_NO_ING_VID_CHK		2 /* do not check */
+#define   VC4_ING_VID_VIO_TO_IMP	3 /* redirect to MII port */
+
+/* VLAN Control 5 (8 bit) */
+#define B53_VLAN_CTRL5			0x06
+#define B53_VLAN_CTRL5_25		0x05
+#define B53_VLAN_CTRL5_63XX		0x07
+#define   VC5_VID_FFF_EN		BIT(2)
+#define   VC5_DROP_VTABLE_MISS		BIT(3)
+
+/* VLAN Control 6 (8 bit) */
+#define B53_VLAN_CTRL6			0x07
+#define B53_VLAN_CTRL6_63XX		0x08
+
+/* VLAN Table Access Register (16 bit) */
+#define B53_VLAN_TABLE_ACCESS_25	0x06	/* BCM5325E/5350 */
+#define B53_VLAN_TABLE_ACCESS_65	0x08	/* BCM5365 */
+#define   VTA_VID_LOW_MASK_25		0xf
+#define   VTA_VID_LOW_MASK_65		0xff
+#define   VTA_VID_HIGH_S_25		4
+#define   VTA_VID_HIGH_S_65		8
+#define   VTA_VID_HIGH_MASK_25		(0xff << VTA_VID_HIGH_S_25E)
+#define   VTA_VID_HIGH_MASK_65		(0xf << VTA_VID_HIGH_S_65)
+#define   VTA_RW_STATE			BIT(12)
+#define   VTA_RW_STATE_RD		0
+#define   VTA_RW_STATE_WR		BIT(12)
+#define   VTA_RW_OP_EN			BIT(13)
+
+/* VLAN Read/Write Registers for (16/32 bit) */
+#define B53_VLAN_WRITE_25		0x08
+#define B53_VLAN_WRITE_65		0x0a
+#define B53_VLAN_READ			0x0c
+#define   VA_MEMBER_MASK		0x3f
+#define   VA_UNTAG_S_25			6
+#define   VA_UNTAG_MASK_25		0x3f
+#define   VA_UNTAG_S_65			7
+#define   VA_UNTAG_MASK_65		0x1f
+#define   VA_VID_HIGH_S			12
+#define   VA_VID_HIGH_MASK		(0xffff << VA_VID_HIGH_S)
+#define   VA_VALID_25			BIT(20)
+#define   VA_VALID_25_R4		BIT(24)
+#define   VA_VALID_65			BIT(14)
+
+/* VLAN Port Default Tag (16 bit) */
+#define B53_VLAN_PORT_DEF_TAG(i)	(0x10 + 2 * (i))
+
+/*************************************************************************
+ * Jumbo Frame Page Registers
+ *************************************************************************/
+
+/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */
+#define B53_JUMBO_PORT_MASK		0x01
+#define B53_JUMBO_PORT_MASK_63XX	0x04
+#define   JPM_10_100_JUMBO_EN		BIT(24) /* GigE always enabled */
+
+/* Good Frame Max Size without 802.1Q TAG (16 bit) */
+#define B53_JUMBO_MAX_SIZE		0x05
+#define B53_JUMBO_MAX_SIZE_63XX		0x08
+#define   JMS_MIN_SIZE			1518
+#define   JMS_MAX_SIZE			9724
+
+/*************************************************************************
+ * CFP Configuration Page Registers
+ *************************************************************************/
+
+/* CFP Control Register with ports map (8 bit) */
+#define B53_CFP_CTRL			0x00
+
+#endif /* !__B53_REGS_H */
diff -Naur linux-orig/drivers/net/phy/b53/b53_spi.c linux/drivers/net/phy/b53/b53_spi.c
--- linux-orig/drivers/net/phy/b53/b53_spi.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_spi.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,344 @@
+/*
+ * B53 register access through SPI
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/platform_data/b53.h>
+
+#include "b53_priv.h"
+
+#define B53_SPI_DATA		0xf0
+
+#define B53_SPI_STATUS		0xfe
+#define B53_SPI_CMD_SPIF	BIT(7)
+#define B53_SPI_CMD_RACK	BIT(5)
+
+#define B53_SPI_CMD_READ	0x00
+#define B53_SPI_CMD_WRITE	0x01
+#define B53_SPI_CMD_NORMAL	0x60
+#define B53_SPI_CMD_FAST	0x10
+
+#define B53_SPI_PAGE_SELECT	0xff
+
+static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val,
+				     unsigned len)
+{
+	u8 txbuf[2];
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ;
+	txbuf[1] = reg;
+
+	return spi_write_then_read(spi, txbuf, 2, val, len);
+}
+
+static inline int b53_spi_clear_status(struct spi_device *spi)
+{
+	unsigned int i;
+	u8 rxbuf;
+	int ret;
+
+	for (i = 0; i < 10; i++) {
+		ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
+		if (ret)
+			return ret;
+
+		if (!(rxbuf & B53_SPI_CMD_SPIF))
+			break;
+
+		mdelay(1);
+	}
+
+	if (i == 10)
+		return -EIO;
+
+	return 0;
+}
+
+static inline int b53_spi_set_page(struct spi_device *spi, u8 page)
+{
+	u8 txbuf[3];
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
+	txbuf[1] = B53_SPI_PAGE_SELECT;
+	txbuf[2] = page;
+
+	return spi_write(spi, txbuf, sizeof(txbuf));
+}
+
+static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page)
+{
+	int ret = b53_spi_clear_status(spi);
+
+	if (ret)
+		return ret;
+
+	return b53_spi_set_page(spi, page);
+}
+
+static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg)
+{
+	u8 rxbuf;
+	int retry_count;
+	int ret;
+
+	ret = b53_spi_read_reg(spi, reg, &rxbuf, 1);
+	if (ret)
+		return ret;
+
+	for (retry_count = 0; retry_count < 10; retry_count++) {
+		ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
+		if (ret)
+			return ret;
+
+		if (rxbuf & B53_SPI_CMD_RACK)
+			break;
+
+		mdelay(1);
+	}
+
+	if (retry_count == 10)
+		return -EIO;
+
+	return 0;
+}
+
+static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data,
+			unsigned len)
+{
+	struct spi_device *spi = dev->priv;
+	int ret;
+
+	ret = b53_prepare_reg_access(spi, page);
+	if (ret)
+		return ret;
+
+	ret = b53_spi_prepare_reg_read(spi, reg);
+	if (ret)
+		return ret;
+
+	return b53_spi_read_reg(spi, B53_SPI_DATA, data, len);
+}
+
+static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
+{
+	return b53_spi_read(dev, page, reg, val, 1);
+}
+
+static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
+{
+	int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2);
+
+	if (!ret)
+		*val = le16_to_cpu(*val);
+
+	return ret;
+}
+
+static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
+{
+	int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4);
+
+	if (!ret)
+		*val = le32_to_cpu(*val);
+
+	return ret;
+}
+
+static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	int ret;
+
+	*val = 0;
+	ret = b53_spi_read(dev, page, reg, (u8 *)val, 6);
+	if (!ret)
+		*val = le64_to_cpu(*val);
+
+	return ret;
+}
+
+static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8);
+
+	if (!ret)
+		*val = le64_to_cpu(*val);
+
+	return ret;
+}
+
+static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
+{
+	struct spi_device *spi = dev->priv;
+	int ret;
+	u8 txbuf[3];
+
+	ret = b53_prepare_reg_access(spi, page);
+	if (ret)
+		return ret;
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
+	txbuf[1] = reg;
+	txbuf[2] = value;
+
+	return spi_write(spi, txbuf, sizeof(txbuf));
+}
+
+static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value)
+{
+	struct spi_device *spi = dev->priv;
+	int ret;
+	u8 txbuf[4];
+
+	ret = b53_prepare_reg_access(spi, page);
+	if (ret)
+		return ret;
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
+	txbuf[1] = reg;
+	put_unaligned_le16(value, &txbuf[2]);
+
+	return spi_write(spi, txbuf, sizeof(txbuf));
+}
+
+static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value)
+{
+	struct spi_device *spi = dev->priv;
+	int ret;
+	u8 txbuf[6];
+
+	ret = b53_prepare_reg_access(spi, page);
+	if (ret)
+		return ret;
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
+	txbuf[1] = reg;
+	put_unaligned_le32(value, &txbuf[2]);
+
+	return spi_write(spi, txbuf, sizeof(txbuf));
+}
+
+static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value)
+{
+	struct spi_device *spi = dev->priv;
+	int ret;
+	u8 txbuf[10];
+
+	ret = b53_prepare_reg_access(spi, page);
+	if (ret)
+		return ret;
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
+	txbuf[1] = reg;
+	put_unaligned_le64(value, &txbuf[2]);
+
+	return spi_write(spi, txbuf, sizeof(txbuf) - 2);
+}
+
+static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value)
+{
+	struct spi_device *spi = dev->priv;
+	int ret;
+	u8 txbuf[10];
+
+	ret = b53_prepare_reg_access(spi, page);
+	if (ret)
+		return ret;
+
+	txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
+	txbuf[1] = reg;
+	put_unaligned_le64(value, &txbuf[2]);
+
+	return spi_write(spi, txbuf, sizeof(txbuf));
+}
+
+static struct b53_io_ops b53_spi_ops = {
+	.read8 = b53_spi_read8,
+	.read16 = b53_spi_read16,
+	.read32 = b53_spi_read32,
+	.read48 = b53_spi_read48,
+	.read64 = b53_spi_read64,
+	.write8 = b53_spi_write8,
+	.write16 = b53_spi_write16,
+	.write32 = b53_spi_write32,
+	.write48 = b53_spi_write48,
+	.write64 = b53_spi_write64,
+};
+
+static int b53_spi_probe(struct spi_device *spi)
+{
+	struct b53_device *dev;
+	int ret;
+
+	dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi);
+	if (!dev)
+		return -ENOMEM;
+
+	if (spi->dev.platform_data)
+		dev->pdata = spi->dev.platform_data;
+
+	ret = b53_switch_register(dev);
+	if (ret)
+		return ret;
+
+	spi_set_drvdata(spi, dev);
+
+	return 0;
+}
+
+static int b53_spi_remove(struct spi_device *spi)
+{
+	struct b53_device *dev = spi_get_drvdata(spi);
+
+	if (dev)
+		b53_switch_remove(dev);
+
+	return 0;
+}
+
+static const struct of_device_id b53_of_match[] = {
+	{ .compatible = "brcm,bcm5325" },
+	{ .compatible = "brcm,bcm53115" },
+	{ .compatible = "brcm,bcm53125" },
+	{ .compatible = "brcm,bcm53128" },
+	{ .compatible = "brcm,bcm5365" },
+	{ .compatible = "brcm,bcm5395" },
+	{ .compatible = "brcm,bcm5397" },
+	{ .compatible = "brcm,bcm5398" },
+	{ /* sentinel */ },
+};
+
+static struct spi_driver b53_spi_driver = {
+	.driver = {
+		.name	= "b53-switch",
+		.bus	= &spi_bus_type,
+		.owner	= THIS_MODULE,
+		.of_match_table = b53_of_match,
+	},
+	.probe	= b53_spi_probe,
+	.remove	= b53_spi_remove,
+};
+
+module_spi_driver(b53_spi_driver);
+
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 SPI access driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff -Naur linux-orig/drivers/net/phy/b53/b53_srab.c linux/drivers/net/phy/b53/b53_srab.c
--- linux-orig/drivers/net/phy/b53/b53_srab.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/b53/b53_srab.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,378 @@
+/*
+ * B53 register access through Switch Register Access Bridge Registers
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#include "b53_priv.h"
+
+/* command and status register of the SRAB */
+#define B53_SRAB_CMDSTAT		0x2c
+#define  B53_SRAB_CMDSTAT_RST		BIT(2)
+#define  B53_SRAB_CMDSTAT_WRITE		BIT(1)
+#define  B53_SRAB_CMDSTAT_GORDYN	BIT(0)
+#define  B53_SRAB_CMDSTAT_PAGE		24
+#define  B53_SRAB_CMDSTAT_REG		16
+
+/* high order word of write data to switch registe */
+#define B53_SRAB_WD_H			0x30
+
+/* low order word of write data to switch registe */
+#define B53_SRAB_WD_L			0x34
+
+/* high order word of read data from switch register */
+#define B53_SRAB_RD_H			0x38
+
+/* low order word of read data from switch register */
+#define B53_SRAB_RD_L			0x3c
+
+/* command and status register of the SRAB */
+#define B53_SRAB_CTRLS			0x40
+#define  B53_SRAB_CTRLS_RCAREQ		BIT(3)
+#define  B53_SRAB_CTRLS_RCAGNT		BIT(4)
+#define  B53_SRAB_CTRLS_SW_INIT_DONE	BIT(6)
+
+/* the register captures interrupt pulses from the switch */
+#define B53_SRAB_INTR			0x44
+
+static int b53_srab_request_grant(struct b53_device *dev)
+{
+	u8 __iomem *regs = dev->priv;
+	u32 ctrls;
+	int i;
+
+	ctrls = readl(regs + B53_SRAB_CTRLS);
+	ctrls |= B53_SRAB_CTRLS_RCAREQ;
+	writel(ctrls, regs + B53_SRAB_CTRLS);
+
+	for (i = 0; i < 20; i++) {
+		ctrls = readl(regs + B53_SRAB_CTRLS);
+		if (ctrls & B53_SRAB_CTRLS_RCAGNT)
+			break;
+		usleep_range(10, 100);
+	}
+	if (WARN_ON(i == 5))
+		return -EIO;
+
+	return 0;
+}
+
+static void b53_srab_release_grant(struct b53_device *dev)
+{
+	u8 __iomem *regs = dev->priv;
+	u32 ctrls;
+
+	ctrls = readl(regs + B53_SRAB_CTRLS);
+	ctrls &= ~B53_SRAB_CTRLS_RCAREQ;
+	writel(ctrls, regs + B53_SRAB_CTRLS);
+}
+
+static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op)
+{
+	int i;
+	u32 cmdstat;
+	u8 __iomem *regs = dev->priv;
+
+	/* set register address */
+	cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) |
+		  (reg << B53_SRAB_CMDSTAT_REG) |
+		  B53_SRAB_CMDSTAT_GORDYN |
+		  op;
+	writel(cmdstat, regs + B53_SRAB_CMDSTAT);
+
+	/* check if operation completed */
+	for (i = 0; i < 5; ++i) {
+		cmdstat = readl(regs + B53_SRAB_CMDSTAT);
+		if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN))
+			break;
+		usleep_range(10, 100);
+	}
+
+	if (WARN_ON(i == 5))
+		return -EIO;
+
+	return 0;
+}
+
+static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	ret = b53_srab_op(dev, page, reg, 0);
+	if (ret)
+		goto err;
+
+	*val = readl(regs + B53_SRAB_RD_L) & 0xff;
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	ret = b53_srab_op(dev, page, reg, 0);
+	if (ret)
+		goto err;
+
+	*val = readl(regs + B53_SRAB_RD_L) & 0xffff;
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	ret = b53_srab_op(dev, page, reg, 0);
+	if (ret)
+		goto err;
+
+	*val = readl(regs + B53_SRAB_RD_L);
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	ret = b53_srab_op(dev, page, reg, 0);
+	if (ret)
+		goto err;
+
+	*val = readl(regs + B53_SRAB_RD_L);
+	*val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32;
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	ret = b53_srab_op(dev, page, reg, 0);
+	if (ret)
+		goto err;
+
+	*val = readl(regs + B53_SRAB_RD_L);
+	*val += (u64)readl(regs + B53_SRAB_RD_H) << 32;
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	writel(value, regs + B53_SRAB_WD_L);
+
+	ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg,
+			     u16 value)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	writel(value, regs + B53_SRAB_WD_L);
+
+	ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg,
+				    u32 value)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	writel(value, regs + B53_SRAB_WD_L);
+
+	ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+
+}
+
+static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg,
+				    u64 value)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	writel((u32)value, regs + B53_SRAB_WD_L);
+	writel((u16)(value >> 32), regs + B53_SRAB_WD_H);
+
+	ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+
+}
+
+static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg,
+			     u64 value)
+{
+	u8 __iomem *regs = dev->priv;
+	int ret = 0;
+
+	ret = b53_srab_request_grant(dev);
+	if (ret)
+		goto err;
+
+	writel((u32)value, regs + B53_SRAB_WD_L);
+	writel((u32)(value >> 32), regs + B53_SRAB_WD_H);
+
+	ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
+
+err:
+	b53_srab_release_grant(dev);
+
+	return ret;
+}
+
+static struct b53_io_ops b53_srab_ops = {
+	.read8 = b53_srab_read8,
+	.read16 = b53_srab_read16,
+	.read32 = b53_srab_read32,
+	.read48 = b53_srab_read48,
+	.read64 = b53_srab_read64,
+	.write8 = b53_srab_write8,
+	.write16 = b53_srab_write16,
+	.write32 = b53_srab_write32,
+	.write48 = b53_srab_write48,
+	.write64 = b53_srab_write64,
+};
+
+static int b53_srab_probe(struct platform_device *pdev)
+{
+	struct b53_platform_data *pdata = pdev->dev.platform_data;
+	struct b53_device *dev;
+
+	if (!pdata)
+		return -EINVAL;
+
+	dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs);
+	if (!dev)
+		return -ENOMEM;
+
+	if (pdata)
+		dev->pdata = pdata;
+
+	platform_set_drvdata(pdev, dev);
+
+	return b53_switch_register(dev);
+}
+
+static int b53_srab_remove(struct platform_device *pdev)
+{
+	struct b53_device *dev = platform_get_drvdata(pdev);
+
+	if (dev)
+		b53_switch_remove(dev);
+
+	return 0;
+}
+
+static struct platform_driver b53_srab_driver = {
+	.probe = b53_srab_probe,
+	.remove = b53_srab_remove,
+	.driver = {
+		.name = "b53-srab-switch",
+	},
+};
+
+module_platform_driver(b53_srab_driver);
+MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
+MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff -Naur linux-orig/drivers/net/phy/ip17xx.c linux/drivers/net/phy/ip17xx.c
--- linux-orig/drivers/net/phy/ip17xx.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/ip17xx.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1370 @@
+/*
+ * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family
+ *
+ * Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com>
+ * Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz>
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+
+#define MAX_VLANS 16
+#define MAX_PORTS 9
+#undef DUMP_MII_IO
+
+typedef struct ip17xx_reg {
+	u16 p;			// phy
+	u16 m;			// mii
+} reg;
+typedef char bitnum;
+
+#define NOTSUPPORTED {-1,-1}
+
+#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1))
+
+struct ip17xx_state;
+
+/*********** CONSTANTS ***********/
+struct register_mappings {
+	char *NAME;
+	u16 MODEL_NO;			// Compare to bits 4-9 of MII register 0,3.
+	bitnum NUM_PORTS;
+	bitnum CPU_PORT;
+
+/* The default VLAN for each port.
+	 Default: 0x0001 for Ports 0,1,2,3
+		  0x0002 for Ports 4,5 */
+	reg VLAN_DEFAULT_TAG_REG[MAX_PORTS];
+
+/* These ports are tagged.
+	 Default: 0x00 */
+	reg ADD_TAG_REG;
+	reg REMOVE_TAG_REG;
+	bitnum ADD_TAG_BIT[MAX_PORTS];
+/* These ports are untagged.
+	 Default: 0x00 (i.e. do not alter any VLAN tags...)
+	 Maybe set to 0 if user disables VLANs. */
+	bitnum REMOVE_TAG_BIT[MAX_PORTS];
+
+/* Port M and Port N are on the same VLAN.
+	 Default: All ports on all VLANs. */
+// Use register {29, 19+N/2}
+	reg VLAN_LOOKUP_REG;
+// Port 5 uses register {30, 18} but same as odd bits.
+	reg VLAN_LOOKUP_REG_5;		// in a different register on IP175C.
+	bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS];
+	bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS];
+
+/* This VLAN corresponds to which ports.
+	 Default: 0x2f,0x30,0x3f,0x3f... */
+	reg TAG_VLAN_MASK_REG;
+	bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS];
+	bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS];
+
+	int RESET_VAL;
+	reg RESET_REG;
+
+	reg MODE_REG;
+	int MODE_VAL;
+
+/* General flags */
+	reg ROUTER_CONTROL_REG;
+	reg VLAN_CONTROL_REG;
+	bitnum TAG_VLAN_BIT;
+	bitnum ROUTER_EN_BIT;
+	bitnum NUMLAN_GROUPS_MAX;
+	bitnum NUMLAN_GROUPS_BIT;
+
+	reg MII_REGISTER_EN;
+	bitnum MII_REGISTER_EN_BIT;
+
+	// set to 1 for 178C, 0 for 175C.
+	bitnum SIMPLE_VLAN_REGISTERS;	// 175C has two vlans per register but 178C has only one.
+
+	// Pointers to functions which manipulate hardware state
+	int (*update_state)(struct ip17xx_state *state);
+	int (*set_vlan_mode)(struct ip17xx_state *state);
+	int (*reset)(struct ip17xx_state *state);
+};
+
+static int ip175c_update_state(struct ip17xx_state *state);
+static int ip175c_set_vlan_mode(struct ip17xx_state *state);
+static int ip175c_reset(struct ip17xx_state *state);
+
+static const struct register_mappings IP178C = {
+	.NAME = "IP178C",
+	.MODEL_NO = 0x18,
+	.VLAN_DEFAULT_TAG_REG = {
+		{30,3},{30,4},{30,5},{30,6},{30,7},{30,8},
+		{30,9},{30,10},{30,11},
+	},
+
+	.ADD_TAG_REG = {30,12},
+	.ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8},
+	.REMOVE_TAG_REG = {30,13},
+	.REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12},
+
+	.SIMPLE_VLAN_REGISTERS = 1,
+
+	.VLAN_LOOKUP_REG = {31,0},// +N
+	.VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS
+	.VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8},
+	.VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8},
+
+	.TAG_VLAN_MASK_REG = {30,14}, // +N
+	.TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8},
+	.TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8},
+
+	.RESET_VAL = 0x55AA,
+	.RESET_REG = {30,0},
+	.MODE_VAL = 0,
+	.MODE_REG = NOTSUPPORTED,
+
+	.ROUTER_CONTROL_REG = {30,30},
+	.ROUTER_EN_BIT = 11,
+	.NUMLAN_GROUPS_MAX = 8,
+	.NUMLAN_GROUPS_BIT = 8, // {0-2}
+
+	.VLAN_CONTROL_REG = {30,13},
+	.TAG_VLAN_BIT = 3,
+
+	.CPU_PORT = 8,
+	.NUM_PORTS = 9,
+
+	.MII_REGISTER_EN = NOTSUPPORTED,
+
+	.update_state = ip175c_update_state,
+	.set_vlan_mode = ip175c_set_vlan_mode,
+	.reset = ip175c_reset,
+};
+
+static const struct register_mappings IP175C = {
+	.NAME = "IP175C",
+	.MODEL_NO = 0x18,
+	.VLAN_DEFAULT_TAG_REG = {
+		{29,24},{29,25},{29,26},{29,27},{29,28},{29,30},
+		NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED
+	},
+
+	.ADD_TAG_REG = {29,23},
+	.REMOVE_TAG_REG = {29,23},
+	.ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1},
+	.REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1},
+
+	.SIMPLE_VLAN_REGISTERS = 0,
+
+	.VLAN_LOOKUP_REG = {29,19},// +N/2
+	.VLAN_LOOKUP_REG_5 = {30,18},
+	.VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1},
+	.VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1},
+
+	.TAG_VLAN_MASK_REG = {30,1}, // +N/2
+	.TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1},
+	.TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1},
+
+	.RESET_VAL = 0x175C,
+	.RESET_REG = {30,0},
+	.MODE_VAL = 0x175C,
+	.MODE_REG = {29,31},
+
+	.ROUTER_CONTROL_REG = {30,9},
+	.ROUTER_EN_BIT = 3,
+	.NUMLAN_GROUPS_MAX = 8,
+	.NUMLAN_GROUPS_BIT = 0, // {0-2}
+
+	.VLAN_CONTROL_REG = {30,9},
+	.TAG_VLAN_BIT = 7,
+
+	.NUM_PORTS = 6,
+	.CPU_PORT = 5,
+
+	.MII_REGISTER_EN = NOTSUPPORTED,
+
+	.update_state = ip175c_update_state,
+	.set_vlan_mode = ip175c_set_vlan_mode,
+	.reset = ip175c_reset,
+};
+
+static const struct register_mappings IP175A = {
+	.NAME = "IP175A",
+	.MODEL_NO = 0x05,
+	.VLAN_DEFAULT_TAG_REG = {
+		{0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED,
+		NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED
+	},
+
+	.ADD_TAG_REG = {0,23},
+	.REMOVE_TAG_REG = {0,23},
+	.ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1},
+	.REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1},
+
+	.SIMPLE_VLAN_REGISTERS = 0,
+
+	// Only programmable via EEPROM
+	.VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2
+	.VLAN_LOOKUP_REG_5 = NOTSUPPORTED,
+	.VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1},
+	.VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1},
+
+	.TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2,
+	.TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1},
+	.TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1},
+
+	.RESET_VAL = -1,
+	.RESET_REG = NOTSUPPORTED,
+	.MODE_VAL = 0,
+	.MODE_REG = NOTSUPPORTED,
+
+	.ROUTER_CONTROL_REG = NOTSUPPORTED,
+	.VLAN_CONTROL_REG = NOTSUPPORTED,
+	.TAG_VLAN_BIT = -1,
+	.ROUTER_EN_BIT = -1,
+	.NUMLAN_GROUPS_MAX = -1,
+	.NUMLAN_GROUPS_BIT = -1, // {0-2}
+
+	.NUM_PORTS = 5,
+	.CPU_PORT = 4,
+
+	.MII_REGISTER_EN = {0, 18},
+	.MII_REGISTER_EN_BIT = 7,
+
+	.update_state = ip175c_update_state,
+	.set_vlan_mode = ip175c_set_vlan_mode,
+	.reset = ip175c_reset,
+};
+
+
+static int ip175d_update_state(struct ip17xx_state *state);
+static int ip175d_set_vlan_mode(struct ip17xx_state *state);
+static int ip175d_reset(struct ip17xx_state *state);
+
+static const struct register_mappings IP175D = {
+	.NAME = "IP175D",
+	.MODEL_NO = 0x18,
+
+	// The IP175D has a completely different interface, so we leave most
+	// of the registers undefined and switch to different code paths.
+
+	.VLAN_DEFAULT_TAG_REG = {
+		NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,
+		NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,
+	},
+
+	.ADD_TAG_REG = NOTSUPPORTED,
+	.REMOVE_TAG_REG = NOTSUPPORTED,
+
+	.SIMPLE_VLAN_REGISTERS = 0,
+
+	.VLAN_LOOKUP_REG = NOTSUPPORTED,
+	.VLAN_LOOKUP_REG_5 = NOTSUPPORTED,
+	.TAG_VLAN_MASK_REG = NOTSUPPORTED,
+
+	.RESET_VAL = 0x175D,
+	.RESET_REG = {20,2},
+	.MODE_REG = NOTSUPPORTED,
+
+	.ROUTER_CONTROL_REG = NOTSUPPORTED,
+	.ROUTER_EN_BIT = -1,
+	.NUMLAN_GROUPS_BIT = -1,
+
+	.VLAN_CONTROL_REG = NOTSUPPORTED,
+	.TAG_VLAN_BIT = -1,
+
+	.NUM_PORTS = 6,
+	.CPU_PORT = 5,
+
+	.MII_REGISTER_EN = NOTSUPPORTED,
+
+	.update_state = ip175d_update_state,
+	.set_vlan_mode = ip175d_set_vlan_mode,
+	.reset = ip175d_reset,
+};
+
+struct ip17xx_state {
+	struct switch_dev dev;
+	struct mii_bus *mii_bus;
+	bool registered;
+
+	int router_mode;		// ROUTER_EN
+	int vlan_enabled;		// TAG_VLAN_EN
+	struct port_state {
+		u16 pvid;
+		unsigned int shareports;
+	} ports[MAX_PORTS];
+	unsigned int add_tag;
+	unsigned int remove_tag;
+	int num_vlans;
+	struct vlan_state {
+		unsigned int ports;
+		unsigned int tag;	// VLAN tag (IP175D only)
+	} vlans[MAX_VLANS];
+	const struct register_mappings *regs;
+	reg proc_mii; 	// phy/reg for the low level register access via swconfig
+
+	char buf[80];
+};
+
+#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev)
+
+static int ip_phy_read(struct ip17xx_state *state, int port, int reg)
+{
+	int val = mdiobus_read(state->mii_bus, port, reg);
+	if (val < 0)
+		pr_warn("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val);
+#ifdef DUMP_MII_IO
+	else
+		pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val);
+#endif
+	return val;
+}
+
+static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val)
+{
+	int err;
+
+#ifdef DUMP_MII_IO
+	pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val);
+#endif
+	err = mdiobus_write(state->mii_bus, port, reg, val);
+	if (err < 0)
+		pr_warn("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err);
+	return err;
+}
+
+static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data)
+{
+	int val = ip_phy_read(state, port, reg);
+	if (val < 0)
+		return 0;
+	return ip_phy_write(state, port, reg, (val & ~mask) | data);
+}
+
+static int getPhy(struct ip17xx_state *state, reg mii)
+{
+	if (!REG_SUPP(mii))
+		return -EFAULT;
+	return ip_phy_read(state, mii.p, mii.m);
+}
+
+static int setPhy(struct ip17xx_state *state, reg mii, u16 value)
+{
+	int err;
+
+	if (!REG_SUPP(mii))
+		return -EFAULT;
+	err = ip_phy_write(state, mii.p, mii.m, value);
+	if (err < 0)
+		return err;
+	mdelay(2);
+	getPhy(state, mii);
+	return 0;
+}
+
+
+/**
+ * These two macros are to simplify the mapping of logical bits to the bits in hardware.
+ * NOTE: these macros will return if there is an error!
+ */
+
+#define GET_PORT_BITS(state, bits, addr, bit_lookup)		\
+	do {							\
+		int i, val = getPhy((state), (addr));		\
+		if (val < 0)					\
+			return val;				\
+		(bits) = 0;					\
+		for (i = 0; i < MAX_PORTS; i++) {		\
+			if ((bit_lookup)[i] == -1) continue;	\
+			if (val & (1<<(bit_lookup)[i]))		\
+				(bits) |= (1<<i);		\
+		}						\
+	} while (0)
+
+#define SET_PORT_BITS(state, bits, addr, bit_lookup)		\
+	do {							\
+		int i, val = getPhy((state), (addr));		\
+		if (val < 0)					\
+			return val;				\
+		for (i = 0; i < MAX_PORTS; i++) {		\
+			unsigned int newmask = ((bits)&(1<<i));	\
+			if ((bit_lookup)[i] == -1) continue;	\
+			val &= ~(1<<(bit_lookup)[i]);		\
+			val |= ((newmask>>i)<<(bit_lookup)[i]);	\
+		}						\
+		val = setPhy((state), (addr), val);		\
+		if (val < 0)					\
+			return val;				\
+	} while (0)
+
+
+static int get_model(struct ip17xx_state *state)
+{
+	int id1, id2;
+	int oui_id, model_no, rev_no, chip_no;
+
+	id1 = ip_phy_read(state, 0, 2);
+	id2 = ip_phy_read(state, 0, 3);
+	oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f);
+	model_no = (id2 >> 4) & 0x3f;
+	rev_no = id2 & 0xf;
+	pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no);
+
+	if (oui_id != 0x0090c3)  // No other oui_id should have reached us anyway
+		return -ENODEV;
+
+	if (model_no == IP175A.MODEL_NO) {
+		state->regs = &IP175A;
+	} else if (model_no == IP175C.MODEL_NO) {
+		/*
+		 *  Several models share the same model_no:
+		 *  178C has more PHYs, so we try whether the device responds to a read from PHY5
+		 *  175D has a new chip ID register
+		 *  175C has neither
+		 */
+		if (ip_phy_read(state, 5, 2) == 0x0243) {
+			state->regs = &IP178C;
+		} else {
+			chip_no = ip_phy_read(state, 20, 0);
+			pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no);
+			if (chip_no == 0x175d) {
+				state->regs = &IP175D;
+			} else {
+				state->regs = &IP175C;
+			}
+		}
+	} else {
+		pr_warn("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no);
+		return -EPERM;
+	}
+	return 0;
+}
+
+/*** Low-level functions for the older models ***/
+
+/** Only set vlan and router flags in the switch **/
+static int ip175c_set_flags(struct ip17xx_state *state)
+{
+	int val;
+
+	if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) {
+		return 0;
+	}
+
+	val = getPhy(state, state->regs->ROUTER_CONTROL_REG);
+	if (val < 0) {
+		return val;
+	}
+	if (state->regs->ROUTER_EN_BIT >= 0) {
+		if (state->router_mode) {
+			val |= (1<<state->regs->ROUTER_EN_BIT);
+		} else {
+			val &= (~(1<<state->regs->ROUTER_EN_BIT));
+		}
+	}
+	if (state->regs->TAG_VLAN_BIT >= 0) {
+		if (state->vlan_enabled) {
+			val |= (1<<state->regs->TAG_VLAN_BIT);
+		} else {
+			val &= (~(1<<state->regs->TAG_VLAN_BIT));
+		}
+	}
+	if (state->regs->NUMLAN_GROUPS_BIT >= 0) {
+		val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->regs->NUMLAN_GROUPS_BIT));
+		if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) {
+			val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT;
+		} else if (state->num_vlans >= 1) {
+			val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT;
+		}
+	}
+	return setPhy(state, state->regs->ROUTER_CONTROL_REG, val);
+}
+
+/** Set all VLAN and port state.  Usually you should call "correct_vlan_state" first. **/
+static int ip175c_set_state(struct ip17xx_state *state)
+{
+	int j;
+	int i;
+	SET_PORT_BITS(state, state->add_tag,
+				  state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT);
+	SET_PORT_BITS(state, state->remove_tag,
+				  state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT);
+
+	if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) {
+		for (j=0; j<state->regs->NUM_PORTS; j++) {
+			reg addr;
+			const bitnum *bit_lookup = (j%2==0)?
+				state->regs->VLAN_LOOKUP_EVEN_BIT:
+				state->regs->VLAN_LOOKUP_ODD_BIT;
+
+			addr = state->regs->VLAN_LOOKUP_REG;
+			if (state->regs->SIMPLE_VLAN_REGISTERS) {
+				addr.m += j;
+			} else {
+				switch (j) {
+				case 0:
+				case 1:
+					break;
+				case 2:
+				case 3:
+					addr.m+=1;
+					break;
+				case 4:
+					addr.m+=2;
+					break;
+				case 5:
+					addr = state->regs->VLAN_LOOKUP_REG_5;
+					break;
+				default:
+					addr.m = -1; // shouldn't get here, but...
+					break;
+				}
+			}
+			//printf("shareports for %d is %02X\n",j,state->ports[j].shareports);
+			if (REG_SUPP(addr)) {
+				SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup);
+			}
+		}
+	}
+	if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) {
+		for (j=0; j<MAX_VLANS; j++) {
+			reg addr = state->regs->TAG_VLAN_MASK_REG;
+			const bitnum *bit_lookup = (j%2==0)?
+				state->regs->TAG_VLAN_MASK_EVEN_BIT:
+				state->regs->TAG_VLAN_MASK_ODD_BIT;
+			unsigned int vlan_mask;
+			if (state->regs->SIMPLE_VLAN_REGISTERS) {
+				addr.m += j;
+			} else {
+				addr.m += j/2;
+			}
+			vlan_mask = state->vlans[j].ports;
+			SET_PORT_BITS(state, vlan_mask, addr, bit_lookup);
+		}
+	}
+
+	for (i=0; i<MAX_PORTS; i++) {
+		if (REG_SUPP(state->regs->VLAN_DEFAULT_TAG_REG[i])) {
+			int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i],
+					state->ports[i].pvid);
+			if (err < 0) {
+				return err;
+			}
+		}
+	}
+
+	return ip175c_set_flags(state);
+}
+
+/**
+ *  Uses only the VLAN port mask and the add tag mask to generate the other fields:
+ *  which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids.
+ */
+static void ip175c_correct_vlan_state(struct ip17xx_state *state)
+{
+	int i, j;
+	state->num_vlans = 0;
+	for (i=0; i<MAX_VLANS; i++) {
+		if (state->vlans[i].ports != 0) {
+			state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere...
+		}
+	}
+
+	for (i=0; i<state->regs->NUM_PORTS; i++) {
+		unsigned int portmask = (1<<i);
+		if (!state->vlan_enabled) {
+			// Share with everybody!
+			state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1;
+			continue;
+		}
+		state->ports[i].shareports = portmask;
+		for (j=0; j<MAX_VLANS; j++) {
+			if (state->vlans[j].ports & portmask)
+				state->ports[i].shareports |= state->vlans[j].ports;
+		}
+	}
+}
+
+static int ip175c_update_state(struct ip17xx_state *state)
+{
+	ip175c_correct_vlan_state(state);
+	return ip175c_set_state(state);
+}
+
+static int ip175c_set_vlan_mode(struct ip17xx_state *state)
+{
+	return ip175c_update_state(state);
+}
+
+static int ip175c_reset(struct ip17xx_state *state)
+{
+	int err;
+
+	if (REG_SUPP(state->regs->MODE_REG)) {
+		err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL);
+		if (err < 0)
+			return err;
+		err = getPhy(state, state->regs->MODE_REG);
+		if (err < 0)
+			return err;
+	}
+
+	return ip175c_update_state(state);
+}
+
+/*** Low-level functions for IP175D ***/
+
+static int ip175d_update_state(struct ip17xx_state *state)
+{
+	unsigned int filter_mask = 0;
+	unsigned int ports[16], add[16], rem[16];
+	int i, j;
+	int err = 0;
+
+	for (i = 0; i < 16; i++) {
+		ports[i] = 0;
+		add[i] = 0;
+		rem[i] = 0;
+		if (!state->vlan_enabled) {
+			err |= ip_phy_write(state, 22, 14+i, i+1);	// default tags
+			ports[i] = 0x3f;
+			continue;
+		}
+		if (!state->vlans[i].tag) {
+			// Reset the filter
+			err |= ip_phy_write(state, 22, 14+i, 0);	// tag
+			continue;
+		}
+		filter_mask |= 1 << i;
+		err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag);
+		ports[i] = state->vlans[i].ports;
+		for (j = 0; j < 6; j++) {
+			if (ports[i] & (1 << j)) {
+				if (state->add_tag & (1 << j))
+					add[i] |= 1 << j;
+				if (state->remove_tag & (1 << j))
+					rem[i] |= 1 << j;
+			}
+		}
+	}
+
+	// Port masks, tag adds and removals
+	for (i = 0; i < 8; i++) {
+		err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8));
+		err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8));
+		err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8));
+	}
+	err |= ip_phy_write(state, 22, 10, filter_mask);
+
+	// Default VLAN tag for each port
+	for (i = 0; i < 6; i++)
+		err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag);
+
+	return (err ? -EIO : 0);
+}
+
+static int ip175d_set_vlan_mode(struct ip17xx_state *state)
+{
+	int i;
+	int err = 0;
+
+	if (state->vlan_enabled) {
+		// VLAN classification rules: tag-based VLANs, use VID to classify,
+		// drop packets that cannot be classified.
+		err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f);
+
+		// Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed,
+		// VID=0xfff discarded, admin both tagged and untagged, ingress
+		// filters enabled.
+		err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f);
+
+		// Egress rules: IGMP processing off, keep VLAN header off
+		err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000);
+	} else {
+		// VLAN classification rules: everything off & clear table
+		err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000);
+
+		// Ingress and egress rules: set to defaults
+		err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f);
+		err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000);
+	}
+
+	// Reset default VLAN for each port to 0
+	for (i = 0; i < 6; i++)
+		state->ports[i].pvid = 0;
+
+	err |= ip175d_update_state(state);
+
+	return (err ? -EIO : 0);
+}
+
+static int ip175d_reset(struct ip17xx_state *state)
+{
+	int err = 0;
+
+	// Disable the special tagging mode
+	err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000);
+
+	// Set 802.1q protocol type
+	err |= ip_phy_write(state, 22, 3, 0x8100);
+
+	state->vlan_enabled = 0;
+	err |= ip175d_set_vlan_mode(state);
+
+	return (err ? -EIO : 0);
+}
+
+/*** High-level functions ***/
+
+static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	val->value.i = state->vlan_enabled;
+	return 0;
+}
+
+static void ip17xx_reset_vlan_config(struct ip17xx_state *state)
+{
+	int i;
+
+	state->remove_tag = (state->vlan_enabled ? ((1<<state->regs->NUM_PORTS)-1) : 0x0000);
+	state->add_tag = 0x0000;
+	for (i = 0; i < MAX_VLANS; i++) {
+		state->vlans[i].ports = 0x0000;
+		state->vlans[i].tag = (i ? i : 16);
+	}
+	for (i = 0; i < MAX_PORTS; i++)
+		state->ports[i].pvid = 0;
+}
+
+static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int enable;
+
+	enable = val->value.i;
+	if (state->vlan_enabled == enable) {
+		// Do not change any state.
+		return 0;
+	}
+	state->vlan_enabled = enable;
+
+	// Otherwise, if we are switching state, set fields to a known default.
+	ip17xx_reset_vlan_config(state);
+
+	return state->regs->set_vlan_mode(state);
+}
+
+static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int b;
+	int ind;
+	unsigned int ports;
+
+	if (val->port_vlan >= dev->vlans || val->port_vlan < 0)
+		return -EINVAL;
+
+	ports = state->vlans[val->port_vlan].ports;
+	b = 0;
+	ind = 0;
+	while (b < MAX_PORTS) {
+		if (ports&1) {
+			int istagged = ((state->add_tag >> b) & 1);
+			val->value.ports[ind].id = b;
+			val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED);
+			ind++;
+		}
+		b++;
+		ports >>= 1;
+	}
+	val->len = ind;
+
+	return 0;
+}
+
+static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int i;
+
+	if (val->port_vlan >= dev->vlans || val->port_vlan < 0)
+		return -EINVAL;
+
+	state->vlans[val->port_vlan].ports = 0;
+	for (i = 0; i < val->len; i++) {
+		unsigned int bitmask = (1<<val->value.ports[i].id);
+		state->vlans[val->port_vlan].ports |= bitmask;
+		if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) {
+			state->add_tag |= bitmask;
+			state->remove_tag &= (~bitmask);
+		} else {
+			state->add_tag &= (~bitmask);
+			state->remove_tag |= bitmask;
+		}
+	}
+
+	return state->regs->update_state(state);
+}
+
+static int ip17xx_apply(struct switch_dev *dev)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	if (REG_SUPP(state->regs->MII_REGISTER_EN)) {
+		int val = getPhy(state, state->regs->MII_REGISTER_EN);
+		if (val < 0) {
+			return val;
+		}
+		val |= (1<<state->regs->MII_REGISTER_EN_BIT);
+		return setPhy(state, state->regs->MII_REGISTER_EN, val);
+	}
+	return 0;
+}
+
+static int ip17xx_reset(struct switch_dev *dev)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int i, err;
+
+	if (REG_SUPP(state->regs->RESET_REG)) {
+		err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL);
+		if (err < 0)
+			return err;
+		err = getPhy(state, state->regs->RESET_REG);
+
+		/*
+		 *  Data sheet specifies reset period to be 2 msec.
+		 *  (I don't see any mention of the 2ms delay in the IP178C spec, only
+		 *  in IP175C, but it can't hurt.)
+		 */
+		mdelay(2);
+	}
+
+	/* reset switch ports */
+	for (i = 0; i < state->regs->NUM_PORTS-1; i++) {
+		err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET);
+		if (err < 0)
+			return err;
+	}
+
+	state->router_mode = 0;
+	state->vlan_enabled = 0;
+	ip17xx_reset_vlan_config(state);
+
+	return state->regs->reset(state);
+}
+
+static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	if (state->add_tag & (1<<val->port_vlan)) {
+		if (state->remove_tag & (1<<val->port_vlan))
+			val->value.i = 3; // shouldn't ever happen.
+		else
+			val->value.i = 1;
+	} else {
+		if (state->remove_tag & (1<<val->port_vlan))
+			val->value.i = 0;
+		else
+			val->value.i = 2;
+	}
+	return 0;
+}
+
+static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	state->add_tag &= ~(1<<val->port_vlan);
+	state->remove_tag &= ~(1<<val->port_vlan);
+
+	if (val->value.i == 0)
+		state->remove_tag |= (1<<val->port_vlan);
+	if (val->value.i == 1)
+		state->add_tag |= (1<<val->port_vlan);
+
+	return state->regs->update_state(state);
+}
+
+/** Get the current phy address */
+static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	val->value.i = state->proc_mii.p;
+	return 0;
+}
+
+/** Set a new phy address for low level access to registers */
+static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int new_reg = val->value.i;
+
+	if (new_reg < 0 || new_reg > 31)
+		state->proc_mii.p = (u16)-1;
+	else
+		state->proc_mii.p = (u16)new_reg;
+	return 0;
+}
+
+/** Get the current register number */
+static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	val->value.i = state->proc_mii.m;
+	return 0;
+}
+
+/** Set a new register address for low level access to registers */
+static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int new_reg = val->value.i;
+
+	if (new_reg < 0 || new_reg > 31)
+		state->proc_mii.m = (u16)-1;
+	else
+		state->proc_mii.m = (u16)new_reg;
+	return 0;
+}
+
+/** Get the register content of state->proc_mii */
+static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int retval = -EINVAL;
+	if (REG_SUPP(state->proc_mii))
+		retval = getPhy(state, state->proc_mii);
+
+	if (retval < 0) {
+		return retval;
+	} else {
+		val->value.i = retval;
+		return 0;
+	}
+}
+
+/** Write a value to the register defined by phy/reg above */
+static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int myval, err = -EINVAL;
+
+	myval = val->value.i;
+	if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) {
+		err = setPhy(state, state->proc_mii, (u16)myval);
+	}
+	return err;
+}
+
+static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig.
+	return 0;
+}
+
+static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int vlan = val->port_vlan;
+
+	if (vlan < 0 || vlan >= MAX_VLANS)
+		return -EINVAL;
+
+	val->value.i = state->vlans[vlan].tag;
+	return 0;
+}
+
+static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int vlan = val->port_vlan;
+	int tag = val->value.i;
+
+	if (vlan < 0 || vlan >= MAX_VLANS)
+		return -EINVAL;
+
+	if (tag < 0 || tag > 4095)
+		return -EINVAL;
+
+	state->vlans[vlan].tag = tag;
+	return state->regs->update_state(state);
+}
+
+static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int nr = val->port_vlan;
+	int ctrl;
+	int autoneg;
+	int speed;
+	if (val->value.i == 100) {
+		speed = 1;
+		autoneg = 0;
+	} else if (val->value.i == 10) {
+		speed = 0;
+		autoneg = 0;
+	} else {
+		autoneg = 1;
+		speed = 1;
+	}
+
+	/* Can't set speed for cpu port */
+	if (nr == state->regs->CPU_PORT)
+		return -EINVAL;
+
+	if (nr >= dev->ports || nr < 0)
+		return -EINVAL;
+
+	ctrl = ip_phy_read(state, nr, 0);
+	if (ctrl < 0)
+		return -EIO;
+
+	ctrl &= (~(1<<12));
+	ctrl &= (~(1<<13));
+	ctrl |= (autoneg<<12);
+	ctrl |= (speed<<13);
+
+	return ip_phy_write(state, nr, 0, ctrl);
+}
+
+static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int nr = val->port_vlan;
+	int speed, status;
+
+	if (nr == state->regs->CPU_PORT) {
+		val->value.i = 100;
+		return 0;
+	}
+
+	if (nr >= dev->ports || nr < 0)
+		return -EINVAL;
+
+	status = ip_phy_read(state, nr, 1);
+	speed = ip_phy_read(state, nr, 18);
+	if (status < 0 || speed < 0)
+		return -EIO;
+
+	if (status & 4)
+		val->value.i = ((speed & (1<<11)) ? 100 : 10);
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+	int ctrl, speed, status;
+	int nr = val->port_vlan;
+	int len;
+	char *buf = state->buf; // fixed-length at 80.
+
+	if (nr == state->regs->CPU_PORT) {
+		sprintf(buf, "up, 100 Mbps, cpu port");
+		val->value.s = buf;
+		return 0;
+	}
+
+	if (nr >= dev->ports || nr < 0)
+		return -EINVAL;
+
+	ctrl = ip_phy_read(state, nr, 0);
+	status = ip_phy_read(state, nr, 1);
+	speed = ip_phy_read(state, nr, 18);
+	if (ctrl < 0 || status < 0 || speed < 0)
+		return -EIO;
+
+	if (status & 4)
+		len = sprintf(buf, "up, %d Mbps, %s duplex",
+			((speed & (1<<11)) ? 100 : 10),
+			((speed & (1<<10)) ? "full" : "half"));
+	else
+		len = sprintf(buf, "down");
+
+	if (ctrl & (1<<12)) {
+		len += sprintf(buf+len, ", auto-negotiate");
+		if (!(status & (1<<5)))
+			len += sprintf(buf+len, " (in progress)");
+	} else {
+		len += sprintf(buf+len, ", fixed speed (%d)",
+			((ctrl & (1<<13)) ? 100 : 10));
+	}
+
+	buf[len] = '\0';
+	val->value.s = buf;
+	return 0;
+}
+
+static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	*val = state->ports[port].pvid;
+	return 0;
+}
+
+static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val)
+{
+	struct ip17xx_state *state = get_state(dev);
+
+	if (val < 0 || val >= MAX_VLANS)
+		return -EINVAL;
+
+	state->ports[port].pvid = val;
+	return state->regs->update_state(state);
+}
+
+
+enum Ports {
+	IP17XX_PORT_STATUS,
+	IP17XX_PORT_LINK,
+	IP17XX_PORT_TAGGED,
+	IP17XX_PORT_PVID,
+};
+
+enum Globals {
+	IP17XX_ENABLE_VLAN,
+	IP17XX_GET_NAME,
+	IP17XX_REGISTER_PHY,
+	IP17XX_REGISTER_MII,
+	IP17XX_REGISTER_VALUE,
+	IP17XX_REGISTER_ERRNO,
+};
+
+enum Vlans {
+	IP17XX_VLAN_TAG,
+};
+
+static const struct switch_attr ip17xx_global[] = {
+	[IP17XX_ENABLE_VLAN] = {
+		.id = IP17XX_ENABLE_VLAN,
+		.type = SWITCH_TYPE_INT,
+		.name  = "enable_vlan",
+		.description = "Flag to enable or disable VLANs and tagging",
+		.get  = ip17xx_get_enable_vlan,
+		.set = ip17xx_set_enable_vlan,
+	},
+	[IP17XX_GET_NAME] = {
+		.id = IP17XX_GET_NAME,
+		.type = SWITCH_TYPE_STRING,
+		.description = "Returns the type of IC+ chip.",
+		.name  = "name",
+		.get  = ip17xx_read_name,
+		.set = NULL,
+	},
+	/* jal: added for low level debugging etc. */
+	[IP17XX_REGISTER_PHY] = {
+		.id = IP17XX_REGISTER_PHY,
+		.type = SWITCH_TYPE_INT,
+		.description = "Direct register access: set PHY (0-4, or 29,30,31)",
+		.name  = "phy",
+		.get  = ip17xx_get_phy,
+		.set = ip17xx_set_phy,
+	},
+	[IP17XX_REGISTER_MII] = {
+		.id = IP17XX_REGISTER_MII,
+		.type = SWITCH_TYPE_INT,
+		.description = "Direct register access: set MII register number (0-31)",
+		.name  = "reg",
+		.get  = ip17xx_get_reg,
+		.set = ip17xx_set_reg,
+	},
+	[IP17XX_REGISTER_VALUE] = {
+		.id = IP17XX_REGISTER_VALUE,
+		.type = SWITCH_TYPE_INT,
+		.description = "Direct register access: read/write to register (0-65535)",
+		.name  = "val",
+		.get  = ip17xx_get_val,
+		.set = ip17xx_set_val,
+	},
+};
+
+static const struct switch_attr ip17xx_vlan[] = {
+	[IP17XX_VLAN_TAG] = {
+		.id = IP17XX_VLAN_TAG,
+		.type = SWITCH_TYPE_INT,
+		.description = "VLAN ID (0-4095) [IP175D only]",
+		.name = "vid",
+		.get = ip17xx_get_tag,
+		.set = ip17xx_set_tag,
+	}
+};
+
+static const struct switch_attr ip17xx_port[] = {
+	[IP17XX_PORT_STATUS] = {
+		.id = IP17XX_PORT_STATUS,
+		.type = SWITCH_TYPE_STRING,
+		.description = "Returns Detailed port status",
+		.name  = "status",
+		.get  = ip17xx_get_port_status,
+		.set = NULL,
+	},
+	[IP17XX_PORT_LINK] = {
+		.id = IP17XX_PORT_LINK,
+		.type = SWITCH_TYPE_INT,
+		.description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100",
+		.name  = "link",
+		.get  = ip17xx_get_port_speed,
+		.set = ip17xx_set_port_speed,
+	},
+	[IP17XX_PORT_TAGGED] = {
+		.id = IP17XX_PORT_LINK,
+		.type = SWITCH_TYPE_INT,
+		.description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)",
+		.name  = "tagged",
+		.get  = ip17xx_get_tagged,
+		.set = ip17xx_set_tagged,
+	},
+};
+
+static const struct switch_dev_ops ip17xx_ops = {
+	.attr_global = {
+		.attr = ip17xx_global,
+		.n_attr = ARRAY_SIZE(ip17xx_global),
+	},
+	.attr_port = {
+		.attr = ip17xx_port,
+		.n_attr = ARRAY_SIZE(ip17xx_port),
+	},
+	.attr_vlan = {
+		.attr = ip17xx_vlan,
+		.n_attr = ARRAY_SIZE(ip17xx_vlan),
+	},
+
+	.get_port_pvid = ip17xx_get_pvid,
+	.set_port_pvid = ip17xx_set_pvid,
+	.get_vlan_ports = ip17xx_get_ports,
+	.set_vlan_ports = ip17xx_set_ports,
+	.apply_config = ip17xx_apply,
+	.reset_switch = ip17xx_reset,
+};
+
+static int ip17xx_probe(struct phy_device *pdev)
+{
+	struct ip17xx_state *state;
+	struct switch_dev *dev;
+	int err;
+
+	/* We only attach to PHY 0, but use all available PHYs */
+	if (pdev->mdio.addr != 0)
+		return -ENODEV;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	dev = &state->dev;
+
+	pdev->priv = state;
+	state->mii_bus = pdev->mdio.bus;
+
+	err = get_model(state);
+	if (err < 0)
+		goto error;
+
+	dev->vlans = MAX_VLANS;
+	dev->cpu_port = state->regs->CPU_PORT;
+	dev->ports = state->regs->NUM_PORTS;
+	dev->name = state->regs->NAME;
+	dev->ops = &ip17xx_ops;
+
+	pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->mdio.dev));
+	return 0;
+
+error:
+	kfree(state);
+	return err;
+}
+
+static int ip17xx_config_init(struct phy_device *pdev)
+{
+	struct ip17xx_state *state = pdev->priv;
+	struct net_device *dev = pdev->attached_dev;
+	int err;
+
+	err = register_switch(&state->dev, dev);
+	if (err < 0)
+		return err;
+
+	state->registered = true;
+	ip17xx_reset(&state->dev);
+	return 0;
+}
+
+static void ip17xx_remove(struct phy_device *pdev)
+{
+	struct ip17xx_state *state = pdev->priv;
+
+	if (state->registered)
+		unregister_switch(&state->dev);
+	kfree(state);
+}
+
+static int ip17xx_config_aneg(struct phy_device *pdev)
+{
+	return 0;
+}
+
+static int ip17xx_aneg_done(struct phy_device *pdev)
+{
+	return 1;	/* Return any positive value */
+}
+
+static int ip17xx_read_status(struct phy_device *pdev)
+{
+	pdev->speed = SPEED_100;
+	pdev->duplex = DUPLEX_FULL;
+	pdev->pause = pdev->asym_pause = 0;
+	pdev->link = 1;
+
+	return 0;
+}
+
+static struct phy_driver ip17xx_driver[] = {
+	{
+		.name		= "IC+ IP17xx",
+		.phy_id		= 0x02430c00,
+		.phy_id_mask	= 0x0ffffc00,
+		.features	= PHY_BASIC_FEATURES,
+		.probe		= ip17xx_probe,
+		.remove		= ip17xx_remove,
+		.config_init	= ip17xx_config_init,
+		.config_aneg	= ip17xx_config_aneg,
+		.aneg_done	= ip17xx_aneg_done,
+		.read_status	= ip17xx_read_status,
+	}
+};
+
+module_phy_driver(ip17xx_driver);
+
+MODULE_AUTHOR("Patrick Horn <patrick.horn@gmail.com>");
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+MODULE_AUTHOR("Martin Mares <mj@ucw.cz>");
+MODULE_LICENSE("GPL");
diff -Naur linux-orig/drivers/net/phy/mvswitch.c linux/drivers/net/phy/mvswitch.c
--- linux-orig/drivers/net/phy/mvswitch.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/mvswitch.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,446 @@
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of the GNU General Public License v2 as published by the
+ * Free Software Foundation
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/if_vlan.h>
+#include <linux/version.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include "mvswitch.h"
+
+/* Undefine this to use trailer mode instead.
+ * I don't know if header mode works with all chips */
+#define HEADER_MODE	1
+
+MODULE_DESCRIPTION("Marvell 88E6060 Switch driver");
+MODULE_AUTHOR("Felix Fietkau");
+MODULE_LICENSE("GPL");
+
+#define MVSWITCH_MAGIC 0x88E6060
+
+struct mvswitch_priv {
+	netdev_features_t orig_features;
+	u8 vlans[16];
+};
+
+#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv)
+
+static inline u16
+r16(struct phy_device *phydev, int addr, int reg)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+
+	return bus->read(bus, addr, reg);
+}
+
+static inline void
+w16(struct phy_device *phydev, int addr, int reg, u16 val)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+
+	bus->write(bus, addr, reg, val);
+}
+
+
+static struct sk_buff *
+mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb)
+{
+	struct mvswitch_priv *priv;
+	char *buf = NULL;
+	u16 vid;
+
+	priv = dev->phy_ptr;
+	if (unlikely(!priv))
+		goto error;
+
+	if (unlikely(skb->len < 16))
+		goto error;
+
+#ifdef HEADER_MODE
+	if (__vlan_hwaccel_get_tag(skb, &vid))
+		goto error;
+
+	if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) {
+		if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC))
+			goto error_expand;
+		if (skb->len < 62)
+			skb->len = 62;
+	}
+	buf = skb_push(skb, MV_HEADER_SIZE);
+#else
+	if (__vlan_get_tag(skb, &vid))
+		goto error;
+
+	if (unlikely((vid > 15 || !priv->vlans[vid])))
+		goto error;
+
+	if (skb->len <= 64) {
+		if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC))
+			goto error_expand;
+
+		buf = skb->data + 64;
+		skb->len = 64 + MV_TRAILER_SIZE;
+	} else {
+		if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) {
+			if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC))
+				goto error_expand;
+		}
+		buf = skb_put(skb, 4);
+	}
+
+	/* move the ethernet header 4 bytes forward, overwriting the vlan tag */
+	memmove(skb->data + 4, skb->data, 12);
+	skb->data += 4;
+	skb->len -= 4;
+	skb->mac_header += 4;
+#endif
+
+	if (!buf)
+		goto error;
+
+
+#ifdef HEADER_MODE
+	/* prepend the tag */
+	*((__be16 *) buf) = cpu_to_be16(
+		((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) |
+		((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M)
+	);
+#else
+	/* append the tag */
+	*((__be32 *) buf) = cpu_to_be32((
+		(MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) |
+		((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S)
+	));
+#endif
+
+	return skb;
+
+error_expand:
+	if (net_ratelimit())
+		printk("%s: failed to expand/update skb for the switch\n", dev->name);
+
+error:
+	/* any errors? drop the packet! */
+	dev_kfree_skb_any(skb);
+	return NULL;
+}
+
+static void
+mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb)
+{
+	struct mvswitch_priv *priv;
+	unsigned char *buf;
+	int vlan = -1;
+	int i;
+
+	priv = dev->phy_ptr;
+	if (WARN_ON_ONCE(!priv))
+		return;
+
+#ifdef HEADER_MODE
+	buf = skb->data;
+	skb_pull(skb, MV_HEADER_SIZE);
+#else
+	buf = skb->data + skb->len - MV_TRAILER_SIZE;
+	if (buf[0] != 0x80)
+		return;
+#endif
+
+	/* look for the vlan matching the incoming port */
+	for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) {
+		if ((1 << buf[1]) & priv->vlans[i])
+			vlan = i;
+	}
+
+	if (vlan == -1)
+		return;
+
+	__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan);
+}
+
+
+static int
+mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val)
+{
+	int i = 100;
+	u16 r;
+
+	do {
+		r = r16(pdev, addr, reg) & mask;
+		if (r == val)
+			return 0;
+	} while(--i > 0);
+	return -ETIMEDOUT;
+}
+
+static int
+mvswitch_config_init(struct phy_device *pdev)
+{
+	struct mvswitch_priv *priv = to_mvsw(pdev);
+	struct net_device *dev = pdev->attached_dev;
+	u8 vlmap = 0;
+	int i;
+
+	if (!dev)
+		return -EINVAL;
+
+	printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name);
+	linkmode_zero(pdev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pdev->supported);
+	linkmode_copy(pdev->advertising, pdev->supported);
+	dev->phy_ptr = priv;
+	pdev->irq = PHY_POLL;
+#ifdef HEADER_MODE
+	dev->flags |= IFF_PROMISC;
+#endif
+
+	/* initialize default vlans */
+	for (i = 0; i < MV_PORTS; i++)
+		priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i);
+
+	/* before entering reset, disable all ports */
+	for (i = 0; i < MV_PORTS; i++)
+		w16(pdev, MV_PORTREG(CONTROL, i), 0x00);
+
+	msleep(2); /* wait for the status change to settle in */
+
+	/* put the ATU in reset */
+	w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET);
+
+	i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0);
+	if (i < 0) {
+		printk("%s: Timeout waiting for the switch to reset.\n", dev->name);
+		return i;
+	}
+
+	/* set the ATU flags */
+	w16(pdev, MV_SWITCHREG(ATU_CTRL),
+		MV_ATUCTL_NO_LEARN |
+		MV_ATUCTL_ATU_1K |
+		MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */
+	);
+
+	/* initialize the cpu port */
+	w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT),
+#ifdef HEADER_MODE
+		MV_PORTCTRL_HEADER |
+#else
+		MV_PORTCTRL_RXTR |
+		MV_PORTCTRL_TXTR |
+#endif
+		MV_PORTCTRL_ENABLED
+	);
+	/* wait for the phy change to settle in */
+	msleep(2);
+	for (i = 0; i < MV_PORTS; i++) {
+		u8 pvid = 0;
+		int j;
+
+		vlmap = 0;
+
+		/* look for the matching vlan */
+		for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) {
+			if (priv->vlans[j] & (1 << i)) {
+				vlmap = priv->vlans[j];
+				pvid = j;
+			}
+		}
+		/* leave port unconfigured if it's not part of a vlan */
+		if (!vlmap)
+			continue;
+
+		/* add the cpu port to the allowed destinations list */
+		vlmap |= (1 << MV_CPUPORT);
+
+		/* take port out of its own vlan destination map */
+		vlmap &= ~(1 << i);
+
+		/* apply vlan settings */
+		w16(pdev, MV_PORTREG(VLANMAP, i),
+			MV_PORTVLAN_PORTS(vlmap) |
+			MV_PORTVLAN_ID(i)
+		);
+
+		/* re-enable port */
+		w16(pdev, MV_PORTREG(CONTROL, i),
+			MV_PORTCTRL_ENABLED
+		);
+	}
+
+	w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT),
+		MV_PORTVLAN_ID(MV_CPUPORT)
+	);
+
+	/* set the port association vector */
+	for (i = 0; i <= MV_PORTS; i++) {
+		w16(pdev, MV_PORTREG(ASSOC, i),
+			MV_PORTASSOC_PORTS(1 << i)
+		);
+	}
+
+	/* init switch control */
+	w16(pdev, MV_SWITCHREG(CTRL),
+		MV_SWITCHCTL_MSIZE |
+		MV_SWITCHCTL_DROP
+	);
+
+	dev->eth_mangle_rx = mvswitch_mangle_rx;
+	dev->eth_mangle_tx = mvswitch_mangle_tx;
+	priv->orig_features = dev->features;
+
+#ifdef HEADER_MODE
+	dev->priv_flags |= IFF_NO_IP_ALIGN;
+	dev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX;
+#else
+	dev->features |= NETIF_F_HW_VLAN_CTAG_RX;
+#endif
+
+	return 0;
+}
+
+static int
+mvswitch_read_status(struct phy_device *pdev)
+{
+	pdev->speed = SPEED_100;
+	pdev->duplex = DUPLEX_FULL;
+	pdev->link = 1;
+
+	/* XXX ugly workaround: we can't force the switch
+	 * to gracefully handle hosts moving from one port to another,
+	 * so we have to regularly clear the ATU database */
+
+	/* wait for the ATU to become available */
+	mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0);
+
+	/* flush the ATU */
+	w16(pdev, MV_SWITCHREG(ATU_OP),
+		MV_ATUOP_INPROGRESS |
+		MV_ATUOP_FLUSH_ALL
+	);
+
+	/* wait for operation to complete */
+	mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0);
+
+	return 0;
+}
+
+static int
+mvswitch_aneg_done(struct phy_device *phydev)
+{
+	return 1;	/* Return any positive value */
+}
+
+static int
+mvswitch_config_aneg(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static void
+mvswitch_detach(struct phy_device *pdev)
+{
+	struct mvswitch_priv *priv = to_mvsw(pdev);
+	struct net_device *dev = pdev->attached_dev;
+
+	if (!dev)
+		return;
+
+	dev->phy_ptr = NULL;
+	dev->eth_mangle_rx = NULL;
+	dev->eth_mangle_tx = NULL;
+	dev->features = priv->orig_features;
+	dev->priv_flags &= ~IFF_NO_IP_ALIGN;
+}
+
+static void
+mvswitch_remove(struct phy_device *pdev)
+{
+	struct mvswitch_priv *priv = to_mvsw(pdev);
+
+	kfree(priv);
+}
+
+static int
+mvswitch_probe(struct phy_device *pdev)
+{
+	struct mvswitch_priv *priv;
+
+	priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL);
+	if (priv == NULL)
+		return -ENOMEM;
+
+	pdev->priv = priv;
+
+	return 0;
+}
+
+static int
+mvswitch_fixup(struct phy_device *dev)
+{
+	struct mii_bus *bus = dev->mdio.bus;
+	u16 reg;
+
+	if (dev->mdio.addr != 0x10)
+		return 0;
+
+	reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
+	if (reg != MV_IDENT_VALUE)
+		return 0;
+
+	dev->phy_id = MVSWITCH_MAGIC;
+	return 0;
+}
+
+
+static struct phy_driver mvswitch_driver = {
+	.name		= "Marvell 88E6060",
+	.phy_id		= MVSWITCH_MAGIC,
+	.phy_id_mask	= 0xffffffff,
+	.features	= PHY_BASIC_FEATURES,
+	.probe		= &mvswitch_probe,
+	.remove		= &mvswitch_remove,
+	.detach		= &mvswitch_detach,
+	.config_init	= &mvswitch_config_init,
+	.config_aneg	= &mvswitch_config_aneg,
+	.aneg_done	= &mvswitch_aneg_done,
+	.read_status	= &mvswitch_read_status,
+};
+
+static int __init
+mvswitch_init(void)
+{
+	phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup);
+	return phy_driver_register(&mvswitch_driver, THIS_MODULE);
+}
+
+static void __exit
+mvswitch_exit(void)
+{
+	phy_driver_unregister(&mvswitch_driver);
+}
+
+module_init(mvswitch_init);
+module_exit(mvswitch_exit);
diff -Naur linux-orig/drivers/net/phy/mvswitch.h linux/drivers/net/phy/mvswitch.h
--- linux-orig/drivers/net/phy/mvswitch.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/mvswitch.h	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,145 @@
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of the GNU General Public License v2 as published by the
+ * Free Software Foundation
+ */
+#ifndef __MVSWITCH_H
+#define __MVSWITCH_H
+
+#define MV_HEADER_SIZE	2
+#define MV_HEADER_PORTS_M	0x001f
+#define MV_HEADER_PORTS_S	0
+#define MV_HEADER_VLAN_M	0xf000
+#define MV_HEADER_VLAN_S	12
+
+#define MV_TRAILER_SIZE	4
+#define MV_TRAILER_PORTS_M	0x1f
+#define MV_TRAILER_PORTS_S	16
+#define MV_TRAILER_FLAGS_S	24
+#define MV_TRAILER_OVERRIDE	0x80
+
+
+#define MV_PORTS	5
+#define MV_WANPORT	4
+#define MV_CPUPORT	5
+
+#define MV_BASE		0x10
+
+#define MV_PHYPORT_BASE		(MV_BASE + 0x0)
+#define MV_PHYPORT(_n)		(MV_PHYPORT_BASE + (_n))
+#define MV_SWITCHPORT_BASE	(MV_BASE + 0x8)
+#define MV_SWITCHPORT(_n)	(MV_SWITCHPORT_BASE + (_n))
+#define MV_SWITCHREGS		(MV_BASE + 0xf)
+
+enum {
+	MV_PHY_CONTROL      = 0x00,
+	MV_PHY_STATUS       = 0x01,
+	MV_PHY_IDENT0       = 0x02,
+	MV_PHY_IDENT1       = 0x03,
+	MV_PHY_ANEG         = 0x04,
+	MV_PHY_LINK_ABILITY = 0x05,
+	MV_PHY_ANEG_EXPAND  = 0x06,
+	MV_PHY_XMIT_NEXTP   = 0x07,
+	MV_PHY_LINK_NEXTP   = 0x08,
+	MV_PHY_CONTROL1     = 0x10,
+	MV_PHY_STATUS1      = 0x11,
+	MV_PHY_INTR_EN      = 0x12,
+	MV_PHY_INTR_STATUS  = 0x13,
+	MV_PHY_INTR_PORT    = 0x14,
+	MV_PHY_RECV_COUNTER = 0x16,
+	MV_PHY_LED_PARALLEL = 0x16,
+	MV_PHY_LED_STREAM   = 0x17,
+	MV_PHY_LED_CTRL     = 0x18,
+	MV_PHY_LED_OVERRIDE = 0x19,
+	MV_PHY_VCT_CTRL     = 0x1a,
+	MV_PHY_VCT_STATUS   = 0x1b,
+	MV_PHY_CONTROL2     = 0x1e
+};
+#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type
+
+enum {
+	MV_PORT_STATUS      = 0x00,
+	MV_PORT_IDENT       = 0x03,
+	MV_PORT_CONTROL     = 0x04,
+	MV_PORT_VLANMAP     = 0x06,
+	MV_PORT_ASSOC       = 0x0b,
+	MV_PORT_RXCOUNT     = 0x10,
+	MV_PORT_TXCOUNT     = 0x11,
+};
+#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type
+
+enum {
+	MV_PORTCTRL_BLOCK   =  (1 << 0),
+	MV_PORTCTRL_LEARN   =  (2 << 0),
+	MV_PORTCTRL_ENABLED =  (3 << 0),
+	MV_PORTCTRL_VLANTUN =  (1 << 7),	/* Enforce VLANs on packets */
+	MV_PORTCTRL_RXTR    =  (1 << 8),	/* Enable Marvell packet trailer for ingress */
+	MV_PORTCTRL_HEADER	= (1 << 11),	/* Enable Marvell packet header mode for port */
+	MV_PORTCTRL_TXTR    = (1 << 14),	/* Enable Marvell packet trailer for egress */
+	MV_PORTCTRL_FORCEFL = (1 << 15),	/* force flow control */
+};
+
+#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12)
+#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f)
+
+#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f)
+#define MV_PORTASSOC_MONITOR	(1 << 15)
+
+enum {
+	MV_SWITCH_MAC0      = 0x01,
+	MV_SWITCH_MAC1      = 0x02,
+	MV_SWITCH_MAC2      = 0x03,
+	MV_SWITCH_CTRL      = 0x04,
+	MV_SWITCH_ATU_CTRL  = 0x0a,
+	MV_SWITCH_ATU_OP    = 0x0b,
+	MV_SWITCH_ATU_DATA  = 0x0c,
+	MV_SWITCH_ATU_MAC0  = 0x0d,
+	MV_SWITCH_ATU_MAC1  = 0x0e,
+	MV_SWITCH_ATU_MAC2  = 0x0f,
+};
+#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type
+
+enum {
+	MV_SWITCHCTL_EEIE   =  (1 << 0),	/* EEPROM interrupt enable */
+	MV_SWITCHCTL_PHYIE  =  (1 << 1),	/* PHY interrupt enable */
+	MV_SWITCHCTL_ATUDONE=  (1 << 2),	/* ATU done interrupt enable */
+	MV_SWITCHCTL_ATUIE  =  (1 << 3),	/* ATU interrupt enable */
+	MV_SWITCHCTL_CTRMODE=  (1 << 8),	/* statistics for rx and tx errors */
+	MV_SWITCHCTL_RELOAD =  (1 << 9),	/* reload registers from eeprom */
+	MV_SWITCHCTL_MSIZE  = (1 << 10),	/* increase maximum frame size */
+	MV_SWITCHCTL_DROP   = (1 << 13),	/* discard frames with excessive collisions */
+};
+
+enum {
+#define MV_ATUCTL_AGETIME_MIN	16
+#define MV_ATUCTL_AGETIME_MAX	4080
+#define MV_ATUCTL_AGETIME(_n)	((((_n) / 16) & 0xff) << 4)
+	MV_ATUCTL_ATU_256   = (0 << 12),
+	MV_ATUCTL_ATU_512   = (1 << 12),
+	MV_ATUCTL_ATU_1K	= (2 << 12),
+	MV_ATUCTL_ATUMASK   = (3 << 12),
+	MV_ATUCTL_NO_LEARN  = (1 << 14),
+	MV_ATUCTL_RESET     = (1 << 15),
+};
+
+enum {
+#define MV_ATUOP_DBNUM(_n)	((_n) & 0x0f)
+
+	MV_ATUOP_NOOP       = (0 << 12),
+	MV_ATUOP_FLUSH_ALL  = (1 << 12),
+	MV_ATUOP_FLUSH_U    = (2 << 12),
+	MV_ATUOP_LOAD_DB    = (3 << 12),
+	MV_ATUOP_GET_NEXT   = (4 << 12),
+	MV_ATUOP_FLUSH_DB   = (5 << 12),
+	MV_ATUOP_FLUSH_DB_UU= (6 << 12),
+
+	MV_ATUOP_INPROGRESS = (1 << 15),
+};
+
+#define MV_IDENT_MASK		0xfff0
+#define MV_IDENT_VALUE		0x0600
+
+#endif
diff -Naur linux-orig/drivers/net/phy/psb6970.c linux/drivers/net/phy/psb6970.c
--- linux-orig/drivers/net/phy/psb6970.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/psb6970.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,442 @@
+/*
+ * Lantiq PSB6970 (Tantos) Switch driver
+ *
+ * Copyright (c) 2009,2010 Team Embedded.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of the GNU General Public License v2 as published by the
+ * Free Software Foundation.
+ *
+ * The switch programming done in this driver follows the 
+ * "Ethernet Traffic Separation using VLAN" Application Note as
+ * published by Lantiq.
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+#include <linux/version.h>
+
+#define PSB6970_MAX_VLANS		16
+#define PSB6970_NUM_PORTS		7
+#define PSB6970_DEFAULT_PORT_CPU	6
+#define PSB6970_IS_CPU_PORT(x)		((x) > 4)
+
+#define PHYADDR(_reg)		((_reg >> 5) & 0xff), (_reg & 0x1f)
+
+/* --- Identification --- */
+#define PSB6970_CI0		0x0100
+#define PSB6970_CI0_MASK	0x000f
+#define PSB6970_CI1		0x0101
+#define PSB6970_CI1_VAL		0x2599
+#define PSB6970_CI1_MASK	0xffff
+
+/* --- VLAN filter table --- */
+#define PSB6970_VFxL(i)		((i)*2+0x10)	/* VLAN Filter Low */
+#define PSB6970_VFxL_VV		(1 << 15)	/* VLAN_Valid */
+
+#define PSB6970_VFxH(i)		((i)*2+0x11)	/* VLAN Filter High */
+#define PSB6970_VFxH_TM_SHIFT	7		/* Tagged Member */
+
+/* --- Port registers --- */
+#define PSB6970_EC(p)		((p)*0x20+2)	/* Extended Control */
+#define PSB6970_EC_IFNTE	(1 << 1)	/* Input Force No Tag Enable */
+
+#define PSB6970_PBVM(p)		((p)*0x20+3)	/* Port Base VLAN Map */
+#define PSB6970_PBVM_VMCE	(1 << 8)
+#define PSB6970_PBVM_AOVTP	(1 << 9)
+#define PSB6970_PBVM_VSD	(1 << 10)
+#define PSB6970_PBVM_VC		(1 << 11)	/* VID Check with VID table */
+#define PSB6970_PBVM_TBVE	(1 << 13)	/* Tag-Based VLAN enable */
+
+#define PSB6970_DVID(p)		((p)*0x20+4)	/* Default VLAN ID & Priority */
+
+struct psb6970_priv {
+	struct switch_dev dev;
+	struct phy_device *phy;
+	u16 (*read) (struct phy_device* phydev, int reg);
+	void (*write) (struct phy_device* phydev, int reg, u16 val);
+	struct mutex reg_mutex;
+
+	/* all fields below are cleared on reset */
+	bool vlan;
+	u16 vlan_id[PSB6970_MAX_VLANS];
+	u8 vlan_table[PSB6970_MAX_VLANS];
+	u8 vlan_tagged;
+	u16 pvid[PSB6970_NUM_PORTS];
+};
+
+#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev)
+
+static u16 psb6970_mii_read(struct phy_device *phydev, int reg)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+
+	return bus->read(bus, PHYADDR(reg));
+}
+
+static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+
+	bus->write(bus, PHYADDR(reg), val);
+}
+
+static int
+psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+		 struct switch_val *val)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	priv->vlan = !!val->value.i;
+	return 0;
+}
+
+static int
+psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+		 struct switch_val *val)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	val->value.i = priv->vlan;
+	return 0;
+}
+
+static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+
+	/* make sure no invalid PVIDs get set */
+	if (vlan >= dev->vlans)
+		return -EINVAL;
+
+	priv->pvid[port] = vlan;
+	return 0;
+}
+
+static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	*vlan = priv->pvid[port];
+	return 0;
+}
+
+static int
+psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
+		struct switch_val *val)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	priv->vlan_id[val->port_vlan] = val->value.i;
+	return 0;
+}
+
+static int
+psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
+		struct switch_val *val)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	val->value.i = priv->vlan_id[val->port_vlan];
+	return 0;
+}
+
+static struct switch_attr psb6970_globals[] = {
+	{
+	 .type = SWITCH_TYPE_INT,
+	 .name = "enable_vlan",
+	 .description = "Enable VLAN mode",
+	 .set = psb6970_set_vlan,
+	 .get = psb6970_get_vlan,
+	 .max = 1},
+};
+
+static struct switch_attr psb6970_port[] = {
+};
+
+static struct switch_attr psb6970_vlan[] = {
+	{
+	 .type = SWITCH_TYPE_INT,
+	 .name = "vid",
+	 .description = "VLAN ID (0-4094)",
+	 .set = psb6970_set_vid,
+	 .get = psb6970_get_vid,
+	 .max = 4094,
+	 },
+};
+
+static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	u8 ports = priv->vlan_table[val->port_vlan];
+	int i;
+
+	val->len = 0;
+	for (i = 0; i < PSB6970_NUM_PORTS; i++) {
+		struct switch_port *p;
+
+		if (!(ports & (1 << i)))
+			continue;
+
+		p = &val->value.ports[val->len++];
+		p->id = i;
+		if (priv->vlan_tagged & (1 << i))
+			p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+		else
+			p->flags = 0;
+	}
+	return 0;
+}
+
+static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	u8 *vt = &priv->vlan_table[val->port_vlan];
+	int i, j;
+
+	*vt = 0;
+	for (i = 0; i < val->len; i++) {
+		struct switch_port *p = &val->value.ports[i];
+
+		if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
+			priv->vlan_tagged |= (1 << p->id);
+		else {
+			priv->vlan_tagged &= ~(1 << p->id);
+			priv->pvid[p->id] = val->port_vlan;
+
+			/* make sure that an untagged port does not
+			 * appear in other vlans */
+			for (j = 0; j < PSB6970_MAX_VLANS; j++) {
+				if (j == val->port_vlan)
+					continue;
+				priv->vlan_table[j] &= ~(1 << p->id);
+			}
+		}
+
+		*vt |= 1 << p->id;
+	}
+	return 0;
+}
+
+static int psb6970_hw_apply(struct switch_dev *dev)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	int i, j;
+
+	mutex_lock(&priv->reg_mutex);
+
+	if (priv->vlan) {
+		/* into the vlan translation unit */
+		for (j = 0; j < PSB6970_MAX_VLANS; j++) {
+			u8 vp = priv->vlan_table[j];
+
+			if (vp) {
+				priv->write(priv->phy, PSB6970_VFxL(j),
+					    PSB6970_VFxL_VV | priv->vlan_id[j]);
+				priv->write(priv->phy, PSB6970_VFxH(j),
+					    ((vp & priv->
+					      vlan_tagged) <<
+					     PSB6970_VFxH_TM_SHIFT) | vp);
+			} else	/* clear VLAN Valid flag for unused vlans */
+				priv->write(priv->phy, PSB6970_VFxL(j), 0);
+
+		}
+	}
+
+	/* update the port destination mask registers and tag settings */
+	for (i = 0; i < PSB6970_NUM_PORTS; i++) {
+		int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0;
+
+		if (priv->vlan) {
+			ec = PSB6970_EC_IFNTE;
+			dvid = priv->vlan_id[priv->pvid[i]];
+			pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE;
+
+			if ((i << 1) & priv->vlan_tagged)
+				pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC;
+		}
+
+		priv->write(priv->phy, PSB6970_PBVM(i), pbvm);
+
+		if (!PSB6970_IS_CPU_PORT(i)) {
+			priv->write(priv->phy, PSB6970_EC(i), ec);
+			priv->write(priv->phy, PSB6970_DVID(i), dvid);
+		}
+	}
+
+	mutex_unlock(&priv->reg_mutex);
+	return 0;
+}
+
+static int psb6970_reset_switch(struct switch_dev *dev)
+{
+	struct psb6970_priv *priv = to_psb6970(dev);
+	int i;
+
+	mutex_lock(&priv->reg_mutex);
+
+	memset(&priv->vlan, 0, sizeof(struct psb6970_priv) -
+	       offsetof(struct psb6970_priv, vlan));
+
+	for (i = 0; i < PSB6970_MAX_VLANS; i++)
+		priv->vlan_id[i] = i;
+
+	mutex_unlock(&priv->reg_mutex);
+
+	return psb6970_hw_apply(dev);
+}
+
+static const struct switch_dev_ops psb6970_ops = {
+	.attr_global = {
+			.attr = psb6970_globals,
+			.n_attr = ARRAY_SIZE(psb6970_globals),
+			},
+	.attr_port = {
+		      .attr = psb6970_port,
+		      .n_attr = ARRAY_SIZE(psb6970_port),
+		      },
+	.attr_vlan = {
+		      .attr = psb6970_vlan,
+		      .n_attr = ARRAY_SIZE(psb6970_vlan),
+		      },
+	.get_port_pvid = psb6970_get_pvid,
+	.set_port_pvid = psb6970_set_pvid,
+	.get_vlan_ports = psb6970_get_ports,
+	.set_vlan_ports = psb6970_set_ports,
+	.apply_config = psb6970_hw_apply,
+	.reset_switch = psb6970_reset_switch,
+};
+
+static int psb6970_config_init(struct phy_device *pdev)
+{
+	struct psb6970_priv *priv;
+	struct net_device *dev = pdev->attached_dev;
+	struct switch_dev *swdev;
+	int ret;
+
+	priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL);
+	if (priv == NULL)
+		return -ENOMEM;
+
+	priv->phy = pdev;
+
+	if (pdev->mdio.addr == 0)
+		printk(KERN_INFO "%s: psb6970 switch driver attached.\n",
+		       pdev->attached_dev->name);
+
+	if (pdev->mdio.addr != 0) {
+		kfree(priv);
+		return 0;
+	}
+
+	linkmode_zero(pdev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pdev->supported);
+	linkmode_copy(pdev->advertising, pdev->supported);
+
+	mutex_init(&priv->reg_mutex);
+	priv->read = psb6970_mii_read;
+	priv->write = psb6970_mii_write;
+
+	pdev->priv = priv;
+
+	swdev = &priv->dev;
+	swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU;
+	swdev->ops = &psb6970_ops;
+
+	swdev->name = "Lantiq PSB6970";
+	swdev->vlans = PSB6970_MAX_VLANS;
+	swdev->ports = PSB6970_NUM_PORTS;
+
+	if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) {
+		kfree(priv);
+		goto done;
+	}
+
+	ret = psb6970_reset_switch(&priv->dev);
+	if (ret) {
+		kfree(priv);
+		goto done;
+	}
+
+done:
+	return ret;
+}
+
+static int psb6970_read_status(struct phy_device *phydev)
+{
+	phydev->speed = SPEED_100;
+	phydev->duplex = DUPLEX_FULL;
+	phydev->link = 1;
+
+	phydev->state = PHY_RUNNING;
+	netif_carrier_on(phydev->attached_dev);
+	phydev->adjust_link(phydev->attached_dev);
+
+	return 0;
+}
+
+static int psb6970_config_aneg(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int psb6970_probe(struct phy_device *pdev)
+{
+	return 0;
+}
+
+static void psb6970_remove(struct phy_device *pdev)
+{
+	struct psb6970_priv *priv = pdev->priv;
+
+	if (!priv)
+		return;
+
+	if (pdev->mdio.addr == 0)
+		unregister_switch(&priv->dev);
+	kfree(priv);
+}
+
+static int psb6970_fixup(struct phy_device *dev)
+{
+	struct mii_bus *bus = dev->mdio.bus;
+	u16 reg;
+
+	/* look for the switch on the bus */
+	reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK;
+	if (reg != PSB6970_CI1_VAL)
+		return 0;
+
+	dev->phy_id = (reg << 16);
+	dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK;
+
+	return 0;
+}
+
+static struct phy_driver psb6970_driver = {
+	.name = "Lantiq PSB6970",
+	.phy_id = PSB6970_CI1_VAL << 16,
+	.phy_id_mask = 0xffff0000,
+	.features = PHY_BASIC_FEATURES,
+	.probe = psb6970_probe,
+	.remove = psb6970_remove,
+	.config_init = &psb6970_config_init,
+	.config_aneg = &psb6970_config_aneg,
+	.read_status = &psb6970_read_status,
+};
+
+int __init psb6970_init(void)
+{
+	phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup);
+	return phy_driver_register(&psb6970_driver, THIS_MODULE);
+}
+
+module_init(psb6970_init);
+
+void __exit psb6970_exit(void)
+{
+	phy_driver_unregister(&psb6970_driver);
+}
+
+module_exit(psb6970_exit);
+
+MODULE_DESCRIPTION("Lantiq PSB6970 Switch");
+MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>");
+MODULE_LICENSE("GPL");
diff -Naur linux-orig/drivers/net/phy/rtl8306.c linux/drivers/net/phy/rtl8306.c
--- linux-orig/drivers/net/phy/rtl8306.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8306.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1063 @@
+/*
+ * rtl8306.c: RTL8306S switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/version.h>
+
+//#define DEBUG 1
+
+/* Global (PHY0) */
+#define RTL8306_REG_PAGE		16
+#define RTL8306_REG_PAGE_LO		(1 << 15)
+#define RTL8306_REG_PAGE_HI		(1 << 1) /* inverted */
+
+#define RTL8306_NUM_VLANS		16
+#define RTL8306_NUM_PORTS		6
+#define RTL8306_PORT_CPU		5
+#define RTL8306_NUM_PAGES		4
+#define RTL8306_NUM_REGS		32
+
+#define RTL_NAME_S          "RTL8306S"
+#define RTL_NAME_SD         "RTL8306SD"
+#define RTL_NAME_SDM        "RTL8306SDM"
+#define RTL_NAME_UNKNOWN    "RTL8306(unknown)"
+
+#define RTL8306_MAGIC	0x8306
+
+static LIST_HEAD(phydevs);
+
+struct rtl_priv {
+	struct list_head list;
+	struct switch_dev dev;
+	int page;
+	int type;
+	int do_cpu;
+	struct mii_bus *bus;
+	char hwname[sizeof(RTL_NAME_UNKNOWN)];
+	bool fixup;
+};
+
+struct rtl_phyregs {
+	int nway;
+	int speed;
+	int duplex;
+};
+
+#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev)
+
+enum {
+	RTL_TYPE_S,
+	RTL_TYPE_SD,
+	RTL_TYPE_SDM,
+};
+
+struct rtl_reg {
+	int page;
+	int phy;
+	int reg;
+	int bits;
+	int shift;
+	int inverted;
+};
+
+#define RTL_VLAN_REGOFS(name) \
+	(RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name)
+
+#define RTL_PORT_REGOFS(name) \
+	(RTL_REG_PORT1_##name - RTL_REG_PORT0_##name)
+
+#define RTL_PORT_REG(id, reg) \
+	(RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg)))
+
+#define RTL_VLAN_REG(id, reg) \
+	(RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg)))
+
+#define RTL_GLOBAL_REGATTR(reg) \
+	.id = RTL_REG_##reg, \
+	.type = SWITCH_TYPE_INT, \
+	.ofs = 0, \
+	.set = rtl_attr_set_int, \
+	.get = rtl_attr_get_int
+
+#define RTL_PORT_REGATTR(reg) \
+	.id = RTL_REG_PORT0_##reg, \
+	.type = SWITCH_TYPE_INT, \
+	.ofs = RTL_PORT_REGOFS(reg), \
+	.set = rtl_attr_set_port_int, \
+	.get = rtl_attr_get_port_int
+
+#define RTL_VLAN_REGATTR(reg) \
+	.id = RTL_REG_VLAN0_##reg, \
+	.type = SWITCH_TYPE_INT, \
+	.ofs = RTL_VLAN_REGOFS(reg), \
+	.set = rtl_attr_set_vlan_int, \
+	.get = rtl_attr_get_vlan_int
+
+enum rtl_regidx {
+	RTL_REG_CHIPID,
+	RTL_REG_CHIPVER,
+	RTL_REG_CHIPTYPE,
+	RTL_REG_CPUPORT,
+
+	RTL_REG_EN_CPUPORT,
+	RTL_REG_EN_TAG_OUT,
+	RTL_REG_EN_TAG_CLR,
+	RTL_REG_EN_TAG_IN,
+	RTL_REG_TRAP_CPU,
+	RTL_REG_CPU_LINKUP,
+	RTL_REG_TRUNK_PORTSEL,
+	RTL_REG_EN_TRUNK,
+	RTL_REG_RESET,
+
+	RTL_REG_VLAN_ENABLE,
+	RTL_REG_VLAN_FILTER,
+	RTL_REG_VLAN_TAG_ONLY,
+	RTL_REG_VLAN_TAG_AWARE,
+#define RTL_VLAN_ENUM(id) \
+	RTL_REG_VLAN##id##_VID, \
+	RTL_REG_VLAN##id##_PORTMASK
+	RTL_VLAN_ENUM(0),
+	RTL_VLAN_ENUM(1),
+	RTL_VLAN_ENUM(2),
+	RTL_VLAN_ENUM(3),
+	RTL_VLAN_ENUM(4),
+	RTL_VLAN_ENUM(5),
+	RTL_VLAN_ENUM(6),
+	RTL_VLAN_ENUM(7),
+	RTL_VLAN_ENUM(8),
+	RTL_VLAN_ENUM(9),
+	RTL_VLAN_ENUM(10),
+	RTL_VLAN_ENUM(11),
+	RTL_VLAN_ENUM(12),
+	RTL_VLAN_ENUM(13),
+	RTL_VLAN_ENUM(14),
+	RTL_VLAN_ENUM(15),
+#define RTL_PORT_ENUM(id) \
+	RTL_REG_PORT##id##_PVID, \
+	RTL_REG_PORT##id##_NULL_VID_REPLACE, \
+	RTL_REG_PORT##id##_NON_PVID_DISCARD, \
+	RTL_REG_PORT##id##_VID_INSERT, \
+	RTL_REG_PORT##id##_TAG_INSERT, \
+	RTL_REG_PORT##id##_LINK, \
+	RTL_REG_PORT##id##_SPEED, \
+	RTL_REG_PORT##id##_NWAY, \
+	RTL_REG_PORT##id##_NRESTART, \
+	RTL_REG_PORT##id##_DUPLEX, \
+	RTL_REG_PORT##id##_RXEN, \
+	RTL_REG_PORT##id##_TXEN
+	RTL_PORT_ENUM(0),
+	RTL_PORT_ENUM(1),
+	RTL_PORT_ENUM(2),
+	RTL_PORT_ENUM(3),
+	RTL_PORT_ENUM(4),
+	RTL_PORT_ENUM(5),
+};
+
+static const struct rtl_reg rtl_regs[] = {
+	[RTL_REG_CHIPID]         = { 0, 4, 30, 16,  0, 0 },
+	[RTL_REG_CHIPVER]        = { 0, 4, 31,  8,  0, 0 },
+	[RTL_REG_CHIPTYPE]       = { 0, 4, 31,  2,  8, 0 },
+
+	/* CPU port number */
+	[RTL_REG_CPUPORT]        = { 2, 4, 21,  3,  0, 0 },
+	/* Enable CPU port function */
+	[RTL_REG_EN_CPUPORT]     = { 3, 2, 21,  1, 15, 1 },
+	/* Enable CPU port tag insertion */
+	[RTL_REG_EN_TAG_OUT]     = { 3, 2, 21,  1, 12, 0 },
+	/* Enable CPU port tag removal */
+	[RTL_REG_EN_TAG_CLR]     = { 3, 2, 21,  1, 11, 0 },
+	/* Enable CPU port tag checking */
+	[RTL_REG_EN_TAG_IN]      = { 0, 4, 21,  1,  7, 0 },
+	[RTL_REG_EN_TRUNK]       = { 0, 0, 19,  1, 11, 1 },
+	[RTL_REG_TRUNK_PORTSEL]  = { 0, 0, 16,  1,  6, 1 },
+	[RTL_REG_RESET]          = { 0, 0, 16,  1, 12, 0 },
+
+	[RTL_REG_TRAP_CPU]       = { 3, 2, 22,  1,  6, 0 },
+	[RTL_REG_CPU_LINKUP]     = { 0, 6, 22,  1, 15, 0 },
+
+	[RTL_REG_VLAN_TAG_ONLY]  = { 0, 0, 16,  1,  8, 1 },
+	[RTL_REG_VLAN_FILTER]    = { 0, 0, 16,  1,  9, 1 },
+	[RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16,  1, 10, 1 },
+	[RTL_REG_VLAN_ENABLE]    = { 0, 0, 18,  1,  8, 1 },
+
+#define RTL_VLAN_REGS(id, phy, page, regofs) \
+	[RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \
+	[RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 }
+	RTL_VLAN_REGS( 0, 0, 0, 0),
+	RTL_VLAN_REGS( 1, 1, 0, 0),
+	RTL_VLAN_REGS( 2, 2, 0, 0),
+	RTL_VLAN_REGS( 3, 3, 0, 0),
+	RTL_VLAN_REGS( 4, 4, 0, 0),
+	RTL_VLAN_REGS( 5, 0, 1, 2),
+	RTL_VLAN_REGS( 6, 1, 1, 2),
+	RTL_VLAN_REGS( 7, 2, 1, 2),
+	RTL_VLAN_REGS( 8, 3, 1, 2),
+	RTL_VLAN_REGS( 9, 4, 1, 2),
+	RTL_VLAN_REGS(10, 0, 1, 4),
+	RTL_VLAN_REGS(11, 1, 1, 4),
+	RTL_VLAN_REGS(12, 2, 1, 4),
+	RTL_VLAN_REGS(13, 3, 1, 4),
+	RTL_VLAN_REGS(14, 4, 1, 4),
+	RTL_VLAN_REGS(15, 0, 1, 6),
+
+#define REG_PORT_SETTING(port, phy) \
+	[RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \
+	[RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \
+	[RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \
+	[RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \
+	[RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \
+	[RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \
+	[RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \
+	[RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \
+	[RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \
+	[RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \
+	[RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 }
+
+	REG_PORT_SETTING(0, 0),
+	REG_PORT_SETTING(1, 1),
+	REG_PORT_SETTING(2, 2),
+	REG_PORT_SETTING(3, 3),
+	REG_PORT_SETTING(4, 4),
+	REG_PORT_SETTING(5, 6),
+
+#define REG_PORT_PVID(phy, page, regofs) \
+	{ page, phy, 24 + regofs, 4, 12, 0 }
+	[RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0),
+	[RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0),
+	[RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0),
+	[RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0),
+	[RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0),
+	[RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2),
+};
+
+
+static inline void
+rtl_set_page(struct rtl_priv *priv, unsigned int page)
+{
+	struct mii_bus *bus = priv->bus;
+	u16 pgsel;
+
+	if (priv->fixup)
+		return;
+
+	if (priv->page == page)
+		return;
+
+	BUG_ON(page > RTL8306_NUM_PAGES);
+	pgsel = bus->read(bus, 0, RTL8306_REG_PAGE);
+	pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI);
+	if (page & (1 << 0))
+		pgsel |= RTL8306_REG_PAGE_LO;
+	if (!(page & (1 << 1))) /* bit is inverted */
+		pgsel |= RTL8306_REG_PAGE_HI;
+	bus->write(bus, 0, RTL8306_REG_PAGE, pgsel);
+}
+
+static inline int
+rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	struct mii_bus *bus = priv->bus;
+
+	rtl_set_page(priv, page);
+	bus->write(bus, phy, reg, val);
+	bus->read(bus, phy, reg); /* flush */
+	return 0;
+}
+
+static inline int
+rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	struct mii_bus *bus = priv->bus;
+
+	rtl_set_page(priv, page);
+	return bus->read(bus, phy, reg);
+}
+
+static inline u16
+rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	struct mii_bus *bus = priv->bus;
+	u16 r;
+
+	rtl_set_page(priv, page);
+	r = bus->read(bus, phy, reg);
+	r &= ~mask;
+	r |= val;
+	bus->write(bus, phy, reg, r);
+	return bus->read(bus, phy, reg); /* flush */
+}
+
+
+static inline int
+rtl_get(struct switch_dev *dev, enum rtl_regidx s)
+{
+	const struct rtl_reg *r = &rtl_regs[s];
+	u16 val;
+
+	BUG_ON(s >= ARRAY_SIZE(rtl_regs));
+	if (r->bits == 0) /* unimplemented */
+		return 0;
+
+	val = rtl_r16(dev, r->page, r->phy, r->reg);
+
+	if (r->shift > 0)
+		val >>= r->shift;
+
+	if (r->inverted)
+		val = ~val;
+
+	val &= (1 << r->bits) - 1;
+
+	return val;
+}
+
+static int
+rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val)
+{
+	const struct rtl_reg *r = &rtl_regs[s];
+	u16 mask = 0xffff;
+
+	BUG_ON(s >= ARRAY_SIZE(rtl_regs));
+
+	if (r->bits == 0) /* unimplemented */
+		return 0;
+
+	if (r->shift > 0)
+		val <<= r->shift;
+
+	if (r->inverted)
+		val = ~val;
+
+	if (r->bits != 16) {
+		mask = (1 << r->bits) - 1;
+		mask <<= r->shift;
+	}
+	val &= mask;
+	return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val);
+}
+
+static void
+rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs)
+{
+	regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY));
+	regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED));
+	regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX));
+}
+
+static void
+rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs)
+{
+	rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway);
+	rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed);
+	rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex);
+}
+
+static void
+rtl_port_set_enable(struct switch_dev *dev, int port, int enabled)
+{
+	rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled);
+	rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled);
+
+	if ((port >= 5) || !enabled)
+		return;
+
+	/* restart autonegotiation if enabled */
+	rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1);
+}
+
+static int
+rtl_hw_apply(struct switch_dev *dev)
+{
+	int i;
+	int trunk_en, trunk_psel;
+	struct rtl_phyregs port5;
+
+	rtl_phy_save(dev, 5, &port5);
+
+	/* disable rx/tx from PHYs */
+	for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) {
+		rtl_port_set_enable(dev, i, 0);
+	}
+
+	/* save trunking status */
+	trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK);
+	trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL);
+
+	/* trunk port 3 and 4
+	 * XXX: Big WTF, but RealTek seems to do it */
+	rtl_set(dev, RTL_REG_EN_TRUNK, 1);
+	rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1);
+
+	/* execute the software reset */
+	rtl_set(dev, RTL_REG_RESET, 1);
+
+	/* wait for the reset to complete,
+	 * but don't wait for too long */
+	for (i = 0; i < 10; i++) {
+		if (rtl_get(dev, RTL_REG_RESET) == 0)
+			break;
+
+		msleep(1);
+	}
+
+	/* enable rx/tx from PHYs */
+	for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) {
+		rtl_port_set_enable(dev, i, 1);
+	}
+
+	/* restore trunking settings */
+	rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en);
+	rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel);
+	rtl_phy_restore(dev, 5, &port5);
+
+	rtl_set(dev, RTL_REG_CPU_LINKUP, 1);
+
+	return 0;
+}
+
+static void
+rtl_hw_init(struct switch_dev *dev)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	int cpu_mask = 1 << dev->cpu_port;
+	int i;
+
+	rtl_set(dev, RTL_REG_VLAN_ENABLE, 0);
+	rtl_set(dev, RTL_REG_VLAN_FILTER, 0);
+	rtl_set(dev, RTL_REG_EN_TRUNK, 0);
+	rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0);
+
+	/* initialize cpu port settings */
+	if (priv->do_cpu) {
+		rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port);
+		rtl_set(dev, RTL_REG_EN_CPUPORT, 1);
+	} else {
+		rtl_set(dev, RTL_REG_CPUPORT, 7);
+		rtl_set(dev, RTL_REG_EN_CPUPORT, 0);
+	}
+	rtl_set(dev, RTL_REG_EN_TAG_OUT, 0);
+	rtl_set(dev, RTL_REG_EN_TAG_IN, 0);
+	rtl_set(dev, RTL_REG_EN_TAG_CLR, 0);
+
+	/* reset all vlans */
+	for (i = 0; i < RTL8306_NUM_VLANS; i++) {
+		rtl_set(dev, RTL_VLAN_REG(i, VID), i);
+		rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0);
+	}
+
+	/* default to port isolation */
+	for (i = 0; i < RTL8306_NUM_PORTS; i++) {
+		unsigned long mask;
+
+		if ((1 << i) == cpu_mask)
+			mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */
+		else
+			mask = cpu_mask | (1 << i);
+
+		rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask);
+		rtl_set(dev, RTL_PORT_REG(i, PVID), i);
+		rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1);
+		rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1);
+		rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3);
+	}
+	rtl_hw_apply(dev);
+}
+
+#ifdef DEBUG
+static int
+rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	priv->do_cpu = val->value.i;
+	rtl_hw_init(dev);
+	return 0;
+}
+
+static int
+rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	val->value.i = priv->do_cpu;
+	return 0;
+}
+
+static int
+rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	dev->cpu_port = val->value.i;
+	rtl_hw_init(dev);
+	return 0;
+}
+
+static int
+rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	val->value.i = dev->cpu_port;
+	return 0;
+}
+#endif
+
+static int
+rtl_reset(struct switch_dev *dev)
+{
+	rtl_hw_init(dev);
+	return 0;
+}
+
+static int
+rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	int idx = attr->id + (val->port_vlan * attr->ofs);
+	struct rtl_phyregs port;
+
+	if (attr->id >= ARRAY_SIZE(rtl_regs))
+		return -EINVAL;
+
+	if ((attr->max > 0) && (val->value.i > attr->max))
+		return -EINVAL;
+
+	/* access to phy register 22 on port 4/5
+	 * needs phy status save/restore */
+	if ((val->port_vlan > 3) &&
+		(rtl_regs[idx].reg == 22) &&
+		(rtl_regs[idx].page == 0)) {
+
+		rtl_phy_save(dev, val->port_vlan, &port);
+		rtl_set(dev, idx, val->value.i);
+		rtl_phy_restore(dev, val->port_vlan, &port);
+	} else {
+		rtl_set(dev, idx, val->value.i);
+	}
+
+	return 0;
+}
+
+static int
+rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	int idx = attr->id + (val->port_vlan * attr->ofs);
+
+	if (idx >= ARRAY_SIZE(rtl_regs))
+		return -EINVAL;
+
+	val->value.i = rtl_get(dev, idx);
+	return 0;
+}
+
+static int
+rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	if (val->port_vlan >= RTL8306_NUM_PORTS)
+		return -EINVAL;
+
+	return rtl_attr_set_int(dev, attr, val);
+}
+
+static int
+rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	if (val->port_vlan >= RTL8306_NUM_PORTS)
+		return -EINVAL;
+	return rtl_attr_get_int(dev, attr, val);
+}
+
+static int 
+rtl_get_port_link(struct switch_dev *dev, int port, struct switch_port_link *link)
+{
+	if (port >= RTL8306_NUM_PORTS)
+		return -EINVAL;
+
+	/* in case the link changes from down to up, the register is only updated on read */
+	link->link = rtl_get(dev, RTL_PORT_REG(port, LINK));
+	if (!link->link)
+		link->link = rtl_get(dev, RTL_PORT_REG(port, LINK));
+
+	if (!link->link)
+		return 0;
+
+	link->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX));
+	link->aneg = rtl_get(dev, RTL_PORT_REG(port, NWAY));
+
+	if (rtl_get(dev, RTL_PORT_REG(port, SPEED)))
+		link->speed = SWITCH_PORT_SPEED_100;
+	else
+		link->speed = SWITCH_PORT_SPEED_10;
+
+	return 0;
+}
+
+static int
+rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	if (val->port_vlan >= dev->vlans)
+		return -EINVAL;
+
+	return rtl_attr_set_int(dev, attr, val);
+}
+
+static int
+rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	if (val->port_vlan >= dev->vlans)
+		return -EINVAL;
+
+	return rtl_attr_get_int(dev, attr, val);
+}
+
+static int
+rtl_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	unsigned int i, mask;
+
+	mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK));
+	for (i = 0; i < RTL8306_NUM_PORTS; i++) {
+		struct switch_port *port;
+
+		if (!(mask & (1 << i)))
+			continue;
+
+		port = &val->value.ports[val->len];
+		port->id = i;
+		if (rtl_get(dev, RTL_PORT_REG(i, TAG_INSERT)) == 2 || i == dev->cpu_port)
+			port->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+		val->len++;
+	}
+
+	return 0;
+}
+
+static int
+rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	struct rtl_priv *priv = to_rtl(dev);
+	struct rtl_phyregs port;
+	int en = val->value.i;
+	int i;
+
+	rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu);
+	rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu);
+	rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu);
+	rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en);
+	if (en)
+		rtl_set(dev, RTL_REG_VLAN_FILTER, en);
+
+	for (i = 0; i < RTL8306_NUM_PORTS; i++) {
+		if (i > 3)
+			rtl_phy_save(dev, val->port_vlan, &port);
+		rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1);
+		rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1));
+		rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3));
+		if (i > 3)
+			rtl_phy_restore(dev, val->port_vlan, &port);
+	}
+	rtl_set(dev, RTL_REG_VLAN_ENABLE, en);
+
+	return 0;
+}
+
+static int
+rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val)
+{
+	val->value.i = rtl_get(dev, RTL_REG_VLAN_ENABLE);
+	return 0;
+}
+
+static int
+rtl_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	unsigned int mask = 0;
+	unsigned int oldmask;
+	int i;
+
+	for(i = 0; i < val->len; i++)
+	{
+		struct switch_port *port = &val->value.ports[i];
+		bool tagged = false;
+
+		mask |= (1 << port->id);
+
+		if (port->id == dev->cpu_port)
+			continue;
+
+		if ((i == dev->cpu_port) ||
+			(port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)))
+			tagged = true;
+
+		/* fix up PVIDs for added ports */
+		if (!tagged)
+			rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan);
+
+		rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1));
+		rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1));
+		rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1));
+	}
+
+	oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK));
+	rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask);
+
+	/* fix up PVIDs for removed ports, default to last vlan */
+	oldmask &= ~mask;
+	for (i = 0; i < RTL8306_NUM_PORTS; i++) {
+		if (!(oldmask & (1 << i)))
+			continue;
+
+		if (i == dev->cpu_port)
+			continue;
+
+		if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan)
+			rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1);
+	}
+
+	return 0;
+}
+
+static struct switch_attr rtl_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.max = 1,
+		.set = rtl_set_vlan,
+		.get = rtl_get_vlan,
+	},
+	{
+		RTL_GLOBAL_REGATTR(EN_TRUNK),
+		.name = "trunk",
+		.description = "Enable port trunking",
+		.max = 1,
+	},
+	{
+		RTL_GLOBAL_REGATTR(TRUNK_PORTSEL),
+		.name = "trunk_sel",
+		.description = "Select ports for trunking (0: 0,1 - 1: 3,4)",
+		.max = 1,
+	},
+#ifdef DEBUG
+	{
+		RTL_GLOBAL_REGATTR(VLAN_FILTER),
+		.name = "vlan_filter",
+		.description = "Filter incoming packets for allowed VLANS",
+		.max = 1,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "cpuport",
+		.description = "CPU Port",
+		.set = rtl_set_cpuport,
+		.get = rtl_get_cpuport,
+		.max = RTL8306_NUM_PORTS,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "use_cpuport",
+		.description = "CPU Port handling flag",
+		.set = rtl_set_use_cpuport,
+		.get = rtl_get_use_cpuport,
+		.max = RTL8306_NUM_PORTS,
+	},
+	{
+		RTL_GLOBAL_REGATTR(TRAP_CPU),
+		.name = "trap_cpu",
+		.description = "VLAN trap to CPU",
+		.max = 1,
+	},
+	{
+		RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE),
+		.name = "vlan_tag_aware",
+		.description = "Enable VLAN tag awareness",
+		.max = 1,
+	},
+	{
+		RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY),
+		.name = "tag_only",
+		.description = "Only accept tagged packets",
+		.max = 1,
+	},
+#endif
+};
+static struct switch_attr rtl_port[] = {
+	{
+		RTL_PORT_REGATTR(PVID),
+		.name = "pvid",
+		.description = "Port VLAN ID",
+		.max = RTL8306_NUM_VLANS - 1,
+	},
+#ifdef DEBUG
+	{
+		RTL_PORT_REGATTR(NULL_VID_REPLACE),
+		.name = "null_vid",
+		.description = "NULL VID gets replaced by port default vid",
+		.max = 1,
+	},
+	{
+		RTL_PORT_REGATTR(NON_PVID_DISCARD),
+		.name = "non_pvid_discard",
+		.description = "discard packets with VID != PVID",
+		.max = 1,
+	},
+	{
+		RTL_PORT_REGATTR(VID_INSERT),
+		.name = "vid_insert_remove",
+		.description = "how should the switch insert and remove vids ?",
+		.max = 3,
+	},
+	{
+		RTL_PORT_REGATTR(TAG_INSERT),
+		.name = "tag_insert",
+		.description = "tag insertion handling",
+		.max = 3,
+	},
+#endif
+};
+
+static struct switch_attr rtl_vlan[] = {
+	{
+		RTL_VLAN_REGATTR(VID),
+		.name = "vid",
+		.description = "VLAN ID (1-4095)",
+		.max = 4095,
+	},
+};
+
+static const struct switch_dev_ops rtl8306_ops = {
+	.attr_global = {
+		.attr = rtl_globals,
+		.n_attr = ARRAY_SIZE(rtl_globals),
+	},
+	.attr_port = {
+		.attr = rtl_port,
+		.n_attr = ARRAY_SIZE(rtl_port),
+	},
+	.attr_vlan = {
+		.attr = rtl_vlan,
+		.n_attr = ARRAY_SIZE(rtl_vlan),
+	},
+
+	.get_vlan_ports = rtl_get_ports,
+	.set_vlan_ports = rtl_set_ports,
+	.apply_config = rtl_hw_apply,
+	.reset_switch = rtl_reset,
+	.get_port_link = rtl_get_port_link,
+};
+
+static int
+rtl8306_config_init(struct phy_device *pdev)
+{
+	struct net_device *netdev = pdev->attached_dev;
+	struct rtl_priv *priv = pdev->priv;
+	struct switch_dev *dev = &priv->dev;
+	struct switch_val val;
+	unsigned int chipid, chipver, chiptype;
+	int err;
+
+	/* Only init the switch for the primary PHY */
+	if (pdev->mdio.addr != 0)
+		return 0;
+
+	val.value.i = 1;
+	priv->dev.cpu_port = RTL8306_PORT_CPU;
+	priv->dev.ports = RTL8306_NUM_PORTS;
+	priv->dev.vlans = RTL8306_NUM_VLANS;
+	priv->dev.ops = &rtl8306_ops;
+	priv->do_cpu = 0;
+	priv->page = -1;
+	priv->bus = pdev->mdio.bus;
+
+	chipid = rtl_get(dev, RTL_REG_CHIPID);
+	chipver = rtl_get(dev, RTL_REG_CHIPVER);
+	chiptype = rtl_get(dev, RTL_REG_CHIPTYPE);
+	switch(chiptype) {
+	case 0:
+	case 2:
+		strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname));
+		priv->type = RTL_TYPE_S;
+		break;
+	case 1:
+		strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname));
+		priv->type = RTL_TYPE_SD;
+		break;
+	case 3:
+		strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname));
+		priv->type = RTL_TYPE_SDM;
+		break;
+	default:
+		strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname));
+		break;
+	}
+
+	dev->name = priv->hwname;
+	rtl_hw_init(dev);
+
+	printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver);
+
+	err = register_switch(dev, netdev);
+	if (err < 0) {
+		kfree(priv);
+		return err;
+	}
+
+	return 0;
+}
+
+
+static int
+rtl8306_fixup(struct phy_device *pdev)
+{
+	struct rtl_priv priv;
+	u16 chipid;
+
+	/* Attach to primary LAN port and WAN port */
+	if (pdev->mdio.addr != 0 && pdev->mdio.addr != 4)
+		return 0;
+
+	memset(&priv, 0, sizeof(priv));
+	priv.fixup = true;
+	priv.page = -1;
+	priv.bus = pdev->mdio.bus;
+	chipid = rtl_get(&priv.dev, RTL_REG_CHIPID);
+	if (chipid == 0x5988)
+		pdev->phy_id = RTL8306_MAGIC;
+
+	return 0;
+}
+
+static int
+rtl8306_probe(struct phy_device *pdev)
+{
+	struct rtl_priv *priv;
+
+	list_for_each_entry(priv, &phydevs, list) {
+		/*
+		 * share one rtl_priv instance between virtual phy
+		 * devices on the same bus
+		 */
+		if (priv->bus == pdev->mdio.bus)
+			goto found;
+	}
+	priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->bus = pdev->mdio.bus;
+
+found:
+	pdev->priv = priv;
+	return 0;
+}
+
+static void
+rtl8306_remove(struct phy_device *pdev)
+{
+	struct rtl_priv *priv = pdev->priv;
+	unregister_switch(&priv->dev);
+	kfree(priv);
+}
+
+static int
+rtl8306_config_aneg(struct phy_device *pdev)
+{
+	struct rtl_priv *priv = pdev->priv;
+
+	/* Only for WAN */
+	if (pdev->mdio.addr == 0)
+		return 0;
+
+	/* Restart autonegotiation */
+	rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1);
+	rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1);
+
+	return 0;
+}
+
+static int
+rtl8306_read_status(struct phy_device *pdev)
+{
+	struct rtl_priv *priv = pdev->priv;
+	struct switch_dev *dev = &priv->dev;
+
+	if (pdev->mdio.addr == 4) {
+		/* WAN */
+		pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10;
+		pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF;
+		pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK));
+	} else {
+		/* LAN */
+		pdev->speed = SPEED_100;
+		pdev->duplex = DUPLEX_FULL;
+		pdev->link = 1;
+	}
+
+	/*
+	 * Bypass generic PHY status read,
+	 * it doesn't work with this switch
+	 */
+	if (pdev->link) {
+		pdev->state = PHY_RUNNING;
+		netif_carrier_on(pdev->attached_dev);
+		pdev->adjust_link(pdev->attached_dev);
+	} else {
+		pdev->state = PHY_NOLINK;
+		netif_carrier_off(pdev->attached_dev);
+		pdev->adjust_link(pdev->attached_dev);
+	}
+
+	return 0;
+}
+
+
+static struct phy_driver rtl8306_driver = {
+	.name		= "Realtek RTL8306S",
+	.phy_id		= RTL8306_MAGIC,
+	.phy_id_mask	= 0xffffffff,
+	.features	= PHY_BASIC_FEATURES,
+	.probe		= &rtl8306_probe,
+	.remove		= &rtl8306_remove,
+	.config_init	= &rtl8306_config_init,
+	.config_aneg	= &rtl8306_config_aneg,
+	.read_status	= &rtl8306_read_status,
+};
+
+
+static int __init
+rtl_init(void)
+{
+	phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup);
+	return phy_driver_register(&rtl8306_driver, THIS_MODULE);
+}
+
+static void __exit
+rtl_exit(void)
+{
+	phy_driver_unregister(&rtl8306_driver);
+}
+
+module_init(rtl_init);
+module_exit(rtl_exit);
+MODULE_LICENSE("GPL");
+
diff -Naur linux-orig/drivers/net/phy/rtl8366_smi.c linux/drivers/net/phy/rtl8366_smi.c
--- linux-orig/drivers/net/phy/rtl8366_smi.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8366_smi.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1628 @@
+/*
+ * Realtek RTL8366 SMI interface driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/rtl8366.h>
+#include <linux/version.h>
+#include <linux/of_mdio.h>
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+#include <linux/debugfs.h>
+#endif
+
+#include "rtl8366_smi.h"
+
+#define RTL8366_SMI_ACK_RETRY_COUNT         5
+
+#define RTL8366_SMI_HW_STOP_DELAY		25	/* msecs */
+#define RTL8366_SMI_HW_START_DELAY		100	/* msecs */
+
+static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi)
+{
+	ndelay(smi->clk_delay);
+}
+
+static void rtl8366_smi_start(struct rtl8366_smi *smi)
+{
+	unsigned int sda = smi->gpio_sda;
+	unsigned int sck = smi->gpio_sck;
+
+	/*
+	 * Set GPIO pins to output mode, with initial state:
+	 * SCK = 0, SDA = 1
+	 */
+	gpio_direction_output(sck, 0);
+	gpio_direction_output(sda, 1);
+	rtl8366_smi_clk_delay(smi);
+
+	/* CLK 1: 0 -> 1, 1 -> 0 */
+	gpio_set_value(sck, 1);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 0);
+	rtl8366_smi_clk_delay(smi);
+
+	/* CLK 2: */
+	gpio_set_value(sck, 1);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sda, 0);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 0);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sda, 1);
+}
+
+static void rtl8366_smi_stop(struct rtl8366_smi *smi)
+{
+	unsigned int sda = smi->gpio_sda;
+	unsigned int sck = smi->gpio_sck;
+
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sda, 0);
+	gpio_set_value(sck, 1);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sda, 1);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 1);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 0);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 1);
+
+	/* add a click */
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 0);
+	rtl8366_smi_clk_delay(smi);
+	gpio_set_value(sck, 1);
+
+	/* set GPIO pins to input mode */
+	gpio_direction_input(sda);
+	gpio_direction_input(sck);
+}
+
+static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len)
+{
+	unsigned int sda = smi->gpio_sda;
+	unsigned int sck = smi->gpio_sck;
+
+	for (; len > 0; len--) {
+		rtl8366_smi_clk_delay(smi);
+
+		/* prepare data */
+		gpio_set_value(sda, !!(data & ( 1 << (len - 1))));
+		rtl8366_smi_clk_delay(smi);
+
+		/* clocking */
+		gpio_set_value(sck, 1);
+		rtl8366_smi_clk_delay(smi);
+		gpio_set_value(sck, 0);
+	}
+}
+
+static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data)
+{
+	unsigned int sda = smi->gpio_sda;
+	unsigned int sck = smi->gpio_sck;
+
+	gpio_direction_input(sda);
+
+	for (*data = 0; len > 0; len--) {
+		u32 u;
+
+		rtl8366_smi_clk_delay(smi);
+
+		/* clocking */
+		gpio_set_value(sck, 1);
+		rtl8366_smi_clk_delay(smi);
+		u = !!gpio_get_value(sda);
+		gpio_set_value(sck, 0);
+
+		*data |= (u << (len - 1));
+	}
+
+	gpio_direction_output(sda, 0);
+}
+
+static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi)
+{
+	int retry_cnt;
+
+	retry_cnt = 0;
+	do {
+		u32 ack;
+
+		rtl8366_smi_read_bits(smi, 1, &ack);
+		if (ack == 0)
+			break;
+
+		if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) {
+			dev_err(smi->parent, "ACK timeout\n");
+			return -ETIMEDOUT;
+		}
+	} while (1);
+
+	return 0;
+}
+
+static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data)
+{
+	rtl8366_smi_write_bits(smi, data, 8);
+	return rtl8366_smi_wait_for_ack(smi);
+}
+
+static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data)
+{
+	rtl8366_smi_write_bits(smi, data, 8);
+	return 0;
+}
+
+static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data)
+{
+	u32 t;
+
+	/* read data */
+	rtl8366_smi_read_bits(smi, 8, &t);
+	*data = (t & 0xff);
+
+	/* send an ACK */
+	rtl8366_smi_write_bits(smi, 0x00, 1);
+
+	return 0;
+}
+
+static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data)
+{
+	u32 t;
+
+	/* read data */
+	rtl8366_smi_read_bits(smi, 8, &t);
+	*data = (t & 0xff);
+
+	/* send an ACK */
+	rtl8366_smi_write_bits(smi, 0x01, 1);
+
+	return 0;
+}
+
+static int __rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data)
+{
+	unsigned long flags;
+	u8 lo = 0;
+	u8 hi = 0;
+	int ret;
+
+	spin_lock_irqsave(&smi->lock, flags);
+
+	rtl8366_smi_start(smi);
+
+	/* send READ command */
+	ret = rtl8366_smi_write_byte(smi, smi->cmd_read);
+	if (ret)
+		goto out;
+
+	/* set ADDR[7:0] */
+	ret = rtl8366_smi_write_byte(smi, addr & 0xff);
+	if (ret)
+		goto out;
+
+	/* set ADDR[15:8] */
+	ret = rtl8366_smi_write_byte(smi, addr >> 8);
+	if (ret)
+		goto out;
+
+	/* read DATA[7:0] */
+	rtl8366_smi_read_byte0(smi, &lo);
+	/* read DATA[15:8] */
+	rtl8366_smi_read_byte1(smi, &hi);
+
+	*data = ((u32) lo) | (((u32) hi) << 8);
+
+	ret = 0;
+
+ out:
+	rtl8366_smi_stop(smi);
+	spin_unlock_irqrestore(&smi->lock, flags);
+
+	return ret;
+}
+/* Read/write via mdiobus */
+#define MDC_MDIO_CTRL0_REG		31
+#define MDC_MDIO_START_REG		29
+#define MDC_MDIO_CTRL1_REG		21
+#define MDC_MDIO_ADDRESS_REG		23
+#define MDC_MDIO_DATA_WRITE_REG		24
+#define MDC_MDIO_DATA_READ_REG		25
+
+#define MDC_MDIO_START_OP		0xFFFF
+#define MDC_MDIO_ADDR_OP		0x000E
+#define MDC_MDIO_READ_OP		0x0001
+#define MDC_MDIO_WRITE_OP		0x0003
+#define MDC_REALTEK_PHY_ADDR		0x0
+
+int __rtl8366_mdio_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data)
+{
+	u32 phy_id = smi->phy_id;
+	struct mii_bus *mbus = smi->ext_mbus;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&mbus->mdio_lock);
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write address control code to register 31 */
+	mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP);
+
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write address to register 23 */
+	mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr);
+
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write read control code to register 21 */
+	mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_READ_OP);
+
+	/* Write Start command to register 29 */
+	mbus->write(smi->ext_mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Read data from register 25 */
+	*data = mbus->read(mbus, phy_id, MDC_MDIO_DATA_READ_REG);
+
+	mutex_unlock(&mbus->mdio_lock);
+
+	return 0;
+}
+
+static int __rtl8366_mdio_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data)
+{
+	u32 phy_id = smi->phy_id;
+	struct mii_bus *mbus = smi->ext_mbus;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&mbus->mdio_lock);
+
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write address control code to register 31 */
+	mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP);
+
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write address to register 23 */
+	mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr);
+
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write data to register 24 */
+	mbus->write(mbus, phy_id, MDC_MDIO_DATA_WRITE_REG, data);
+
+	/* Write Start command to register 29 */
+	mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP);
+
+	/* Write data control code to register 21 */
+	mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_WRITE_OP);
+
+	mutex_unlock(&mbus->mdio_lock);
+	return 0;
+}
+
+int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data)
+{
+	if (smi->ext_mbus)
+		return __rtl8366_mdio_read_reg(smi, addr, data);
+	else
+		return __rtl8366_smi_read_reg(smi, addr, data);
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg);
+
+static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi,
+				   u32 addr, u32 data, bool ack)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&smi->lock, flags);
+
+	rtl8366_smi_start(smi);
+
+	/* send WRITE command */
+	ret = rtl8366_smi_write_byte(smi, smi->cmd_write);
+	if (ret)
+		goto out;
+
+	/* set ADDR[7:0] */
+	ret = rtl8366_smi_write_byte(smi, addr & 0xff);
+	if (ret)
+		goto out;
+
+	/* set ADDR[15:8] */
+	ret = rtl8366_smi_write_byte(smi, addr >> 8);
+	if (ret)
+		goto out;
+
+	/* write DATA[7:0] */
+	ret = rtl8366_smi_write_byte(smi, data & 0xff);
+	if (ret)
+		goto out;
+
+	/* write DATA[15:8] */
+	if (ack)
+		ret = rtl8366_smi_write_byte(smi, data >> 8);
+	else
+		ret = rtl8366_smi_write_byte_noack(smi, data >> 8);
+	if (ret)
+		goto out;
+
+	ret = 0;
+
+ out:
+	rtl8366_smi_stop(smi);
+	spin_unlock_irqrestore(&smi->lock, flags);
+
+	return ret;
+}
+
+int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data)
+{
+	if (smi->ext_mbus)
+		return __rtl8366_mdio_write_reg(smi, addr, data);
+	else
+		return __rtl8366_smi_write_reg(smi, addr, data, true);
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg);
+
+int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data)
+{
+	return __rtl8366_smi_write_reg(smi, addr, data, false);
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack);
+
+int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data)
+{
+	u32 t;
+	int err;
+
+	err = rtl8366_smi_read_reg(smi, addr, &t);
+	if (err)
+		return err;
+
+	err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data);
+	return err;
+
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr);
+
+static int rtl8366_reset(struct rtl8366_smi *smi)
+{
+	if (smi->hw_reset) {
+		smi->hw_reset(smi, true);
+		msleep(RTL8366_SMI_HW_STOP_DELAY);
+		smi->hw_reset(smi, false);
+		msleep(RTL8366_SMI_HW_START_DELAY);
+		return 0;
+	}
+
+	return smi->ops->reset_chip(smi);
+}
+
+static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used)
+{
+	int err;
+	int i;
+
+	*used = 0;
+	for (i = 0; i < smi->num_ports; i++) {
+		int index = 0;
+
+		err = smi->ops->get_mc_index(smi, i, &index);
+		if (err)
+			return err;
+
+		if (mc_index == index) {
+			*used = 1;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member,
+			    u32 untag, u32 fid)
+{
+	struct rtl8366_vlan_4k vlan4k;
+	int err;
+	int i;
+
+	/* Update the 4K table */
+	err = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
+	if (err)
+		return err;
+
+	vlan4k.member = member;
+	vlan4k.untag = untag;
+	vlan4k.fid = fid;
+	err = smi->ops->set_vlan_4k(smi, &vlan4k);
+	if (err)
+		return err;
+
+	/* Try to find an existing MC entry for this VID */
+	for (i = 0; i < smi->num_vlan_mc; i++) {
+		struct rtl8366_vlan_mc vlanmc;
+
+		err = smi->ops->get_vlan_mc(smi, i, &vlanmc);
+		if (err)
+			return err;
+
+		if (vid == vlanmc.vid) {
+			/* update the MC entry */
+			vlanmc.member = member;
+			vlanmc.untag = untag;
+			vlanmc.fid = fid;
+
+			err = smi->ops->set_vlan_mc(smi, i, &vlanmc);
+			break;
+		}
+	}
+
+	return err;
+}
+
+static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val)
+{
+	struct rtl8366_vlan_mc vlanmc;
+	int err;
+	int index;
+
+	err = smi->ops->get_mc_index(smi, port, &index);
+	if (err)
+		return err;
+
+	err = smi->ops->get_vlan_mc(smi, index, &vlanmc);
+	if (err)
+		return err;
+
+	*val = vlanmc.vid;
+	return 0;
+}
+
+static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port,
+			    unsigned vid)
+{
+	struct rtl8366_vlan_mc vlanmc;
+	struct rtl8366_vlan_4k vlan4k;
+	int err;
+	int i;
+
+	/* Try to find an existing MC entry for this VID */
+	for (i = 0; i < smi->num_vlan_mc; i++) {
+		err = smi->ops->get_vlan_mc(smi, i, &vlanmc);
+		if (err)
+			return err;
+
+		if (vid == vlanmc.vid) {
+			err = smi->ops->set_vlan_mc(smi, i, &vlanmc);
+			if (err)
+				return err;
+
+			err = smi->ops->set_mc_index(smi, port, i);
+			return err;
+		}
+	}
+
+	/* We have no MC entry for this VID, try to find an empty one */
+	for (i = 0; i < smi->num_vlan_mc; i++) {
+		err = smi->ops->get_vlan_mc(smi, i, &vlanmc);
+		if (err)
+			return err;
+
+		if (vlanmc.vid == 0 && vlanmc.member == 0) {
+			/* Update the entry from the 4K table */
+			err = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
+			if (err)
+				return err;
+
+			vlanmc.vid = vid;
+			vlanmc.member = vlan4k.member;
+			vlanmc.untag = vlan4k.untag;
+			vlanmc.fid = vlan4k.fid;
+			err = smi->ops->set_vlan_mc(smi, i, &vlanmc);
+			if (err)
+				return err;
+
+			err = smi->ops->set_mc_index(smi, port, i);
+			return err;
+		}
+	}
+
+	/* MC table is full, try to find an unused entry and replace it */
+	for (i = 0; i < smi->num_vlan_mc; i++) {
+		int used;
+
+		err = rtl8366_mc_is_used(smi, i, &used);
+		if (err)
+			return err;
+
+		if (!used) {
+			/* Update the entry from the 4K table */
+			err = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
+			if (err)
+				return err;
+
+			vlanmc.vid = vid;
+			vlanmc.member = vlan4k.member;
+			vlanmc.untag = vlan4k.untag;
+			vlanmc.fid = vlan4k.fid;
+			err = smi->ops->set_vlan_mc(smi, i, &vlanmc);
+			if (err)
+				return err;
+
+			err = smi->ops->set_mc_index(smi, port, i);
+			return err;
+		}
+	}
+
+	dev_err(smi->parent,
+		"all VLAN member configurations are in use\n");
+
+	return -ENOSPC;
+}
+
+int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable)
+{
+	int err;
+
+	err = smi->ops->enable_vlan(smi, enable);
+	if (err)
+		return err;
+
+	smi->vlan_enabled = enable;
+
+	if (!enable) {
+		smi->vlan4k_enabled = 0;
+		err = smi->ops->enable_vlan4k(smi, enable);
+	}
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(rtl8366_enable_vlan);
+
+static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable)
+{
+	int err;
+
+	if (enable) {
+		err = smi->ops->enable_vlan(smi, enable);
+		if (err)
+			return err;
+
+		smi->vlan_enabled = enable;
+	}
+
+	err = smi->ops->enable_vlan4k(smi, enable);
+	if (err)
+		return err;
+
+	smi->vlan4k_enabled = enable;
+	return 0;
+}
+
+int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable)
+{
+	int port;
+	int err;
+
+	for (port = 0; port < smi->num_ports; port++) {
+		err = smi->ops->enable_port(smi, port, enable);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports);
+
+int rtl8366_reset_vlan(struct rtl8366_smi *smi)
+{
+	struct rtl8366_vlan_mc vlanmc;
+	int err;
+	int i;
+
+	rtl8366_enable_vlan(smi, 0);
+	rtl8366_enable_vlan4k(smi, 0);
+
+	/* clear VLAN member configurations */
+	vlanmc.vid = 0;
+	vlanmc.priority = 0;
+	vlanmc.member = 0;
+	vlanmc.untag = 0;
+	vlanmc.fid = 0;
+	for (i = 0; i < smi->num_vlan_mc; i++) {
+		err = smi->ops->set_vlan_mc(smi, i, &vlanmc);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
+
+static int rtl8366_init_vlan(struct rtl8366_smi *smi)
+{
+	int port;
+	int err;
+
+	err = rtl8366_reset_vlan(smi);
+	if (err)
+		return err;
+
+	for (port = 0; port < smi->num_ports; port++) {
+		u32 mask;
+
+		if (port == smi->cpu_port)
+			mask = (1 << smi->num_ports) - 1;
+		else
+			mask = (1 << port) | (1 << smi->cpu_port);
+
+		err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0);
+		if (err)
+			return err;
+
+		err = rtl8366_set_pvid(smi, port, (port + 1));
+		if (err)
+			return err;
+	}
+
+	return rtl8366_enable_vlan(smi, 1);
+}
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+int rtl8366_debugfs_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_debugfs_open);
+
+static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file,
+					      char __user *user_buf,
+					      size_t count, loff_t *ppos)
+{
+	struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data;
+	int i, len = 0;
+	char *buf = smi->buf;
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len,
+			"%2s %6s %4s %6s %6s %3s\n",
+			"id", "vid","prio", "member", "untag", "fid");
+
+	for (i = 0; i < smi->num_vlan_mc; ++i) {
+		struct rtl8366_vlan_mc vlanmc;
+
+		smi->ops->get_vlan_mc(smi, i, &vlanmc);
+
+		len += snprintf(buf + len, sizeof(smi->buf) - len,
+				"%2d %6d %4d 0x%04x 0x%04x %3d\n",
+				i, vlanmc.vid, vlanmc.priority,
+				vlanmc.member, vlanmc.untag, vlanmc.fid);
+	}
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+#define RTL8366_VLAN4K_PAGE_SIZE	64
+#define RTL8366_VLAN4K_NUM_PAGES	(4096 / RTL8366_VLAN4K_PAGE_SIZE)
+
+static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file,
+					    char __user *user_buf,
+					    size_t count, loff_t *ppos)
+{
+	struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data;
+	int i, len = 0;
+	int offset;
+	char *buf = smi->buf;
+
+	if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) {
+		len += snprintf(buf + len, sizeof(smi->buf) - len,
+				"invalid page: %u\n", smi->dbg_vlan_4k_page);
+		return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+	}
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len,
+			"%4s %6s %6s %3s\n",
+			"vid", "member", "untag", "fid");
+
+	offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page;
+	for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) {
+		struct rtl8366_vlan_4k vlan4k;
+
+		smi->ops->get_vlan_4k(smi, offset + i, &vlan4k);
+
+		len += snprintf(buf + len, sizeof(smi->buf) - len,
+				"%4d 0x%04x 0x%04x %3d\n",
+				vlan4k.vid, vlan4k.member,
+				vlan4k.untag, vlan4k.fid);
+	}
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t rtl8366_read_debugfs_pvid(struct file *file,
+					 char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data;
+	char *buf = smi->buf;
+	int len = 0;
+	int i;
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n",
+			"port", "pvid");
+
+	for (i = 0; i < smi->num_ports; i++) {
+		int pvid;
+		int err;
+
+		err = rtl8366_get_pvid(smi, i, &pvid);
+		if (err)
+			len += snprintf(buf + len, sizeof(smi->buf) - len,
+				"%4d error\n", i);
+		else
+			len += snprintf(buf + len, sizeof(smi->buf) - len,
+				"%4d %4d\n", i, pvid);
+	}
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t rtl8366_read_debugfs_reg(struct file *file,
+					 char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data;
+	u32 t, reg = smi->dbg_reg;
+	int err, len = 0;
+	char *buf = smi->buf;
+
+	memset(buf, '\0', sizeof(smi->buf));
+
+	err = rtl8366_smi_read_reg(smi, reg, &t);
+	if (err) {
+		len += snprintf(buf, sizeof(smi->buf),
+				"Read failed (reg: 0x%04x)\n", reg);
+		return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+	}
+
+	len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n",
+			reg, t);
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t rtl8366_write_debugfs_reg(struct file *file,
+					  const char __user *user_buf,
+					  size_t count, loff_t *ppos)
+{
+	struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data;
+	unsigned long data;
+	u32 reg = smi->dbg_reg;
+	int err;
+	size_t len;
+	char *buf = smi->buf;
+
+	len = min(count, sizeof(smi->buf) - 1);
+	if (copy_from_user(buf, user_buf, len)) {
+		dev_err(smi->parent, "copy from user failed\n");
+		return -EFAULT;
+	}
+
+	buf[len] = '\0';
+	if (len > 0 && buf[len - 1] == '\n')
+		buf[len - 1] = '\0';
+
+
+	if (kstrtoul(buf, 16, &data)) {
+		dev_err(smi->parent, "Invalid reg value %s\n", buf);
+	} else {
+		err = rtl8366_smi_write_reg(smi, reg, data);
+		if (err) {
+			dev_err(smi->parent,
+				"writing reg 0x%04x val 0x%04lx failed\n",
+				reg, data);
+		}
+	}
+
+	return count;
+}
+
+static ssize_t rtl8366_read_debugfs_mibs(struct file *file,
+					 char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct rtl8366_smi *smi = file->private_data;
+	int i, j, len = 0;
+	char *buf = smi->buf;
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s",
+			"Counter");
+
+	for (i = 0; i < smi->num_ports; i++) {
+		char port_buf[10];
+
+		snprintf(port_buf, sizeof(port_buf), "Port %d", i);
+		len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s",
+				port_buf);
+	}
+	len += snprintf(buf + len, sizeof(smi->buf) - len, "\n");
+
+	for (i = 0; i < smi->num_mib_counters; i++) {
+		len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ",
+				smi->mib_counters[i].name);
+		for (j = 0; j < smi->num_ports; j++) {
+			unsigned long long counter = 0;
+
+			if (!smi->ops->get_mib_counter(smi, i, j, &counter))
+				len += snprintf(buf + len,
+						sizeof(smi->buf) - len,
+						"%12llu ", counter);
+			else
+				len += snprintf(buf + len,
+						sizeof(smi->buf) - len,
+						"%12s ", "error");
+		}
+		len += snprintf(buf + len, sizeof(smi->buf) - len, "\n");
+	}
+
+	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_rtl8366_regs = {
+	.read	= rtl8366_read_debugfs_reg,
+	.write	= rtl8366_write_debugfs_reg,
+	.open	= rtl8366_debugfs_open,
+	.owner	= THIS_MODULE
+};
+
+static const struct file_operations fops_rtl8366_vlan_mc = {
+	.read	= rtl8366_read_debugfs_vlan_mc,
+	.open	= rtl8366_debugfs_open,
+	.owner	= THIS_MODULE
+};
+
+static const struct file_operations fops_rtl8366_vlan_4k = {
+	.read	= rtl8366_read_debugfs_vlan_4k,
+	.open	= rtl8366_debugfs_open,
+	.owner	= THIS_MODULE
+};
+
+static const struct file_operations fops_rtl8366_pvid = {
+	.read	= rtl8366_read_debugfs_pvid,
+	.open	= rtl8366_debugfs_open,
+	.owner	= THIS_MODULE
+};
+
+static const struct file_operations fops_rtl8366_mibs = {
+	.read = rtl8366_read_debugfs_mibs,
+	.open = rtl8366_debugfs_open,
+	.owner = THIS_MODULE
+};
+
+static void rtl8366_debugfs_init(struct rtl8366_smi *smi)
+{
+	struct dentry *node;
+	struct dentry *root;
+
+	if (!smi->debugfs_root)
+		smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent),
+						       NULL);
+
+	if (!smi->debugfs_root) {
+		dev_err(smi->parent, "Unable to create debugfs dir\n");
+		return;
+	}
+	root = smi->debugfs_root;
+
+	node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root,
+				  &smi->dbg_reg);
+	if (!node) {
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"reg");
+		return;
+	}
+
+	node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi,
+				   &fops_rtl8366_regs);
+	if (!node) {
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"val");
+		return;
+	}
+
+	node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi,
+				   &fops_rtl8366_vlan_mc);
+	if (!node) {
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"vlan_mc");
+		return;
+	}
+
+	node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root,
+				  &smi->dbg_vlan_4k_page);
+	if (!node) {
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"vlan_4k_page");
+		return;
+	}
+
+	node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi,
+				   &fops_rtl8366_vlan_4k);
+	if (!node) {
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"vlan_4k");
+		return;
+	}
+
+	node = debugfs_create_file("pvid", S_IRUSR, root, smi,
+				   &fops_rtl8366_pvid);
+	if (!node) {
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"pvid");
+		return;
+	}
+
+	node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi,
+				   &fops_rtl8366_mibs);
+	if (!node)
+		dev_err(smi->parent, "Creating debugfs file '%s' failed\n",
+			"mibs");
+}
+
+static void rtl8366_debugfs_remove(struct rtl8366_smi *smi)
+{
+	if (smi->debugfs_root) {
+		debugfs_remove_recursive(smi->debugfs_root);
+		smi->debugfs_root = NULL;
+	}
+}
+#else
+static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {}
+static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {}
+#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */
+
+static int rtl8366_smi_mii_init(struct rtl8366_smi *smi)
+{
+	int ret;
+
+#ifdef CONFIG_OF
+	struct device_node *np = NULL;
+
+	np = of_get_child_by_name(smi->parent->of_node, "mdio-bus");
+#endif
+
+	smi->mii_bus = mdiobus_alloc();
+	if (smi->mii_bus == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	smi->mii_bus->priv = (void *) smi;
+	smi->mii_bus->name = dev_name(smi->parent);
+	smi->mii_bus->read = smi->ops->mii_read;
+	smi->mii_bus->write = smi->ops->mii_write;
+	snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s",
+		 dev_name(smi->parent));
+	smi->mii_bus->parent = smi->parent;
+	smi->mii_bus->phy_mask = ~(0x1f);
+
+#ifdef CONFIG_OF
+	if (np)
+		ret = of_mdiobus_register(smi->mii_bus, np);
+	else
+#endif
+		ret = mdiobus_register(smi->mii_bus);
+
+	if (ret)
+		goto err_free;
+
+	return 0;
+
+ err_free:
+	mdiobus_free(smi->mii_bus);
+ err:
+	return ret;
+}
+
+static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi)
+{
+	mdiobus_unregister(smi->mii_bus);
+	mdiobus_free(smi->mii_bus);
+}
+
+int rtl8366_sw_reset_switch(struct switch_dev *dev)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int err;
+
+	err = rtl8366_reset(smi);
+	if (err)
+		return err;
+
+	err = smi->ops->setup(smi);
+	if (err)
+		return err;
+
+	err = rtl8366_reset_vlan(smi);
+	if (err)
+		return err;
+
+	err = rtl8366_enable_vlan(smi, 1);
+	if (err)
+		return err;
+
+	return rtl8366_enable_all_ports(smi, 1);
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch);
+
+int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	return rtl8366_get_pvid(smi, port, val);
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid);
+
+int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	return rtl8366_set_pvid(smi, port, val);
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid);
+
+int rtl8366_sw_get_port_mib(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int i, len = 0;
+	unsigned long long counter = 0;
+	char *buf = smi->buf;
+
+	if (val->port_vlan >= smi->num_ports)
+		return -EINVAL;
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len,
+			"Port %d MIB counters\n",
+			val->port_vlan);
+
+	for (i = 0; i < smi->num_mib_counters; ++i) {
+		len += snprintf(buf + len, sizeof(smi->buf) - len,
+				"%-36s: ", smi->mib_counters[i].name);
+		if (!smi->ops->get_mib_counter(smi, i, val->port_vlan,
+					       &counter))
+			len += snprintf(buf + len, sizeof(smi->buf) - len,
+					"%llu\n", counter);
+		else
+			len += snprintf(buf + len, sizeof(smi->buf) - len,
+					"%s\n", "error");
+	}
+
+	val->value.s = buf;
+	val->len = len;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib);
+
+int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port,
+				struct switch_port_stats *stats,
+				int txb_id, int rxb_id)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	unsigned long long counter = 0;
+	int ret;
+
+	if (port >= smi->num_ports)
+		return -EINVAL;
+
+	ret = smi->ops->get_mib_counter(smi, txb_id, port, &counter);
+	if (ret)
+		return ret;
+
+	stats->tx_bytes = counter;
+
+	ret = smi->ops->get_mib_counter(smi, rxb_id, port, &counter);
+	if (ret)
+		return ret;
+
+	stats->rx_bytes = counter;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_stats);
+
+int rtl8366_sw_get_vlan_info(struct switch_dev *dev,
+			     const struct switch_attr *attr,
+			     struct switch_val *val)
+{
+	int i;
+	u32 len = 0;
+	struct rtl8366_vlan_4k vlan4k;
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	char *buf = smi->buf;
+	int err;
+
+	if (!smi->ops->is_vlan_valid(smi, val->port_vlan))
+		return -EINVAL;
+
+	memset(buf, '\0', sizeof(smi->buf));
+
+	err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k);
+	if (err)
+		return err;
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len,
+			"VLAN %d: Ports: '", vlan4k.vid);
+
+	for (i = 0; i < smi->num_ports; i++) {
+		if (!(vlan4k.member & (1 << i)))
+			continue;
+
+		len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i,
+				(vlan4k.untag & (1 << i)) ? "" : "t");
+	}
+
+	len += snprintf(buf + len, sizeof(smi->buf) - len,
+			"', members=%04x, untag=%04x, fid=%u",
+			vlan4k.member, vlan4k.untag, vlan4k.fid);
+
+	val->value.s = buf;
+	val->len = len;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info);
+
+int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	struct switch_port *port;
+	struct rtl8366_vlan_4k vlan4k;
+	int i;
+
+	if (!smi->ops->is_vlan_valid(smi, val->port_vlan))
+		return -EINVAL;
+
+	smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k);
+
+	port = &val->value.ports[0];
+	val->len = 0;
+	for (i = 0; i < smi->num_ports; i++) {
+		if (!(vlan4k.member & BIT(i)))
+			continue;
+
+		port->id = i;
+		port->flags = (vlan4k.untag & BIT(i)) ?
+					0 : BIT(SWITCH_PORT_FLAG_TAGGED);
+		val->len++;
+		port++;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports);
+
+int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	struct switch_port *port;
+	u32 member = 0;
+	u32 untag = 0;
+	int err;
+	int i;
+
+	if (!smi->ops->is_vlan_valid(smi, val->port_vlan))
+		return -EINVAL;
+
+	port = &val->value.ports[0];
+	for (i = 0; i < val->len; i++, port++) {
+		int pvid = 0;
+		member |= BIT(port->id);
+
+		if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED)))
+			untag |= BIT(port->id);
+
+		/*
+		 * To ensure that we have a valid MC entry for this VLAN,
+		 * initialize the port VLAN ID here.
+		 */
+		err = rtl8366_get_pvid(smi, port->id, &pvid);
+		if (err < 0)
+			return err;
+		if (pvid == 0) {
+			err = rtl8366_set_pvid(smi, port->id, val->port_vlan);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0);
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports);
+
+int rtl8366_sw_get_vlan_fid(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct rtl8366_vlan_4k vlan4k;
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int err;
+
+	if (!smi->ops->is_vlan_valid(smi, val->port_vlan))
+		return -EINVAL;
+
+	err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k);
+	if (err)
+		return err;
+
+	val->value.i = vlan4k.fid;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid);
+
+int rtl8366_sw_set_vlan_fid(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val)
+{
+	struct rtl8366_vlan_4k vlan4k;
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int err;
+
+	if (!smi->ops->is_vlan_valid(smi, val->port_vlan))
+		return -EINVAL;
+
+	if (val->value.i < 0 || val->value.i > attr->max)
+		return -EINVAL;
+
+	err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k);
+	if (err)
+		return err;
+
+	return rtl8366_set_vlan(smi, val->port_vlan,
+				vlan4k.member,
+				vlan4k.untag,
+				val->value.i);
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid);
+
+int rtl8366_sw_get_vlan_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (attr->ofs > 2)
+		return -EINVAL;
+
+	if (attr->ofs == 1)
+		val->value.i = smi->vlan_enabled;
+	else
+		val->value.i = smi->vlan4k_enabled;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable);
+
+int rtl8366_sw_set_vlan_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int err;
+
+	if (attr->ofs > 2)
+		return -EINVAL;
+
+	if (attr->ofs == 1)
+		err = rtl8366_enable_vlan(smi, val->value.i);
+	else
+		err = rtl8366_enable_vlan4k(smi, val->value.i);
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable);
+
+struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent)
+{
+	struct rtl8366_smi *smi;
+
+	BUG_ON(!parent);
+
+	smi = kzalloc(sizeof(*smi), GFP_KERNEL);
+	if (!smi) {
+		dev_err(parent, "no memory for private data\n");
+		return NULL;
+	}
+
+	smi->parent = parent;
+	return smi;
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_alloc);
+
+static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name)
+{
+	int err;
+
+	if (!smi->ext_mbus) {
+		err = gpio_request(smi->gpio_sda, name);
+		if (err) {
+			printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n",
+				smi->gpio_sda, err);
+			goto err_out;
+		}
+
+		err = gpio_request(smi->gpio_sck, name);
+		if (err) {
+			printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n",
+				smi->gpio_sck, err);
+			goto err_free_sda;
+		}
+	}
+
+	spin_lock_init(&smi->lock);
+
+	/* start the switch */
+	if (smi->hw_reset) {
+		smi->hw_reset(smi, false);
+		msleep(RTL8366_SMI_HW_START_DELAY);
+	}
+
+	return 0;
+
+ err_free_sda:
+	gpio_free(smi->gpio_sda);
+ err_out:
+	return err;
+}
+
+static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi)
+{
+	if (smi->hw_reset)
+		smi->hw_reset(smi, true);
+
+	if (!smi->ext_mbus) {
+		gpio_free(smi->gpio_sck);
+		gpio_free(smi->gpio_sda);
+	}
+}
+
+enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata)
+{
+	static struct rtl8366_smi smi;
+	enum rtl8366_type type = RTL8366_TYPE_UNKNOWN;
+	u32 reg = 0;
+
+	memset(&smi, 0, sizeof(smi));
+	smi.gpio_sda = pdata->gpio_sda;
+	smi.gpio_sck = pdata->gpio_sck;
+	smi.clk_delay = 10;
+	smi.cmd_read  = 0xa9;
+	smi.cmd_write = 0xa8;
+
+	if (__rtl8366_smi_init(&smi, "rtl8366"))
+		goto out;
+
+	if (rtl8366_smi_read_reg(&smi, 0x5c, &reg))
+		goto cleanup;
+
+	switch(reg) {
+	case 0x6027:
+		printk("Found an RTL8366S switch\n");
+		type = RTL8366_TYPE_S;
+		break;
+	case 0x5937:
+		printk("Found an RTL8366RB switch\n");
+		type = RTL8366_TYPE_RB;
+		break;
+	default:
+		printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg);
+		break;
+	}
+
+cleanup:
+	__rtl8366_smi_cleanup(&smi);
+out:
+	return type;
+}
+
+int rtl8366_smi_init(struct rtl8366_smi *smi)
+{
+	int err;
+
+	if (!smi->ops)
+		return -EINVAL;
+
+	err = __rtl8366_smi_init(smi, dev_name(smi->parent));
+	if (err)
+		goto err_out;
+
+	if (!smi->ext_mbus)
+		dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n",
+			 smi->gpio_sda, smi->gpio_sck);
+	else
+		dev_info(smi->parent, "using MDIO bus '%s'\n", smi->ext_mbus->name);
+
+	err = smi->ops->detect(smi);
+	if (err) {
+		dev_err(smi->parent, "chip detection failed, err=%d\n", err);
+		goto err_free_sck;
+	}
+
+	err = rtl8366_reset(smi);
+	if (err)
+		goto err_free_sck;
+
+	err = smi->ops->setup(smi);
+	if (err) {
+		dev_err(smi->parent, "chip setup failed, err=%d\n", err);
+		goto err_free_sck;
+	}
+
+	err = rtl8366_init_vlan(smi);
+	if (err) {
+		dev_err(smi->parent, "VLAN initialization failed, err=%d\n",
+			err);
+		goto err_free_sck;
+	}
+
+	err = rtl8366_enable_all_ports(smi, 1);
+	if (err)
+		goto err_free_sck;
+
+	err = rtl8366_smi_mii_init(smi);
+	if (err)
+		goto err_free_sck;
+
+	rtl8366_debugfs_init(smi);
+
+	return 0;
+
+ err_free_sck:
+	__rtl8366_smi_cleanup(smi);
+ err_out:
+	return err;
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_init);
+
+void rtl8366_smi_cleanup(struct rtl8366_smi *smi)
+{
+	rtl8366_debugfs_remove(smi);
+	rtl8366_smi_mii_cleanup(smi);
+	__rtl8366_smi_cleanup(smi);
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup);
+
+#ifdef CONFIG_OF
+static void rtl8366_smi_reset(struct rtl8366_smi *smi, bool active)
+{
+	if (active)
+		reset_control_assert(smi->reset);
+	else
+		reset_control_deassert(smi->reset);
+}
+
+int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi)
+{
+	int sck = of_get_named_gpio(pdev->dev.of_node, "gpio-sck", 0);
+	int sda = of_get_named_gpio(pdev->dev.of_node, "gpio-sda", 0);
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *mdio_node;
+
+	mdio_node = of_parse_phandle(np, "mii-bus", 0);
+	if (!mdio_node) {
+		dev_err(&pdev->dev, "cannot find mdio node phandle");
+		goto try_gpio;
+	}
+
+	smi->ext_mbus = of_mdio_find_bus(mdio_node);
+	if (!smi->ext_mbus) {
+		dev_info(&pdev->dev,
+			"cannot find mdio bus from bus handle (yet)");
+		goto try_gpio;
+	}
+
+	if (of_property_read_u32(np, "phy-id", &smi->phy_id))
+		smi->phy_id = MDC_REALTEK_PHY_ADDR;
+
+	return 0;
+
+try_gpio:
+	if (!gpio_is_valid(sck) || !gpio_is_valid(sda)) {
+		if (!mdio_node) {
+			dev_err(&pdev->dev, "gpios missing in devictree\n");
+			return -EINVAL;
+		} else {
+			return -EPROBE_DEFER;
+		}
+	}
+
+	smi->gpio_sda = sda;
+	smi->gpio_sck = sck;
+	smi->reset = devm_reset_control_get(&pdev->dev, "switch");
+	if (!IS_ERR(smi->reset))
+		smi->hw_reset = rtl8366_smi_reset;
+
+	return 0;
+}
+#else
+static inline int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi)
+{
+	return -ENODEV;
+}
+#endif
+
+int rtl8366_smi_probe_plat(struct platform_device *pdev, struct rtl8366_smi *smi)
+{
+	struct rtl8366_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdev->dev.platform_data) {
+		dev_err(&pdev->dev, "no platform data specified\n");
+		return -EINVAL;
+	}
+
+	smi->gpio_sda = pdata->gpio_sda;
+	smi->gpio_sck = pdata->gpio_sck;
+	smi->hw_reset = pdata->hw_reset;
+	smi->phy_id = MDC_REALTEK_PHY_ADDR;
+
+	return 0;
+}
+
+
+struct rtl8366_smi *rtl8366_smi_probe(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi;
+	int err;
+
+	smi = rtl8366_smi_alloc(&pdev->dev);
+	if (!smi)
+		return NULL;
+
+	if (pdev->dev.of_node)
+		err = rtl8366_smi_probe_of(pdev, smi);
+	else
+		err = rtl8366_smi_probe_plat(pdev, smi);
+
+	if (err)
+		goto free_smi;
+
+	return smi;
+
+free_smi:
+	kfree(smi);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(rtl8366_smi_probe);
+
+MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
diff -Naur linux-orig/drivers/net/phy/rtl8366_smi.h linux/drivers/net/phy/rtl8366_smi.h
--- linux-orig/drivers/net/phy/rtl8366_smi.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8366_smi.h	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,162 @@
+/*
+ * Realtek RTL8366 SMI interface driver defines
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef _RTL8366_SMI_H
+#define _RTL8366_SMI_H
+
+#include <linux/phy.h>
+#include <linux/switch.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+struct rtl8366_smi_ops;
+struct rtl8366_vlan_ops;
+struct mii_bus;
+struct dentry;
+struct inode;
+struct file;
+
+struct rtl8366_mib_counter {
+	unsigned	base;
+	unsigned	offset;
+	unsigned	length;
+	const char	*name;
+};
+
+struct rtl8366_smi {
+	struct device		*parent;
+	unsigned int		gpio_sda;
+	unsigned int		gpio_sck;
+	void			(*hw_reset)(struct rtl8366_smi *smi, bool active);
+	unsigned int		clk_delay;	/* ns */
+	u8			cmd_read;
+	u8			cmd_write;
+	spinlock_t		lock;
+	struct mii_bus		*mii_bus;
+	int			mii_irq[PHY_MAX_ADDR];
+	struct switch_dev	sw_dev;
+
+	unsigned int		cpu_port;
+	unsigned int		num_ports;
+	unsigned int		num_vlan_mc;
+	unsigned int		num_mib_counters;
+	struct rtl8366_mib_counter *mib_counters;
+
+	struct rtl8366_smi_ops	*ops;
+
+	int			vlan_enabled;
+	int			vlan4k_enabled;
+
+	char			buf[4096];
+
+	struct reset_control	*reset;
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+	struct dentry           *debugfs_root;
+	u16			dbg_reg;
+	u8			dbg_vlan_4k_page;
+#endif
+	struct mii_bus		*ext_mbus;
+	int			phy_id;
+	u32			flags;
+};
+
+struct rtl8366_vlan_mc {
+	u16	vid;
+	u16	untag;
+	u16	member;
+	u8	fid;
+	u8	priority;
+};
+
+struct rtl8366_vlan_4k {
+	u16	vid;
+	u16	untag;
+	u16	member;
+	u8	fid;
+};
+
+struct rtl8366_smi_ops {
+	int	(*detect)(struct rtl8366_smi *smi);
+	int	(*reset_chip)(struct rtl8366_smi *smi);
+	int	(*setup)(struct rtl8366_smi *smi);
+
+	int	(*mii_read)(struct mii_bus *bus, int addr, int reg);
+	int	(*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val);
+
+	int	(*get_vlan_mc)(struct rtl8366_smi *smi, u32 index,
+			       struct rtl8366_vlan_mc *vlanmc);
+	int	(*set_vlan_mc)(struct rtl8366_smi *smi, u32 index,
+			       const struct rtl8366_vlan_mc *vlanmc);
+	int	(*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid,
+			       struct rtl8366_vlan_4k *vlan4k);
+	int	(*set_vlan_4k)(struct rtl8366_smi *smi,
+			       const struct rtl8366_vlan_4k *vlan4k);
+	int	(*get_mc_index)(struct rtl8366_smi *smi, int port, int *val);
+	int	(*set_mc_index)(struct rtl8366_smi *smi, int port, int index);
+	int	(*get_mib_counter)(struct rtl8366_smi *smi, int counter,
+				   int port, unsigned long long *val);
+	int	(*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan);
+	int	(*enable_vlan)(struct rtl8366_smi *smi, int enable);
+	int	(*enable_vlan4k)(struct rtl8366_smi *smi, int enable);
+	int	(*enable_port)(struct rtl8366_smi *smi, int port, int enable);
+};
+
+struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent);
+int rtl8366_smi_init(struct rtl8366_smi *smi);
+void rtl8366_smi_cleanup(struct rtl8366_smi *smi);
+int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data);
+int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data);
+int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data);
+int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data);
+
+int rtl8366_reset_vlan(struct rtl8366_smi *smi);
+int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable);
+int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable);
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+int rtl8366_debugfs_open(struct inode *inode, struct file *file);
+#endif
+
+static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw)
+{
+	return container_of(sw, struct rtl8366_smi, sw_dev);
+}
+
+int rtl8366_sw_reset_switch(struct switch_dev *dev);
+int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val);
+int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val);
+int rtl8366_sw_get_port_mib(struct switch_dev *dev,
+			    const struct switch_attr *attr,
+			    struct switch_val *val);
+int rtl8366_sw_get_vlan_info(struct switch_dev *dev,
+			     const struct switch_attr *attr,
+			     struct switch_val *val);
+int rtl8366_sw_get_vlan_fid(struct switch_dev *dev,
+			     const struct switch_attr *attr,
+			     struct switch_val *val);
+int rtl8366_sw_set_vlan_fid(struct switch_dev *dev,
+			     const struct switch_attr *attr,
+			     struct switch_val *val);
+int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val);
+int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val);
+int rtl8366_sw_get_vlan_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int rtl8366_sw_set_vlan_enable(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val);
+int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port,
+				struct switch_port_stats *stats,
+				int txb_id, int rxb_id);
+
+struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev);
+
+#endif /*  _RTL8366_SMI_H */
diff -Naur linux-orig/drivers/net/phy/rtl8366rb.c linux/drivers/net/phy/rtl8366rb.c
--- linux-orig/drivers/net/phy/rtl8366rb.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8366rb.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1532 @@
+/*
+ * Platform driver for the Realtek RTL8366RB ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
+ * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#include "rtl8366_smi.h"
+
+#define RTL8366RB_DRIVER_DESC	"Realtek RTL8366RB ethernet switch driver"
+#define RTL8366RB_DRIVER_VER	"0.2.4"
+
+#define RTL8366RB_PHY_NO_MAX	4
+#define RTL8366RB_PHY_PAGE_MAX	7
+#define RTL8366RB_PHY_ADDR_MAX	31
+
+/* Switch Global Configuration register */
+#define RTL8366RB_SGCR				0x0000
+#define RTL8366RB_SGCR_EN_BC_STORM_CTRL		BIT(0)
+#define RTL8366RB_SGCR_MAX_LENGTH(_x)		(_x << 4)
+#define RTL8366RB_SGCR_MAX_LENGTH_MASK		RTL8366RB_SGCR_MAX_LENGTH(0x3)
+#define RTL8366RB_SGCR_MAX_LENGTH_1522		RTL8366RB_SGCR_MAX_LENGTH(0x0)
+#define RTL8366RB_SGCR_MAX_LENGTH_1536		RTL8366RB_SGCR_MAX_LENGTH(0x1)
+#define RTL8366RB_SGCR_MAX_LENGTH_1552		RTL8366RB_SGCR_MAX_LENGTH(0x2)
+#define RTL8366RB_SGCR_MAX_LENGTH_9216		RTL8366RB_SGCR_MAX_LENGTH(0x3)
+#define RTL8366RB_SGCR_EN_VLAN			BIT(13)
+#define RTL8366RB_SGCR_EN_VLAN_4KTB		BIT(14)
+
+/* Port Enable Control register */
+#define RTL8366RB_PECR				0x0001
+
+/* Port Mirror Control Register */
+#define RTL8366RB_PMCR				0x0007
+#define RTL8366RB_PMCR_SOURCE_PORT(_x)		(_x)
+#define RTL8366RB_PMCR_SOURCE_PORT_MASK		0x000f
+#define RTL8366RB_PMCR_MONITOR_PORT(_x)		((_x) << 4)
+#define RTL8366RB_PMCR_MONITOR_PORT_MASK	0x00f0
+#define RTL8366RB_PMCR_MIRROR_RX		BIT(8)
+#define RTL8366RB_PMCR_MIRROR_TX		BIT(9)
+#define RTL8366RB_PMCR_MIRROR_SPC		BIT(10)
+#define RTL8366RB_PMCR_MIRROR_ISO		BIT(11)
+
+/* Switch Security Control registers */
+#define RTL8366RB_SSCR0				0x0002
+#define RTL8366RB_SSCR1				0x0003
+#define RTL8366RB_SSCR2				0x0004
+#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA		BIT(0)
+
+#define RTL8366RB_RESET_CTRL_REG		0x0100
+#define RTL8366RB_CHIP_CTRL_RESET_HW		1
+#define RTL8366RB_CHIP_CTRL_RESET_SW		(1 << 1)
+
+#define RTL8366RB_CHIP_VERSION_CTRL_REG		0x050A
+#define RTL8366RB_CHIP_VERSION_MASK		0xf
+#define RTL8366RB_CHIP_ID_REG			0x0509
+#define RTL8366RB_CHIP_ID_8366			0x5937
+
+/* PHY registers control */
+#define RTL8366RB_PHY_ACCESS_CTRL_REG		0x8000
+#define RTL8366RB_PHY_ACCESS_DATA_REG		0x8002
+
+#define RTL8366RB_PHY_CTRL_READ			1
+#define RTL8366RB_PHY_CTRL_WRITE		0
+
+#define RTL8366RB_PHY_REG_MASK			0x1f
+#define RTL8366RB_PHY_PAGE_OFFSET		5
+#define RTL8366RB_PHY_PAGE_MASK			(0xf << 5)
+#define RTL8366RB_PHY_NO_OFFSET			9
+#define RTL8366RB_PHY_NO_MASK			(0x1f << 9)
+
+#define RTL8366RB_VLAN_INGRESS_CTRL2_REG	0x037f
+
+/* LED control registers */
+#define RTL8366RB_LED_BLINKRATE_REG		0x0430
+#define RTL8366RB_LED_BLINKRATE_BIT		0
+#define RTL8366RB_LED_BLINKRATE_MASK		0x0007
+
+#define RTL8366RB_LED_CTRL_REG			0x0431
+#define RTL8366RB_LED_0_1_CTRL_REG		0x0432
+#define RTL8366RB_LED_2_3_CTRL_REG		0x0433
+
+#define RTL8366RB_MIB_COUNT			33
+#define RTL8366RB_GLOBAL_MIB_COUNT		1
+#define RTL8366RB_MIB_COUNTER_PORT_OFFSET	0x0050
+#define RTL8366RB_MIB_COUNTER_BASE		0x1000
+#define RTL8366RB_MIB_CTRL_REG			0x13F0
+#define RTL8366RB_MIB_CTRL_USER_MASK		0x0FFC
+#define RTL8366RB_MIB_CTRL_BUSY_MASK		BIT(0)
+#define RTL8366RB_MIB_CTRL_RESET_MASK		BIT(1)
+#define RTL8366RB_MIB_CTRL_PORT_RESET(_p)	BIT(2 + (_p))
+#define RTL8366RB_MIB_CTRL_GLOBAL_RESET		BIT(11)
+
+#define RTL8366RB_PORT_VLAN_CTRL_BASE		0x0063
+#define RTL8366RB_PORT_VLAN_CTRL_REG(_p)  \
+		(RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4)
+#define RTL8366RB_PORT_VLAN_CTRL_MASK		0xf
+#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p)	(4 * ((_p) % 4))
+
+
+#define RTL8366RB_VLAN_TABLE_READ_BASE		0x018C
+#define RTL8366RB_VLAN_TABLE_WRITE_BASE		0x0185
+
+
+#define RTL8366RB_TABLE_ACCESS_CTRL_REG		0x0180
+#define RTL8366RB_TABLE_VLAN_READ_CTRL		0x0E01
+#define RTL8366RB_TABLE_VLAN_WRITE_CTRL		0x0F01
+
+#define RTL8366RB_VLAN_MC_BASE(_x)		(0x0020 + (_x) * 3)
+
+
+#define RTL8366RB_PORT_LINK_STATUS_BASE		0x0014
+#define RTL8366RB_PORT_STATUS_SPEED_MASK	0x0003
+#define RTL8366RB_PORT_STATUS_DUPLEX_MASK	0x0004
+#define RTL8366RB_PORT_STATUS_LINK_MASK		0x0010
+#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK	0x0020
+#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK	0x0040
+#define RTL8366RB_PORT_STATUS_AN_MASK		0x0080
+
+
+#define RTL8366RB_PORT_NUM_CPU		5
+#define RTL8366RB_NUM_PORTS		6
+#define RTL8366RB_NUM_VLANS		16
+#define RTL8366RB_NUM_LEDGROUPS		4
+#define RTL8366RB_NUM_VIDS		4096
+#define RTL8366RB_PRIORITYMAX		7
+#define RTL8366RB_FIDMAX		7
+
+
+#define RTL8366RB_PORT_1		(1 << 0) /* In userspace port 0 */
+#define RTL8366RB_PORT_2		(1 << 1) /* In userspace port 1 */
+#define RTL8366RB_PORT_3		(1 << 2) /* In userspace port 2 */
+#define RTL8366RB_PORT_4		(1 << 3) /* In userspace port 3 */
+#define RTL8366RB_PORT_5		(1 << 4) /* In userspace port 4 */
+
+#define RTL8366RB_PORT_CPU		(1 << 5) /* CPU port */
+
+#define RTL8366RB_PORT_ALL		(RTL8366RB_PORT_1 |	\
+					 RTL8366RB_PORT_2 |	\
+					 RTL8366RB_PORT_3 |	\
+					 RTL8366RB_PORT_4 |	\
+					 RTL8366RB_PORT_5 |	\
+					 RTL8366RB_PORT_CPU)
+
+#define RTL8366RB_PORT_ALL_BUT_CPU	(RTL8366RB_PORT_1 |	\
+					 RTL8366RB_PORT_2 |	\
+					 RTL8366RB_PORT_3 |	\
+					 RTL8366RB_PORT_4 |	\
+					 RTL8366RB_PORT_5)
+
+#define RTL8366RB_PORT_ALL_EXTERNAL	(RTL8366RB_PORT_1 |	\
+					 RTL8366RB_PORT_2 |	\
+					 RTL8366RB_PORT_3 |	\
+					 RTL8366RB_PORT_4)
+
+#define RTL8366RB_PORT_ALL_INTERNAL	 RTL8366RB_PORT_CPU
+
+#define RTL8366RB_VLAN_VID_MASK		0xfff
+#define RTL8366RB_VLAN_PRIORITY_SHIFT	12
+#define RTL8366RB_VLAN_PRIORITY_MASK	0x7
+#define RTL8366RB_VLAN_UNTAG_SHIFT	8
+#define RTL8366RB_VLAN_UNTAG_MASK	0xff
+#define RTL8366RB_VLAN_MEMBER_MASK	0xff
+#define RTL8366RB_VLAN_FID_MASK		0x7
+
+
+/* Port ingress bandwidth control */
+#define RTL8366RB_IB_BASE		0x0200
+#define RTL8366RB_IB_REG(pnum)		(RTL8366RB_IB_BASE + pnum)
+#define RTL8366RB_IB_BDTH_MASK		0x3fff
+#define RTL8366RB_IB_PREIFG_OFFSET	14
+#define RTL8366RB_IB_PREIFG_MASK	(1 << RTL8366RB_IB_PREIFG_OFFSET)
+
+/* Port egress bandwidth control */
+#define RTL8366RB_EB_BASE		0x02d1
+#define RTL8366RB_EB_REG(pnum)		(RTL8366RB_EB_BASE + pnum)
+#define RTL8366RB_EB_BDTH_MASK		0x3fff
+#define RTL8366RB_EB_PREIFG_REG	0x02f8
+#define RTL8366RB_EB_PREIFG_OFFSET	9
+#define RTL8366RB_EB_PREIFG_MASK	(1 << RTL8366RB_EB_PREIFG_OFFSET)
+
+#define RTL8366RB_BDTH_SW_MAX		1048512
+#define RTL8366RB_BDTH_UNIT		64
+#define RTL8366RB_BDTH_REG_DEFAULT	16383
+
+/* QOS */
+#define RTL8366RB_QOS_BIT		15
+#define RTL8366RB_QOS_MASK		(1 << RTL8366RB_QOS_BIT)
+/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */
+#define RTL8366RB_QOS_DEFAULT_PREIFG	1
+
+
+#define RTL8366RB_MIB_RXB_ID		0	/* IfInOctets */
+#define RTL8366RB_MIB_TXB_ID		20	/* IfOutOctets */
+
+static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = {
+	{ 0,  0, 4, "IfInOctets"				},
+	{ 0,  4, 4, "EtherStatsOctets"				},
+	{ 0,  8, 2, "EtherStatsUnderSizePkts"			},
+	{ 0, 10, 2, "EtherFragments"				},
+	{ 0, 12, 2, "EtherStatsPkts64Octets"			},
+	{ 0, 14, 2, "EtherStatsPkts65to127Octets"		},
+	{ 0, 16, 2, "EtherStatsPkts128to255Octets"		},
+	{ 0, 18, 2, "EtherStatsPkts256to511Octets"		},
+	{ 0, 20, 2, "EtherStatsPkts512to1023Octets"		},
+	{ 0, 22, 2, "EtherStatsPkts1024to1518Octets"		},
+	{ 0, 24, 2, "EtherOversizeStats"			},
+	{ 0, 26, 2, "EtherStatsJabbers"				},
+	{ 0, 28, 2, "IfInUcastPkts"				},
+	{ 0, 30, 2, "EtherStatsMulticastPkts"			},
+	{ 0, 32, 2, "EtherStatsBroadcastPkts"			},
+	{ 0, 34, 2, "EtherStatsDropEvents"			},
+	{ 0, 36, 2, "Dot3StatsFCSErrors"			},
+	{ 0, 38, 2, "Dot3StatsSymbolErrors"			},
+	{ 0, 40, 2, "Dot3InPauseFrames"				},
+	{ 0, 42, 2, "Dot3ControlInUnknownOpcodes"		},
+	{ 0, 44, 4, "IfOutOctets"				},
+	{ 0, 48, 2, "Dot3StatsSingleCollisionFrames"		},
+	{ 0, 50, 2, "Dot3StatMultipleCollisionFrames"		},
+	{ 0, 52, 2, "Dot3sDeferredTransmissions"		},
+	{ 0, 54, 2, "Dot3StatsLateCollisions"			},
+	{ 0, 56, 2, "EtherStatsCollisions"			},
+	{ 0, 58, 2, "Dot3StatsExcessiveCollisions"		},
+	{ 0, 60, 2, "Dot3OutPauseFrames"			},
+	{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards"	},
+	{ 0, 64, 2, "Dot1dTpPortInDiscards"			},
+	{ 0, 66, 2, "IfOutUcastPkts"				},
+	{ 0, 68, 2, "IfOutMulticastPkts"			},
+	{ 0, 70, 2, "IfOutBroadcastPkts"			},
+};
+
+#define REG_WR(_smi, _reg, _val)					\
+	do {								\
+		err = rtl8366_smi_write_reg(_smi, _reg, _val);		\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+#define REG_RMW(_smi, _reg, _mask, _val)				\
+	do {								\
+		err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val);	\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+static int rtl8366rb_reset_chip(struct rtl8366_smi *smi)
+{
+	int timeout = 10;
+	u32 data;
+
+	rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG,
+			 	    RTL8366RB_CHIP_CTRL_RESET_HW);
+	do {
+		msleep(1);
+		if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data))
+			return -EIO;
+
+		if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW))
+			break;
+	} while (--timeout);
+
+	if (!timeout) {
+		printk("Timeout waiting for the switch to reset\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int rtl8366rb_setup(struct rtl8366_smi *smi)
+{
+	int err;
+#ifdef CONFIG_OF
+	unsigned i;
+	struct device_node *np;
+	unsigned num_initvals;
+	const __be32 *paddr;
+
+	np = smi->parent->of_node;
+
+	paddr = of_get_property(np, "realtek,initvals", &num_initvals);
+	if (paddr) {
+		dev_info(smi->parent, "applying initvals from DTS\n");
+
+		if (num_initvals < (2 * sizeof(*paddr)))
+			return -EINVAL;
+
+		num_initvals /= sizeof(*paddr);
+
+		for (i = 0; i < num_initvals - 1; i += 2) {
+			u32 reg = be32_to_cpup(paddr + i);
+			u32 val = be32_to_cpup(paddr + i + 1);
+
+			REG_WR(smi, reg, val);
+		}
+	}
+#endif
+
+	/* set maximum packet length to 1536 bytes */
+	REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK,
+		RTL8366RB_SGCR_MAX_LENGTH_1536);
+
+	/* enable learning for all ports */
+	REG_WR(smi, RTL8366RB_SSCR0, 0);
+
+	/* enable auto ageing for all ports */
+	REG_WR(smi, RTL8366RB_SSCR1, 0);
+
+	/*
+	 * discard VLAN tagged packets if the port is not a member of
+	 * the VLAN with which the packets is associated.
+	 */
+	REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL);
+
+	/* don't drop packets whose DA has not been learned */
+	REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0);
+
+	return 0;
+}
+
+static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi,
+				 u32 phy_no, u32 page, u32 addr, u32 *data)
+{
+	u32 reg;
+	int ret;
+
+	if (phy_no > RTL8366RB_PHY_NO_MAX)
+		return -EINVAL;
+
+	if (page > RTL8366RB_PHY_PAGE_MAX)
+		return -EINVAL;
+
+	if (addr > RTL8366RB_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG,
+				    RTL8366RB_PHY_CTRL_READ);
+	if (ret)
+		return ret;
+
+	reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) |
+	      ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) |
+	      (addr & RTL8366RB_PHY_REG_MASK);
+
+	ret = rtl8366_smi_write_reg(smi, reg, 0);
+	if (ret)
+		return ret;
+
+	ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi,
+				  u32 phy_no, u32 page, u32 addr, u32 data)
+{
+	u32 reg;
+	int ret;
+
+	if (phy_no > RTL8366RB_PHY_NO_MAX)
+		return -EINVAL;
+
+	if (page > RTL8366RB_PHY_PAGE_MAX)
+		return -EINVAL;
+
+	if (addr > RTL8366RB_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG,
+				    RTL8366RB_PHY_CTRL_WRITE);
+	if (ret)
+		return ret;
+
+	reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) |
+	      ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) |
+	      (addr & RTL8366RB_PHY_REG_MASK);
+
+	ret = rtl8366_smi_write_reg(smi, reg, data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter,
+				     int port, unsigned long long *val)
+{
+	int i;
+	int err;
+	u32 addr, data;
+	u64 mibvalue;
+
+	if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT)
+		return -EINVAL;
+
+	addr = RTL8366RB_MIB_COUNTER_BASE +
+	       RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) +
+	       rtl8366rb_mib_counters[counter].offset;
+
+	/*
+	 * Writing access counter address first
+	 * then ASIC will prepare 64bits counter wait for being retrived
+	 */
+	data = 0; /* writing data will be discard by ASIC */
+	err = rtl8366_smi_write_reg(smi, addr, data);
+	if (err)
+		return err;
+
+	/* read MIB control register */
+	err =  rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data);
+	if (err)
+		return err;
+
+	if (data & RTL8366RB_MIB_CTRL_BUSY_MASK)
+		return -EBUSY;
+
+	if (data & RTL8366RB_MIB_CTRL_RESET_MASK)
+		return -EIO;
+
+	mibvalue = 0;
+	for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) {
+		err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data);
+		if (err)
+			return err;
+
+		mibvalue = (mibvalue << 16) | (data & 0xFFFF);
+	}
+
+	*val = mibvalue;
+	return 0;
+}
+
+static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid,
+				 struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[3];
+	int err;
+	int i;
+
+	memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k));
+
+	if (vid >= RTL8366RB_NUM_VIDS)
+		return -EINVAL;
+
+	/* write VID */
+	err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE,
+				    vid & RTL8366RB_VLAN_VID_MASK);
+	if (err)
+		return err;
+
+	/* write table access control word */
+	err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG,
+				    RTL8366RB_TABLE_VLAN_READ_CTRL);
+	if (err)
+		return err;
+
+	for (i = 0; i < 3; i++) {
+		err = rtl8366_smi_read_reg(smi,
+					   RTL8366RB_VLAN_TABLE_READ_BASE + i,
+					   &data[i]);
+		if (err)
+			return err;
+	}
+
+	vlan4k->vid = vid;
+	vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) &
+			RTL8366RB_VLAN_UNTAG_MASK;
+	vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK;
+	vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi,
+				 const struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[3];
+	int err;
+	int i;
+
+	if (vlan4k->vid >= RTL8366RB_NUM_VIDS ||
+	    vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK ||
+	    vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK ||
+	    vlan4k->fid > RTL8366RB_FIDMAX)
+		return -EINVAL;
+
+	data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK;
+	data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) |
+		  ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) <<
+			RTL8366RB_VLAN_UNTAG_SHIFT);
+	data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK;
+
+	for (i = 0; i < 3; i++) {
+		err = rtl8366_smi_write_reg(smi,
+					    RTL8366RB_VLAN_TABLE_WRITE_BASE + i,
+					    data[i]);
+		if (err)
+			return err;
+	}
+
+	/* write table access control word */
+	err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG,
+				    RTL8366RB_TABLE_VLAN_WRITE_CTRL);
+
+	return err;
+}
+
+static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				 struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[3];
+	int err;
+	int i;
+
+	memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc));
+
+	if (index >= RTL8366RB_NUM_VLANS)
+		return -EINVAL;
+
+	for (i = 0; i < 3; i++) {
+		err = rtl8366_smi_read_reg(smi,
+					   RTL8366RB_VLAN_MC_BASE(index) + i,
+					   &data[i]);
+		if (err)
+			return err;
+	}
+
+	vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK;
+	vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) &
+			   RTL8366RB_VLAN_PRIORITY_MASK;
+	vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) &
+			RTL8366RB_VLAN_UNTAG_MASK;
+	vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK;
+	vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				 const struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[3];
+	int err;
+	int i;
+
+	if (index >= RTL8366RB_NUM_VLANS ||
+	    vlanmc->vid >= RTL8366RB_NUM_VIDS ||
+	    vlanmc->priority > RTL8366RB_PRIORITYMAX ||
+	    vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK ||
+	    vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK ||
+	    vlanmc->fid > RTL8366RB_FIDMAX)
+		return -EINVAL;
+
+	data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) |
+		  ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) <<
+			RTL8366RB_VLAN_PRIORITY_SHIFT);
+	data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) |
+		  ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) <<
+			RTL8366RB_VLAN_UNTAG_SHIFT);
+	data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK;
+
+	for (i = 0; i < 3; i++) {
+		err = rtl8366_smi_write_reg(smi,
+					    RTL8366RB_VLAN_MC_BASE(index) + i,
+					    data[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val)
+{
+	u32 data;
+	int err;
+
+	if (port >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port),
+				   &data);
+	if (err)
+		return err;
+
+	*val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) &
+	       RTL8366RB_PORT_VLAN_CTRL_MASK;
+
+	return 0;
+
+}
+
+static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index)
+{
+	if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port),
+				RTL8366RB_PORT_VLAN_CTRL_MASK <<
+					RTL8366RB_PORT_VLAN_CTRL_SHIFT(port),
+				(index & RTL8366RB_PORT_VLAN_CTRL_MASK) <<
+					RTL8366RB_PORT_VLAN_CTRL_SHIFT(port));
+}
+
+static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan)
+{
+	unsigned max = RTL8366RB_NUM_VLANS;
+
+	if (smi->vlan4k_enabled)
+		max = RTL8366RB_NUM_VIDS - 1;
+
+	if (vlan == 0 || vlan >= max)
+		return 0;
+
+	return 1;
+}
+
+static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN,
+				(enable) ? RTL8366RB_SGCR_EN_VLAN : 0);
+}
+
+static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR,
+				RTL8366RB_SGCR_EN_VLAN_4KTB,
+				(enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0);
+}
+
+static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port),
+				(enable) ? 0 : (1 << port));
+}
+
+static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0,
+			        RTL8366RB_MIB_CTRL_GLOBAL_RESET);
+}
+
+static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev,
+				     const struct switch_attr *attr,
+				     struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data);
+
+	val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK));
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (val->value.i >= 6)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG,
+				RTL8366RB_LED_BLINKRATE_MASK,
+				val->value.i);
+}
+
+static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data);
+	val->value.i = !data;
+
+	return 0;
+}
+
+
+static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 portmask = 0;
+	int err = 0;
+
+	if (!val->value.i)
+		portmask = RTL8366RB_PORT_ALL;
+
+	/* set learning for all ports */
+	REG_WR(smi, RTL8366RB_SSCR0, portmask);
+
+	/* set auto ageing for all ports */
+	REG_WR(smi, RTL8366RB_SSCR1, portmask);
+
+	return 0;
+}
+
+static int rtl8366rb_sw_get_port_link(struct switch_dev *dev,
+				     int port,
+				     struct switch_port_link *link)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data = 0;
+	u32 speed;
+
+	if (port >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2),
+			     &data);
+
+	if (port % 2)
+		data = data >> 8;
+
+	link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK);
+	if (!link->link)
+		return 0;
+
+	link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK);
+	link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK);
+	link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK);
+	link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK);
+
+	speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK);
+	switch (speed) {
+	case 0:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case 1:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case 2:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	default:
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_port_led(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+	u32 mask;
+	u32 reg;
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) {
+		reg = RTL8366RB_LED_BLINKRATE_REG;
+		mask = 0xF << 4;
+		data = val->value.i << 4;
+	} else {
+		reg = RTL8366RB_LED_CTRL_REG;
+		mask = 0xF << (val->port_vlan * 4),
+		data = val->value.i << (val->port_vlan * 4);
+	}
+
+	return rtl8366_smi_rmwr(smi, reg, mask, data);
+}
+
+static int rtl8366rb_sw_get_port_led(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data = 0;
+
+	if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data);
+	val->value.i = (data >> (val->port_vlan * 4)) & 0x000F;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 mask, data;
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	mask = 1 << val->port_vlan ;
+	if (val->value.i)
+		data = mask;
+	else
+		data = 0;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data);
+}
+
+static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data);
+	if (data & (1 << val->port_vlan))
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX)
+		val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT;
+	else
+		val->value.i = RTL8366RB_BDTH_REG_DEFAULT;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan),
+		RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK,
+		val->value.i |
+		(RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET));
+
+}
+
+static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data);
+	data &= RTL8366RB_IB_BDTH_MASK;
+	if (data < RTL8366RB_IB_BDTH_MASK)
+		data += 1;
+
+	val->value.i = (int)data * RTL8366RB_BDTH_UNIT;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG,
+		RTL8366RB_EB_PREIFG_MASK,
+		(RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET));
+
+	if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX)
+		val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT;
+	else
+		val->value.i = RTL8366RB_BDTH_REG_DEFAULT;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan),
+			RTL8366RB_EB_BDTH_MASK, val->value.i );
+
+}
+
+static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data);
+	data &= RTL8366RB_EB_BDTH_MASK;
+	if (data < RTL8366RB_EB_BDTH_MASK)
+		data += 1;
+
+	val->value.i = (int)data * RTL8366RB_BDTH_UNIT;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->value.i)
+		data = RTL8366RB_QOS_MASK;
+	else
+		data = 0;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data);
+}
+
+static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data);
+	if (data & RTL8366RB_QOS_MASK)
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_mirror_rx_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->value.i)
+		data = RTL8366RB_PMCR_MIRROR_RX;
+	else
+		data = 0;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_RX, data);
+}
+
+static int rtl8366rb_sw_get_mirror_rx_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data);
+	if (data & RTL8366RB_PMCR_MIRROR_RX)
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_mirror_tx_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->value.i)
+		data = RTL8366RB_PMCR_MIRROR_TX;
+	else
+		data = 0;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_TX, data);
+}
+
+static int rtl8366rb_sw_get_mirror_tx_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data);
+	if (data & RTL8366RB_PMCR_MIRROR_TX)
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_monitor_isolation_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->value.i)
+		data = RTL8366RB_PMCR_MIRROR_ISO;
+	else
+		data = 0;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_ISO, data);
+}
+
+static int rtl8366rb_sw_get_monitor_isolation_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data);
+	if (data & RTL8366RB_PMCR_MIRROR_ISO)
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_mirror_pause_frames_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	if (val->value.i)
+		data = RTL8366RB_PMCR_MIRROR_SPC;
+	else
+		data = 0;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_SPC, data);
+}
+
+static int rtl8366rb_sw_get_mirror_pause_frames_enable(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data);
+	if (data & RTL8366RB_PMCR_MIRROR_SPC)
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_mirror_monitor_port(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	data = RTL8366RB_PMCR_MONITOR_PORT(val->value.i);
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MONITOR_PORT_MASK, data);
+}
+
+static int rtl8366rb_sw_get_mirror_monitor_port(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data);
+	val->value.i = (data & RTL8366RB_PMCR_MONITOR_PORT_MASK) >> 4;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_set_mirror_source_port(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	data = RTL8366RB_PMCR_SOURCE_PORT(val->value.i);
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_SOURCE_PORT_MASK, data);
+}
+
+static int rtl8366rb_sw_get_mirror_source_port(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data);
+	val->value.i = data & RTL8366RB_PMCR_SOURCE_PORT_MASK;
+
+	return 0;
+}
+
+static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (val->port_vlan >= RTL8366RB_NUM_PORTS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0,
+				RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan));
+}
+
+static int rtl8366rb_sw_get_port_stats(struct switch_dev *dev, int port,
+					struct switch_port_stats *stats)
+{
+	return (rtl8366_sw_get_port_stats(dev, port, stats,
+				RTL8366RB_MIB_TXB_ID, RTL8366RB_MIB_RXB_ID));
+}
+
+static struct switch_attr rtl8366rb_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_learning",
+		.description = "Enable learning, enable aging",
+		.set = rtl8366rb_sw_set_learning_enable,
+		.get = rtl8366rb_sw_get_learning_enable,
+		.max = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan4k",
+		.description = "Enable VLAN 4K mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 2
+	}, {
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mibs",
+		.description = "Reset all MIB counters",
+		.set = rtl8366rb_sw_reset_mibs,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "blinkrate",
+		.description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms,"
+		" 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)",
+		.set = rtl8366rb_sw_set_blinkrate,
+		.get = rtl8366rb_sw_get_blinkrate,
+		.max = 5
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_qos",
+		.description = "Enable QOS",
+		.set = rtl8366rb_sw_set_qos_enable,
+		.get = rtl8366rb_sw_get_qos_enable,
+		.max = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_rx",
+		.description = "Enable mirroring of RX packets",
+		.set = rtl8366rb_sw_set_mirror_rx_enable,
+		.get = rtl8366rb_sw_get_mirror_rx_enable,
+		.max = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_tx",
+		.description = "Enable mirroring of TX packets",
+		.set = rtl8366rb_sw_set_mirror_tx_enable,
+		.get = rtl8366rb_sw_get_mirror_tx_enable,
+		.max = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_monitor_isolation",
+		.description = "Enable isolation of monitor port (TX packets will be dropped)",
+		.set = rtl8366rb_sw_set_monitor_isolation_enable,
+		.get = rtl8366rb_sw_get_monitor_isolation_enable,
+		.max = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_pause_frames",
+		.description = "Enable mirroring of RX pause frames",
+		.set = rtl8366rb_sw_set_mirror_pause_frames_enable,
+		.get = rtl8366rb_sw_get_mirror_pause_frames_enable,
+		.max = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_monitor_port",
+		.description = "Mirror monitor port",
+		.set = rtl8366rb_sw_set_mirror_monitor_port,
+		.get = rtl8366rb_sw_get_mirror_monitor_port,
+		.max = 5
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_source_port",
+		.description = "Mirror source port",
+		.set = rtl8366rb_sw_set_mirror_source_port,
+		.get = rtl8366rb_sw_get_mirror_source_port,
+		.max = 5
+	},
+};
+
+static struct switch_attr rtl8366rb_port[] = {
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mib",
+		.description = "Reset single port MIB counters",
+		.set = rtl8366rb_sw_reset_port_mibs,
+	}, {
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get MIB counters for port",
+		.max = 33,
+		.set = NULL,
+		.get = rtl8366_sw_get_port_mib,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "led",
+		.description = "Get/Set port group (0 - 3) led mode (0 - 15)",
+		.max = 15,
+		.set = rtl8366rb_sw_set_port_led,
+		.get = rtl8366rb_sw_get_port_led,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "disable",
+		.description = "Get/Set port state (enabled or disabled)",
+		.max = 1,
+		.set = rtl8366rb_sw_set_port_disable,
+		.get = rtl8366rb_sw_get_port_disable,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "rate_in",
+		.description = "Get/Set port ingress (incoming) bandwidth limit in kbps",
+		.max = RTL8366RB_BDTH_SW_MAX,
+		.set = rtl8366rb_sw_set_port_rate_in,
+		.get = rtl8366rb_sw_get_port_rate_in,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "rate_out",
+		.description = "Get/Set port egress (outgoing) bandwidth limit in kbps",
+		.max = RTL8366RB_BDTH_SW_MAX,
+		.set = rtl8366rb_sw_set_port_rate_out,
+		.get = rtl8366rb_sw_get_port_rate_out,
+	},
+};
+
+static struct switch_attr rtl8366rb_vlan[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "info",
+		.description = "Get vlan information",
+		.max = 1,
+		.set = NULL,
+		.get = rtl8366_sw_get_vlan_info,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "fid",
+		.description = "Get/Set vlan FID",
+		.max = RTL8366RB_FIDMAX,
+		.set = rtl8366_sw_set_vlan_fid,
+		.get = rtl8366_sw_get_vlan_fid,
+	},
+};
+
+static const struct switch_dev_ops rtl8366_ops = {
+	.attr_global = {
+		.attr = rtl8366rb_globals,
+		.n_attr = ARRAY_SIZE(rtl8366rb_globals),
+	},
+	.attr_port = {
+		.attr = rtl8366rb_port,
+		.n_attr = ARRAY_SIZE(rtl8366rb_port),
+	},
+	.attr_vlan = {
+		.attr = rtl8366rb_vlan,
+		.n_attr = ARRAY_SIZE(rtl8366rb_vlan),
+	},
+
+	.get_vlan_ports = rtl8366_sw_get_vlan_ports,
+	.set_vlan_ports = rtl8366_sw_set_vlan_ports,
+	.get_port_pvid = rtl8366_sw_get_port_pvid,
+	.set_port_pvid = rtl8366_sw_set_port_pvid,
+	.reset_switch = rtl8366_sw_reset_switch,
+	.get_port_link = rtl8366rb_sw_get_port_link,
+	.get_port_stats = rtl8366rb_sw_get_port_stats,
+};
+
+static int rtl8366rb_switch_init(struct rtl8366_smi *smi)
+{
+	struct switch_dev *dev = &smi->sw_dev;
+	int err;
+
+	dev->name = "RTL8366RB";
+	dev->cpu_port = RTL8366RB_PORT_NUM_CPU;
+	dev->ports = RTL8366RB_NUM_PORTS;
+	dev->vlans = RTL8366RB_NUM_VIDS;
+	dev->ops = &rtl8366_ops;
+	dev->alias = dev_name(smi->parent);
+
+	err = register_switch(dev, NULL);
+	if (err)
+		dev_err(smi->parent, "switch registration failed\n");
+
+	return err;
+}
+
+static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi)
+{
+	unregister_switch(&smi->sw_dev);
+}
+
+static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 val = 0;
+	int err;
+
+	err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val);
+	if (err)
+		return 0xffff;
+
+	return val;
+}
+
+static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 t;
+	int err;
+
+	err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val);
+	/* flush write */
+	(void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t);
+
+	return err;
+}
+
+static int rtl8366rb_detect(struct rtl8366_smi *smi)
+{
+	u32 chip_id = 0;
+	u32 chip_ver = 0;
+	int ret;
+
+	ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id);
+	if (ret) {
+		dev_err(smi->parent, "unable to read chip id\n");
+		return ret;
+	}
+
+	switch (chip_id) {
+	case RTL8366RB_CHIP_ID_8366:
+		break;
+	default:
+		dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id);
+		return -ENODEV;
+	}
+
+	ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG,
+				   &chip_ver);
+	if (ret) {
+		dev_err(smi->parent, "unable to read chip version\n");
+		return ret;
+	}
+
+	dev_info(smi->parent, "RTL%04x ver. %u chip found\n",
+		 chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK);
+
+	return 0;
+}
+
+static struct rtl8366_smi_ops rtl8366rb_smi_ops = {
+	.detect		= rtl8366rb_detect,
+	.reset_chip	= rtl8366rb_reset_chip,
+	.setup		= rtl8366rb_setup,
+
+	.mii_read	= rtl8366rb_mii_read,
+	.mii_write	= rtl8366rb_mii_write,
+
+	.get_vlan_mc	= rtl8366rb_get_vlan_mc,
+	.set_vlan_mc	= rtl8366rb_set_vlan_mc,
+	.get_vlan_4k	= rtl8366rb_get_vlan_4k,
+	.set_vlan_4k	= rtl8366rb_set_vlan_4k,
+	.get_mc_index	= rtl8366rb_get_mc_index,
+	.set_mc_index	= rtl8366rb_set_mc_index,
+	.get_mib_counter = rtl8366rb_get_mib_counter,
+	.is_vlan_valid	= rtl8366rb_is_vlan_valid,
+	.enable_vlan	= rtl8366rb_enable_vlan,
+	.enable_vlan4k	= rtl8366rb_enable_vlan4k,
+	.enable_port	= rtl8366rb_enable_port,
+};
+
+static int rtl8366rb_probe(struct platform_device *pdev)
+{
+	static int rtl8366_smi_version_printed;
+	struct rtl8366_smi *smi;
+	int err;
+
+	if (!rtl8366_smi_version_printed++)
+		printk(KERN_NOTICE RTL8366RB_DRIVER_DESC
+		       " version " RTL8366RB_DRIVER_VER"\n");
+
+	smi = rtl8366_smi_probe(pdev);
+	if (IS_ERR(smi))
+		return PTR_ERR(smi);
+
+	smi->clk_delay = 10;
+	smi->cmd_read = 0xa9;
+	smi->cmd_write = 0xa8;
+	smi->ops = &rtl8366rb_smi_ops;
+	smi->cpu_port = RTL8366RB_PORT_NUM_CPU;
+	smi->num_ports = RTL8366RB_NUM_PORTS;
+	smi->num_vlan_mc = RTL8366RB_NUM_VLANS;
+	smi->mib_counters = rtl8366rb_mib_counters;
+	smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters);
+
+	err = rtl8366_smi_init(smi);
+	if (err)
+		goto err_free_smi;
+
+	platform_set_drvdata(pdev, smi);
+
+	err = rtl8366rb_switch_init(smi);
+	if (err)
+		goto err_clear_drvdata;
+
+	return 0;
+
+ err_clear_drvdata:
+	platform_set_drvdata(pdev, NULL);
+	rtl8366_smi_cleanup(smi);
+ err_free_smi:
+	kfree(smi);
+	return err;
+}
+
+static int rtl8366rb_remove(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi = platform_get_drvdata(pdev);
+
+	if (smi) {
+		rtl8366rb_switch_cleanup(smi);
+		platform_set_drvdata(pdev, NULL);
+		rtl8366_smi_cleanup(smi);
+		kfree(smi);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id rtl8366rb_match[] = {
+	{ .compatible = "realtek,rtl8366rb" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtl8366rb_match);
+#endif
+
+static struct platform_driver rtl8366rb_driver = {
+	.driver = {
+		.name		= RTL8366RB_DRIVER_NAME,
+		.owner		= THIS_MODULE,
+		.of_match_table = of_match_ptr(rtl8366rb_match),
+	},
+	.probe		= rtl8366rb_probe,
+	.remove		= rtl8366rb_remove,
+};
+
+static int __init rtl8366rb_module_init(void)
+{
+	return platform_driver_register(&rtl8366rb_driver);
+}
+module_init(rtl8366rb_module_init);
+
+static void __exit rtl8366rb_module_exit(void)
+{
+	platform_driver_unregister(&rtl8366rb_driver);
+}
+module_exit(rtl8366rb_module_exit);
+
+MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC);
+MODULE_VERSION(RTL8366RB_DRIVER_VER);
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>");
+MODULE_AUTHOR("Colin Leitner <colin.leitner@googlemail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME);
diff -Naur linux-orig/drivers/net/phy/rtl8366s.c linux/drivers/net/phy/rtl8366s.c
--- linux-orig/drivers/net/phy/rtl8366s.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8366s.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1320 @@
+/*
+ * Platform driver for the Realtek RTL8366S ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#include "rtl8366_smi.h"
+
+#define RTL8366S_DRIVER_DESC	"Realtek RTL8366S ethernet switch driver"
+#define RTL8366S_DRIVER_VER	"0.2.2"
+
+#define RTL8366S_PHY_NO_MAX	4
+#define RTL8366S_PHY_PAGE_MAX	7
+#define RTL8366S_PHY_ADDR_MAX	31
+
+/* Switch Global Configuration register */
+#define RTL8366S_SGCR				0x0000
+#define RTL8366S_SGCR_EN_BC_STORM_CTRL		BIT(0)
+#define RTL8366S_SGCR_MAX_LENGTH(_x)		(_x << 4)
+#define RTL8366S_SGCR_MAX_LENGTH_MASK		RTL8366S_SGCR_MAX_LENGTH(0x3)
+#define RTL8366S_SGCR_MAX_LENGTH_1522		RTL8366S_SGCR_MAX_LENGTH(0x0)
+#define RTL8366S_SGCR_MAX_LENGTH_1536		RTL8366S_SGCR_MAX_LENGTH(0x1)
+#define RTL8366S_SGCR_MAX_LENGTH_1552		RTL8366S_SGCR_MAX_LENGTH(0x2)
+#define RTL8366S_SGCR_MAX_LENGTH_16000		RTL8366S_SGCR_MAX_LENGTH(0x3)
+#define RTL8366S_SGCR_EN_VLAN			BIT(13)
+
+/* Port Enable Control register */
+#define RTL8366S_PECR				0x0001
+
+/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */
+#define RTL8366S_GREEN_ETHERNET_CTRL_REG	0x000a
+#define RTL8366S_GREEN_ETHERNET_CTRL_MASK	0x0018
+#define RTL8366S_GREEN_ETHERNET_TX_BIT		(1 << 3)
+#define RTL8366S_GREEN_ETHERNET_RX_BIT		(1 << 4)
+
+/* Switch Security Control registers */
+#define RTL8366S_SSCR0				0x0002
+#define RTL8366S_SSCR1				0x0003
+#define RTL8366S_SSCR2				0x0004
+#define RTL8366S_SSCR2_DROP_UNKNOWN_DA		BIT(0)
+
+#define RTL8366S_RESET_CTRL_REG			0x0100
+#define RTL8366S_CHIP_CTRL_RESET_HW		1
+#define RTL8366S_CHIP_CTRL_RESET_SW		(1 << 1)
+
+#define RTL8366S_CHIP_VERSION_CTRL_REG		0x0104
+#define RTL8366S_CHIP_VERSION_MASK		0xf
+#define RTL8366S_CHIP_ID_REG			0x0105
+#define RTL8366S_CHIP_ID_8366			0x8366
+
+/* PHY registers control */
+#define RTL8366S_PHY_ACCESS_CTRL_REG		0x8028
+#define RTL8366S_PHY_ACCESS_DATA_REG		0x8029
+
+#define RTL8366S_PHY_CTRL_READ			1
+#define RTL8366S_PHY_CTRL_WRITE			0
+
+#define RTL8366S_PHY_REG_MASK			0x1f
+#define RTL8366S_PHY_PAGE_OFFSET		5
+#define RTL8366S_PHY_PAGE_MASK			(0x7 << 5)
+#define RTL8366S_PHY_NO_OFFSET			9
+#define RTL8366S_PHY_NO_MASK			(0x1f << 9)
+
+/* Green Ethernet Feature for PHY ports */
+#define RTL8366S_PHY_POWER_SAVING_CTRL_REG	12
+#define RTL8366S_PHY_POWER_SAVING_MASK		0x1000
+
+/* LED control registers */
+#define RTL8366S_LED_BLINKRATE_REG		0x0420
+#define RTL8366S_LED_BLINKRATE_BIT		0
+#define RTL8366S_LED_BLINKRATE_MASK		0x0007
+
+#define RTL8366S_LED_CTRL_REG			0x0421
+#define RTL8366S_LED_0_1_CTRL_REG		0x0422
+#define RTL8366S_LED_2_3_CTRL_REG		0x0423
+
+#define RTL8366S_MIB_COUNT			33
+#define RTL8366S_GLOBAL_MIB_COUNT		1
+#define RTL8366S_MIB_COUNTER_PORT_OFFSET	0x0040
+#define RTL8366S_MIB_COUNTER_BASE		0x1000
+#define RTL8366S_MIB_COUNTER_PORT_OFFSET2	0x0008
+#define RTL8366S_MIB_COUNTER_BASE2		0x1180
+#define RTL8366S_MIB_CTRL_REG			0x11F0
+#define RTL8366S_MIB_CTRL_USER_MASK		0x01FF
+#define RTL8366S_MIB_CTRL_BUSY_MASK		0x0001
+#define RTL8366S_MIB_CTRL_RESET_MASK		0x0002
+
+#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK	0x0004
+#define RTL8366S_MIB_CTRL_PORT_RESET_BIT	0x0003
+#define RTL8366S_MIB_CTRL_PORT_RESET_MASK	0x01FC
+
+
+#define RTL8366S_PORT_VLAN_CTRL_BASE		0x0058
+#define RTL8366S_PORT_VLAN_CTRL_REG(_p)  \
+		(RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4)
+#define RTL8366S_PORT_VLAN_CTRL_MASK		0xf
+#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p)	(4 * ((_p) % 4))
+
+
+#define RTL8366S_VLAN_TABLE_READ_BASE		0x018B
+#define RTL8366S_VLAN_TABLE_WRITE_BASE		0x0185
+
+#define RTL8366S_VLAN_TB_CTRL_REG		0x010F
+
+#define RTL8366S_TABLE_ACCESS_CTRL_REG		0x0180
+#define RTL8366S_TABLE_VLAN_READ_CTRL		0x0E01
+#define RTL8366S_TABLE_VLAN_WRITE_CTRL		0x0F01
+
+#define RTL8366S_VLAN_MC_BASE(_x)		(0x0016 + (_x) * 2)
+
+#define RTL8366S_VLAN_MEMBERINGRESS_REG		0x0379
+
+#define RTL8366S_PORT_LINK_STATUS_BASE		0x0060
+#define RTL8366S_PORT_STATUS_SPEED_MASK		0x0003
+#define RTL8366S_PORT_STATUS_DUPLEX_MASK	0x0004
+#define RTL8366S_PORT_STATUS_LINK_MASK		0x0010
+#define RTL8366S_PORT_STATUS_TXPAUSE_MASK	0x0020
+#define RTL8366S_PORT_STATUS_RXPAUSE_MASK	0x0040
+#define RTL8366S_PORT_STATUS_AN_MASK		0x0080
+
+
+#define RTL8366S_PORT_NUM_CPU		5
+#define RTL8366S_NUM_PORTS		6
+#define RTL8366S_NUM_VLANS		16
+#define RTL8366S_NUM_LEDGROUPS		4
+#define RTL8366S_NUM_VIDS		4096
+#define RTL8366S_PRIORITYMAX		7
+#define RTL8366S_FIDMAX			7
+
+
+#define RTL8366S_PORT_1			(1 << 0) /* In userspace port 0 */
+#define RTL8366S_PORT_2			(1 << 1) /* In userspace port 1 */
+#define RTL8366S_PORT_3			(1 << 2) /* In userspace port 2 */
+#define RTL8366S_PORT_4			(1 << 3) /* In userspace port 3 */
+
+#define RTL8366S_PORT_UNKNOWN		(1 << 4) /* No known connection */
+#define RTL8366S_PORT_CPU		(1 << 5) /* CPU port */
+
+#define RTL8366S_PORT_ALL		(RTL8366S_PORT_1 |	\
+					 RTL8366S_PORT_2 |	\
+					 RTL8366S_PORT_3 |	\
+					 RTL8366S_PORT_4 |	\
+					 RTL8366S_PORT_UNKNOWN | \
+					 RTL8366S_PORT_CPU)
+
+#define RTL8366S_PORT_ALL_BUT_CPU	(RTL8366S_PORT_1 |	\
+					 RTL8366S_PORT_2 |	\
+					 RTL8366S_PORT_3 |	\
+					 RTL8366S_PORT_4 |	\
+					 RTL8366S_PORT_UNKNOWN)
+
+#define RTL8366S_PORT_ALL_EXTERNAL	(RTL8366S_PORT_1 |	\
+					 RTL8366S_PORT_2 |	\
+					 RTL8366S_PORT_3 |	\
+					 RTL8366S_PORT_4)
+
+#define RTL8366S_PORT_ALL_INTERNAL	(RTL8366S_PORT_UNKNOWN | \
+					 RTL8366S_PORT_CPU)
+
+#define RTL8366S_VLAN_VID_MASK		0xfff
+#define RTL8366S_VLAN_PRIORITY_SHIFT	12
+#define RTL8366S_VLAN_PRIORITY_MASK	0x7
+#define RTL8366S_VLAN_MEMBER_MASK	0x3f
+#define RTL8366S_VLAN_UNTAG_SHIFT	6
+#define RTL8366S_VLAN_UNTAG_MASK	0x3f
+#define RTL8366S_VLAN_FID_SHIFT		12
+#define RTL8366S_VLAN_FID_MASK		0x7
+
+#define RTL8366S_MIB_RXB_ID		0	/* IfInOctets */
+#define RTL8366S_MIB_TXB_ID		20	/* IfOutOctets */
+
+static struct rtl8366_mib_counter rtl8366s_mib_counters[] = {
+	{ 0,  0, 4, "IfInOctets"				},
+	{ 0,  4, 4, "EtherStatsOctets"				},
+	{ 0,  8, 2, "EtherStatsUnderSizePkts"			},
+	{ 0, 10, 2, "EtherFragments"				},
+	{ 0, 12, 2, "EtherStatsPkts64Octets"			},
+	{ 0, 14, 2, "EtherStatsPkts65to127Octets"		},
+	{ 0, 16, 2, "EtherStatsPkts128to255Octets"		},
+	{ 0, 18, 2, "EtherStatsPkts256to511Octets"		},
+	{ 0, 20, 2, "EtherStatsPkts512to1023Octets"		},
+	{ 0, 22, 2, "EtherStatsPkts1024to1518Octets"		},
+	{ 0, 24, 2, "EtherOversizeStats"			},
+	{ 0, 26, 2, "EtherStatsJabbers"				},
+	{ 0, 28, 2, "IfInUcastPkts"				},
+	{ 0, 30, 2, "EtherStatsMulticastPkts"			},
+	{ 0, 32, 2, "EtherStatsBroadcastPkts"			},
+	{ 0, 34, 2, "EtherStatsDropEvents"			},
+	{ 0, 36, 2, "Dot3StatsFCSErrors"			},
+	{ 0, 38, 2, "Dot3StatsSymbolErrors"			},
+	{ 0, 40, 2, "Dot3InPauseFrames"				},
+	{ 0, 42, 2, "Dot3ControlInUnknownOpcodes"		},
+	{ 0, 44, 4, "IfOutOctets"				},
+	{ 0, 48, 2, "Dot3StatsSingleCollisionFrames"		},
+	{ 0, 50, 2, "Dot3StatMultipleCollisionFrames"		},
+	{ 0, 52, 2, "Dot3sDeferredTransmissions"		},
+	{ 0, 54, 2, "Dot3StatsLateCollisions"			},
+	{ 0, 56, 2, "EtherStatsCollisions"			},
+	{ 0, 58, 2, "Dot3StatsExcessiveCollisions"		},
+	{ 0, 60, 2, "Dot3OutPauseFrames"			},
+	{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards"	},
+
+	/*
+	 * The following counters are accessible at a different
+	 * base address.
+	 */
+	{ 1,  0, 2, "Dot1dTpPortInDiscards"			},
+	{ 1,  2, 2, "IfOutUcastPkts"				},
+	{ 1,  4, 2, "IfOutMulticastPkts"			},
+	{ 1,  6, 2, "IfOutBroadcastPkts"			},
+};
+
+#define REG_WR(_smi, _reg, _val)					\
+	do {								\
+		err = rtl8366_smi_write_reg(_smi, _reg, _val);		\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+#define REG_RMW(_smi, _reg, _mask, _val)				\
+	do {								\
+		err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val);	\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+static int rtl8366s_reset_chip(struct rtl8366_smi *smi)
+{
+	int timeout = 10;
+	u32 data;
+
+	rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG,
+				    RTL8366S_CHIP_CTRL_RESET_HW);
+	do {
+		msleep(1);
+		if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data))
+			return -EIO;
+
+		if (!(data & RTL8366S_CHIP_CTRL_RESET_HW))
+			break;
+	} while (--timeout);
+
+	if (!timeout) {
+		printk("Timeout waiting for the switch to reset\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi,
+				 u32 phy_no, u32 page, u32 addr, u32 *data)
+{
+	u32 reg;
+	int ret;
+
+	if (phy_no > RTL8366S_PHY_NO_MAX)
+		return -EINVAL;
+
+	if (page > RTL8366S_PHY_PAGE_MAX)
+		return -EINVAL;
+
+	if (addr > RTL8366S_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG,
+				    RTL8366S_PHY_CTRL_READ);
+	if (ret)
+		return ret;
+
+	reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) |
+	      ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) |
+	      (addr & RTL8366S_PHY_REG_MASK);
+
+	ret = rtl8366_smi_write_reg(smi, reg, 0);
+	if (ret)
+		return ret;
+
+	ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi,
+				  u32 phy_no, u32 page, u32 addr, u32 data)
+{
+	u32 reg;
+	int ret;
+
+	if (phy_no > RTL8366S_PHY_NO_MAX)
+		return -EINVAL;
+
+	if (page > RTL8366S_PHY_PAGE_MAX)
+		return -EINVAL;
+
+	if (addr > RTL8366S_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG,
+				    RTL8366S_PHY_CTRL_WRITE);
+	if (ret)
+		return ret;
+
+	reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) |
+	      ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) |
+	      (addr & RTL8366S_PHY_REG_MASK);
+
+	ret = rtl8366_smi_write_reg(smi, reg, data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8366s_set_green_port(struct rtl8366_smi *smi, int port, int enable)
+{
+	int err;
+	u32 phyData;
+
+	if (port >= RTL8366S_NUM_PORTS)
+		return -EINVAL;
+
+	err = rtl8366s_read_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData);
+	if (err)
+		return err;
+
+	if (enable)
+		phyData |= RTL8366S_PHY_POWER_SAVING_MASK;
+	else
+		phyData &= ~RTL8366S_PHY_POWER_SAVING_MASK;
+
+	err = rtl8366s_write_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, phyData);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int rtl8366s_set_green(struct rtl8366_smi *smi, int enable)
+{
+	int err;
+	unsigned i;
+	u32 data = 0;
+
+	if (!enable) {
+		for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) {
+			rtl8366s_set_green_port(smi, i, 0);
+		}
+	}
+
+	if (enable)
+		data = (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT);
+
+	REG_RMW(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, RTL8366S_GREEN_ETHERNET_CTRL_MASK, data);
+
+	return 0;
+}
+
+static int rtl8366s_setup(struct rtl8366_smi *smi)
+{
+	struct rtl8366_platform_data *pdata;
+	int err;
+	unsigned i;
+#ifdef CONFIG_OF
+	struct device_node *np;
+	unsigned num_initvals;
+	const __be32 *paddr;
+#endif
+
+	pdata = smi->parent->platform_data;
+	if (pdata && pdata->num_initvals && pdata->initvals) {
+		dev_info(smi->parent, "applying initvals\n");
+		for (i = 0; i < pdata->num_initvals; i++)
+			REG_WR(smi, pdata->initvals[i].reg,
+			       pdata->initvals[i].val);
+	}
+
+#ifdef CONFIG_OF
+	np = smi->parent->of_node;
+
+	paddr = of_get_property(np, "realtek,initvals", &num_initvals);
+	if (paddr) {
+		dev_info(smi->parent, "applying initvals from DTS\n");
+
+		if (num_initvals < (2 * sizeof(*paddr)))
+			return -EINVAL;
+
+		num_initvals /= sizeof(*paddr);
+
+		for (i = 0; i < num_initvals - 1; i += 2) {
+			u32 reg = be32_to_cpup(paddr + i);
+			u32 val = be32_to_cpup(paddr + i + 1);
+
+			REG_WR(smi, reg, val);
+		}
+	}
+
+	if (of_property_read_bool(np, "realtek,green-ethernet-features")) {
+		dev_info(smi->parent, "activating Green Ethernet features\n");
+
+		err = rtl8366s_set_green(smi, 1);
+		if (err)
+			return err;
+
+		for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) {
+			err = rtl8366s_set_green_port(smi, i, 1);
+			if (err)
+				return err;
+		}
+	}
+#endif
+
+	/* set maximum packet length to 1536 bytes */
+	REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK,
+		RTL8366S_SGCR_MAX_LENGTH_1536);
+
+	/* enable learning for all ports */
+	REG_WR(smi, RTL8366S_SSCR0, 0);
+
+	/* enable auto ageing for all ports */
+	REG_WR(smi, RTL8366S_SSCR1, 0);
+
+	/*
+	 * discard VLAN tagged packets if the port is not a member of
+	 * the VLAN with which the packets is associated.
+	 */
+	REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL);
+
+	/* don't drop packets whose DA has not been learned */
+	REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0);
+
+	return 0;
+}
+
+static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter,
+				   int port, unsigned long long *val)
+{
+	int i;
+	int err;
+	u32 addr, data;
+	u64 mibvalue;
+
+	if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT)
+		return -EINVAL;
+
+	switch (rtl8366s_mib_counters[counter].base) {
+	case 0:
+		addr = RTL8366S_MIB_COUNTER_BASE +
+		       RTL8366S_MIB_COUNTER_PORT_OFFSET * port;
+		break;
+
+	case 1:
+		addr = RTL8366S_MIB_COUNTER_BASE2 +
+			RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	addr += rtl8366s_mib_counters[counter].offset;
+
+	/*
+	 * Writing access counter address first
+	 * then ASIC will prepare 64bits counter wait for being retrived
+	 */
+	data = 0; /* writing data will be discard by ASIC */
+	err = rtl8366_smi_write_reg(smi, addr, data);
+	if (err)
+		return err;
+
+	/* read MIB control register */
+	err =  rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data);
+	if (err)
+		return err;
+
+	if (data & RTL8366S_MIB_CTRL_BUSY_MASK)
+		return -EBUSY;
+
+	if (data & RTL8366S_MIB_CTRL_RESET_MASK)
+		return -EIO;
+
+	mibvalue = 0;
+	for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) {
+		err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data);
+		if (err)
+			return err;
+
+		mibvalue = (mibvalue << 16) | (data & 0xFFFF);
+	}
+
+	*val = mibvalue;
+	return 0;
+}
+
+static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid,
+				struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[2];
+	int err;
+	int i;
+
+	memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k));
+
+	if (vid >= RTL8366S_NUM_VIDS)
+		return -EINVAL;
+
+	/* write VID */
+	err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE,
+				    vid & RTL8366S_VLAN_VID_MASK);
+	if (err)
+		return err;
+
+	/* write table access control word */
+	err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG,
+				    RTL8366S_TABLE_VLAN_READ_CTRL);
+	if (err)
+		return err;
+
+	for (i = 0; i < 2; i++) {
+		err = rtl8366_smi_read_reg(smi,
+					   RTL8366S_VLAN_TABLE_READ_BASE + i,
+					   &data[i]);
+		if (err)
+			return err;
+	}
+
+	vlan4k->vid = vid;
+	vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) &
+			RTL8366S_VLAN_UNTAG_MASK;
+	vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK;
+	vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) &
+			RTL8366S_VLAN_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi,
+				const struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[2];
+	int err;
+	int i;
+
+	if (vlan4k->vid >= RTL8366S_NUM_VIDS ||
+	    vlan4k->member > RTL8366S_VLAN_MEMBER_MASK ||
+	    vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK ||
+	    vlan4k->fid > RTL8366S_FIDMAX)
+		return -EINVAL;
+
+	data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK;
+	data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) |
+		  ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) <<
+			RTL8366S_VLAN_UNTAG_SHIFT) |
+		  ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) <<
+			RTL8366S_VLAN_FID_SHIFT);
+
+	for (i = 0; i < 2; i++) {
+		err = rtl8366_smi_write_reg(smi,
+					    RTL8366S_VLAN_TABLE_WRITE_BASE + i,
+					    data[i]);
+		if (err)
+			return err;
+	}
+
+	/* write table access control word */
+	err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG,
+				    RTL8366S_TABLE_VLAN_WRITE_CTRL);
+
+	return err;
+}
+
+static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[2];
+	int err;
+	int i;
+
+	memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc));
+
+	if (index >= RTL8366S_NUM_VLANS)
+		return -EINVAL;
+
+	for (i = 0; i < 2; i++) {
+		err = rtl8366_smi_read_reg(smi,
+					   RTL8366S_VLAN_MC_BASE(index) + i,
+					   &data[i]);
+		if (err)
+			return err;
+	}
+
+	vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK;
+	vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) &
+			   RTL8366S_VLAN_PRIORITY_MASK;
+	vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) &
+			RTL8366S_VLAN_UNTAG_MASK;
+	vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK;
+	vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) &
+		      RTL8366S_VLAN_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				const struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[2];
+	int err;
+	int i;
+
+	if (index >= RTL8366S_NUM_VLANS ||
+	    vlanmc->vid >= RTL8366S_NUM_VIDS ||
+	    vlanmc->priority > RTL8366S_PRIORITYMAX ||
+	    vlanmc->member > RTL8366S_VLAN_MEMBER_MASK ||
+	    vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK ||
+	    vlanmc->fid > RTL8366S_FIDMAX)
+		return -EINVAL;
+
+	data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) |
+		  ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) <<
+			RTL8366S_VLAN_PRIORITY_SHIFT);
+	data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) |
+		  ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) <<
+			RTL8366S_VLAN_UNTAG_SHIFT) |
+		  ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) <<
+			RTL8366S_VLAN_FID_SHIFT);
+
+	for (i = 0; i < 2; i++) {
+		err = rtl8366_smi_write_reg(smi,
+					    RTL8366S_VLAN_MC_BASE(index) + i,
+					    data[i]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val)
+{
+	u32 data;
+	int err;
+
+	if (port >= RTL8366S_NUM_PORTS)
+		return -EINVAL;
+
+	err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port),
+				   &data);
+	if (err)
+		return err;
+
+	*val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) &
+	       RTL8366S_PORT_VLAN_CTRL_MASK;
+
+	return 0;
+}
+
+static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index)
+{
+	if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port),
+				RTL8366S_PORT_VLAN_CTRL_MASK <<
+					RTL8366S_PORT_VLAN_CTRL_SHIFT(port),
+				(index & RTL8366S_PORT_VLAN_CTRL_MASK) <<
+					RTL8366S_PORT_VLAN_CTRL_SHIFT(port));
+}
+
+static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN,
+				(enable) ? RTL8366S_SGCR_EN_VLAN : 0);
+}
+
+static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG,
+				1, (enable) ? 1 : 0);
+}
+
+static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan)
+{
+	unsigned max = RTL8366S_NUM_VLANS;
+
+	if (smi->vlan4k_enabled)
+		max = RTL8366S_NUM_VIDS - 1;
+
+	if (vlan == 0 || vlan >= max)
+		return 0;
+
+	return 1;
+}
+
+static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port),
+				(enable) ? 0 : (1 << port));
+}
+
+static int rtl8366s_sw_reset_mibs(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2));
+}
+
+static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev,
+				     const struct switch_attr *attr,
+				     struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data);
+
+	val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK));
+
+	return 0;
+}
+
+static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (val->value.i >= 6)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG,
+				RTL8366S_LED_BLINKRATE_MASK,
+				val->value.i);
+}
+
+static int rtl8366s_sw_get_max_length(struct switch_dev *dev,
+					const struct switch_attr *attr,
+					struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data);
+
+	val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4);
+
+	return 0;
+}
+
+static int rtl8366s_sw_set_max_length(struct switch_dev *dev,
+					const struct switch_attr *attr,
+					struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	char length_code;
+
+	switch (val->value.i) {
+		case 0:
+			length_code = RTL8366S_SGCR_MAX_LENGTH_1522;
+			break;
+		case 1:
+			length_code = RTL8366S_SGCR_MAX_LENGTH_1536;
+			break;
+		case 2:
+			length_code = RTL8366S_SGCR_MAX_LENGTH_1552;
+			break;
+		case 3:
+			length_code = RTL8366S_SGCR_MAX_LENGTH_16000;
+			break;
+		default:
+			return -EINVAL;
+	}
+
+	return rtl8366_smi_rmwr(smi, RTL8366S_SGCR,
+			RTL8366S_SGCR_MAX_LENGTH_MASK,
+			length_code);
+}
+
+static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev,
+					   const struct switch_attr *attr,
+					   struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data);
+	val->value.i = !data;
+
+	return 0;
+}
+
+
+static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev,
+					   const struct switch_attr *attr,
+					   struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 portmask = 0;
+	int err = 0;
+
+	if (!val->value.i)
+		portmask = RTL8366S_PORT_ALL;
+
+	/* set learning for all ports */
+	REG_WR(smi, RTL8366S_SSCR0, portmask);
+
+	/* set auto ageing for all ports */
+	REG_WR(smi, RTL8366S_SSCR1, portmask);
+
+	return 0;
+}
+
+static int rtl8366s_sw_get_green(struct switch_dev *dev,
+			      const struct switch_attr *attr,
+			      struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+	int err;
+
+	err = rtl8366_smi_read_reg(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, &data);
+	if (err)
+		return err;
+
+	val->value.i = ((data & (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT)) != 0) ? 1 : 0;
+
+	return 0;
+}
+
+static int rtl8366s_sw_set_green(struct switch_dev *dev,
+				 const struct switch_attr *attr,
+				 struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	return rtl8366s_set_green(smi, val->value.i);
+}
+
+static int rtl8366s_sw_get_port_link(struct switch_dev *dev,
+				     int port,
+				     struct switch_port_link *link)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data = 0;
+	u32 speed;
+
+	if (port >= RTL8366S_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2),
+			     &data);
+
+	if (port % 2)
+		data = data >> 8;
+
+	link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK);
+	if (!link->link)
+		return 0;
+
+	link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK);
+	link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK);
+	link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK);
+	link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK);
+
+	speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK);
+	switch (speed) {
+	case 0:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case 1:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case 2:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	default:
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int rtl8366s_sw_set_port_led(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+	u32 mask;
+	u32 reg;
+
+	if (val->port_vlan >= RTL8366S_NUM_PORTS ||
+	    (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN)
+		return -EINVAL;
+
+	if (val->port_vlan == RTL8366S_PORT_NUM_CPU) {
+		reg = RTL8366S_LED_BLINKRATE_REG;
+		mask = 0xF << 4;
+		data = val->value.i << 4;
+	} else {
+		reg = RTL8366S_LED_CTRL_REG;
+		mask = 0xF << (val->port_vlan * 4),
+		data = val->value.i << (val->port_vlan * 4);
+	}
+
+	return rtl8366_smi_rmwr(smi, reg, mask, data);
+}
+
+static int rtl8366s_sw_get_port_led(struct switch_dev *dev,
+				    const struct switch_attr *attr,
+				    struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data = 0;
+
+	if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data);
+	val->value.i = (data >> (val->port_vlan * 4)) & 0x000F;
+
+	return 0;
+}
+
+static int rtl8366s_sw_get_green_port(struct switch_dev *dev,
+				      const struct switch_attr *attr,
+				      struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int err;
+	u32 phyData;
+
+	if (val->port_vlan >= RTL8366S_NUM_PORTS)
+		return -EINVAL;
+
+	err = rtl8366s_read_phy_reg(smi, val->port_vlan, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData);
+	if (err)
+		return err;
+
+	val->value.i = ((phyData & RTL8366S_PHY_POWER_SAVING_MASK) != 0) ? 1 : 0;
+
+	return 0;
+}
+
+static int rtl8366s_sw_set_green_port(struct switch_dev *dev,
+				      const struct switch_attr *attr,
+				      struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	return rtl8366s_set_green_port(smi, val->port_vlan, val->value.i);
+}
+
+static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	if (val->port_vlan >= RTL8366S_NUM_PORTS)
+		return -EINVAL;
+
+
+	return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG,
+				0, (1 << (val->port_vlan + 3)));
+}
+
+static int rtl8366s_sw_get_port_stats(struct switch_dev *dev, int port,
+                                        struct switch_port_stats *stats)
+{
+	return (rtl8366_sw_get_port_stats(dev, port, stats,
+				RTL8366S_MIB_TXB_ID, RTL8366S_MIB_RXB_ID));
+}
+
+static struct switch_attr rtl8366s_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_learning",
+		.description = "Enable learning, enable aging",
+		.set = rtl8366s_sw_set_learning_enable,
+		.get = rtl8366s_sw_get_learning_enable,
+		.max = 1,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan4k",
+		.description = "Enable VLAN 4K mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 2
+	}, {
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mibs",
+		.description = "Reset all MIB counters",
+		.set = rtl8366s_sw_reset_mibs,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "blinkrate",
+		.description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms,"
+		" 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)",
+		.set = rtl8366s_sw_set_blinkrate,
+		.get = rtl8366s_sw_get_blinkrate,
+		.max = 5
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "max_length",
+		.description = "Get/Set the maximum length of valid packets"
+		" (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))",
+		.set = rtl8366s_sw_set_max_length,
+		.get = rtl8366s_sw_get_max_length,
+		.max = 3,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "green_mode",
+		.description = "Get/Set the router green feature",
+		.set = rtl8366s_sw_set_green,
+		.get = rtl8366s_sw_get_green,
+		.max = 1,
+	},
+};
+
+static struct switch_attr rtl8366s_port[] = {
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mib",
+		.description = "Reset single port MIB counters",
+		.set = rtl8366s_sw_reset_port_mibs,
+	}, {
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get MIB counters for port",
+		.max = 33,
+		.set = NULL,
+		.get = rtl8366_sw_get_port_mib,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "led",
+		.description = "Get/Set port group (0 - 3) led mode (0 - 15)",
+		.max = 15,
+		.set = rtl8366s_sw_set_port_led,
+		.get = rtl8366s_sw_get_port_led,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "green_port",
+		.description = "Get/Set port green feature (0 - 1)",
+		.max = 1,
+		.set = rtl8366s_sw_set_green_port,
+		.get = rtl8366s_sw_get_green_port,
+	},
+};
+
+static struct switch_attr rtl8366s_vlan[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "info",
+		.description = "Get vlan information",
+		.max = 1,
+		.set = NULL,
+		.get = rtl8366_sw_get_vlan_info,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "fid",
+		.description = "Get/Set vlan FID",
+		.max = RTL8366S_FIDMAX,
+		.set = rtl8366_sw_set_vlan_fid,
+		.get = rtl8366_sw_get_vlan_fid,
+	},
+};
+
+static const struct switch_dev_ops rtl8366_ops = {
+	.attr_global = {
+		.attr = rtl8366s_globals,
+		.n_attr = ARRAY_SIZE(rtl8366s_globals),
+	},
+	.attr_port = {
+		.attr = rtl8366s_port,
+		.n_attr = ARRAY_SIZE(rtl8366s_port),
+	},
+	.attr_vlan = {
+		.attr = rtl8366s_vlan,
+		.n_attr = ARRAY_SIZE(rtl8366s_vlan),
+	},
+
+	.get_vlan_ports = rtl8366_sw_get_vlan_ports,
+	.set_vlan_ports = rtl8366_sw_set_vlan_ports,
+	.get_port_pvid = rtl8366_sw_get_port_pvid,
+	.set_port_pvid = rtl8366_sw_set_port_pvid,
+	.reset_switch = rtl8366_sw_reset_switch,
+	.get_port_link = rtl8366s_sw_get_port_link,
+	.get_port_stats = rtl8366s_sw_get_port_stats,
+};
+
+static int rtl8366s_switch_init(struct rtl8366_smi *smi)
+{
+	struct switch_dev *dev = &smi->sw_dev;
+	int err;
+
+	dev->name = "RTL8366S";
+	dev->cpu_port = RTL8366S_PORT_NUM_CPU;
+	dev->ports = RTL8366S_NUM_PORTS;
+	dev->vlans = RTL8366S_NUM_VIDS;
+	dev->ops = &rtl8366_ops;
+	dev->alias = dev_name(smi->parent);
+
+	err = register_switch(dev, NULL);
+	if (err)
+		dev_err(smi->parent, "switch registration failed\n");
+
+	return err;
+}
+
+static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi)
+{
+	unregister_switch(&smi->sw_dev);
+}
+
+static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 val = 0;
+	int err;
+
+	err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val);
+	if (err)
+		return 0xffff;
+
+	return val;
+}
+
+static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 t;
+	int err;
+
+	err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val);
+	/* flush write */
+	(void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t);
+
+	return err;
+}
+
+static int rtl8366s_detect(struct rtl8366_smi *smi)
+{
+	u32 chip_id = 0;
+	u32 chip_ver = 0;
+	int ret;
+
+	ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id);
+	if (ret) {
+		dev_err(smi->parent, "unable to read chip id\n");
+		return ret;
+	}
+
+	switch (chip_id) {
+	case RTL8366S_CHIP_ID_8366:
+		break;
+	default:
+		dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id);
+		return -ENODEV;
+	}
+
+	ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG,
+				   &chip_ver);
+	if (ret) {
+		dev_err(smi->parent, "unable to read chip version\n");
+		return ret;
+	}
+
+	dev_info(smi->parent, "RTL%04x ver. %u chip found\n",
+		 chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK);
+
+	return 0;
+}
+
+static struct rtl8366_smi_ops rtl8366s_smi_ops = {
+	.detect		= rtl8366s_detect,
+	.reset_chip	= rtl8366s_reset_chip,
+	.setup		= rtl8366s_setup,
+
+	.mii_read	= rtl8366s_mii_read,
+	.mii_write	= rtl8366s_mii_write,
+
+	.get_vlan_mc	= rtl8366s_get_vlan_mc,
+	.set_vlan_mc	= rtl8366s_set_vlan_mc,
+	.get_vlan_4k	= rtl8366s_get_vlan_4k,
+	.set_vlan_4k	= rtl8366s_set_vlan_4k,
+	.get_mc_index	= rtl8366s_get_mc_index,
+	.set_mc_index	= rtl8366s_set_mc_index,
+	.get_mib_counter = rtl8366_get_mib_counter,
+	.is_vlan_valid	= rtl8366s_is_vlan_valid,
+	.enable_vlan	= rtl8366s_enable_vlan,
+	.enable_vlan4k	= rtl8366s_enable_vlan4k,
+	.enable_port	= rtl8366s_enable_port,
+};
+
+static int rtl8366s_probe(struct platform_device *pdev)
+{
+	static int rtl8366_smi_version_printed;
+	struct rtl8366_smi *smi;
+	int err;
+
+	if (!rtl8366_smi_version_printed++)
+		printk(KERN_NOTICE RTL8366S_DRIVER_DESC
+		       " version " RTL8366S_DRIVER_VER"\n");
+
+	smi = rtl8366_smi_probe(pdev);
+	if (IS_ERR(smi))
+		return PTR_ERR(smi);
+
+	smi->clk_delay = 10;
+	smi->cmd_read = 0xa9;
+	smi->cmd_write = 0xa8;
+	smi->ops = &rtl8366s_smi_ops;
+	smi->cpu_port = RTL8366S_PORT_NUM_CPU;
+	smi->num_ports = RTL8366S_NUM_PORTS;
+	smi->num_vlan_mc = RTL8366S_NUM_VLANS;
+	smi->mib_counters = rtl8366s_mib_counters;
+	smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters);
+
+	err = rtl8366_smi_init(smi);
+	if (err)
+		goto err_free_smi;
+
+	platform_set_drvdata(pdev, smi);
+
+	err = rtl8366s_switch_init(smi);
+	if (err)
+		goto err_clear_drvdata;
+
+	return 0;
+
+ err_clear_drvdata:
+	platform_set_drvdata(pdev, NULL);
+	rtl8366_smi_cleanup(smi);
+ err_free_smi:
+	kfree(smi);
+	return err;
+}
+
+static int rtl8366s_remove(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi = platform_get_drvdata(pdev);
+
+	if (smi) {
+		rtl8366s_switch_cleanup(smi);
+		platform_set_drvdata(pdev, NULL);
+		rtl8366_smi_cleanup(smi);
+		kfree(smi);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id rtl8366s_match[] = {
+	{ .compatible = "realtek,rtl8366s" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtl8366s_match);
+#endif
+
+static struct platform_driver rtl8366s_driver = {
+	.driver = {
+		.name		= RTL8366S_DRIVER_NAME,
+		.owner		= THIS_MODULE,
+#ifdef CONFIG_OF
+		.of_match_table = of_match_ptr(rtl8366s_match),
+#endif
+	},
+	.probe		= rtl8366s_probe,
+	.remove		= rtl8366s_remove,
+};
+
+static int __init rtl8366s_module_init(void)
+{
+	return platform_driver_register(&rtl8366s_driver);
+}
+module_init(rtl8366s_module_init);
+
+static void __exit rtl8366s_module_exit(void)
+{
+	platform_driver_unregister(&rtl8366s_driver);
+}
+module_exit(rtl8366s_module_exit);
+
+MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC);
+MODULE_VERSION(RTL8366S_DRIVER_VER);
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME);
diff -Naur linux-orig/drivers/net/phy/rtl8367.c linux/drivers/net/phy/rtl8367.c
--- linux-orig/drivers/net/phy/rtl8367.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8367.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1846 @@
+/*
+ * Platform driver for the Realtek RTL8367R/M ethernet switches
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#include "rtl8366_smi.h"
+
+#define RTL8367_RESET_DELAY	1000	/* msecs*/
+
+#define RTL8367_PHY_ADDR_MAX	8
+#define RTL8367_PHY_REG_MAX	31
+
+#define RTL8367_VID_MASK	0xffff
+#define RTL8367_FID_MASK	0xfff
+#define RTL8367_UNTAG_MASK	0xffff
+#define RTL8367_MEMBER_MASK	0xffff
+
+#define RTL8367_PORT_CFG_REG(_p)		(0x000e + 0x20 * (_p))
+#define   RTL8367_PORT_CFG_EGRESS_MODE_SHIFT	4
+#define   RTL8367_PORT_CFG_EGRESS_MODE_MASK	0x3
+#define   RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL	0
+#define   RTL8367_PORT_CFG_EGRESS_MODE_KEEP	1
+#define   RTL8367_PORT_CFG_EGRESS_MODE_PRI	2
+#define   RTL8367_PORT_CFG_EGRESS_MODE_REAL	3
+
+#define RTL8367_BYPASS_LINE_RATE_REG		0x03f7
+
+#define RTL8367_TA_CTRL_REG			0x0500
+#define   RTL8367_TA_CTRL_STATUS		BIT(12)
+#define   RTL8367_TA_CTRL_METHOD		BIT(5)
+#define   RTL8367_TA_CTRL_CMD_SHIFT		4
+#define   RTL8367_TA_CTRL_CMD_READ		0
+#define   RTL8367_TA_CTRL_CMD_WRITE		1
+#define   RTL8367_TA_CTRL_TABLE_SHIFT		0
+#define   RTL8367_TA_CTRL_TABLE_ACLRULE		1
+#define   RTL8367_TA_CTRL_TABLE_ACLACT		2
+#define   RTL8367_TA_CTRL_TABLE_CVLAN		3
+#define   RTL8367_TA_CTRL_TABLE_L2		4
+#define   RTL8367_TA_CTRL_CVLAN_READ \
+		((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \
+		 RTL8367_TA_CTRL_TABLE_CVLAN)
+#define   RTL8367_TA_CTRL_CVLAN_WRITE \
+		((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \
+		 RTL8367_TA_CTRL_TABLE_CVLAN)
+
+#define RTL8367_TA_ADDR_REG			0x0501
+#define   RTL8367_TA_ADDR_MASK			0x3fff
+
+#define RTL8367_TA_DATA_REG(_x)			(0x0503 + (_x))
+#define   RTL8367_TA_VLAN_DATA_SIZE		4
+#define   RTL8367_TA_VLAN_VID_MASK		RTL8367_VID_MASK
+#define   RTL8367_TA_VLAN_MEMBER_SHIFT		0
+#define   RTL8367_TA_VLAN_MEMBER_MASK		RTL8367_MEMBER_MASK
+#define   RTL8367_TA_VLAN_FID_SHIFT		0
+#define   RTL8367_TA_VLAN_FID_MASK		RTL8367_FID_MASK
+#define   RTL8367_TA_VLAN_UNTAG1_SHIFT		14
+#define   RTL8367_TA_VLAN_UNTAG1_MASK		0x3
+#define   RTL8367_TA_VLAN_UNTAG2_SHIFT		0
+#define   RTL8367_TA_VLAN_UNTAG2_MASK		0x3fff
+
+#define RTL8367_VLAN_PVID_CTRL_REG(_p)		(0x0700 + (_p) / 2)
+#define RTL8367_VLAN_PVID_CTRL_MASK		0x1f
+#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p)	(8 * ((_p) % 2))
+
+#define RTL8367_VLAN_MC_BASE(_x)		(0x0728 + (_x) * 4)
+#define   RTL8367_VLAN_MC_DATA_SIZE		4
+#define   RTL8367_VLAN_MC_MEMBER_SHIFT		0
+#define   RTL8367_VLAN_MC_MEMBER_MASK		RTL8367_MEMBER_MASK
+#define   RTL8367_VLAN_MC_FID_SHIFT		0
+#define   RTL8367_VLAN_MC_FID_MASK		RTL8367_FID_MASK
+#define   RTL8367_VLAN_MC_EVID_SHIFT		0
+#define   RTL8367_VLAN_MC_EVID_MASK		RTL8367_VID_MASK
+
+#define RTL8367_VLAN_CTRL_REG			0x07a8
+#define   RTL8367_VLAN_CTRL_ENABLE		BIT(0)
+
+#define RTL8367_VLAN_INGRESS_REG		0x07a9
+
+#define RTL8367_PORT_ISOLATION_REG(_p)		(0x08a2 + (_p))
+
+#define RTL8367_MIB_COUNTER_REG(_x)		(0x1000 + (_x))
+
+#define RTL8367_MIB_ADDRESS_REG			0x1004
+
+#define RTL8367_MIB_CTRL_REG(_x)		(0x1005 + (_x))
+#define   RTL8367_MIB_CTRL_GLOBAL_RESET_MASK	BIT(11)
+#define   RTL8367_MIB_CTRL_QM_RESET_MASK	BIT(10)
+#define   RTL8367_MIB_CTRL_PORT_RESET_MASK(_p)	BIT(2 + (_p))
+#define   RTL8367_MIB_CTRL_RESET_MASK		BIT(1)
+#define   RTL8367_MIB_CTRL_BUSY_MASK		BIT(0)
+
+#define RTL8367_MIB_COUNT			36
+#define RTL8367_MIB_COUNTER_PORT_OFFSET		0x0050
+
+#define RTL8367_SWC0_REG			0x1200
+#define   RTL8367_SWC0_MAX_LENGTH_SHIFT		13
+#define   RTL8367_SWC0_MAX_LENGTH(_x)		((_x) << 13)
+#define   RTL8367_SWC0_MAX_LENGTH_MASK		RTL8367_SWC0_MAX_LENGTH(0x3)
+#define   RTL8367_SWC0_MAX_LENGTH_1522		RTL8367_SWC0_MAX_LENGTH(0)
+#define   RTL8367_SWC0_MAX_LENGTH_1536		RTL8367_SWC0_MAX_LENGTH(1)
+#define   RTL8367_SWC0_MAX_LENGTH_1552		RTL8367_SWC0_MAX_LENGTH(2)
+#define   RTL8367_SWC0_MAX_LENGTH_16000		RTL8367_SWC0_MAX_LENGTH(3)
+
+#define RTL8367_CHIP_NUMBER_REG			0x1300
+
+#define RTL8367_CHIP_VER_REG			0x1301
+#define   RTL8367_CHIP_VER_RLVID_SHIFT		12
+#define   RTL8367_CHIP_VER_RLVID_MASK		0xf
+#define   RTL8367_CHIP_VER_MCID_SHIFT		8
+#define   RTL8367_CHIP_VER_MCID_MASK		0xf
+#define   RTL8367_CHIP_VER_BOID_SHIFT		4
+#define   RTL8367_CHIP_VER_BOID_MASK		0xf
+
+#define RTL8367_CHIP_MODE_REG			0x1302
+#define   RTL8367_CHIP_MODE_MASK		0x7
+
+#define RTL8367_CHIP_DEBUG0_REG			0x1303
+#define   RTL8367_CHIP_DEBUG0_DUMMY0(_x)	BIT(8 + (_x))
+
+#define RTL8367_CHIP_DEBUG1_REG			0x1304
+
+#define RTL8367_DIS_REG				0x1305
+#define   RTL8367_DIS_SKIP_MII_RXER(_x)		BIT(12 + (_x))
+#define   RTL8367_DIS_RGMII_SHIFT(_x)		(4 * (_x))
+#define   RTL8367_DIS_RGMII_MASK		0x7
+
+#define RTL8367_EXT_RGMXF_REG(_x)		(0x1306 + (_x))
+#define   RTL8367_EXT_RGMXF_DUMMY0_SHIFT	5
+#define   RTL8367_EXT_RGMXF_DUMMY0_MASK	0x7ff
+#define   RTL8367_EXT_RGMXF_TXDELAY_SHIFT	3
+#define   RTL8367_EXT_RGMXF_TXDELAY_MASK	1
+#define   RTL8367_EXT_RGMXF_RXDELAY_MASK	0x7
+
+#define RTL8367_DI_FORCE_REG(_x)		(0x1310 + (_x))
+#define   RTL8367_DI_FORCE_MODE			BIT(12)
+#define   RTL8367_DI_FORCE_NWAY			BIT(7)
+#define   RTL8367_DI_FORCE_TXPAUSE		BIT(6)
+#define   RTL8367_DI_FORCE_RXPAUSE		BIT(5)
+#define   RTL8367_DI_FORCE_LINK			BIT(4)
+#define   RTL8367_DI_FORCE_DUPLEX		BIT(2)
+#define   RTL8367_DI_FORCE_SPEED_MASK		3
+#define   RTL8367_DI_FORCE_SPEED_10		0
+#define   RTL8367_DI_FORCE_SPEED_100		1
+#define   RTL8367_DI_FORCE_SPEED_1000		2
+
+#define RTL8367_MAC_FORCE_REG(_x)		(0x1312 + (_x))
+
+#define RTL8367_CHIP_RESET_REG			0x1322
+#define   RTL8367_CHIP_RESET_SW			BIT(1)
+#define   RTL8367_CHIP_RESET_HW			BIT(0)
+
+#define RTL8367_PORT_STATUS_REG(_p)		(0x1352 + (_p))
+#define   RTL8367_PORT_STATUS_NWAY		BIT(7)
+#define   RTL8367_PORT_STATUS_TXPAUSE		BIT(6)
+#define   RTL8367_PORT_STATUS_RXPAUSE		BIT(5)
+#define   RTL8367_PORT_STATUS_LINK		BIT(4)
+#define   RTL8367_PORT_STATUS_DUPLEX		BIT(2)
+#define   RTL8367_PORT_STATUS_SPEED_MASK	0x0003
+#define   RTL8367_PORT_STATUS_SPEED_10		0
+#define   RTL8367_PORT_STATUS_SPEED_100		1
+#define   RTL8367_PORT_STATUS_SPEED_1000	2
+
+#define RTL8367_RTL_NO_REG			0x13c0
+#define   RTL8367_RTL_NO_8367R			0x3670
+#define   RTL8367_RTL_NO_8367M			0x3671
+
+#define RTL8367_RTL_VER_REG			0x13c1
+#define   RTL8367_RTL_VER_MASK			0xf
+
+#define RTL8367_RTL_MAGIC_ID_REG		0x13c2
+#define   RTL8367_RTL_MAGIC_ID_VAL		0x0249
+
+#define RTL8367_LED_SYS_CONFIG_REG		0x1b00
+#define RTL8367_LED_MODE_REG			0x1b02
+#define   RTL8367_LED_MODE_RATE_M		0x7
+#define   RTL8367_LED_MODE_RATE_S		1
+
+#define RTL8367_LED_CONFIG_REG			0x1b03
+#define   RTL8367_LED_CONFIG_DATA_S		12
+#define   RTL8367_LED_CONFIG_DATA_M		0x3
+#define   RTL8367_LED_CONFIG_SEL		BIT(14)
+#define   RTL8367_LED_CONFIG_LED_CFG_M		0xf
+
+#define RTL8367_PARA_LED_IO_EN1_REG		0x1b24
+#define RTL8367_PARA_LED_IO_EN2_REG		0x1b25
+#define   RTL8367_PARA_LED_IO_EN_PMASK		0xff
+
+#define RTL8367_IA_CTRL_REG			0x1f00
+#define   RTL8367_IA_CTRL_RW(_x)		((_x) << 1)
+#define   RTL8367_IA_CTRL_RW_READ		RTL8367_IA_CTRL_RW(0)
+#define   RTL8367_IA_CTRL_RW_WRITE		RTL8367_IA_CTRL_RW(1)
+#define   RTL8367_IA_CTRL_CMD_MASK		BIT(0)
+
+#define RTL8367_IA_STATUS_REG			0x1f01
+#define   RTL8367_IA_STATUS_PHY_BUSY		BIT(2)
+#define   RTL8367_IA_STATUS_SDS_BUSY		BIT(1)
+#define   RTL8367_IA_STATUS_MDX_BUSY		BIT(0)
+
+#define RTL8367_IA_ADDRESS_REG			0x1f02
+
+#define RTL8367_IA_WRITE_DATA_REG		0x1f03
+#define RTL8367_IA_READ_DATA_REG		0x1f04
+
+#define RTL8367_INTERNAL_PHY_REG(_a, _r)	(0x2000 + 32 * (_a) + (_r))
+
+#define RTL8367_CPU_PORT_NUM		9
+#define RTL8367_NUM_PORTS		10
+#define RTL8367_NUM_VLANS		32
+#define RTL8367_NUM_LEDGROUPS		4
+#define RTL8367_NUM_VIDS		4096
+#define RTL8367_PRIORITYMAX		7
+#define RTL8367_FIDMAX			7
+
+#define RTL8367_PORT_0			BIT(0)
+#define RTL8367_PORT_1			BIT(1)
+#define RTL8367_PORT_2			BIT(2)
+#define RTL8367_PORT_3			BIT(3)
+#define RTL8367_PORT_4			BIT(4)
+#define RTL8367_PORT_5			BIT(5)
+#define RTL8367_PORT_6			BIT(6)
+#define RTL8367_PORT_7			BIT(7)
+#define RTL8367_PORT_E1			BIT(8)	/* external port 1 */
+#define RTL8367_PORT_E0			BIT(9)	/* external port 0 */
+
+#define RTL8367_PORTS_ALL					\
+	(RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 |	\
+	 RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 |	\
+	 RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 |	\
+	 RTL8367_PORT_E0)
+
+#define RTL8367_PORTS_ALL_BUT_CPU				\
+	(RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 |	\
+	 RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 |	\
+	 RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1)
+
+struct rtl8367_initval {
+	u16 reg;
+	u16 val;
+};
+
+#define RTL8367_MIB_RXB_ID		0	/* IfInOctets */
+#define RTL8367_MIB_TXB_ID		20	/* IfOutOctets */
+
+static struct rtl8366_mib_counter rtl8367_mib_counters[] = {
+	{ 0,  0, 4, "IfInOctets"				},
+	{ 0,  4, 2, "Dot3StatsFCSErrors"			},
+	{ 0,  6, 2, "Dot3StatsSymbolErrors"			},
+	{ 0,  8, 2, "Dot3InPauseFrames"				},
+	{ 0, 10, 2, "Dot3ControlInUnknownOpcodes"		},
+	{ 0, 12, 2, "EtherStatsFragments"			},
+	{ 0, 14, 2, "EtherStatsJabbers"				},
+	{ 0, 16, 2, "IfInUcastPkts"				},
+	{ 0, 18, 2, "EtherStatsDropEvents"			},
+	{ 0, 20, 4, "EtherStatsOctets"				},
+
+	{ 0, 24, 2, "EtherStatsUnderSizePkts"			},
+	{ 0, 26, 2, "EtherOversizeStats"			},
+	{ 0, 28, 2, "EtherStatsPkts64Octets"			},
+	{ 0, 30, 2, "EtherStatsPkts65to127Octets"		},
+	{ 0, 32, 2, "EtherStatsPkts128to255Octets"		},
+	{ 0, 34, 2, "EtherStatsPkts256to511Octets"		},
+	{ 0, 36, 2, "EtherStatsPkts512to1023Octets"		},
+	{ 0, 38, 2, "EtherStatsPkts1024to1518Octets"		},
+	{ 0, 40, 2, "EtherStatsMulticastPkts"			},
+	{ 0, 42, 2, "EtherStatsBroadcastPkts"			},
+
+	{ 0, 44, 4, "IfOutOctets"				},
+
+	{ 0, 48, 2, "Dot3StatsSingleCollisionFrames"		},
+	{ 0, 50, 2, "Dot3StatMultipleCollisionFrames"		},
+	{ 0, 52, 2, "Dot3sDeferredTransmissions"		},
+	{ 0, 54, 2, "Dot3StatsLateCollisions"			},
+	{ 0, 56, 2, "EtherStatsCollisions"			},
+	{ 0, 58, 2, "Dot3StatsExcessiveCollisions"		},
+	{ 0, 60, 2, "Dot3OutPauseFrames"			},
+	{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards"	},
+	{ 0, 64, 2, "Dot1dTpPortInDiscards"			},
+	{ 0, 66, 2, "IfOutUcastPkts"				},
+	{ 0, 68, 2, "IfOutMulticastPkts"			},
+	{ 0, 70, 2, "IfOutBroadcastPkts"			},
+	{ 0, 72, 2, "OutOampduPkts"				},
+	{ 0, 74, 2, "InOampduPkts"				},
+	{ 0, 76, 2, "PktgenPkts"				},
+};
+
+#define REG_RD(_smi, _reg, _val)					\
+	do {								\
+		err = rtl8366_smi_read_reg(_smi, _reg, _val);		\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+#define REG_WR(_smi, _reg, _val)					\
+	do {								\
+		err = rtl8366_smi_write_reg(_smi, _reg, _val);		\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+#define REG_RMW(_smi, _reg, _mask, _val)				\
+	do {								\
+		err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val);	\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+static const struct rtl8367_initval rtl8367_initvals_0_0[] = {
+	{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006},
+	{0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048},
+	{0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412},
+	{0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0},
+	{0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4},
+	{0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7},
+	{0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003},
+	{0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2},
+	{0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207},
+	{0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620},
+	{0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede},
+	{0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1},
+	{0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00},
+	{0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002},
+	{0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000},
+	{0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f},
+	{0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A},
+	{0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005},
+	{0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA},
+	{0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055},
+	{0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354},
+	{0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB},
+	{0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8},
+	{0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277},
+	{0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277},
+	{0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4},
+	{0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024},
+	{0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8},
+	{0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8},
+	{0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b},
+	{0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b},
+	{0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b},
+	{0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b},
+	{0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000},
+	{0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000},
+	{0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100},
+	{0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096},
+	{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4},
+	{0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340},
+	{0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940},
+};
+
+static const struct rtl8367_initval rtl8367_initvals_0_1[] = {
+	{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006},
+	{0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048},
+	{0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412},
+	{0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0},
+	{0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4},
+	{0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7},
+	{0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003},
+	{0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2},
+	{0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207},
+	{0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620},
+	{0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede},
+	{0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1},
+	{0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00},
+	{0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002},
+	{0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000},
+	{0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f},
+	{0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A},
+	{0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005},
+	{0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA},
+	{0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055},
+	{0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354},
+	{0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB},
+	{0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0},
+	{0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb},
+	{0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb},
+	{0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138},
+	{0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024},
+	{0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0},
+	{0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0},
+	{0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190},
+	{0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190},
+	{0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190},
+	{0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190},
+	{0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000},
+	{0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000},
+	{0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100},
+	{0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096},
+	{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4},
+	{0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340},
+	{0x133f, 0x0010},
+};
+
+static const struct rtl8367_initval rtl8367_initvals_1_0[] = {
+	{0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000},
+	{0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030},
+	{0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82},
+	{0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938},
+	{0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001},
+	{0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007},
+	{0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C},
+	{0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080},
+	{0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0},
+	{0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7},
+	{0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA},
+	{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A},
+	{0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D},
+	{0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806},
+	{0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5},
+	{0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB},
+	{0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0},
+	{0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89},
+	{0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF},
+	{0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640},
+	{0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729},
+	{0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00},
+	{0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B},
+	{0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32},
+	{0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52},
+	{0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C},
+	{0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D},
+	{0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053},
+	{0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B},
+	{0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771},
+	{0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7},
+	{0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A},
+	{0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600},
+	{0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000},
+	{0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65},
+	{0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007},
+	{0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010},
+	{0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000},
+	{0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115},
+	{0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C},
+	{0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4},
+	{0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230},
+	{0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230},
+	{0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4},
+	{0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8},
+	{0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8},
+	{0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8},
+	{0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8},
+	{0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000},
+	{0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000},
+	{0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100},
+	{0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040},
+	{0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040},
+	{0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340},
+	{0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940},
+	{0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050},
+};
+
+static const struct rtl8367_initval rtl8367_initvals_1_1[] = {
+	{0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000},
+	{0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030},
+	{0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82},
+	{0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938},
+	{0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001},
+	{0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007},
+	{0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C},
+	{0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080},
+	{0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0},
+	{0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7},
+	{0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA},
+	{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A},
+	{0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D},
+	{0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806},
+	{0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5},
+	{0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB},
+	{0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0},
+	{0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89},
+	{0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF},
+	{0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640},
+	{0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729},
+	{0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00},
+	{0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B},
+	{0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32},
+	{0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52},
+	{0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C},
+	{0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D},
+	{0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053},
+	{0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B},
+	{0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771},
+	{0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7},
+	{0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A},
+	{0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600},
+	{0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000},
+	{0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65},
+	{0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007},
+	{0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010},
+	{0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000},
+	{0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115},
+	{0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C},
+	{0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4},
+	{0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000},
+	{0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000},
+	{0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040},
+	{0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040},
+	{0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040},
+	{0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000},
+	{0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE},
+	{0x1B03, 0x0876},
+};
+
+static const struct rtl8367_initval rtl8367_initvals_2_0[] = {
+	{0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000},
+	{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048},
+	{0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8},
+	{0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207},
+	{0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000},
+	{0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005},
+	{0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000},
+	{0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7},
+	{0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e},
+	{0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201},
+	{0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e},
+	{0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000},
+	{0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00},
+	{0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6},
+	{0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140},
+	{0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4},
+	{0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa},
+	{0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a},
+	{0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978},
+	{0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806},
+	{0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5},
+	{0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425},
+	{0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e},
+	{0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68},
+	{0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d},
+	{0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365},
+	{0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d},
+	{0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036},
+	{0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce},
+	{0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530},
+	{0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a},
+	{0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100},
+	{0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306},
+	{0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77},
+	{0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f},
+	{0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405},
+	{0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010},
+	{0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16},
+	{0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244},
+	{0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244},
+	{0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0},
+	{0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8},
+	{0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8},
+	{0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8},
+	{0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8},
+	{0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000},
+	{0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00},
+	{0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100},
+	{0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040},
+	{0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940},
+	{0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050},
+};
+
+static const struct rtl8367_initval rtl8367_initvals_2_1[] = {
+	{0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000},
+	{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048},
+	{0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8},
+	{0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207},
+	{0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000},
+	{0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005},
+	{0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000},
+	{0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7},
+	{0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e},
+	{0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201},
+	{0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e},
+	{0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000},
+	{0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00},
+	{0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6},
+	{0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140},
+	{0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4},
+	{0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa},
+	{0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a},
+	{0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978},
+	{0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806},
+	{0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5},
+	{0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425},
+	{0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e},
+	{0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68},
+	{0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d},
+	{0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365},
+	{0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d},
+	{0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036},
+	{0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce},
+	{0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530},
+	{0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a},
+	{0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100},
+	{0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306},
+	{0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77},
+	{0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f},
+	{0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405},
+	{0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010},
+	{0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000},
+	{0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210},
+	{0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000},
+	{0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040},
+	{0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040},
+	{0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040},
+	{0x130c, 0x0050},
+};
+
+static int rtl8367_write_initvals(struct rtl8366_smi *smi,
+				  const struct rtl8367_initval *initvals,
+				  int count)
+{
+	int err;
+	int i;
+
+	for (i = 0; i < count; i++)
+		REG_WR(smi, initvals[i].reg, initvals[i].val);
+
+	return 0;
+}
+
+static int rtl8367_read_phy_reg(struct rtl8366_smi *smi,
+				u32 phy_addr, u32 phy_reg, u32 *val)
+{
+	int timeout;
+	u32 data;
+	int err;
+
+	if (phy_addr > RTL8367_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	if (phy_reg > RTL8367_PHY_REG_MAX)
+		return -EINVAL;
+
+	REG_RD(smi, RTL8367_IA_STATUS_REG, &data);
+	if (data & RTL8367_IA_STATUS_PHY_BUSY)
+		return -ETIMEDOUT;
+
+	/* prepare address */
+	REG_WR(smi, RTL8367_IA_ADDRESS_REG,
+	       RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg));
+
+	/* send read command */
+	REG_WR(smi, RTL8367_IA_CTRL_REG,
+	       RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ);
+
+	timeout = 5;
+	do {
+		REG_RD(smi, RTL8367_IA_STATUS_REG, &data);
+		if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0)
+			break;
+
+		if (timeout--) {
+			dev_err(smi->parent, "phy read timed out\n");
+			return -ETIMEDOUT;
+		}
+
+		udelay(1);
+	} while (1);
+
+	/* read data */
+	REG_RD(smi, RTL8367_IA_READ_DATA_REG, val);
+
+	dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n",
+		phy_addr, phy_reg, *val);
+	return 0;
+}
+
+static int rtl8367_write_phy_reg(struct rtl8366_smi *smi,
+				 u32 phy_addr, u32 phy_reg, u32 val)
+{
+	int timeout;
+	u32 data;
+	int err;
+
+	dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n",
+		phy_addr, phy_reg, val);
+
+	if (phy_addr > RTL8367_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	if (phy_reg > RTL8367_PHY_REG_MAX)
+		return -EINVAL;
+
+	REG_RD(smi, RTL8367_IA_STATUS_REG, &data);
+	if (data & RTL8367_IA_STATUS_PHY_BUSY)
+		return -ETIMEDOUT;
+
+	/* preapre data */
+	REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val);
+
+	/* prepare address */
+	REG_WR(smi, RTL8367_IA_ADDRESS_REG,
+	       RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg));
+
+	/* send write command */
+	REG_WR(smi, RTL8367_IA_CTRL_REG,
+	       RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE);
+
+	timeout = 5;
+	do {
+		REG_RD(smi, RTL8367_IA_STATUS_REG, &data);
+		if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0)
+			break;
+
+		if (timeout--) {
+			dev_err(smi->parent, "phy write timed out\n");
+			return -ETIMEDOUT;
+		}
+
+		udelay(1);
+	} while (1);
+
+	return 0;
+}
+
+static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode)
+{
+	const struct rtl8367_initval *initvals;
+	int count;
+	int err;
+
+	switch (mode) {
+	case 0:
+		initvals = rtl8367_initvals_0_0;
+		count = ARRAY_SIZE(rtl8367_initvals_0_0);
+		break;
+
+	case 1:
+	case 2:
+		initvals = rtl8367_initvals_0_1;
+		count = ARRAY_SIZE(rtl8367_initvals_0_1);
+		break;
+
+	default:
+		dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode);
+		return -ENODEV;
+	}
+
+	err = rtl8367_write_initvals(smi, initvals, count);
+	if (err)
+		return err;
+
+	/* TODO: complete this */
+
+	return 0;
+}
+
+static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode)
+{
+	const struct rtl8367_initval *initvals;
+	int count;
+
+	switch (mode) {
+	case 0:
+		initvals = rtl8367_initvals_1_0;
+		count = ARRAY_SIZE(rtl8367_initvals_1_0);
+		break;
+
+	case 1:
+	case 2:
+		initvals = rtl8367_initvals_1_1;
+		count = ARRAY_SIZE(rtl8367_initvals_1_1);
+		break;
+
+	default:
+		dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode);
+		return -ENODEV;
+	}
+
+	return rtl8367_write_initvals(smi, initvals, count);
+}
+
+static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode)
+{
+	const struct rtl8367_initval *initvals;
+	int count;
+
+	switch (mode) {
+	case 0:
+		initvals = rtl8367_initvals_2_0;
+		count = ARRAY_SIZE(rtl8367_initvals_2_0);
+		break;
+
+	case 1:
+	case 2:
+		initvals = rtl8367_initvals_2_1;
+		count = ARRAY_SIZE(rtl8367_initvals_2_1);
+		break;
+
+	default:
+		dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode);
+		return -ENODEV;
+	}
+
+	return rtl8367_write_initvals(smi, initvals, count);
+}
+
+static int rtl8367_init_regs(struct rtl8366_smi *smi)
+{
+	u32 data;
+	u32 rlvid;
+	u32 mode;
+	int err;
+
+	REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL);
+
+	REG_RD(smi, RTL8367_CHIP_VER_REG, &data);
+	rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) &
+		RTL8367_CHIP_VER_RLVID_MASK;
+
+	REG_RD(smi, RTL8367_CHIP_MODE_REG, &data);
+	mode = data & RTL8367_CHIP_MODE_MASK;
+
+	switch (rlvid) {
+	case 0:
+		err = rtl8367_init_regs0(smi, mode);
+		break;
+
+	case 1:
+		err = rtl8367_write_phy_reg(smi, 0, 31, 5);
+		if (err)
+			break;
+
+		err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe);
+		if (err)
+			break;
+
+		err = rtl8367_read_phy_reg(smi, 0, 6, &data);
+		if (err)
+			break;
+
+		if (data == 0x94eb) {
+			err = rtl8367_init_regs1(smi, mode);
+		} else if (data == 0x2104) {
+			err = rtl8367_init_regs2(smi, mode);
+		} else {
+			dev_err(smi->parent, "unknow phy data %04x\n", data);
+			return -ENODEV;
+		}
+
+		break;
+
+	default:
+		dev_err(smi->parent, "unknow rlvid %u\n", rlvid);
+		err = -ENODEV;
+		break;
+	}
+
+	return err;
+}
+
+static int rtl8367_reset_chip(struct rtl8366_smi *smi)
+{
+	int timeout = 10;
+	int err;
+	u32 data;
+
+	REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW);
+	msleep(RTL8367_RESET_DELAY);
+
+	do {
+		REG_RD(smi, RTL8367_CHIP_RESET_REG, &data);
+		if (!(data & RTL8367_CHIP_RESET_HW))
+			break;
+
+		msleep(1);
+	} while (--timeout);
+
+	if (!timeout) {
+		dev_err(smi->parent, "chip reset timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id,
+				  enum rtl8367_extif_mode mode)
+{
+	int err;
+
+	/* set port mode */
+	switch (mode) {
+	case RTL8367_EXTIF_MODE_RGMII:
+	case RTL8367_EXTIF_MODE_RGMII_33V:
+		REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367);
+		REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777);
+		break;
+
+	case RTL8367_EXTIF_MODE_TMII_MAC:
+	case RTL8367_EXTIF_MODE_TMII_PHY:
+		REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG,
+			BIT((id + 1) % 2), BIT((id + 1) % 2));
+		break;
+
+	case RTL8367_EXTIF_MODE_GMII:
+		REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG,
+		        RTL8367_CHIP_DEBUG0_DUMMY0(id),
+			RTL8367_CHIP_DEBUG0_DUMMY0(id));
+		REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6));
+		break;
+
+	case RTL8367_EXTIF_MODE_MII_MAC:
+	case RTL8367_EXTIF_MODE_MII_PHY:
+	case RTL8367_EXTIF_MODE_DISABLED:
+		REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG,
+			BIT((id + 1) % 2), 0);
+		REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0);
+		break;
+
+	default:
+		dev_err(smi->parent,
+			"invalid mode for external interface %d\n", id);
+		return -EINVAL;
+	}
+
+	REG_RMW(smi, RTL8367_DIS_REG,
+		RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id),
+		mode << RTL8367_DIS_RGMII_SHIFT(id));
+
+	return 0;
+}
+
+static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id,
+				   struct rtl8367_port_ability *pa)
+{
+	u32 mask;
+	u32 val;
+	int err;
+
+	mask = (RTL8367_DI_FORCE_MODE |
+		RTL8367_DI_FORCE_NWAY |
+		RTL8367_DI_FORCE_TXPAUSE |
+		RTL8367_DI_FORCE_RXPAUSE |
+		RTL8367_DI_FORCE_LINK |
+		RTL8367_DI_FORCE_DUPLEX |
+		RTL8367_DI_FORCE_SPEED_MASK);
+
+	val = pa->speed;
+	val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0;
+	val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0;
+	val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0;
+	val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0;
+	val |= pa->link ? RTL8367_DI_FORCE_LINK : 0;
+	val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0;
+
+	REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val);
+
+	return 0;
+}
+
+static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id,
+					 unsigned txdelay, unsigned rxdelay)
+{
+	u32 mask;
+	u32 val;
+	int err;
+
+	mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK |
+		(RTL8367_EXT_RGMXF_TXDELAY_MASK <<
+			RTL8367_EXT_RGMXF_TXDELAY_SHIFT));
+
+	val = rxdelay;
+	val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT;
+
+	REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val);
+
+	return 0;
+}
+
+static int rtl8367_extif_init(struct rtl8366_smi *smi, int id,
+			      struct rtl8367_extif_config *cfg)
+{
+	enum rtl8367_extif_mode mode;
+	int err;
+
+	mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED;
+
+	err = rtl8367_extif_set_mode(smi, id, mode);
+	if (err)
+		return err;
+
+	if (mode != RTL8367_EXTIF_MODE_DISABLED) {
+		err = rtl8367_extif_set_force(smi, id, &cfg->ability);
+		if (err)
+			return err;
+
+		err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay,
+						     cfg->rxdelay);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi,
+				       unsigned int group, u16 port_mask)
+{
+	u32 reg;
+	u32 s;
+	int err;
+
+	port_mask &= RTL8367_PARA_LED_IO_EN_PMASK;
+	s = (group % 2) * 8;
+	reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2);
+
+	REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s);
+
+	return 0;
+}
+
+static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi,
+				      unsigned int mode)
+{
+	u16 mask;
+	u16 set;
+	int err;
+
+	mode &= RTL8367_LED_CONFIG_DATA_M;
+
+	mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) |
+		RTL8367_LED_CONFIG_SEL;
+	set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL;
+
+	REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set);
+
+	return 0;
+}
+
+static int rtl8367_led_group_set_config(struct rtl8366_smi *smi,
+				        unsigned int led, unsigned int cfg)
+{
+	u16 mask;
+	u16 set;
+	int err;
+
+	mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) |
+		RTL8367_LED_CONFIG_SEL;
+	set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4);
+
+	REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set);
+	return 0;
+}
+
+static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi)
+{
+	int err;
+
+	REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472);
+	return 0;
+}
+
+static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate)
+{
+	u16 mask;
+	u16 set;
+	int err;
+
+	mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S;
+	set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S;
+	REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id,
+				 const char *name)
+{
+	struct rtl8367_extif_config *cfg;
+	const __be32 *prop;
+	int size;
+	int err;
+
+	prop = of_get_property(smi->parent->of_node, name, &size);
+	if (!prop)
+		return rtl8367_extif_init(smi, id, NULL);
+
+	if (size != (9 * sizeof(*prop))) {
+		dev_err(smi->parent, "%s property is invalid\n", name);
+		return -EINVAL;
+	}
+
+	cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL);
+	if (!cfg)
+		return -ENOMEM;
+
+	cfg->txdelay = be32_to_cpup(prop++);
+	cfg->rxdelay = be32_to_cpup(prop++);
+	cfg->mode = be32_to_cpup(prop++);
+	cfg->ability.force_mode = be32_to_cpup(prop++);
+	cfg->ability.txpause = be32_to_cpup(prop++);
+	cfg->ability.rxpause = be32_to_cpup(prop++);
+	cfg->ability.link = be32_to_cpup(prop++);
+	cfg->ability.duplex = be32_to_cpup(prop++);
+	cfg->ability.speed = be32_to_cpup(prop++);
+
+	err = rtl8367_extif_init(smi, id, cfg);
+	kfree(cfg);
+
+	return err;
+}
+#else
+static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id,
+				 const char *name)
+{
+	return -EINVAL;
+}
+#endif
+
+static int rtl8367_setup(struct rtl8366_smi *smi)
+{
+	struct rtl8367_platform_data *pdata;
+	int err;
+	int i;
+
+	pdata = smi->parent->platform_data;
+
+	err = rtl8367_init_regs(smi);
+	if (err)
+		return err;
+
+	/* initialize external interfaces */
+	if (smi->parent->of_node) {
+		err = rtl8367_extif_init_of(smi, 0, "realtek,extif0");
+		if (err)
+			return err;
+
+		err = rtl8367_extif_init_of(smi, 1, "realtek,extif1");
+		if (err)
+			return err;
+	} else {
+		err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg);
+		if (err)
+			return err;
+
+		err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg);
+		if (err)
+			return err;
+	}
+
+	/* set maximum packet length to 1536 bytes */
+	REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK,
+		RTL8367_SWC0_MAX_LENGTH_1536);
+
+	/*
+	 * discard VLAN tagged packets if the port is not a member of
+	 * the VLAN with which the packets is associated.
+	 */
+	REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL);
+
+	/*
+	 * Setup egress tag mode for each port.
+	 */
+	for (i = 0; i < RTL8367_NUM_PORTS; i++)
+		REG_RMW(smi,
+			RTL8367_PORT_CFG_REG(i),
+			RTL8367_PORT_CFG_EGRESS_MODE_MASK <<
+				RTL8367_PORT_CFG_EGRESS_MODE_SHIFT,
+			RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL <<
+				RTL8367_PORT_CFG_EGRESS_MODE_SHIFT);
+
+	/* setup LEDs */
+	err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL);
+	if (err)
+		return err;
+
+	err = rtl8367_led_group_set_mode(smi, 0);
+	if (err)
+		return err;
+
+	err = rtl8367_led_op_select_parallel(smi);
+	if (err)
+		return err;
+
+	err = rtl8367_led_blinkrate_set(smi, 1);
+	if (err)
+		return err;
+
+	err = rtl8367_led_group_set_config(smi, 0, 2);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter,
+				   int port, unsigned long long *val)
+{
+	struct rtl8366_mib_counter *mib;
+	int offset;
+	int i;
+	int err;
+	u32 addr, data;
+	u64 mibvalue;
+
+	if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT)
+		return -EINVAL;
+
+	mib = &rtl8367_mib_counters[counter];
+	addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset;
+
+	/*
+	 * Writing access counter address first
+	 * then ASIC will prepare 64bits counter wait for being retrived
+	 */
+	REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2);
+
+	/* read MIB control register */
+	REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data);
+
+	if (data & RTL8367_MIB_CTRL_BUSY_MASK)
+		return -EBUSY;
+
+	if (data & RTL8367_MIB_CTRL_RESET_MASK)
+		return -EIO;
+
+	if (mib->length == 4)
+		offset = 3;
+	else
+		offset = (mib->offset + 1) % 4;
+
+	mibvalue = 0;
+	for (i = 0; i < mib->length; i++) {
+		REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data);
+		mibvalue = (mibvalue << 16) | (data & 0xFFFF);
+	}
+
+	*val = mibvalue;
+	return 0;
+}
+
+static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid,
+				struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[RTL8367_TA_VLAN_DATA_SIZE];
+	int err;
+	int i;
+
+	memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k));
+
+	if (vid >= RTL8367_NUM_VIDS)
+		return -EINVAL;
+
+	/* write VID */
+	REG_WR(smi, RTL8367_TA_ADDR_REG, vid);
+
+	/* write table access control word */
+	REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ);
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]);
+
+	vlan4k->vid = vid;
+	vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) &
+			 RTL8367_TA_VLAN_MEMBER_MASK;
+	vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) &
+		      RTL8367_TA_VLAN_FID_MASK;
+	vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) &
+			RTL8367_TA_VLAN_UNTAG1_MASK;
+	vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) &
+			  RTL8367_TA_VLAN_UNTAG2_MASK) << 2;
+
+	return 0;
+}
+
+static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi,
+				const struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[RTL8367_TA_VLAN_DATA_SIZE];
+	int err;
+	int i;
+
+	if (vlan4k->vid >= RTL8367_NUM_VIDS ||
+	    vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK ||
+	    vlan4k->untag > RTL8367_UNTAG_MASK ||
+	    vlan4k->fid > RTL8367_FIDMAX)
+		return -EINVAL;
+
+	data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) <<
+		  RTL8367_TA_VLAN_MEMBER_SHIFT;
+	data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) <<
+		  RTL8367_TA_VLAN_FID_SHIFT;
+	data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) <<
+		  RTL8367_TA_VLAN_UNTAG1_SHIFT;
+	data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) <<
+		  RTL8367_TA_VLAN_UNTAG2_SHIFT;
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]);
+
+	/* write VID */
+	REG_WR(smi, RTL8367_TA_ADDR_REG,
+	       vlan4k->vid & RTL8367_TA_VLAN_VID_MASK);
+
+	/* write table access control word */
+	REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE);
+
+	return 0;
+}
+
+static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[RTL8367_VLAN_MC_DATA_SIZE];
+	int err;
+	int i;
+
+	memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc));
+
+	if (index >= RTL8367_NUM_VLANS)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]);
+
+	vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) &
+			 RTL8367_VLAN_MC_MEMBER_MASK;
+	vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) &
+		      RTL8367_VLAN_MC_FID_MASK;
+	vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) &
+		      RTL8367_VLAN_MC_EVID_MASK;
+
+	return 0;
+}
+
+static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				const struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[RTL8367_VLAN_MC_DATA_SIZE];
+	int err;
+	int i;
+
+	if (index >= RTL8367_NUM_VLANS ||
+	    vlanmc->vid >= RTL8367_NUM_VIDS ||
+	    vlanmc->priority > RTL8367_PRIORITYMAX ||
+	    vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK ||
+	    vlanmc->untag > RTL8367_UNTAG_MASK ||
+	    vlanmc->fid > RTL8367_FIDMAX)
+		return -EINVAL;
+
+	data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) <<
+		  RTL8367_VLAN_MC_MEMBER_SHIFT;
+	data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) <<
+		  RTL8367_VLAN_MC_FID_SHIFT;
+	data[2] = 0;
+	data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) <<
+		   RTL8367_VLAN_MC_EVID_SHIFT;
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]);
+
+	return 0;
+}
+
+static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val)
+{
+	u32 data;
+	int err;
+
+	if (port >= RTL8367_NUM_PORTS)
+		return -EINVAL;
+
+	REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data);
+
+	*val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) &
+	       RTL8367_VLAN_PVID_CTRL_MASK;
+
+	return 0;
+}
+
+static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index)
+{
+	if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port),
+				RTL8367_VLAN_PVID_CTRL_MASK <<
+					RTL8367_VLAN_PVID_CTRL_SHIFT(port),
+				(index & RTL8367_VLAN_PVID_CTRL_MASK) <<
+					RTL8367_VLAN_PVID_CTRL_SHIFT(port));
+}
+
+static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG,
+				RTL8367_VLAN_CTRL_ENABLE,
+				(enable) ? RTL8367_VLAN_CTRL_ENABLE : 0);
+}
+
+static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable)
+{
+	return 0;
+}
+
+static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan)
+{
+	unsigned max = RTL8367_NUM_VLANS;
+
+	if (smi->vlan4k_enabled)
+		max = RTL8367_NUM_VIDS - 1;
+
+	if (vlan == 0 || vlan >= max)
+		return 0;
+
+	return 1;
+}
+
+static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable)
+{
+	int err;
+
+	REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port),
+	       (enable) ? RTL8367_PORTS_ALL : 0);
+
+	return 0;
+}
+
+static int rtl8367_sw_reset_mibs(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0,
+				RTL8367_MIB_CTRL_GLOBAL_RESET_MASK);
+}
+
+static int rtl8367_sw_get_port_link(struct switch_dev *dev,
+				    int port,
+				    struct switch_port_link *link)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data = 0;
+	u32 speed;
+
+	if (port >= RTL8367_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data);
+
+	link->link = !!(data & RTL8367_PORT_STATUS_LINK);
+	if (!link->link)
+		return 0;
+
+	link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX);
+	link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE);
+	link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE);
+	link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY);
+
+	speed = (data & RTL8367_PORT_STATUS_SPEED_MASK);
+	switch (speed) {
+	case 0:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case 1:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case 2:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	default:
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int rtl8367_sw_get_max_length(struct switch_dev *dev,
+				     const struct switch_attr *attr,
+				     struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data);
+	val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >>
+			RTL8367_SWC0_MAX_LENGTH_SHIFT;
+
+	return 0;
+}
+
+static int rtl8367_sw_set_max_length(struct switch_dev *dev,
+				     const struct switch_attr *attr,
+				     struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 max_len;
+
+	switch (val->value.i) {
+	case 0:
+		max_len = RTL8367_SWC0_MAX_LENGTH_1522;
+		break;
+	case 1:
+		max_len = RTL8367_SWC0_MAX_LENGTH_1536;
+		break;
+	case 2:
+		max_len = RTL8367_SWC0_MAX_LENGTH_1552;
+		break;
+	case 3:
+		max_len = RTL8367_SWC0_MAX_LENGTH_16000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG,
+			        RTL8367_SWC0_MAX_LENGTH_MASK, max_len);
+}
+
+
+static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int port;
+
+	port = val->port_vlan;
+	if (port >= RTL8367_NUM_PORTS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0,
+				RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8));
+}
+
+static int rtl8367_sw_get_port_stats(struct switch_dev *dev, int port,
+                                        struct switch_port_stats *stats)
+{
+	return (rtl8366_sw_get_port_stats(dev, port, stats,
+				RTL8367_MIB_TXB_ID, RTL8367_MIB_RXB_ID));
+}
+
+static struct switch_attr rtl8367_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan4k",
+		.description = "Enable VLAN 4K mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 2
+	}, {
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mibs",
+		.description = "Reset all MIB counters",
+		.set = rtl8367_sw_reset_mibs,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "max_length",
+		.description = "Get/Set the maximum length of valid packets"
+			       "(0:1522, 1:1536, 2:1552, 3:16000)",
+		.set = rtl8367_sw_set_max_length,
+		.get = rtl8367_sw_get_max_length,
+		.max = 3,
+	}
+};
+
+static struct switch_attr rtl8367_port[] = {
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mib",
+		.description = "Reset single port MIB counters",
+		.set = rtl8367_sw_reset_port_mibs,
+	}, {
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get MIB counters for port",
+		.max = 33,
+		.set = NULL,
+		.get = rtl8366_sw_get_port_mib,
+	},
+};
+
+static struct switch_attr rtl8367_vlan[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "info",
+		.description = "Get vlan information",
+		.max = 1,
+		.set = NULL,
+		.get = rtl8366_sw_get_vlan_info,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "fid",
+		.description = "Get/Set vlan FID",
+		.max = RTL8367_FIDMAX,
+		.set = rtl8366_sw_set_vlan_fid,
+		.get = rtl8366_sw_get_vlan_fid,
+	},
+};
+
+static const struct switch_dev_ops rtl8367_sw_ops = {
+	.attr_global = {
+		.attr = rtl8367_globals,
+		.n_attr = ARRAY_SIZE(rtl8367_globals),
+	},
+	.attr_port = {
+		.attr = rtl8367_port,
+		.n_attr = ARRAY_SIZE(rtl8367_port),
+	},
+	.attr_vlan = {
+		.attr = rtl8367_vlan,
+		.n_attr = ARRAY_SIZE(rtl8367_vlan),
+	},
+
+	.get_vlan_ports = rtl8366_sw_get_vlan_ports,
+	.set_vlan_ports = rtl8366_sw_set_vlan_ports,
+	.get_port_pvid = rtl8366_sw_get_port_pvid,
+	.set_port_pvid = rtl8366_sw_set_port_pvid,
+	.reset_switch = rtl8366_sw_reset_switch,
+	.get_port_link = rtl8367_sw_get_port_link,
+	.get_port_stats = rtl8367_sw_get_port_stats,
+};
+
+static int rtl8367_switch_init(struct rtl8366_smi *smi)
+{
+	struct switch_dev *dev = &smi->sw_dev;
+	int err;
+
+	dev->name = "RTL8367";
+	dev->cpu_port = RTL8367_CPU_PORT_NUM;
+	dev->ports = RTL8367_NUM_PORTS;
+	dev->vlans = RTL8367_NUM_VIDS;
+	dev->ops = &rtl8367_sw_ops;
+	dev->alias = dev_name(smi->parent);
+
+	err = register_switch(dev, NULL);
+	if (err)
+		dev_err(smi->parent, "switch registration failed\n");
+
+	return err;
+}
+
+static void rtl8367_switch_cleanup(struct rtl8366_smi *smi)
+{
+	unregister_switch(&smi->sw_dev);
+}
+
+static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 val = 0;
+	int err;
+
+	err = rtl8367_read_phy_reg(smi, addr, reg, &val);
+	if (err)
+		return 0xffff;
+
+	return val;
+}
+
+static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 t;
+	int err;
+
+	err = rtl8367_write_phy_reg(smi, addr, reg, val);
+	if (err)
+		return err;
+
+	/* flush write */
+	(void) rtl8367_read_phy_reg(smi, addr, reg, &t);
+
+	return err;
+}
+
+static int rtl8367_detect(struct rtl8366_smi *smi)
+{
+	u32 rtl_no = 0;
+	u32 rtl_ver = 0;
+	char *chip_name;
+	int ret;
+
+	ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no);
+	if (ret) {
+		dev_err(smi->parent, "unable to read chip number\n");
+		return ret;
+	}
+
+	switch (rtl_no) {
+	case RTL8367_RTL_NO_8367R:
+		chip_name = "8367R";
+		break;
+	case RTL8367_RTL_NO_8367M:
+		chip_name = "8367M";
+		break;
+	default:
+		dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no);
+		return -ENODEV;
+	}
+
+	ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver);
+	if (ret) {
+		dev_err(smi->parent, "unable to read chip version\n");
+		return ret;
+	}
+
+	dev_info(smi->parent, "RTL%s ver. %u chip found\n",
+		 chip_name, rtl_ver & RTL8367_RTL_VER_MASK);
+
+	return 0;
+}
+
+static struct rtl8366_smi_ops rtl8367_smi_ops = {
+	.detect		= rtl8367_detect,
+	.reset_chip	= rtl8367_reset_chip,
+	.setup		= rtl8367_setup,
+
+	.mii_read	= rtl8367_mii_read,
+	.mii_write	= rtl8367_mii_write,
+
+	.get_vlan_mc	= rtl8367_get_vlan_mc,
+	.set_vlan_mc	= rtl8367_set_vlan_mc,
+	.get_vlan_4k	= rtl8367_get_vlan_4k,
+	.set_vlan_4k	= rtl8367_set_vlan_4k,
+	.get_mc_index	= rtl8367_get_mc_index,
+	.set_mc_index	= rtl8367_set_mc_index,
+	.get_mib_counter = rtl8367_get_mib_counter,
+	.is_vlan_valid	= rtl8367_is_vlan_valid,
+	.enable_vlan	= rtl8367_enable_vlan,
+	.enable_vlan4k	= rtl8367_enable_vlan4k,
+	.enable_port	= rtl8367_enable_port,
+};
+
+static int rtl8367_probe(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi;
+	int err;
+
+	smi = rtl8366_smi_probe(pdev);
+	if (IS_ERR(smi))
+		return PTR_ERR(smi);
+
+	smi->clk_delay = 1500;
+	smi->cmd_read = 0xb9;
+	smi->cmd_write = 0xb8;
+	smi->ops = &rtl8367_smi_ops;
+	smi->cpu_port = RTL8367_CPU_PORT_NUM;
+	smi->num_ports = RTL8367_NUM_PORTS;
+	smi->num_vlan_mc = RTL8367_NUM_VLANS;
+	smi->mib_counters = rtl8367_mib_counters;
+	smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters);
+
+	err = rtl8366_smi_init(smi);
+	if (err)
+		goto err_free_smi;
+
+	platform_set_drvdata(pdev, smi);
+
+	err = rtl8367_switch_init(smi);
+	if (err)
+		goto err_clear_drvdata;
+
+	return 0;
+
+ err_clear_drvdata:
+	platform_set_drvdata(pdev, NULL);
+	rtl8366_smi_cleanup(smi);
+ err_free_smi:
+	kfree(smi);
+	return err;
+}
+
+static int rtl8367_remove(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi = platform_get_drvdata(pdev);
+
+	if (smi) {
+		rtl8367_switch_cleanup(smi);
+		platform_set_drvdata(pdev, NULL);
+		rtl8366_smi_cleanup(smi);
+		kfree(smi);
+	}
+
+	return 0;
+}
+
+static void rtl8367_shutdown(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi = platform_get_drvdata(pdev);
+
+	if (smi)
+		rtl8367_reset_chip(smi);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id rtl8367_match[] = {
+       { .compatible = "realtek,rtl8367" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, rtl8367_match);
+#endif
+
+static struct platform_driver rtl8367_driver = {
+	.driver = {
+		.name		= RTL8367_DRIVER_NAME,
+		.owner		= THIS_MODULE,
+#ifdef CONFIG_OF
+		.of_match_table = of_match_ptr(rtl8367_match),
+#endif
+	},
+	.probe		= rtl8367_probe,
+	.remove		= rtl8367_remove,
+	.shutdown	= rtl8367_shutdown,
+};
+
+static int __init rtl8367_module_init(void)
+{
+	return platform_driver_register(&rtl8367_driver);
+}
+module_init(rtl8367_module_init);
+
+static void __exit rtl8367_module_exit(void)
+{
+	platform_driver_unregister(&rtl8367_driver);
+}
+module_exit(rtl8367_module_exit);
+
+MODULE_DESCRIPTION("Realtek RTL8367 ethernet switch driver");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME);
diff -Naur linux-orig/drivers/net/phy/rtl8367b.c linux/drivers/net/phy/rtl8367b.c
--- linux-orig/drivers/net/phy/rtl8367b.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/rtl8367b.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1739 @@
+/*
+ * Platform driver for the Realtek RTL8367R-VB ethernet switches
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <linux/rtl8367.h>
+
+#include "rtl8366_smi.h"
+
+#define RTL8367B_RESET_DELAY	1000	/* msecs*/
+
+#define RTL8367B_PHY_ADDR_MAX	8
+#define RTL8367B_PHY_REG_MAX	31
+
+#define RTL8367B_VID_MASK	0x3fff
+#define RTL8367B_FID_MASK	0xf
+#define RTL8367B_UNTAG_MASK	0xff
+#define RTL8367B_MEMBER_MASK	0xff
+
+#define RTL8367B_PORT_MISC_CFG_REG(_p)		(0x000e + 0x20 * (_p))
+#define   RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT	4
+#define   RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK	0x3
+#define   RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL	0
+#define   RTL8367B_PORT_MISC_CFG_EGRESS_MODE_KEEP	1
+#define   RTL8367B_PORT_MISC_CFG_EGRESS_MODE_PRI	2
+#define   RTL8367B_PORT_MISC_CFG_EGRESS_MODE_REAL	3
+
+#define RTL8367B_BYPASS_LINE_RATE_REG		0x03f7
+
+#define RTL8367B_TA_CTRL_REG			0x0500 /*GOOD*/
+#define   RTL8367B_TA_CTRL_SPA_SHIFT		8
+#define   RTL8367B_TA_CTRL_SPA_MASK		0x7
+#define   RTL8367B_TA_CTRL_METHOD		BIT(4)/*GOOD*/
+#define   RTL8367B_TA_CTRL_CMD_SHIFT		3
+#define   RTL8367B_TA_CTRL_CMD_READ		0
+#define   RTL8367B_TA_CTRL_CMD_WRITE		1
+#define   RTL8367B_TA_CTRL_TABLE_SHIFT		0 /*GOOD*/
+#define   RTL8367B_TA_CTRL_TABLE_ACLRULE	1
+#define   RTL8367B_TA_CTRL_TABLE_ACLACT		2
+#define   RTL8367B_TA_CTRL_TABLE_CVLAN		3
+#define   RTL8367B_TA_CTRL_TABLE_L2		4
+#define   RTL8367B_TA_CTRL_CVLAN_READ \
+		((RTL8367B_TA_CTRL_CMD_READ << RTL8367B_TA_CTRL_CMD_SHIFT) | \
+		 RTL8367B_TA_CTRL_TABLE_CVLAN)
+#define   RTL8367B_TA_CTRL_CVLAN_WRITE \
+		((RTL8367B_TA_CTRL_CMD_WRITE << RTL8367B_TA_CTRL_CMD_SHIFT) | \
+		 RTL8367B_TA_CTRL_TABLE_CVLAN)
+
+#define RTL8367B_TA_ADDR_REG			0x0501/*GOOD*/
+#define   RTL8367B_TA_ADDR_MASK			0x3fff/*GOOD*/
+
+#define RTL8367B_TA_LUT_REG			0x0502/*GOOD*/
+
+#define RTL8367B_TA_WRDATA_REG(_x)		(0x0510 + (_x))/*GOOD*/
+#define   RTL8367B_TA_VLAN_NUM_WORDS		2
+#define   RTL8367B_TA_VLAN_VID_MASK		RTL8367B_VID_MASK
+#define   RTL8367B_TA_VLAN0_MEMBER_SHIFT	0
+#define   RTL8367B_TA_VLAN0_MEMBER_MASK		RTL8367B_MEMBER_MASK
+#define   RTL8367B_TA_VLAN0_UNTAG_SHIFT		8
+#define   RTL8367B_TA_VLAN0_UNTAG_MASK		RTL8367B_MEMBER_MASK
+#define   RTL8367B_TA_VLAN1_FID_SHIFT		0
+#define   RTL8367B_TA_VLAN1_FID_MASK		RTL8367B_FID_MASK
+
+#define RTL8367B_TA_RDDATA_REG(_x)		(0x0520 + (_x))/*GOOD*/
+
+#define RTL8367B_VLAN_PVID_CTRL_REG(_p)		(0x0700 + (_p) / 2) /*GOOD*/
+#define RTL8367B_VLAN_PVID_CTRL_MASK		0x1f /*GOOD*/
+#define RTL8367B_VLAN_PVID_CTRL_SHIFT(_p)	(8 * ((_p) % 2)) /*GOOD*/
+
+#define RTL8367B_VLAN_MC_BASE(_x)		(0x0728 + (_x) * 4) /*GOOD*/
+#define   RTL8367B_VLAN_MC_NUM_WORDS		4 /*GOOD*/
+#define   RTL8367B_VLAN_MC0_MEMBER_SHIFT	0/*GOOD*/
+#define   RTL8367B_VLAN_MC0_MEMBER_MASK		RTL8367B_MEMBER_MASK/*GOOD*/
+#define   RTL8367B_VLAN_MC1_FID_SHIFT		0/*GOOD*/
+#define   RTL8367B_VLAN_MC1_FID_MASK		RTL8367B_FID_MASK/*GOOD*/
+#define   RTL8367B_VLAN_MC3_EVID_SHIFT		0/*GOOD*/
+#define   RTL8367B_VLAN_MC3_EVID_MASK		RTL8367B_VID_MASK/*GOOD*/
+
+#define RTL8367B_VLAN_CTRL_REG			0x07a8 /*GOOD*/
+#define   RTL8367B_VLAN_CTRL_ENABLE		BIT(0)
+
+#define RTL8367B_VLAN_INGRESS_REG		0x07a9 /*GOOD*/
+
+#define RTL8367B_PORT_ISOLATION_REG(_p)		(0x08a2 + (_p)) /*GOOD*/
+
+#define RTL8367B_MIB_COUNTER_REG(_x)		(0x1000 + (_x))	/*GOOD*/
+#define RTL8367B_MIB_COUNTER_PORT_OFFSET	0x007c /*GOOD*/
+
+#define RTL8367B_MIB_ADDRESS_REG		0x1004 /*GOOD*/
+
+#define RTL8367B_MIB_CTRL0_REG(_x)		(0x1005 + (_x)) /*GOOD*/
+#define   RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK	BIT(11)	/*GOOD*/
+#define   RTL8367B_MIB_CTRL0_QM_RESET_MASK	BIT(10) /*GOOD*/
+#define   RTL8367B_MIB_CTRL0_PORT_RESET_MASK(_p) BIT(2 + (_p)) /*GOOD*/
+#define   RTL8367B_MIB_CTRL0_RESET_MASK		BIT(1) /*GOOD*/
+#define   RTL8367B_MIB_CTRL0_BUSY_MASK		BIT(0) /*GOOD*/
+
+#define RTL8367B_SWC0_REG			0x1200/*GOOD*/
+#define   RTL8367B_SWC0_MAX_LENGTH_SHIFT	13/*GOOD*/
+#define   RTL8367B_SWC0_MAX_LENGTH(_x)		((_x) << 13) /*GOOD*/
+#define   RTL8367B_SWC0_MAX_LENGTH_MASK		RTL8367B_SWC0_MAX_LENGTH(0x3)
+#define   RTL8367B_SWC0_MAX_LENGTH_1522		RTL8367B_SWC0_MAX_LENGTH(0)
+#define   RTL8367B_SWC0_MAX_LENGTH_1536		RTL8367B_SWC0_MAX_LENGTH(1)
+#define   RTL8367B_SWC0_MAX_LENGTH_1552		RTL8367B_SWC0_MAX_LENGTH(2)
+#define   RTL8367B_SWC0_MAX_LENGTH_16000	RTL8367B_SWC0_MAX_LENGTH(3)
+
+#define RTL8367B_CHIP_NUMBER_REG		0x1300/*GOOD*/
+
+#define RTL8367B_CHIP_VER_REG			0x1301/*GOOD*/
+#define   RTL8367B_CHIP_VER_RLVID_SHIFT		12/*GOOD*/
+#define   RTL8367B_CHIP_VER_RLVID_MASK		0xf/*GOOD*/
+#define   RTL8367B_CHIP_VER_MCID_SHIFT		8/*GOOD*/
+#define   RTL8367B_CHIP_VER_MCID_MASK		0xf/*GOOD*/
+#define   RTL8367B_CHIP_VER_BOID_SHIFT		4/*GOOD*/
+#define   RTL8367B_CHIP_VER_BOID_MASK		0xf/*GOOD*/
+#define   RTL8367B_CHIP_VER_AFE_SHIFT		0/*GOOD*/
+#define   RTL8367B_CHIP_VER_AFE_MASK		0x1/*GOOD*/
+
+#define RTL8367B_CHIP_MODE_REG			0x1302
+#define   RTL8367B_CHIP_MODE_MASK		0x7
+
+#define RTL8367B_CHIP_DEBUG0_REG		0x1303
+#define   RTL8367B_DEBUG0_SEL33(_x)		BIT(8 + (_x))
+#define   RTL8367B_DEBUG0_DRI_OTHER		BIT(7)
+#define   RTL8367B_DEBUG0_DRI_RG(_x)		BIT(5 + (_x))
+#define   RTL8367B_DEBUG0_DRI(_x)		BIT(3 + (_x))
+#define   RTL8367B_DEBUG0_SLR_OTHER		BIT(2)
+#define   RTL8367B_DEBUG0_SLR(_x)		BIT(_x)
+
+#define RTL8367B_CHIP_DEBUG1_REG		0x1304
+#define   RTL8367B_DEBUG1_DN_MASK(_x)		\
+	    GENMASK(6 + (_x)*8, 4 + (_x)*8)
+#define   RTL8367B_DEBUG1_DN_SHIFT(_x)		(4 + (_x) * 8)
+#define   RTL8367B_DEBUG1_DP_MASK(_x)		\
+	    GENMASK(2 + (_x) * 8, (_x) * 8)
+#define   RTL8367B_DEBUG1_DP_SHIFT(_x)		((_x) * 8)
+
+#define RTL8367B_CHIP_DEBUG2_REG		0x13e2
+#define   RTL8367B_DEBUG2_RG2_DN_MASK		GENMASK(8, 6)
+#define   RTL8367B_DEBUG2_RG2_DN_SHIFT		6
+#define   RTL8367B_DEBUG2_RG2_DP_MASK		GENMASK(5, 3)
+#define   RTL8367B_DEBUG2_RG2_DP_SHIFT		3
+#define   RTL8367B_DEBUG2_DRI_EXT2_RG		BIT(2)
+#define   RTL8367B_DEBUG2_DRI_EXT2		BIT(1)
+#define   RTL8367B_DEBUG2_SLR_EXT2		BIT(0)
+
+#define RTL8367B_DIS_REG			0x1305
+#define   RTL8367B_DIS_SKIP_MII_RXER(_x)	BIT(12 + (_x))
+#define   RTL8367B_DIS_RGMII_SHIFT(_x)		(4 * (_x))
+#define   RTL8367B_DIS_RGMII_MASK		0x7
+
+#define RTL8367B_DIS2_REG			0x13c3
+#define   RTL8367B_DIS2_SKIP_MII_RXER_SHIFT	4
+#define   RTL8367B_DIS2_SKIP_MII_RXER		0x10
+#define   RTL8367B_DIS2_RGMII_SHIFT		0
+#define   RTL8367B_DIS2_RGMII_MASK		0xf
+
+#define RTL8367B_EXT_RGMXF_REG(_x)		\
+	  ((_x) == 2 ? 0x13c5 : 0x1306 + (_x))
+#define   RTL8367B_EXT_RGMXF_DUMMY0_SHIFT	5
+#define   RTL8367B_EXT_RGMXF_DUMMY0_MASK	0x7ff
+#define   RTL8367B_EXT_RGMXF_TXDELAY_SHIFT	3
+#define   RTL8367B_EXT_RGMXF_TXDELAY_MASK	1
+#define   RTL8367B_EXT_RGMXF_RXDELAY_MASK	0x7
+
+#define RTL8367B_DI_FORCE_REG(_x)		\
+	  ((_x) == 2 ? 0x13c4 : 0x1310 + (_x))
+#define   RTL8367B_DI_FORCE_MODE		BIT(12)
+#define   RTL8367B_DI_FORCE_NWAY		BIT(7)
+#define   RTL8367B_DI_FORCE_TXPAUSE		BIT(6)
+#define   RTL8367B_DI_FORCE_RXPAUSE		BIT(5)
+#define   RTL8367B_DI_FORCE_LINK		BIT(4)
+#define   RTL8367B_DI_FORCE_DUPLEX		BIT(2)
+#define   RTL8367B_DI_FORCE_SPEED_MASK		3
+#define   RTL8367B_DI_FORCE_SPEED_10		0
+#define   RTL8367B_DI_FORCE_SPEED_100		1
+#define   RTL8367B_DI_FORCE_SPEED_1000		2
+
+#define RTL8367B_MAC_FORCE_REG(_x)		(0x1312 + (_x))
+
+#define RTL8367B_CHIP_RESET_REG			0x1322 /*GOOD*/
+#define   RTL8367B_CHIP_RESET_SW		BIT(1) /*GOOD*/
+#define   RTL8367B_CHIP_RESET_HW		BIT(0) /*GOOD*/
+
+#define RTL8367B_PORT_STATUS_REG(_p)		(0x1352 + (_p)) /*GOOD*/
+#define   RTL8367B_PORT_STATUS_EN_1000_SPI	BIT(11) /*GOOD*/
+#define   RTL8367B_PORT_STATUS_EN_100_SPI	BIT(10)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_NWAY_FAULT	BIT(9)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_LINK_MASTER	BIT(8)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_NWAY		BIT(7)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_TXPAUSE		BIT(6)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_RXPAUSE		BIT(5)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_LINK		BIT(4)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_DUPLEX		BIT(2)/*GOOD*/
+#define   RTL8367B_PORT_STATUS_SPEED_MASK	0x0003/*GOOD*/
+#define   RTL8367B_PORT_STATUS_SPEED_10		0/*GOOD*/
+#define   RTL8367B_PORT_STATUS_SPEED_100	1/*GOOD*/
+#define   RTL8367B_PORT_STATUS_SPEED_1000	2/*GOOD*/
+
+#define RTL8367B_RTL_MAGIC_ID_REG		0x13c2
+#define   RTL8367B_RTL_MAGIC_ID_VAL		0x0249
+
+#define RTL8367B_IA_CTRL_REG			0x1f00
+#define   RTL8367B_IA_CTRL_RW(_x)		((_x) << 1)
+#define   RTL8367B_IA_CTRL_RW_READ		RTL8367B_IA_CTRL_RW(0)
+#define   RTL8367B_IA_CTRL_RW_WRITE		RTL8367B_IA_CTRL_RW(1)
+#define   RTL8367B_IA_CTRL_CMD_MASK		BIT(0)
+
+#define RTL8367B_IA_STATUS_REG			0x1f01
+#define   RTL8367B_IA_STATUS_PHY_BUSY		BIT(2)
+#define   RTL8367B_IA_STATUS_SDS_BUSY		BIT(1)
+#define   RTL8367B_IA_STATUS_MDX_BUSY		BIT(0)
+
+#define RTL8367B_IA_ADDRESS_REG			0x1f02
+#define RTL8367B_IA_WRITE_DATA_REG		0x1f03
+#define RTL8367B_IA_READ_DATA_REG		0x1f04
+
+#define RTL8367B_INTERNAL_PHY_REG(_a, _r)	(0x2000 + 32 * (_a) + (_r))
+
+#define RTL8367B_NUM_MIB_COUNTERS	58
+
+#define RTL8367B_CPU_PORT_NUM		5
+#define RTL8367B_NUM_PORTS		8
+#define RTL8367B_NUM_PHYS		5
+#define RTL8367B_NUM_VLANS		32
+#define RTL8367B_NUM_VIDS		4096
+#define RTL8367B_PRIORITYMAX		7
+#define RTL8367B_FIDMAX			7
+
+#define RTL8367B_PORT_0			BIT(0)
+#define RTL8367B_PORT_1			BIT(1)
+#define RTL8367B_PORT_2			BIT(2)
+#define RTL8367B_PORT_3			BIT(3)
+#define RTL8367B_PORT_4			BIT(4)
+#define RTL8367B_PORT_E0		BIT(5)	/* External port 0 */
+#define RTL8367B_PORT_E1		BIT(6)	/* External port 1 */
+#define RTL8367B_PORT_E2		BIT(7)	/* External port 2 */
+
+#define RTL8367B_PORTS_ALL					\
+	(RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 |	\
+	 RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E0 | \
+	 RTL8367B_PORT_E1 | RTL8367B_PORT_E2)
+
+#define RTL8367B_PORTS_ALL_BUT_CPU				\
+	(RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 |	\
+	 RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E1 |	\
+	 RTL8367B_PORT_E2)
+
+#define RTL8367B_FLAG_MATCH_CHIP_NUM	BIT(0)
+
+struct rtl8367b_initval {
+	u16 reg;
+	u16 val;
+};
+
+#define RTL8367B_MIB_RXB_ID		0	/* IfInOctets */
+#define RTL8367B_MIB_TXB_ID		28	/* IfOutOctets */
+
+static struct rtl8366_mib_counter
+rtl8367b_mib_counters[RTL8367B_NUM_MIB_COUNTERS] = {
+	{0,   0, 4, "ifInOctets"			},
+	{0,   4, 2, "dot3StatsFCSErrors"		},
+	{0,   6, 2, "dot3StatsSymbolErrors"		},
+	{0,   8, 2, "dot3InPauseFrames"			},
+	{0,  10, 2, "dot3ControlInUnknownOpcodes"	},
+	{0,  12, 2, "etherStatsFragments"		},
+	{0,  14, 2, "etherStatsJabbers"			},
+	{0,  16, 2, "ifInUcastPkts"			},
+	{0,  18, 2, "etherStatsDropEvents"		},
+	{0,  20, 2, "ifInMulticastPkts"			},
+	{0,  22, 2, "ifInBroadcastPkts"			},
+	{0,  24, 2, "inMldChecksumError"		},
+	{0,  26, 2, "inIgmpChecksumError"		},
+	{0,  28, 2, "inMldSpecificQuery"		},
+	{0,  30, 2, "inMldGeneralQuery"			},
+	{0,  32, 2, "inIgmpSpecificQuery"		},
+	{0,  34, 2, "inIgmpGeneralQuery"		},
+	{0,  36, 2, "inMldLeaves"			},
+	{0,  38, 2, "inIgmpLeaves"			},
+
+	{0,  40, 4, "etherStatsOctets"			},
+	{0,  44, 2, "etherStatsUnderSizePkts"		},
+	{0,  46, 2, "etherOversizeStats"		},
+	{0,  48, 2, "etherStatsPkts64Octets"		},
+	{0,  50, 2, "etherStatsPkts65to127Octets"	},
+	{0,  52, 2, "etherStatsPkts128to255Octets"	},
+	{0,  54, 2, "etherStatsPkts256to511Octets"	},
+	{0,  56, 2, "etherStatsPkts512to1023Octets"	},
+	{0,  58, 2, "etherStatsPkts1024to1518Octets"	},
+
+	{0,  60, 4, "ifOutOctets"			},
+	{0,  64, 2, "dot3StatsSingleCollisionFrames"	},
+	{0,  66, 2, "dot3StatMultipleCollisionFrames"	},
+	{0,  68, 2, "dot3sDeferredTransmissions"	},
+	{0,  70, 2, "dot3StatsLateCollisions"		},
+	{0,  72, 2, "etherStatsCollisions"		},
+	{0,  74, 2, "dot3StatsExcessiveCollisions"	},
+	{0,  76, 2, "dot3OutPauseFrames"		},
+	{0,  78, 2, "ifOutDiscards"			},
+	{0,  80, 2, "dot1dTpPortInDiscards"		},
+	{0,  82, 2, "ifOutUcastPkts"			},
+	{0,  84, 2, "ifOutMulticastPkts"		},
+	{0,  86, 2, "ifOutBroadcastPkts"		},
+	{0,  88, 2, "outOampduPkts"			},
+	{0,  90, 2, "inOampduPkts"			},
+	{0,  92, 2, "inIgmpJoinsSuccess"		},
+	{0,  94, 2, "inIgmpJoinsFail"			},
+	{0,  96, 2, "inMldJoinsSuccess"			},
+	{0,  98, 2, "inMldJoinsFail"			},
+	{0, 100, 2, "inReportSuppressionDrop"		},
+	{0, 102, 2, "inLeaveSuppressionDrop"		},
+	{0, 104, 2, "outIgmpReports"			},
+	{0, 106, 2, "outIgmpLeaves"			},
+	{0, 108, 2, "outIgmpGeneralQuery"		},
+	{0, 110, 2, "outIgmpSpecificQuery"		},
+	{0, 112, 2, "outMldReports"			},
+	{0, 114, 2, "outMldLeaves"			},
+	{0, 116, 2, "outMldGeneralQuery"		},
+	{0, 118, 2, "outMldSpecificQuery"		},
+	{0, 120, 2, "inKnownMulticastPkts"		},
+};
+
+#define REG_RD(_smi, _reg, _val)					\
+	do {								\
+		err = rtl8366_smi_read_reg(_smi, _reg, _val);		\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+#define REG_WR(_smi, _reg, _val)					\
+	do {								\
+		err = rtl8366_smi_write_reg(_smi, _reg, _val);		\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+#define REG_RMW(_smi, _reg, _mask, _val)				\
+	do {								\
+		err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val);	\
+		if (err)						\
+			return err;					\
+	} while (0)
+
+static const struct rtl8367b_initval rtl8367r_vb_initvals_0[] = {
+	{0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x0301, 0x0026}, {0x1722, 0x0E14},
+	{0x205F, 0x0002}, {0x2059, 0x1A00}, {0x205F, 0x0000}, {0x207F, 0x0002},
+	{0x2077, 0x0000}, {0x2078, 0x0000}, {0x2079, 0x0000}, {0x207A, 0x0000},
+	{0x207B, 0x0000}, {0x207F, 0x0000}, {0x205F, 0x0002}, {0x2053, 0x0000},
+	{0x2054, 0x0000}, {0x2055, 0x0000}, {0x2056, 0x0000}, {0x2057, 0x0000},
+	{0x205F, 0x0000}, {0x12A4, 0x110A}, {0x12A6, 0x150A}, {0x13F1, 0x0013},
+	{0x13F4, 0x0010}, {0x13F5, 0x0000}, {0x0018, 0x0F00}, {0x0038, 0x0F00},
+	{0x0058, 0x0F00}, {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x12B6, 0x0C02},
+	{0x12B7, 0x030F}, {0x12B8, 0x11FF}, {0x12BC, 0x0004}, {0x1362, 0x0115},
+	{0x1363, 0x0002}, {0x1363, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E},
+	{0x221F, 0x0007}, {0x221E, 0x002D}, {0x2218, 0xF030}, {0x221F, 0x0007},
+	{0x221E, 0x0023}, {0x2216, 0x0005}, {0x2215, 0x00B9}, {0x2219, 0x0044},
+	{0x2215, 0x00BA}, {0x2219, 0x0020}, {0x2215, 0x00BB}, {0x2219, 0x00C1},
+	{0x2215, 0x0148}, {0x2219, 0x0096}, {0x2215, 0x016E}, {0x2219, 0x0026},
+	{0x2216, 0x0000}, {0x2216, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010},
+	{0x221F, 0x0007}, {0x221E, 0x0020}, {0x2215, 0x0D00}, {0x221F, 0x0000},
+	{0x221F, 0x0000}, {0x2217, 0x2160}, {0x221F, 0x0001}, {0x2210, 0xF25E},
+	{0x221F, 0x0007}, {0x221E, 0x0042}, {0x2215, 0x0F00}, {0x2215, 0x0F00},
+	{0x2216, 0x7408}, {0x2215, 0x0E00}, {0x2215, 0x0F00}, {0x2215, 0x0F01},
+	{0x2216, 0x4000}, {0x2215, 0x0E01}, {0x2215, 0x0F01}, {0x2215, 0x0F02},
+	{0x2216, 0x9400}, {0x2215, 0x0E02}, {0x2215, 0x0F02}, {0x2215, 0x0F03},
+	{0x2216, 0x7408}, {0x2215, 0x0E03}, {0x2215, 0x0F03}, {0x2215, 0x0F04},
+	{0x2216, 0x4008}, {0x2215, 0x0E04}, {0x2215, 0x0F04}, {0x2215, 0x0F05},
+	{0x2216, 0x9400}, {0x2215, 0x0E05}, {0x2215, 0x0F05}, {0x2215, 0x0F06},
+	{0x2216, 0x0803}, {0x2215, 0x0E06}, {0x2215, 0x0F06}, {0x2215, 0x0D00},
+	{0x2215, 0x0100}, {0x221F, 0x0001}, {0x2210, 0xF05E}, {0x221F, 0x0000},
+	{0x2217, 0x2100}, {0x221F, 0x0000}, {0x220D, 0x0003}, {0x220E, 0x0015},
+	{0x220D, 0x4003}, {0x220E, 0x0006}, {0x221F, 0x0000}, {0x2200, 0x1340},
+	{0x133F, 0x0010}, {0x12A0, 0x0058}, {0x12A1, 0x0058}, {0x133E, 0x000E},
+	{0x133F, 0x0030}, {0x221F, 0x0000}, {0x2210, 0x0166}, {0x221F, 0x0000},
+	{0x133E, 0x000E}, {0x133F, 0x0010}, {0x133F, 0x0030}, {0x133E, 0x000E},
+	{0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8B6E},
+	{0x2206, 0x0000}, {0x220F, 0x0100}, {0x2205, 0x8000}, {0x2206, 0x0280},
+	{0x2206, 0x28F7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080},
+	{0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201},
+	{0x2206, 0x6602}, {0x2206, 0x80B9}, {0x2206, 0xE08B}, {0x2206, 0x8CE1},
+	{0x2206, 0x8B8D}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x8E1E},
+	{0x2206, 0x01A0}, {0x2206, 0x00E7}, {0x2206, 0xAEDB}, {0x2206, 0xEEE0},
+	{0x2206, 0x120E}, {0x2206, 0xEEE0}, {0x2206, 0x1300}, {0x2206, 0xEEE0},
+	{0x2206, 0x2001}, {0x2206, 0xEEE0}, {0x2206, 0x2166}, {0x2206, 0xEEE0},
+	{0x2206, 0xC463}, {0x2206, 0xEEE0}, {0x2206, 0xC5E8}, {0x2206, 0xEEE0},
+	{0x2206, 0xC699}, {0x2206, 0xEEE0}, {0x2206, 0xC7C2}, {0x2206, 0xEEE0},
+	{0x2206, 0xC801}, {0x2206, 0xEEE0}, {0x2206, 0xC913}, {0x2206, 0xEEE0},
+	{0x2206, 0xCA30}, {0x2206, 0xEEE0}, {0x2206, 0xCB3E}, {0x2206, 0xEEE0},
+	{0x2206, 0xDCE1}, {0x2206, 0xEEE0}, {0x2206, 0xDD00}, {0x2206, 0xEEE2},
+	{0x2206, 0x0001}, {0x2206, 0xEEE2}, {0x2206, 0x0100}, {0x2206, 0xEEE4},
+	{0x2206, 0x8860}, {0x2206, 0xEEE4}, {0x2206, 0x8902}, {0x2206, 0xEEE4},
+	{0x2206, 0x8C00}, {0x2206, 0xEEE4}, {0x2206, 0x8D30}, {0x2206, 0xEEEA},
+	{0x2206, 0x1480}, {0x2206, 0xEEEA}, {0x2206, 0x1503}, {0x2206, 0xEEEA},
+	{0x2206, 0xC600}, {0x2206, 0xEEEA}, {0x2206, 0xC706}, {0x2206, 0xEE85},
+	{0x2206, 0xEE00}, {0x2206, 0xEE85}, {0x2206, 0xEF00}, {0x2206, 0xEE8B},
+	{0x2206, 0x6750}, {0x2206, 0xEE8B}, {0x2206, 0x6632}, {0x2206, 0xEE8A},
+	{0x2206, 0xD448}, {0x2206, 0xEE8A}, {0x2206, 0xD548}, {0x2206, 0xEE8A},
+	{0x2206, 0xD649}, {0x2206, 0xEE8A}, {0x2206, 0xD7F8}, {0x2206, 0xEE8B},
+	{0x2206, 0x85E2}, {0x2206, 0xEE8B}, {0x2206, 0x8700}, {0x2206, 0xEEFF},
+	{0x2206, 0xF600}, {0x2206, 0xEEFF}, {0x2206, 0xF7FC}, {0x2206, 0x04F8},
+	{0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2023}, {0x2206, 0xF620},
+	{0x2206, 0xE48B}, {0x2206, 0x8E02}, {0x2206, 0x2877}, {0x2206, 0x0225},
+	{0x2206, 0xC702}, {0x2206, 0x26A1}, {0x2206, 0x0281}, {0x2206, 0xB302},
+	{0x2206, 0x8496}, {0x2206, 0x0202}, {0x2206, 0xA102}, {0x2206, 0x27F1},
+	{0x2206, 0x0228}, {0x2206, 0xF902}, {0x2206, 0x2AA0}, {0x2206, 0x0282},
+	{0x2206, 0xB8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD21}, {0x2206, 0x08F6},
+	{0x2206, 0x21E4}, {0x2206, 0x8B8E}, {0x2206, 0x0202}, {0x2206, 0x80E0},
+	{0x2206, 0x8B8E}, {0x2206, 0xAD22}, {0x2206, 0x05F6}, {0x2206, 0x22E4},
+	{0x2206, 0x8B8E}, {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2305},
+	{0x2206, 0xF623}, {0x2206, 0xE48B}, {0x2206, 0x8EE0}, {0x2206, 0x8B8E},
+	{0x2206, 0xAD24}, {0x2206, 0x08F6}, {0x2206, 0x24E4}, {0x2206, 0x8B8E},
+	{0x2206, 0x0227}, {0x2206, 0x6AE0}, {0x2206, 0x8B8E}, {0x2206, 0xAD25},
+	{0x2206, 0x05F6}, {0x2206, 0x25E4}, {0x2206, 0x8B8E}, {0x2206, 0xE08B},
+	{0x2206, 0x8EAD}, {0x2206, 0x260B}, {0x2206, 0xF626}, {0x2206, 0xE48B},
+	{0x2206, 0x8E02}, {0x2206, 0x830D}, {0x2206, 0x021D}, {0x2206, 0x6BE0},
+	{0x2206, 0x8B8E}, {0x2206, 0xAD27}, {0x2206, 0x05F6}, {0x2206, 0x27E4},
+	{0x2206, 0x8B8E}, {0x2206, 0x0281}, {0x2206, 0x4402}, {0x2206, 0x045C},
+	{0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B83}, {0x2206, 0xAD23},
+	{0x2206, 0x30E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x2359},
+	{0x2206, 0x02E0}, {0x2206, 0x85EF}, {0x2206, 0xE585}, {0x2206, 0xEFAC},
+	{0x2206, 0x2907}, {0x2206, 0x1F01}, {0x2206, 0x9E51}, {0x2206, 0xAD29},
+	{0x2206, 0x20E0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x06E1},
+	{0x2206, 0x8B84}, {0x2206, 0xAD28}, {0x2206, 0x42E0}, {0x2206, 0x8B85},
+	{0x2206, 0xAD21}, {0x2206, 0x06E1}, {0x2206, 0x8B84}, {0x2206, 0xAD29},
+	{0x2206, 0x36BF}, {0x2206, 0x34BF}, {0x2206, 0x022C}, {0x2206, 0x31AE},
+	{0x2206, 0x2EE0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x10E0},
+	{0x2206, 0x8B84}, {0x2206, 0xF620}, {0x2206, 0xE48B}, {0x2206, 0x84EE},
+	{0x2206, 0x8ADA}, {0x2206, 0x00EE}, {0x2206, 0x8ADB}, {0x2206, 0x00E0},
+	{0x2206, 0x8B85}, {0x2206, 0xAD21}, {0x2206, 0x0CE0}, {0x2206, 0x8B84},
+	{0x2206, 0xF621}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, {0x2206, 0x8B72},
+	{0x2206, 0xFFBF}, {0x2206, 0x34C2}, {0x2206, 0x022C}, {0x2206, 0x31FC},
+	{0x2206, 0x04F8}, {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B85},
+	{0x2206, 0xAD21}, {0x2206, 0x42E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0},
+	{0x2206, 0x2358}, {0x2206, 0xC059}, {0x2206, 0x021E}, {0x2206, 0x01E1},
+	{0x2206, 0x8B72}, {0x2206, 0x1F10}, {0x2206, 0x9E2F}, {0x2206, 0xE48B},
+	{0x2206, 0x72AD}, {0x2206, 0x2123}, {0x2206, 0xE18B}, {0x2206, 0x84F7},
+	{0x2206, 0x29E5}, {0x2206, 0x8B84}, {0x2206, 0xAC27}, {0x2206, 0x10AC},
+	{0x2206, 0x2605}, {0x2206, 0x0205}, {0x2206, 0x23AE}, {0x2206, 0x1602},
+	{0x2206, 0x0535}, {0x2206, 0x0282}, {0x2206, 0x30AE}, {0x2206, 0x0E02},
+	{0x2206, 0x056A}, {0x2206, 0x0282}, {0x2206, 0x75AE}, {0x2206, 0x0602},
+	{0x2206, 0x04DC}, {0x2206, 0x0282}, {0x2206, 0x04EF}, {0x2206, 0x96FE},
+	{0x2206, 0xFC04}, {0x2206, 0xF8F9}, {0x2206, 0xE08B}, {0x2206, 0x87AD},
+	{0x2206, 0x2321}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15},
+	{0x2206, 0xAD26}, {0x2206, 0x18F6}, {0x2206, 0x27E4}, {0x2206, 0xEA14},
+	{0x2206, 0xE5EA}, {0x2206, 0x15F6}, {0x2206, 0x26E4}, {0x2206, 0xEA14},
+	{0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14},
+	{0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8F9},
+	{0x2206, 0xE08B}, {0x2206, 0x87AD}, {0x2206, 0x233A}, {0x2206, 0xAD22},
+	{0x2206, 0x37E0}, {0x2206, 0xE020}, {0x2206, 0xE1E0}, {0x2206, 0x21AC},
+	{0x2206, 0x212E}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15},
+	{0x2206, 0xF627}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15},
+	{0x2206, 0xE2EA}, {0x2206, 0x12E3}, {0x2206, 0xEA13}, {0x2206, 0x5A8F},
+	{0x2206, 0x6A20}, {0x2206, 0xE6EA}, {0x2206, 0x12E7}, {0x2206, 0xEA13},
+	{0x2206, 0xF726}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15},
+	{0x2206, 0xF727}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15},
+	{0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B87},
+	{0x2206, 0xAD23}, {0x2206, 0x38AD}, {0x2206, 0x2135}, {0x2206, 0xE0E0},
+	{0x2206, 0x20E1}, {0x2206, 0xE021}, {0x2206, 0xAC21}, {0x2206, 0x2CE0},
+	{0x2206, 0xEA14}, {0x2206, 0xE1EA}, {0x2206, 0x15F6}, {0x2206, 0x27E4},
+	{0x2206, 0xEA14}, {0x2206, 0xE5EA}, {0x2206, 0x15E2}, {0x2206, 0xEA12},
+	{0x2206, 0xE3EA}, {0x2206, 0x135A}, {0x2206, 0x8FE6}, {0x2206, 0xEA12},
+	{0x2206, 0xE7EA}, {0x2206, 0x13F7}, {0x2206, 0x26E4}, {0x2206, 0xEA14},
+	{0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14},
+	{0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA},
+	{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2146},
+	{0x2206, 0xE0E0}, {0x2206, 0x22E1}, {0x2206, 0xE023}, {0x2206, 0x58C0},
+	{0x2206, 0x5902}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x651F},
+	{0x2206, 0x109E}, {0x2206, 0x33E4}, {0x2206, 0x8B65}, {0x2206, 0xAD21},
+	{0x2206, 0x22AD}, {0x2206, 0x272A}, {0x2206, 0xD400}, {0x2206, 0x01BF},
+	{0x2206, 0x34F2}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, {0x2206, 0x34F5},
+	{0x2206, 0x022C}, {0x2206, 0xE0E0}, {0x2206, 0x8B67}, {0x2206, 0x1B10},
+	{0x2206, 0xAA14}, {0x2206, 0xE18B}, {0x2206, 0x660D}, {0x2206, 0x1459},
+	{0x2206, 0x0FAE}, {0x2206, 0x05E1}, {0x2206, 0x8B66}, {0x2206, 0x590F},
+	{0x2206, 0xBF85}, {0x2206, 0x6102}, {0x2206, 0x2CA2}, {0x2206, 0xEF96},
+	{0x2206, 0xFEFC}, {0x2206, 0x04F8}, {0x2206, 0xF9FA}, {0x2206, 0xFBEF},
+	{0x2206, 0x79E2}, {0x2206, 0x8AD2}, {0x2206, 0xAC19}, {0x2206, 0x2DE0},
+	{0x2206, 0xE036}, {0x2206, 0xE1E0}, {0x2206, 0x37EF}, {0x2206, 0x311F},
+	{0x2206, 0x325B}, {0x2206, 0x019E}, {0x2206, 0x1F7A}, {0x2206, 0x0159},
+	{0x2206, 0x019F}, {0x2206, 0x0ABF}, {0x2206, 0x348E}, {0x2206, 0x022C},
+	{0x2206, 0x31F6}, {0x2206, 0x06AE}, {0x2206, 0x0FF6}, {0x2206, 0x0302},
+	{0x2206, 0x0470}, {0x2206, 0xF703}, {0x2206, 0xF706}, {0x2206, 0xBF34},
+	{0x2206, 0x9302}, {0x2206, 0x2C31}, {0x2206, 0xAC1A}, {0x2206, 0x25E0},
+	{0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x23EF}, {0x2206, 0x300D},
+	{0x2206, 0x311F}, {0x2206, 0x325B}, {0x2206, 0x029E}, {0x2206, 0x157A},
+	{0x2206, 0x0258}, {0x2206, 0xC4A0}, {0x2206, 0x0408}, {0x2206, 0xBF34},
+	{0x2206, 0x9E02}, {0x2206, 0x2C31}, {0x2206, 0xAE06}, {0x2206, 0xBF34},
+	{0x2206, 0x9C02}, {0x2206, 0x2C31}, {0x2206, 0xAC1B}, {0x2206, 0x4AE0},
+	{0x2206, 0xE012}, {0x2206, 0xE1E0}, {0x2206, 0x13EF}, {0x2206, 0x300D},
+	{0x2206, 0x331F}, {0x2206, 0x325B}, {0x2206, 0x1C9E}, {0x2206, 0x3AEF},
+	{0x2206, 0x325B}, {0x2206, 0x1C9F}, {0x2206, 0x09BF}, {0x2206, 0x3498},
+	{0x2206, 0x022C}, {0x2206, 0x3102}, {0x2206, 0x83C5}, {0x2206, 0x5A03},
+	{0x2206, 0x0D03}, {0x2206, 0x581C}, {0x2206, 0x1E20}, {0x2206, 0x0207},
+	{0x2206, 0xA0A0}, {0x2206, 0x000E}, {0x2206, 0x0284}, {0x2206, 0x17AD},
+	{0x2206, 0x1817}, {0x2206, 0xBF34}, {0x2206, 0x9A02}, {0x2206, 0x2C31},
+	{0x2206, 0xAE0F}, {0x2206, 0xBF34}, {0x2206, 0xC802}, {0x2206, 0x2C31},
+	{0x2206, 0xBF34}, {0x2206, 0xC502}, {0x2206, 0x2C31}, {0x2206, 0x0284},
+	{0x2206, 0x52E6}, {0x2206, 0x8AD2}, {0x2206, 0xEF97}, {0x2206, 0xFFFE},
+	{0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xBF34}, {0x2206, 0xDA02},
+	{0x2206, 0x2CE0}, {0x2206, 0xE58A}, {0x2206, 0xD3BF}, {0x2206, 0x34D4},
+	{0x2206, 0x022C}, {0x2206, 0xE00C}, {0x2206, 0x1159}, {0x2206, 0x02E0},
+	{0x2206, 0x8AD3}, {0x2206, 0x1E01}, {0x2206, 0xE48A}, {0x2206, 0xD3D1},
+	{0x2206, 0x00BF}, {0x2206, 0x34DA}, {0x2206, 0x022C}, {0x2206, 0xA2D1},
+	{0x2206, 0x01BF}, {0x2206, 0x34D4}, {0x2206, 0x022C}, {0x2206, 0xA2BF},
+	{0x2206, 0x34CB}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, {0x2206, 0x8ACE},
+	{0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CE0}, {0x2206, 0xE58A},
+	{0x2206, 0xCFBF}, {0x2206, 0x8564}, {0x2206, 0x022C}, {0x2206, 0xE0E5},
+	{0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, {0x2206, 0x2CE0},
+	{0x2206, 0xE58A}, {0x2206, 0xD1FC}, {0x2206, 0x04F8}, {0x2206, 0xE18A},
+	{0x2206, 0xD1BF}, {0x2206, 0x856A}, {0x2206, 0x022C}, {0x2206, 0xA2E1},
+	{0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2},
+	{0x2206, 0xE18A}, {0x2206, 0xCFBF}, {0x2206, 0x8567}, {0x2206, 0x022C},
+	{0x2206, 0xA2E1}, {0x2206, 0x8ACE}, {0x2206, 0xBF34}, {0x2206, 0xCB02},
+	{0x2206, 0x2CA2}, {0x2206, 0xE18A}, {0x2206, 0xD3BF}, {0x2206, 0x34DA},
+	{0x2206, 0x022C}, {0x2206, 0xA2E1}, {0x2206, 0x8AD3}, {0x2206, 0x0D11},
+	{0x2206, 0xBF34}, {0x2206, 0xD402}, {0x2206, 0x2CA2}, {0x2206, 0xFC04},
+	{0x2206, 0xF9A0}, {0x2206, 0x0405}, {0x2206, 0xE38A}, {0x2206, 0xD4AE},
+	{0x2206, 0x13A0}, {0x2206, 0x0805}, {0x2206, 0xE38A}, {0x2206, 0xD5AE},
+	{0x2206, 0x0BA0}, {0x2206, 0x0C05}, {0x2206, 0xE38A}, {0x2206, 0xD6AE},
+	{0x2206, 0x03E3}, {0x2206, 0x8AD7}, {0x2206, 0xEF13}, {0x2206, 0xBF34},
+	{0x2206, 0xCB02}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, {0x2206, 0x0D11},
+	{0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CA2}, {0x2206, 0xEF13},
+	{0x2206, 0x0D14}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2},
+	{0x2206, 0xEF13}, {0x2206, 0x0D17}, {0x2206, 0xBF85}, {0x2206, 0x6A02},
+	{0x2206, 0x2CA2}, {0x2206, 0xFD04}, {0x2206, 0xF8E0}, {0x2206, 0x8B85},
+	{0x2206, 0xAD27}, {0x2206, 0x2DE0}, {0x2206, 0xE036}, {0x2206, 0xE1E0},
+	{0x2206, 0x37E1}, {0x2206, 0x8B73}, {0x2206, 0x1F10}, {0x2206, 0x9E20},
+	{0x2206, 0xE48B}, {0x2206, 0x73AC}, {0x2206, 0x200B}, {0x2206, 0xAC21},
+	{0x2206, 0x0DAC}, {0x2206, 0x250F}, {0x2206, 0xAC27}, {0x2206, 0x0EAE},
+	{0x2206, 0x0F02}, {0x2206, 0x84CC}, {0x2206, 0xAE0A}, {0x2206, 0x0284},
+	{0x2206, 0xD1AE}, {0x2206, 0x05AE}, {0x2206, 0x0302}, {0x2206, 0x84D8},
+	{0x2206, 0xFC04}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0x0402},
+	{0x2206, 0x84E5}, {0x2206, 0x0285}, {0x2206, 0x2804}, {0x2206, 0x0285},
+	{0x2206, 0x4904}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0xEE8B},
+	{0x2206, 0x6902}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B85},
+	{0x2206, 0xAD26}, {0x2206, 0x38D0}, {0x2206, 0x0B02}, {0x2206, 0x2B4D},
+	{0x2206, 0x5882}, {0x2206, 0x7882}, {0x2206, 0x9F2D}, {0x2206, 0xE08B},
+	{0x2206, 0x68E1}, {0x2206, 0x8B69}, {0x2206, 0x1F10}, {0x2206, 0x9EC8},
+	{0x2206, 0x10E4}, {0x2206, 0x8B68}, {0x2206, 0xE0E0}, {0x2206, 0x00E1},
+	{0x2206, 0xE001}, {0x2206, 0xF727}, {0x2206, 0xE4E0}, {0x2206, 0x00E5},
+	{0x2206, 0xE001}, {0x2206, 0xE2E0}, {0x2206, 0x20E3}, {0x2206, 0xE021},
+	{0x2206, 0xAD30}, {0x2206, 0xF7F6}, {0x2206, 0x27E4}, {0x2206, 0xE000},
+	{0x2206, 0xE5E0}, {0x2206, 0x01FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA},
+	{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2212},
+	{0x2206, 0xE0E0}, {0x2206, 0x14E1}, {0x2206, 0xE015}, {0x2206, 0xAD26},
+	{0x2206, 0x9CE1}, {0x2206, 0x85E0}, {0x2206, 0xBF85}, {0x2206, 0x6D02},
+	{0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x04F8},
+	{0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B86}, {0x2206, 0xAD22},
+	{0x2206, 0x09E1}, {0x2206, 0x85E1}, {0x2206, 0xBF85}, {0x2206, 0x6D02},
+	{0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x0464},
+	{0x2206, 0xE48C}, {0x2206, 0xFDE4}, {0x2206, 0x80CA}, {0x2206, 0xE480},
+	{0x2206, 0x66E0}, {0x2206, 0x8E70}, {0x2206, 0xE076}, {0x2205, 0xE142},
+	{0x2206, 0x0701}, {0x2205, 0xE140}, {0x2206, 0x0405}, {0x220F, 0x0000},
+	{0x221F, 0x0000}, {0x2200, 0x1340}, {0x133E, 0x000E}, {0x133F, 0x0010},
+	{0x13EB, 0x11BB}
+};
+
+static const struct rtl8367b_initval rtl8367r_vb_initvals_1[] = {
+	{0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x1305, 0xC000}, {0x121E, 0x03CA},
+	{0x1233, 0x0352}, {0x1234, 0x0064}, {0x1237, 0x0096}, {0x1238, 0x0078},
+	{0x1239, 0x0084}, {0x123A, 0x0030}, {0x205F, 0x0002}, {0x2059, 0x1A00},
+	{0x205F, 0x0000}, {0x207F, 0x0002}, {0x2077, 0x0000}, {0x2078, 0x0000},
+	{0x2079, 0x0000}, {0x207A, 0x0000}, {0x207B, 0x0000}, {0x207F, 0x0000},
+	{0x205F, 0x0002}, {0x2053, 0x0000}, {0x2054, 0x0000}, {0x2055, 0x0000},
+	{0x2056, 0x0000}, {0x2057, 0x0000}, {0x205F, 0x0000}, {0x133F, 0x0030},
+	{0x133E, 0x000E}, {0x221F, 0x0005}, {0x2205, 0x8B86}, {0x2206, 0x800E},
+	{0x221F, 0x0000}, {0x133F, 0x0010}, {0x12A3, 0x2200}, {0x6107, 0xE58B},
+	{0x6103, 0xA970}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, {0x0058, 0x0F00},
+	{0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x133F, 0x0030}, {0x133E, 0x000E},
+	{0x221F, 0x0005}, {0x2205, 0x8B6E}, {0x2206, 0x0000}, {0x220F, 0x0100},
+	{0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8000}, {0x2206, 0x0280},
+	{0x2206, 0x2BF7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080},
+	{0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201},
+	{0x2206, 0x6602}, {0x2206, 0x8044}, {0x2206, 0x0201}, {0x2206, 0x7CE0},
+	{0x2206, 0x8B8C}, {0x2206, 0xE18B}, {0x2206, 0x8D1E}, {0x2206, 0x01E1},
+	{0x2206, 0x8B8E}, {0x2206, 0x1E01}, {0x2206, 0xA000}, {0x2206, 0xE4AE},
+	{0x2206, 0xD8EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, {0x2206, 0x85C1},
+	{0x2206, 0x00EE}, {0x2206, 0x8AFC}, {0x2206, 0x07EE}, {0x2206, 0x8AFD},
+	{0x2206, 0x73EE}, {0x2206, 0xFFF6}, {0x2206, 0x00EE}, {0x2206, 0xFFF7},
+	{0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD20},
+	{0x2206, 0x0302}, {0x2206, 0x8050}, {0x2206, 0xFC04}, {0x2206, 0xF8F9},
+	{0x2206, 0xE08B}, {0x2206, 0x85AD}, {0x2206, 0x2548}, {0x2206, 0xE08A},
+	{0x2206, 0xE4E1}, {0x2206, 0x8AE5}, {0x2206, 0x7C00}, {0x2206, 0x009E},
+	{0x2206, 0x35EE}, {0x2206, 0x8AE4}, {0x2206, 0x00EE}, {0x2206, 0x8AE5},
+	{0x2206, 0x00E0}, {0x2206, 0x8AFC}, {0x2206, 0xE18A}, {0x2206, 0xFDE2},
+	{0x2206, 0x85C0}, {0x2206, 0xE385}, {0x2206, 0xC102}, {0x2206, 0x2DAC},
+	{0x2206, 0xAD20}, {0x2206, 0x12EE}, {0x2206, 0x8AE4}, {0x2206, 0x03EE},
+	{0x2206, 0x8AE5}, {0x2206, 0xB7EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE},
+	{0x2206, 0x85C1}, {0x2206, 0x00AE}, {0x2206, 0x1115}, {0x2206, 0xE685},
+	{0x2206, 0xC0E7}, {0x2206, 0x85C1}, {0x2206, 0xAE08}, {0x2206, 0xEE85},
+	{0x2206, 0xC000}, {0x2206, 0xEE85}, {0x2206, 0xC100}, {0x2206, 0xFDFC},
+	{0x2206, 0x0400}, {0x2205, 0xE142}, {0x2206, 0x0701}, {0x2205, 0xE140},
+	{0x2206, 0x0405}, {0x220F, 0x0000}, {0x221F, 0x0000}, {0x133E, 0x000E},
+	{0x133F, 0x0010}, {0x13EB, 0x11BB}, {0x207F, 0x0002}, {0x2073, 0x1D22},
+	{0x207F, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x2200, 0x1340},
+	{0x133E, 0x000E}, {0x133F, 0x0010},
+};
+
+static const struct rtl8367b_initval rtl8367c_initvals[] = {
+	{0x13c2, 0x0000}, {0x0018, 0x0f00}, {0x0038, 0x0f00}, {0x0058, 0x0f00},
+	{0x0078, 0x0f00}, {0x0098, 0x0f00}, {0x1d15, 0x0a69}, {0x2000, 0x1340},
+	{0x2020, 0x1340}, {0x2040, 0x1340}, {0x2060, 0x1340}, {0x2080, 0x1340},
+	{0x13eb, 0x15bb}, {0x1303, 0x06d6}, {0x1304, 0x0700}, {0x13E2, 0x003F},
+	{0x13F9, 0x0090}, {0x121e, 0x03CA}, {0x1233, 0x0352}, {0x1237, 0x00a0},
+	{0x123a, 0x0030}, {0x1239, 0x0084}, {0x0301, 0x1000}, {0x1349, 0x001F},
+	{0x18e0, 0x4004}, {0x122b, 0x641c}, {0x1305, 0xc000}, {0x1200, 0x7fcb},
+	{0x0884, 0x0003}, {0x06eb, 0x0001}, {0x00cf, 0xffff}, {0x00d0, 0x0007},
+	{0x00ce, 0x48b0}, {0x00ce, 0x48b0}, {0x0398, 0xffff}, {0x0399, 0x0007},
+	{0x0300, 0x0001}, {0x03fa, 0x0007}, {0x08c8, 0x00c0}, {0x0a30, 0x020e},
+	{0x0800, 0x0000}, {0x0802, 0x0000}, {0x09da, 0x0017}, {0x1d32, 0x0002},
+};
+
+static int rtl8367b_write_initvals(struct rtl8366_smi *smi,
+				  const struct rtl8367b_initval *initvals,
+				  int count)
+{
+	int err;
+	int i;
+
+	for (i = 0; i < count; i++)
+		REG_WR(smi, initvals[i].reg, initvals[i].val);
+
+	return 0;
+}
+
+static int rtl8367b_read_phy_reg(struct rtl8366_smi *smi,
+				u32 phy_addr, u32 phy_reg, u32 *val)
+{
+	int timeout;
+	u32 data;
+	int err;
+
+	if (phy_addr > RTL8367B_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	if (phy_reg > RTL8367B_PHY_REG_MAX)
+		return -EINVAL;
+
+	REG_RD(smi, RTL8367B_IA_STATUS_REG, &data);
+	if (data & RTL8367B_IA_STATUS_PHY_BUSY)
+		return -ETIMEDOUT;
+
+	/* prepare address */
+	REG_WR(smi, RTL8367B_IA_ADDRESS_REG,
+	       RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg));
+
+	/* send read command */
+	REG_WR(smi, RTL8367B_IA_CTRL_REG,
+	       RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_READ);
+
+	timeout = 5;
+	do {
+		REG_RD(smi, RTL8367B_IA_STATUS_REG, &data);
+		if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0)
+			break;
+
+		if (timeout--) {
+			dev_err(smi->parent, "phy read timed out\n");
+			return -ETIMEDOUT;
+		}
+
+		udelay(1);
+	} while (1);
+
+	/* read data */
+	REG_RD(smi, RTL8367B_IA_READ_DATA_REG, val);
+
+	dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n",
+		phy_addr, phy_reg, *val);
+	return 0;
+}
+
+static int rtl8367b_write_phy_reg(struct rtl8366_smi *smi,
+				 u32 phy_addr, u32 phy_reg, u32 val)
+{
+	int timeout;
+	u32 data;
+	int err;
+
+	dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n",
+		phy_addr, phy_reg, val);
+
+	if (phy_addr > RTL8367B_PHY_ADDR_MAX)
+		return -EINVAL;
+
+	if (phy_reg > RTL8367B_PHY_REG_MAX)
+		return -EINVAL;
+
+	REG_RD(smi, RTL8367B_IA_STATUS_REG, &data);
+	if (data & RTL8367B_IA_STATUS_PHY_BUSY)
+		return -ETIMEDOUT;
+
+	/* preapre data */
+	REG_WR(smi, RTL8367B_IA_WRITE_DATA_REG, val);
+
+	/* prepare address */
+	REG_WR(smi, RTL8367B_IA_ADDRESS_REG,
+	       RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg));
+
+	/* send write command */
+	REG_WR(smi, RTL8367B_IA_CTRL_REG,
+	       RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_WRITE);
+
+	timeout = 5;
+	do {
+		REG_RD(smi, RTL8367B_IA_STATUS_REG, &data);
+		if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0)
+			break;
+
+		if (timeout--) {
+			dev_err(smi->parent, "phy write timed out\n");
+			return -ETIMEDOUT;
+		}
+
+		udelay(1);
+	} while (1);
+
+	return 0;
+}
+
+static int rtl8367b_init_regs(struct rtl8366_smi *smi)
+{
+	const struct rtl8367b_initval *initvals;
+	u32 chip_num;
+	u32 chip_ver;
+	u32 rlvid;
+	int count;
+	int err;
+
+	REG_WR(smi, RTL8367B_RTL_MAGIC_ID_REG, RTL8367B_RTL_MAGIC_ID_VAL);
+	REG_RD(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num);
+	REG_RD(smi, RTL8367B_CHIP_VER_REG, &chip_ver);
+
+	rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) &
+		RTL8367B_CHIP_VER_RLVID_MASK;
+
+	if (smi->flags & RTL8367B_FLAG_MATCH_CHIP_NUM) {
+		switch (chip_num) {
+		case 0x0276:
+		case 0x0597:
+		case 0x6367:
+			initvals = rtl8367c_initvals;
+			count = ARRAY_SIZE(rtl8367c_initvals);
+			break;
+		default:
+			return -ENODEV;
+		}
+	} else {
+		switch (rlvid) {
+		case 0:
+			initvals = rtl8367r_vb_initvals_0;
+			count = ARRAY_SIZE(rtl8367r_vb_initvals_0);
+			break;
+
+		case 1:
+			initvals = rtl8367r_vb_initvals_1;
+			count = ARRAY_SIZE(rtl8367r_vb_initvals_1);
+			break;
+
+		default:
+			return -ENODEV;
+		}
+	}
+	/* TODO: disable RLTP */
+
+	return rtl8367b_write_initvals(smi, initvals, count);
+}
+
+static int rtl8367b_reset_chip(struct rtl8366_smi *smi)
+{
+	int timeout = 10;
+	int err;
+	u32 data;
+
+	REG_WR(smi, RTL8367B_CHIP_RESET_REG, RTL8367B_CHIP_RESET_HW);
+	msleep(RTL8367B_RESET_DELAY);
+
+	do {
+		REG_RD(smi, RTL8367B_CHIP_RESET_REG, &data);
+		if (!(data & RTL8367B_CHIP_RESET_HW))
+			break;
+
+		msleep(1);
+	} while (--timeout);
+
+	if (!timeout) {
+		dev_err(smi->parent, "chip reset timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int rtl8367b_extif_set_mode(struct rtl8366_smi *smi, int id,
+				   enum rtl8367_extif_mode mode)
+{
+	int err;
+
+	/* set port mode */
+	switch (mode) {
+	case RTL8367_EXTIF_MODE_RGMII:
+		REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG,
+			RTL8367B_DEBUG0_SEL33(id),
+			RTL8367B_DEBUG0_SEL33(id));
+		if (id <= 1) {
+			REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG,
+				RTL8367B_DEBUG0_DRI(id) |
+					RTL8367B_DEBUG0_DRI_RG(id) |
+					RTL8367B_DEBUG0_SLR(id),
+				RTL8367B_DEBUG0_DRI_RG(id) |
+					RTL8367B_DEBUG0_SLR(id));
+			REG_RMW(smi, RTL8367B_CHIP_DEBUG1_REG,
+				RTL8367B_DEBUG1_DN_MASK(id) |
+					RTL8367B_DEBUG1_DP_MASK(id),
+				(7 << RTL8367B_DEBUG1_DN_SHIFT(id)) |
+					(7 << RTL8367B_DEBUG1_DP_SHIFT(id)));
+		} else {
+			REG_RMW(smi, RTL8367B_CHIP_DEBUG2_REG,
+				RTL8367B_DEBUG2_DRI_EXT2 |
+					RTL8367B_DEBUG2_DRI_EXT2_RG |
+					RTL8367B_DEBUG2_SLR_EXT2 |
+					RTL8367B_DEBUG2_RG2_DN_MASK |
+					RTL8367B_DEBUG2_RG2_DP_MASK,
+				RTL8367B_DEBUG2_DRI_EXT2_RG |
+					RTL8367B_DEBUG2_SLR_EXT2 |
+					(7 << RTL8367B_DEBUG2_RG2_DN_SHIFT) |
+					(7 << RTL8367B_DEBUG2_RG2_DP_SHIFT));
+		}
+		break;
+
+	case RTL8367_EXTIF_MODE_TMII_MAC:
+	case RTL8367_EXTIF_MODE_TMII_PHY:
+		REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, BIT(id), BIT(id));
+		break;
+
+	case RTL8367_EXTIF_MODE_GMII:
+		REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG,
+			RTL8367B_DEBUG0_SEL33(id),
+			RTL8367B_DEBUG0_SEL33(id));
+		REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), BIT(6));
+		break;
+
+	case RTL8367_EXTIF_MODE_MII_MAC:
+	case RTL8367_EXTIF_MODE_MII_PHY:
+	case RTL8367_EXTIF_MODE_DISABLED:
+		REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, BIT(id), 0);
+		REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), 0);
+		break;
+
+	default:
+		dev_err(smi->parent,
+			"invalid mode for external interface %d\n", id);
+		return -EINVAL;
+	}
+
+	if (id <= 1)
+		REG_RMW(smi, RTL8367B_DIS_REG,
+			RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id),
+			mode << RTL8367B_DIS_RGMII_SHIFT(id));
+	else
+		REG_RMW(smi, RTL8367B_DIS2_REG,
+			RTL8367B_DIS2_RGMII_MASK << RTL8367B_DIS2_RGMII_SHIFT,
+			mode << RTL8367B_DIS2_RGMII_SHIFT);
+
+	return 0;
+}
+
+static int rtl8367b_extif_set_force(struct rtl8366_smi *smi, int id,
+				    struct rtl8367_port_ability *pa)
+{
+	u32 mask;
+	u32 val;
+	int err;
+
+	mask = (RTL8367B_DI_FORCE_MODE |
+		RTL8367B_DI_FORCE_NWAY |
+		RTL8367B_DI_FORCE_TXPAUSE |
+		RTL8367B_DI_FORCE_RXPAUSE |
+		RTL8367B_DI_FORCE_LINK |
+		RTL8367B_DI_FORCE_DUPLEX |
+		RTL8367B_DI_FORCE_SPEED_MASK);
+
+	val = pa->speed;
+	val |= pa->force_mode ? RTL8367B_DI_FORCE_MODE : 0;
+	val |= pa->nway ? RTL8367B_DI_FORCE_NWAY : 0;
+	val |= pa->txpause ? RTL8367B_DI_FORCE_TXPAUSE : 0;
+	val |= pa->rxpause ? RTL8367B_DI_FORCE_RXPAUSE : 0;
+	val |= pa->link ? RTL8367B_DI_FORCE_LINK : 0;
+	val |= pa->duplex ? RTL8367B_DI_FORCE_DUPLEX : 0;
+
+	REG_RMW(smi, RTL8367B_DI_FORCE_REG(id), mask, val);
+
+	return 0;
+}
+
+static int rtl8367b_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id,
+					 unsigned txdelay, unsigned rxdelay)
+{
+	u32 mask;
+	u32 val;
+	int err;
+
+	mask = (RTL8367B_EXT_RGMXF_RXDELAY_MASK |
+		(RTL8367B_EXT_RGMXF_TXDELAY_MASK <<
+			RTL8367B_EXT_RGMXF_TXDELAY_SHIFT));
+
+	val = rxdelay;
+	val |= txdelay << RTL8367B_EXT_RGMXF_TXDELAY_SHIFT;
+
+	REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), mask, val);
+
+	return 0;
+}
+
+static int rtl8367b_extif_init(struct rtl8366_smi *smi, int id,
+			       struct rtl8367_extif_config *cfg)
+{
+	enum rtl8367_extif_mode mode;
+	int err;
+
+	mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED;
+
+	err = rtl8367b_extif_set_mode(smi, id, mode);
+	if (err)
+		return err;
+
+	if (mode != RTL8367_EXTIF_MODE_DISABLED) {
+		err = rtl8367b_extif_set_force(smi, id, &cfg->ability);
+		if (err)
+			return err;
+
+		err = rtl8367b_extif_set_rgmii_delay(smi, id, cfg->txdelay,
+						     cfg->rxdelay);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id,
+				  const char *name)
+{
+	struct rtl8367_extif_config *cfg;
+	const __be32 *prop;
+	int size;
+	int err;
+
+	prop = of_get_property(smi->parent->of_node, name, &size);
+	if (!prop)
+		return rtl8367b_extif_init(smi, id, NULL);
+
+	if (size != (9 * sizeof(*prop))) {
+		dev_err(smi->parent, "%s property is invalid\n", name);
+		return -EINVAL;
+	}
+
+	cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL);
+	if (!cfg)
+		return -ENOMEM;
+
+	cfg->txdelay = be32_to_cpup(prop++);
+	cfg->rxdelay = be32_to_cpup(prop++);
+	cfg->mode = be32_to_cpup(prop++);
+	cfg->ability.force_mode = be32_to_cpup(prop++);
+	cfg->ability.txpause = be32_to_cpup(prop++);
+	cfg->ability.rxpause = be32_to_cpup(prop++);
+	cfg->ability.link = be32_to_cpup(prop++);
+	cfg->ability.duplex = be32_to_cpup(prop++);
+	cfg->ability.speed = be32_to_cpup(prop++);
+
+	err = rtl8367b_extif_init(smi, id, cfg);
+	kfree(cfg);
+
+	return err;
+}
+#else
+static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id,
+				  const char *name)
+{
+	return -EINVAL;
+}
+#endif
+
+static int rtl8367b_setup(struct rtl8366_smi *smi)
+{
+	struct rtl8367_platform_data *pdata;
+	int err;
+	int i;
+
+	pdata = smi->parent->platform_data;
+
+	err = rtl8367b_init_regs(smi);
+	if (err)
+		return err;
+
+	/* initialize external interfaces */
+	if (smi->parent->of_node) {
+		err = rtl8367b_extif_init_of(smi, 0, "realtek,extif0");
+		if (err)
+			return err;
+
+		err = rtl8367b_extif_init_of(smi, 1, "realtek,extif1");
+		if (err)
+			return err;
+
+		err = rtl8367b_extif_init_of(smi, 2, "realtek,extif2");
+		if (err)
+			return err;
+	} else {
+		err = rtl8367b_extif_init(smi, 0, pdata->extif0_cfg);
+		if (err)
+			return err;
+
+		err = rtl8367b_extif_init(smi, 1, pdata->extif1_cfg);
+		if (err)
+			return err;
+	}
+
+	/* set maximum packet length to 1536 bytes */
+	REG_RMW(smi, RTL8367B_SWC0_REG, RTL8367B_SWC0_MAX_LENGTH_MASK,
+		RTL8367B_SWC0_MAX_LENGTH_1536);
+
+	/*
+	 * discard VLAN tagged packets if the port is not a member of
+	 * the VLAN with which the packets is associated.
+	 */
+	REG_WR(smi, RTL8367B_VLAN_INGRESS_REG, RTL8367B_PORTS_ALL);
+
+	/*
+	 * Setup egress tag mode for each port.
+	 */
+	for (i = 0; i < RTL8367B_NUM_PORTS; i++)
+		REG_RMW(smi,
+			RTL8367B_PORT_MISC_CFG_REG(i),
+			RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK <<
+				RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT,
+			RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL <<
+				RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT);
+
+	for (i = 0; i < RTL8367B_NUM_PHYS; i++) {
+		int data;
+		rtl8367b_read_phy_reg(smi, i, MII_BMCR, &data);
+		/* release phy power down */
+		data &= ~BMCR_PDOWN;
+		/* restart auto negotiation */
+		data |= BMCR_ANRESTART;
+		rtl8367b_write_phy_reg(smi, i, MII_BMCR, data);
+	}
+
+	return 0;
+}
+
+static int rtl8367b_get_mib_counter(struct rtl8366_smi *smi, int counter,
+				    int port, unsigned long long *val)
+{
+	struct rtl8366_mib_counter *mib;
+	int offset;
+	int i;
+	int err;
+	u32 addr, data;
+	u64 mibvalue;
+
+	if (port > RTL8367B_NUM_PORTS ||
+	    counter >= RTL8367B_NUM_MIB_COUNTERS)
+		return -EINVAL;
+
+	mib = &rtl8367b_mib_counters[counter];
+	addr = RTL8367B_MIB_COUNTER_PORT_OFFSET * port + mib->offset;
+
+	/*
+	 * Writing access counter address first
+	 * then ASIC will prepare 64bits counter wait for being retrived
+	 */
+	REG_WR(smi, RTL8367B_MIB_ADDRESS_REG, addr >> 2);
+
+	/* read MIB control register */
+	REG_RD(smi, RTL8367B_MIB_CTRL0_REG(0), &data);
+
+	if (data & RTL8367B_MIB_CTRL0_BUSY_MASK)
+		return -EBUSY;
+
+	if (data & RTL8367B_MIB_CTRL0_RESET_MASK)
+		return -EIO;
+
+	if (mib->length == 4)
+		offset = 3;
+	else
+		offset = (mib->offset + 1) % 4;
+
+	mibvalue = 0;
+	for (i = 0; i < mib->length; i++) {
+		REG_RD(smi, RTL8367B_MIB_COUNTER_REG(offset - i), &data);
+		mibvalue = (mibvalue << 16) | (data & 0xFFFF);
+	}
+
+	*val = mibvalue;
+	return 0;
+}
+
+static int rtl8367b_get_vlan_4k(struct rtl8366_smi *smi, u32 vid,
+				struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[RTL8367B_TA_VLAN_NUM_WORDS];
+	int err;
+	int i;
+
+	memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k));
+
+	if (vid >= RTL8367B_NUM_VIDS)
+		return -EINVAL;
+
+	/* write VID */
+	REG_WR(smi, RTL8367B_TA_ADDR_REG, vid);
+
+	/* write table access control word */
+	REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_READ);
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_RD(smi, RTL8367B_TA_RDDATA_REG(i), &data[i]);
+
+	vlan4k->vid = vid;
+	vlan4k->member = (data[0] >> RTL8367B_TA_VLAN0_MEMBER_SHIFT) &
+			 RTL8367B_TA_VLAN0_MEMBER_MASK;
+	vlan4k->untag = (data[0] >> RTL8367B_TA_VLAN0_UNTAG_SHIFT) &
+			RTL8367B_TA_VLAN0_UNTAG_MASK;
+	vlan4k->fid = (data[1] >> RTL8367B_TA_VLAN1_FID_SHIFT) &
+		      RTL8367B_TA_VLAN1_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8367b_set_vlan_4k(struct rtl8366_smi *smi,
+				const struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[RTL8367B_TA_VLAN_NUM_WORDS];
+	int err;
+	int i;
+
+	if (vlan4k->vid >= RTL8367B_NUM_VIDS ||
+	    vlan4k->member > RTL8367B_TA_VLAN0_MEMBER_MASK ||
+	    vlan4k->untag > RTL8367B_UNTAG_MASK ||
+	    vlan4k->fid > RTL8367B_FIDMAX)
+		return -EINVAL;
+
+	memset(data, 0, sizeof(data));
+
+	data[0] = (vlan4k->member & RTL8367B_TA_VLAN0_MEMBER_MASK) <<
+		  RTL8367B_TA_VLAN0_MEMBER_SHIFT;
+	data[0] |= (vlan4k->untag & RTL8367B_TA_VLAN0_UNTAG_MASK) <<
+		   RTL8367B_TA_VLAN0_UNTAG_SHIFT;
+	data[1] = (vlan4k->fid & RTL8367B_TA_VLAN1_FID_MASK) <<
+		  RTL8367B_TA_VLAN1_FID_SHIFT;
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_WR(smi, RTL8367B_TA_WRDATA_REG(i), data[i]);
+
+	/* write VID */
+	REG_WR(smi, RTL8367B_TA_ADDR_REG,
+	       vlan4k->vid & RTL8367B_TA_VLAN_VID_MASK);
+
+	/* write table access control word */
+	REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_WRITE);
+
+	return 0;
+}
+
+static int rtl8367b_get_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[RTL8367B_VLAN_MC_NUM_WORDS];
+	int err;
+	int i;
+
+	memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc));
+
+	if (index >= RTL8367B_NUM_VLANS)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_RD(smi, RTL8367B_VLAN_MC_BASE(index) + i, &data[i]);
+
+	vlanmc->member = (data[0] >> RTL8367B_VLAN_MC0_MEMBER_SHIFT) &
+			 RTL8367B_VLAN_MC0_MEMBER_MASK;
+	vlanmc->fid = (data[1] >> RTL8367B_VLAN_MC1_FID_SHIFT) &
+		      RTL8367B_VLAN_MC1_FID_MASK;
+	vlanmc->vid = (data[3] >> RTL8367B_VLAN_MC3_EVID_SHIFT) &
+		      RTL8367B_VLAN_MC3_EVID_MASK;
+
+	return 0;
+}
+
+static int rtl8367b_set_vlan_mc(struct rtl8366_smi *smi, u32 index,
+				const struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[RTL8367B_VLAN_MC_NUM_WORDS];
+	int err;
+	int i;
+
+	if (index >= RTL8367B_NUM_VLANS ||
+	    vlanmc->vid >= RTL8367B_NUM_VIDS ||
+	    vlanmc->priority > RTL8367B_PRIORITYMAX ||
+	    vlanmc->member > RTL8367B_VLAN_MC0_MEMBER_MASK ||
+	    vlanmc->untag > RTL8367B_UNTAG_MASK ||
+	    vlanmc->fid > RTL8367B_FIDMAX)
+		return -EINVAL;
+
+	data[0] = (vlanmc->member & RTL8367B_VLAN_MC0_MEMBER_MASK) <<
+		  RTL8367B_VLAN_MC0_MEMBER_SHIFT;
+	data[1] = (vlanmc->fid & RTL8367B_VLAN_MC1_FID_MASK) <<
+		  RTL8367B_VLAN_MC1_FID_SHIFT;
+	data[2] = 0;
+	data[3] = (vlanmc->vid & RTL8367B_VLAN_MC3_EVID_MASK) <<
+		   RTL8367B_VLAN_MC3_EVID_SHIFT;
+
+	for (i = 0; i < ARRAY_SIZE(data); i++)
+		REG_WR(smi, RTL8367B_VLAN_MC_BASE(index) + i, data[i]);
+
+	return 0;
+}
+
+static int rtl8367b_get_mc_index(struct rtl8366_smi *smi, int port, int *val)
+{
+	u32 data;
+	int err;
+
+	if (port >= RTL8367B_NUM_PORTS)
+		return -EINVAL;
+
+	REG_RD(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), &data);
+
+	*val = (data >> RTL8367B_VLAN_PVID_CTRL_SHIFT(port)) &
+	       RTL8367B_VLAN_PVID_CTRL_MASK;
+
+	return 0;
+}
+
+static int rtl8367b_set_mc_index(struct rtl8366_smi *smi, int port, int index)
+{
+	if (port >= RTL8367B_NUM_PORTS || index >= RTL8367B_NUM_VLANS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_PVID_CTRL_REG(port),
+				RTL8367B_VLAN_PVID_CTRL_MASK <<
+					RTL8367B_VLAN_PVID_CTRL_SHIFT(port),
+				(index & RTL8367B_VLAN_PVID_CTRL_MASK) <<
+					RTL8367B_VLAN_PVID_CTRL_SHIFT(port));
+}
+
+static int rtl8367b_enable_vlan(struct rtl8366_smi *smi, int enable)
+{
+	return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_CTRL_REG,
+				RTL8367B_VLAN_CTRL_ENABLE,
+				(enable) ? RTL8367B_VLAN_CTRL_ENABLE : 0);
+}
+
+static int rtl8367b_enable_vlan4k(struct rtl8366_smi *smi, int enable)
+{
+	return 0;
+}
+
+static int rtl8367b_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan)
+{
+	unsigned max = RTL8367B_NUM_VLANS;
+
+	if (smi->vlan4k_enabled)
+		max = RTL8367B_NUM_VIDS - 1;
+
+	if (vlan == 0 || vlan >= max)
+		return 0;
+
+	return 1;
+}
+
+static int rtl8367b_enable_port(struct rtl8366_smi *smi, int port, int enable)
+{
+	int err;
+
+	REG_WR(smi, RTL8367B_PORT_ISOLATION_REG(port),
+	       (enable) ? RTL8367B_PORTS_ALL : 0);
+
+	return 0;
+}
+
+static int rtl8367b_sw_reset_mibs(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+
+	return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(0), 0,
+				RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK);
+}
+
+static int rtl8367b_sw_get_port_link(struct switch_dev *dev,
+				    int port,
+				    struct switch_port_link *link)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data = 0;
+	u32 speed;
+
+	if (port >= RTL8367B_NUM_PORTS)
+		return -EINVAL;
+
+	rtl8366_smi_read_reg(smi, RTL8367B_PORT_STATUS_REG(port), &data);
+
+	link->link = !!(data & RTL8367B_PORT_STATUS_LINK);
+	if (!link->link)
+		return 0;
+
+	link->duplex = !!(data & RTL8367B_PORT_STATUS_DUPLEX);
+	link->rx_flow = !!(data & RTL8367B_PORT_STATUS_RXPAUSE);
+	link->tx_flow = !!(data & RTL8367B_PORT_STATUS_TXPAUSE);
+	link->aneg = !!(data & RTL8367B_PORT_STATUS_NWAY);
+
+	speed = (data & RTL8367B_PORT_STATUS_SPEED_MASK);
+	switch (speed) {
+	case 0:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case 1:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case 2:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	default:
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int rtl8367b_sw_get_max_length(struct switch_dev *dev,
+				     const struct switch_attr *attr,
+				     struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 data;
+
+	rtl8366_smi_read_reg(smi, RTL8367B_SWC0_REG, &data);
+	val->value.i = (data & RTL8367B_SWC0_MAX_LENGTH_MASK) >>
+			RTL8367B_SWC0_MAX_LENGTH_SHIFT;
+
+	return 0;
+}
+
+static int rtl8367b_sw_set_max_length(struct switch_dev *dev,
+				     const struct switch_attr *attr,
+				     struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	u32 max_len;
+
+	switch (val->value.i) {
+	case 0:
+		max_len = RTL8367B_SWC0_MAX_LENGTH_1522;
+		break;
+	case 1:
+		max_len = RTL8367B_SWC0_MAX_LENGTH_1536;
+		break;
+	case 2:
+		max_len = RTL8367B_SWC0_MAX_LENGTH_1552;
+		break;
+	case 3:
+		max_len = RTL8367B_SWC0_MAX_LENGTH_16000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rtl8366_smi_rmwr(smi, RTL8367B_SWC0_REG,
+			        RTL8367B_SWC0_MAX_LENGTH_MASK, max_len);
+}
+
+
+static int rtl8367b_sw_reset_port_mibs(struct switch_dev *dev,
+				       const struct switch_attr *attr,
+				       struct switch_val *val)
+{
+	struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev);
+	int port;
+
+	port = val->port_vlan;
+	if (port >= RTL8367B_NUM_PORTS)
+		return -EINVAL;
+
+	return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(port / 8), 0,
+				RTL8367B_MIB_CTRL0_PORT_RESET_MASK(port % 8));
+}
+
+static int rtl8367b_sw_get_port_stats(struct switch_dev *dev, int port,
+                                        struct switch_port_stats *stats)
+{
+	return (rtl8366_sw_get_port_stats(dev, port, stats,
+				RTL8367B_MIB_TXB_ID, RTL8367B_MIB_RXB_ID));
+}
+
+static struct switch_attr rtl8367b_globals[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable VLAN mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 1
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan4k",
+		.description = "Enable VLAN 4K mode",
+		.set = rtl8366_sw_set_vlan_enable,
+		.get = rtl8366_sw_get_vlan_enable,
+		.max = 1,
+		.ofs = 2
+	}, {
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mibs",
+		.description = "Reset all MIB counters",
+		.set = rtl8367b_sw_reset_mibs,
+	}, {
+		.type = SWITCH_TYPE_INT,
+		.name = "max_length",
+		.description = "Get/Set the maximum length of valid packets"
+			       "(0:1522, 1:1536, 2:1552, 3:16000)",
+		.set = rtl8367b_sw_set_max_length,
+		.get = rtl8367b_sw_get_max_length,
+		.max = 3,
+	}
+};
+
+static struct switch_attr rtl8367b_port[] = {
+	{
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset_mib",
+		.description = "Reset single port MIB counters",
+		.set = rtl8367b_sw_reset_port_mibs,
+	}, {
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get MIB counters for port",
+		.max = 33,
+		.set = NULL,
+		.get = rtl8366_sw_get_port_mib,
+	},
+};
+
+static struct switch_attr rtl8367b_vlan[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "info",
+		.description = "Get vlan information",
+		.max = 1,
+		.set = NULL,
+		.get = rtl8366_sw_get_vlan_info,
+	},
+};
+
+static const struct switch_dev_ops rtl8367b_sw_ops = {
+	.attr_global = {
+		.attr = rtl8367b_globals,
+		.n_attr = ARRAY_SIZE(rtl8367b_globals),
+	},
+	.attr_port = {
+		.attr = rtl8367b_port,
+		.n_attr = ARRAY_SIZE(rtl8367b_port),
+	},
+	.attr_vlan = {
+		.attr = rtl8367b_vlan,
+		.n_attr = ARRAY_SIZE(rtl8367b_vlan),
+	},
+
+	.get_vlan_ports = rtl8366_sw_get_vlan_ports,
+	.set_vlan_ports = rtl8366_sw_set_vlan_ports,
+	.get_port_pvid = rtl8366_sw_get_port_pvid,
+	.set_port_pvid = rtl8366_sw_set_port_pvid,
+	.reset_switch = rtl8366_sw_reset_switch,
+	.get_port_link = rtl8367b_sw_get_port_link,
+	.get_port_stats = rtl8367b_sw_get_port_stats,
+};
+
+static int rtl8367b_switch_init(struct rtl8366_smi *smi)
+{
+	struct switch_dev *dev = &smi->sw_dev;
+	int err;
+
+	dev->name = "RTL8367B";
+	dev->cpu_port = smi->cpu_port;
+	dev->ports = RTL8367B_NUM_PORTS;
+	dev->vlans = RTL8367B_NUM_VIDS;
+	dev->ops = &rtl8367b_sw_ops;
+	dev->alias = dev_name(smi->parent);
+
+	err = register_switch(dev, NULL);
+	if (err)
+		dev_err(smi->parent, "switch registration failed\n");
+
+	return err;
+}
+
+static void rtl8367b_switch_cleanup(struct rtl8366_smi *smi)
+{
+	unregister_switch(&smi->sw_dev);
+}
+
+static int rtl8367b_mii_read(struct mii_bus *bus, int addr, int reg)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 val = 0;
+	int err;
+
+	err = rtl8367b_read_phy_reg(smi, addr, reg, &val);
+	if (err)
+		return 0xffff;
+
+	return val;
+}
+
+static int rtl8367b_mii_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct rtl8366_smi *smi = bus->priv;
+	u32 t;
+	int err;
+
+	err = rtl8367b_write_phy_reg(smi, addr, reg, val);
+	if (err)
+		return err;
+
+	/* flush write */
+	(void) rtl8367b_read_phy_reg(smi, addr, reg, &t);
+
+	return err;
+}
+
+static int rtl8367b_detect(struct rtl8366_smi *smi)
+{
+	const char *chip_name = NULL;
+	u32 chip_num;
+	u32 chip_ver;
+	u32 chip_mode;
+	int ret;
+
+	/* TODO: improve chip detection */
+	rtl8366_smi_write_reg(smi, RTL8367B_RTL_MAGIC_ID_REG,
+			      RTL8367B_RTL_MAGIC_ID_VAL);
+
+	ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num);
+	if (ret) {
+		dev_err(smi->parent, "unable to read %s register\n",
+			"chip number");
+		return ret;
+	}
+
+	ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_VER_REG, &chip_ver);
+	if (ret) {
+		dev_err(smi->parent, "unable to read %s register\n",
+			"chip version");
+		return ret;
+	}
+
+	ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_MODE_REG, &chip_mode);
+	if (ret) {
+		dev_err(smi->parent, "unable to read %s register\n",
+			"chip mode");
+		return ret;
+	}
+
+	if (smi->flags & RTL8367B_FLAG_MATCH_CHIP_NUM) {
+		switch (chip_num) {
+		case 0x0276:
+		case 0x0597:
+		case 0x6367:
+			chip_name = "8367C";
+			break;
+		}
+	} else {
+		switch (chip_ver) {
+		case 0x1000:
+			chip_name = "8367RB";
+			break;
+		case 0x1010:
+			chip_name = "8367R-VB";
+			break;
+		}
+	}
+
+	if (!chip_name) {
+		dev_err(smi->parent,
+			"unknown chip num:%04x ver:%04x, mode:%04x\n",
+			chip_num, chip_ver, chip_mode);
+		return -ENODEV;
+	}
+
+	dev_info(smi->parent, "RTL%s chip found\n", chip_name);
+
+	return 0;
+}
+
+static struct rtl8366_smi_ops rtl8367b_smi_ops = {
+	.detect		= rtl8367b_detect,
+	.reset_chip	= rtl8367b_reset_chip,
+	.setup		= rtl8367b_setup,
+
+	.mii_read	= rtl8367b_mii_read,
+	.mii_write	= rtl8367b_mii_write,
+
+	.get_vlan_mc	= rtl8367b_get_vlan_mc,
+	.set_vlan_mc	= rtl8367b_set_vlan_mc,
+	.get_vlan_4k	= rtl8367b_get_vlan_4k,
+	.set_vlan_4k	= rtl8367b_set_vlan_4k,
+	.get_mc_index	= rtl8367b_get_mc_index,
+	.set_mc_index	= rtl8367b_set_mc_index,
+	.get_mib_counter = rtl8367b_get_mib_counter,
+	.is_vlan_valid	= rtl8367b_is_vlan_valid,
+	.enable_vlan	= rtl8367b_enable_vlan,
+	.enable_vlan4k	= rtl8367b_enable_vlan4k,
+	.enable_port	= rtl8367b_enable_port,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id rtl8367b_match[] = {
+	{ .compatible = "realtek,rtl8367b" },
+	{
+		.compatible = "realtek,rtl8367c",
+		.data = (void *)RTL8367B_FLAG_MATCH_CHIP_NUM,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtl8367b_match);
+#endif
+
+static int  rtl8367b_probe(struct platform_device *pdev)
+{
+#ifdef CONFIG_OF
+	const struct of_device_id *match;
+#endif
+	struct rtl8366_smi *smi;
+	int err;
+
+	smi = rtl8366_smi_probe(pdev);
+	if (IS_ERR(smi))
+		return PTR_ERR(smi);
+
+#ifdef CONFIG_OF
+	match = of_match_device(rtl8367b_match, &pdev->dev);
+	if (match)
+		smi->flags = (u32)match->data;
+#endif
+
+	smi->clk_delay = 1500;
+	smi->cmd_read = 0xb9;
+	smi->cmd_write = 0xb8;
+	smi->ops = &rtl8367b_smi_ops;
+	smi->num_ports = RTL8367B_NUM_PORTS;
+	if (of_property_read_u32(pdev->dev.of_node, "cpu_port", &smi->cpu_port)
+	    || smi->cpu_port >= smi->num_ports)
+		smi->cpu_port = RTL8367B_CPU_PORT_NUM;
+	smi->num_vlan_mc = RTL8367B_NUM_VLANS;
+	smi->mib_counters = rtl8367b_mib_counters;
+	smi->num_mib_counters = ARRAY_SIZE(rtl8367b_mib_counters);
+
+	err = rtl8366_smi_init(smi);
+	if (err)
+		goto err_free_smi;
+
+	platform_set_drvdata(pdev, smi);
+
+	err = rtl8367b_switch_init(smi);
+	if (err)
+		goto err_clear_drvdata;
+
+	return 0;
+
+ err_clear_drvdata:
+	platform_set_drvdata(pdev, NULL);
+	rtl8366_smi_cleanup(smi);
+ err_free_smi:
+	kfree(smi);
+	return err;
+}
+
+static int rtl8367b_remove(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi = platform_get_drvdata(pdev);
+
+	if (smi) {
+		rtl8367b_switch_cleanup(smi);
+		platform_set_drvdata(pdev, NULL);
+		rtl8366_smi_cleanup(smi);
+		kfree(smi);
+	}
+
+	return 0;
+}
+
+static void rtl8367b_shutdown(struct platform_device *pdev)
+{
+	struct rtl8366_smi *smi = platform_get_drvdata(pdev);
+
+	if (smi)
+		rtl8367b_reset_chip(smi);
+}
+
+static struct platform_driver rtl8367b_driver = {
+	.driver = {
+		.name		= RTL8367B_DRIVER_NAME,
+		.owner		= THIS_MODULE,
+#ifdef CONFIG_OF
+		.of_match_table = of_match_ptr(rtl8367b_match),
+#endif
+	},
+	.probe		= rtl8367b_probe,
+	.remove		= rtl8367b_remove,
+	.shutdown	= rtl8367b_shutdown,
+};
+
+module_platform_driver(rtl8367b_driver);
+
+MODULE_DESCRIPTION("Realtek RTL8367B ethernet switch driver");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME);
+
diff -Naur linux-orig/drivers/net/phy/swconfig.c linux/drivers/net/phy/swconfig.c
--- linux-orig/drivers/net/phy/swconfig.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/swconfig.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,1242 @@
+/*
+ * swconfig.c: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/capability.h>
+#include <linux/skbuff.h>
+#include <linux/switch.h>
+#include <linux/of.h>
+#include <linux/version.h>
+#include <uapi/linux/mii.h>
+
+#define SWCONFIG_DEVNAME	"switch%d"
+
+#include "swconfig_leds.c"
+
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+MODULE_LICENSE("GPL");
+
+static int swdev_id;
+static struct list_head swdevs;
+static DEFINE_MUTEX(swdevs_lock);
+struct swconfig_callback;
+
+struct swconfig_callback {
+	struct sk_buff *msg;
+	struct genlmsghdr *hdr;
+	struct genl_info *info;
+	int cmd;
+
+	/* callback for filling in the message data */
+	int (*fill)(struct swconfig_callback *cb, void *arg);
+
+	/* callback for closing the message before sending it */
+	int (*close)(struct swconfig_callback *cb, void *arg);
+
+	struct nlattr *nest[4];
+	int args[4];
+};
+
+/* defaults */
+
+static int
+swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	int ret;
+	if (val->port_vlan >= dev->vlans)
+		return -EINVAL;
+
+	if (!dev->ops->get_vlan_ports)
+		return -EOPNOTSUPP;
+
+	ret = dev->ops->get_vlan_ports(dev, val);
+	return ret;
+}
+
+static int
+swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	struct switch_port *ports = val->value.ports;
+	const struct switch_dev_ops *ops = dev->ops;
+	int i;
+
+	if (val->port_vlan >= dev->vlans)
+		return -EINVAL;
+
+	/* validate ports */
+	if (val->len > dev->ports)
+		return -EINVAL;
+
+	if (!ops->set_vlan_ports)
+		return -EOPNOTSUPP;
+
+	for (i = 0; i < val->len; i++) {
+		if (ports[i].id >= dev->ports)
+			return -EINVAL;
+
+		if (ops->set_port_pvid &&
+		    !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED)))
+			ops->set_port_pvid(dev, ports[i].id, val->port_vlan);
+	}
+
+	return ops->set_vlan_ports(dev, val);
+}
+
+static int
+swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	if (val->port_vlan >= dev->ports)
+		return -EINVAL;
+
+	if (!dev->ops->set_port_pvid)
+		return -EOPNOTSUPP;
+
+	return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i);
+}
+
+static int
+swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	if (val->port_vlan >= dev->ports)
+		return -EINVAL;
+
+	if (!dev->ops->get_port_pvid)
+		return -EOPNOTSUPP;
+
+	return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i);
+}
+
+static int
+swconfig_set_link(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	if (!dev->ops->set_port_link)
+		return -EOPNOTSUPP;
+
+	return dev->ops->set_port_link(dev, val->port_vlan, val->value.link);
+}
+
+static int
+swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	struct switch_port_link *link = val->value.link;
+
+	if (val->port_vlan >= dev->ports)
+		return -EINVAL;
+
+	if (!dev->ops->get_port_link)
+		return -EOPNOTSUPP;
+
+	memset(link, 0, sizeof(*link));
+	return dev->ops->get_port_link(dev, val->port_vlan, link);
+}
+
+static int
+swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	/* don't complain if not supported by the switch driver */
+	if (!dev->ops->apply_config)
+		return 0;
+
+	return dev->ops->apply_config(dev);
+}
+
+static int
+swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr,
+			struct switch_val *val)
+{
+	/* don't complain if not supported by the switch driver */
+	if (!dev->ops->reset_switch)
+		return 0;
+
+	return dev->ops->reset_switch(dev);
+}
+
+enum global_defaults {
+	GLOBAL_APPLY,
+	GLOBAL_RESET,
+};
+
+enum vlan_defaults {
+	VLAN_PORTS,
+};
+
+enum port_defaults {
+	PORT_PVID,
+	PORT_LINK,
+};
+
+static struct switch_attr default_global[] = {
+	[GLOBAL_APPLY] = {
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "apply",
+		.description = "Activate changes in the hardware",
+		.set = swconfig_apply_config,
+	},
+	[GLOBAL_RESET] = {
+		.type = SWITCH_TYPE_NOVAL,
+		.name = "reset",
+		.description = "Reset the switch",
+		.set = swconfig_reset_switch,
+	}
+};
+
+static struct switch_attr default_port[] = {
+	[PORT_PVID] = {
+		.type = SWITCH_TYPE_INT,
+		.name = "pvid",
+		.description = "Primary VLAN ID",
+		.set = swconfig_set_pvid,
+		.get = swconfig_get_pvid,
+	},
+	[PORT_LINK] = {
+		.type = SWITCH_TYPE_LINK,
+		.name = "link",
+		.description = "Get port link information",
+		.set = swconfig_set_link,
+		.get = swconfig_get_link,
+	}
+};
+
+static struct switch_attr default_vlan[] = {
+	[VLAN_PORTS] = {
+		.type = SWITCH_TYPE_PORTS,
+		.name = "ports",
+		.description = "VLAN port mapping",
+		.set = swconfig_set_vlan_ports,
+		.get = swconfig_get_vlan_ports,
+	},
+};
+
+static const struct switch_attr *
+swconfig_find_attr_by_name(const struct switch_attrlist *alist,
+				const char *name)
+{
+	int i;
+
+	for (i = 0; i < alist->n_attr; i++)
+		if (strcmp(name, alist->attr[i].name) == 0)
+			return &alist->attr[i];
+
+	return NULL;
+}
+
+static void swconfig_defaults_init(struct switch_dev *dev)
+{
+	const struct switch_dev_ops *ops = dev->ops;
+
+	dev->def_global = 0;
+	dev->def_vlan = 0;
+	dev->def_port = 0;
+
+	if (ops->get_vlan_ports || ops->set_vlan_ports)
+		set_bit(VLAN_PORTS, &dev->def_vlan);
+
+	if (ops->get_port_pvid || ops->set_port_pvid)
+		set_bit(PORT_PVID, &dev->def_port);
+
+	if (ops->get_port_link &&
+	    !swconfig_find_attr_by_name(&ops->attr_port, "link"))
+		set_bit(PORT_LINK, &dev->def_port);
+
+	/* always present, can be no-op */
+	set_bit(GLOBAL_APPLY, &dev->def_global);
+	set_bit(GLOBAL_RESET, &dev->def_global);
+}
+
+
+static struct genl_family switch_fam;
+
+static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = {
+	[SWITCH_ATTR_ID] = { .type = NLA_U32 },
+	[SWITCH_ATTR_OP_ID] = { .type = NLA_U32 },
+	[SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 },
+	[SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 },
+	[SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 },
+	[SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING },
+	[SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED },
+	[SWITCH_ATTR_TYPE] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = {
+	[SWITCH_PORT_ID] = { .type = NLA_U32 },
+	[SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG },
+};
+
+static struct nla_policy link_policy[SWITCH_LINK_ATTR_MAX] = {
+	[SWITCH_LINK_FLAG_DUPLEX] = { .type = NLA_FLAG },
+	[SWITCH_LINK_FLAG_ANEG] = { .type = NLA_FLAG },
+	[SWITCH_LINK_SPEED] = { .type = NLA_U32 },
+};
+
+static inline void
+swconfig_lock(void)
+{
+	mutex_lock(&swdevs_lock);
+}
+
+static inline void
+swconfig_unlock(void)
+{
+	mutex_unlock(&swdevs_lock);
+}
+
+static struct switch_dev *
+swconfig_get_dev(struct genl_info *info)
+{
+	struct switch_dev *dev = NULL;
+	struct switch_dev *p;
+	int id;
+
+	if (!info->attrs[SWITCH_ATTR_ID])
+		goto done;
+
+	id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]);
+	swconfig_lock();
+	list_for_each_entry(p, &swdevs, dev_list) {
+		if (id != p->id)
+			continue;
+
+		dev = p;
+		break;
+	}
+	if (dev)
+		mutex_lock(&dev->sw_mutex);
+	else
+		pr_debug("device %d not found\n", id);
+	swconfig_unlock();
+done:
+	return dev;
+}
+
+static inline void
+swconfig_put_dev(struct switch_dev *dev)
+{
+	mutex_unlock(&dev->sw_mutex);
+}
+
+static int
+swconfig_dump_attr(struct swconfig_callback *cb, void *arg)
+{
+	struct switch_attr *op = arg;
+	struct genl_info *info = cb->info;
+	struct sk_buff *msg = cb->msg;
+	int id = cb->args[0];
+	void *hdr;
+
+	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam,
+			NLM_F_MULTI, SWITCH_CMD_NEW_ATTR);
+	if (IS_ERR(hdr))
+		return -1;
+
+	if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id))
+		goto nla_put_failure;
+	if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type))
+		goto nla_put_failure;
+	if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name))
+		goto nla_put_failure;
+	if (op->description)
+		if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION,
+			op->description))
+			goto nla_put_failure;
+
+	genlmsg_end(msg, hdr);
+	return msg->len;
+nla_put_failure:
+	genlmsg_cancel(msg, hdr);
+	return -EMSGSIZE;
+}
+
+/* spread multipart messages across multiple message buffers */
+static int
+swconfig_send_multipart(struct swconfig_callback *cb, void *arg)
+{
+	struct genl_info *info = cb->info;
+	int restart = 0;
+	int err;
+
+	do {
+		if (!cb->msg) {
+			cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+			if (cb->msg == NULL)
+				goto error;
+		}
+
+		if (!(cb->fill(cb, arg) < 0))
+			break;
+
+		/* fill failed, check if this was already the second attempt */
+		if (restart)
+			goto error;
+
+		/* try again in a new message, send the current one */
+		restart = 1;
+		if (cb->close) {
+			if (cb->close(cb, arg) < 0)
+				goto error;
+		}
+		err = genlmsg_reply(cb->msg, info);
+		cb->msg = NULL;
+		if (err < 0)
+			goto error;
+
+	} while (restart);
+
+	return 0;
+
+error:
+	if (cb->msg)
+		nlmsg_free(cb->msg);
+	return -1;
+}
+
+static int
+swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
+	const struct switch_attrlist *alist;
+	struct switch_dev *dev;
+	struct swconfig_callback cb;
+	int err = -EINVAL;
+	int i;
+
+	/* defaults */
+	struct switch_attr *def_list;
+	unsigned long *def_active;
+	int n_def;
+
+	dev = swconfig_get_dev(info);
+	if (!dev)
+		return -EINVAL;
+
+	switch (hdr->cmd) {
+	case SWITCH_CMD_LIST_GLOBAL:
+		alist = &dev->ops->attr_global;
+		def_list = default_global;
+		def_active = &dev->def_global;
+		n_def = ARRAY_SIZE(default_global);
+		break;
+	case SWITCH_CMD_LIST_VLAN:
+		alist = &dev->ops->attr_vlan;
+		def_list = default_vlan;
+		def_active = &dev->def_vlan;
+		n_def = ARRAY_SIZE(default_vlan);
+		break;
+	case SWITCH_CMD_LIST_PORT:
+		alist = &dev->ops->attr_port;
+		def_list = default_port;
+		def_active = &dev->def_port;
+		n_def = ARRAY_SIZE(default_port);
+		break;
+	default:
+		WARN_ON(1);
+		goto out;
+	}
+
+	memset(&cb, 0, sizeof(cb));
+	cb.info = info;
+	cb.fill = swconfig_dump_attr;
+	for (i = 0; i < alist->n_attr; i++) {
+		if (alist->attr[i].disabled)
+			continue;
+		cb.args[0] = i;
+		err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]);
+		if (err < 0)
+			goto error;
+	}
+
+	/* defaults */
+	for (i = 0; i < n_def; i++) {
+		if (!test_bit(i, def_active))
+			continue;
+		cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i;
+		err = swconfig_send_multipart(&cb, (void *) &def_list[i]);
+		if (err < 0)
+			goto error;
+	}
+	swconfig_put_dev(dev);
+
+	if (!cb.msg)
+		return 0;
+
+	return genlmsg_reply(cb.msg, info);
+
+error:
+	if (cb.msg)
+		nlmsg_free(cb.msg);
+out:
+	swconfig_put_dev(dev);
+	return err;
+}
+
+static const struct switch_attr *
+swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info,
+		struct switch_val *val)
+{
+	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
+	const struct switch_attrlist *alist;
+	const struct switch_attr *attr = NULL;
+	unsigned int attr_id;
+
+	/* defaults */
+	struct switch_attr *def_list;
+	unsigned long *def_active;
+	int n_def;
+
+	if (!info->attrs[SWITCH_ATTR_OP_ID])
+		goto done;
+
+	switch (hdr->cmd) {
+	case SWITCH_CMD_SET_GLOBAL:
+	case SWITCH_CMD_GET_GLOBAL:
+		alist = &dev->ops->attr_global;
+		def_list = default_global;
+		def_active = &dev->def_global;
+		n_def = ARRAY_SIZE(default_global);
+		break;
+	case SWITCH_CMD_SET_VLAN:
+	case SWITCH_CMD_GET_VLAN:
+		alist = &dev->ops->attr_vlan;
+		def_list = default_vlan;
+		def_active = &dev->def_vlan;
+		n_def = ARRAY_SIZE(default_vlan);
+		if (!info->attrs[SWITCH_ATTR_OP_VLAN])
+			goto done;
+		val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]);
+		if (val->port_vlan >= dev->vlans)
+			goto done;
+		break;
+	case SWITCH_CMD_SET_PORT:
+	case SWITCH_CMD_GET_PORT:
+		alist = &dev->ops->attr_port;
+		def_list = default_port;
+		def_active = &dev->def_port;
+		n_def = ARRAY_SIZE(default_port);
+		if (!info->attrs[SWITCH_ATTR_OP_PORT])
+			goto done;
+		val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]);
+		if (val->port_vlan >= dev->ports)
+			goto done;
+		break;
+	default:
+		WARN_ON(1);
+		goto done;
+	}
+
+	if (!alist)
+		goto done;
+
+	attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]);
+	if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) {
+		attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET;
+		if (attr_id >= n_def)
+			goto done;
+		if (!test_bit(attr_id, def_active))
+			goto done;
+		attr = &def_list[attr_id];
+	} else {
+		if (attr_id >= alist->n_attr)
+			goto done;
+		attr = &alist->attr[attr_id];
+	}
+
+	if (attr->disabled)
+		attr = NULL;
+
+done:
+	if (!attr)
+		pr_debug("attribute lookup failed\n");
+	val->attr = attr;
+	return attr;
+}
+
+static int
+swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head,
+		struct switch_val *val, int max)
+{
+	struct nlattr *nla;
+	int rem;
+
+	val->len = 0;
+	nla_for_each_nested(nla, head, rem) {
+		struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1];
+		struct switch_port *port;
+
+		if (val->len >= max)
+			return -EINVAL;
+
+		port = &val->value.ports[val->len];
+
+		if (nla_parse_nested_deprecated(tb, SWITCH_PORT_ATTR_MAX, nla,
+				port_policy, NULL))
+			return -EINVAL;
+
+		if (!tb[SWITCH_PORT_ID])
+			return -EINVAL;
+
+		port->id = nla_get_u32(tb[SWITCH_PORT_ID]);
+		if (tb[SWITCH_PORT_FLAG_TAGGED])
+			port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED);
+		val->len++;
+	}
+
+	return 0;
+}
+
+static int
+swconfig_parse_link(struct sk_buff *msg, struct nlattr *nla,
+		    struct switch_port_link *link)
+{
+	struct nlattr *tb[SWITCH_LINK_ATTR_MAX + 1];
+
+	if (nla_parse_nested_deprecated(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL))
+		return -EINVAL;
+
+	link->duplex = !!tb[SWITCH_LINK_FLAG_DUPLEX];
+	link->aneg = !!tb[SWITCH_LINK_FLAG_ANEG];
+	link->speed = nla_get_u32(tb[SWITCH_LINK_SPEED]);
+
+	return 0;
+}
+
+static int
+swconfig_set_attr(struct sk_buff *skb, struct genl_info *info)
+{
+	const struct switch_attr *attr;
+	struct switch_dev *dev;
+	struct switch_val val;
+	int err = -EINVAL;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	dev = swconfig_get_dev(info);
+	if (!dev)
+		return -EINVAL;
+
+	memset(&val, 0, sizeof(val));
+	attr = swconfig_lookup_attr(dev, info, &val);
+	if (!attr || !attr->set)
+		goto error;
+
+	val.attr = attr;
+	switch (attr->type) {
+	case SWITCH_TYPE_NOVAL:
+		break;
+	case SWITCH_TYPE_INT:
+		if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT])
+			goto error;
+		val.value.i =
+			nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]);
+		break;
+	case SWITCH_TYPE_STRING:
+		if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR])
+			goto error;
+		val.value.s =
+			nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]);
+		break;
+	case SWITCH_TYPE_PORTS:
+		val.value.ports = dev->portbuf;
+		memset(dev->portbuf, 0,
+			sizeof(struct switch_port) * dev->ports);
+
+		/* TODO: implement multipart? */
+		if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) {
+			err = swconfig_parse_ports(skb,
+				info->attrs[SWITCH_ATTR_OP_VALUE_PORTS],
+				&val, dev->ports);
+			if (err < 0)
+				goto error;
+		} else {
+			val.len = 0;
+			err = 0;
+		}
+		break;
+	case SWITCH_TYPE_LINK:
+		val.value.link = &dev->linkbuf;
+		memset(&dev->linkbuf, 0, sizeof(struct switch_port_link));
+
+		if (info->attrs[SWITCH_ATTR_OP_VALUE_LINK]) {
+			err = swconfig_parse_link(skb,
+						  info->attrs[SWITCH_ATTR_OP_VALUE_LINK],
+						  val.value.link);
+			if (err < 0)
+				goto error;
+		} else {
+			val.len = 0;
+			err = 0;
+		}
+		break;
+	default:
+		goto error;
+	}
+
+	err = attr->set(dev, attr, &val);
+error:
+	swconfig_put_dev(dev);
+	return err;
+}
+
+static int
+swconfig_close_portlist(struct swconfig_callback *cb, void *arg)
+{
+	if (cb->nest[0])
+		nla_nest_end(cb->msg, cb->nest[0]);
+	return 0;
+}
+
+static int
+swconfig_send_port(struct swconfig_callback *cb, void *arg)
+{
+	const struct switch_port *port = arg;
+	struct nlattr *p = NULL;
+
+	if (!cb->nest[0]) {
+		cb->nest[0] = nla_nest_start(cb->msg, cb->cmd);
+		if (!cb->nest[0])
+			return -1;
+	}
+
+	p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT);
+	if (!p)
+		goto error;
+
+	if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id))
+		goto nla_put_failure;
+	if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) {
+		if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED))
+			goto nla_put_failure;
+	}
+
+	nla_nest_end(cb->msg, p);
+	return 0;
+
+nla_put_failure:
+		nla_nest_cancel(cb->msg, p);
+error:
+	nla_nest_cancel(cb->msg, cb->nest[0]);
+	return -1;
+}
+
+static int
+swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr,
+		const struct switch_val *val)
+{
+	struct swconfig_callback cb;
+	int err = 0;
+	int i;
+
+	if (!val->value.ports)
+		return -EINVAL;
+
+	memset(&cb, 0, sizeof(cb));
+	cb.cmd = attr;
+	cb.msg = *msg;
+	cb.info = info;
+	cb.fill = swconfig_send_port;
+	cb.close = swconfig_close_portlist;
+
+	cb.nest[0] = nla_nest_start(cb.msg, cb.cmd);
+	for (i = 0; i < val->len; i++) {
+		err = swconfig_send_multipart(&cb, &val->value.ports[i]);
+		if (err)
+			goto done;
+	}
+	err = val->len;
+	swconfig_close_portlist(&cb, NULL);
+	*msg = cb.msg;
+
+done:
+	return err;
+}
+
+static int
+swconfig_send_link(struct sk_buff *msg, struct genl_info *info, int attr,
+		   const struct switch_port_link *link)
+{
+	struct nlattr *p = NULL;
+	int err = 0;
+
+	p = nla_nest_start(msg, attr);
+	if (link->link) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_LINK))
+			goto nla_put_failure;
+	}
+	if (link->duplex) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_DUPLEX))
+			goto nla_put_failure;
+	}
+	if (link->aneg) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_ANEG))
+			goto nla_put_failure;
+	}
+	if (link->tx_flow) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_TX_FLOW))
+			goto nla_put_failure;
+	}
+	if (link->rx_flow) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_RX_FLOW))
+			goto nla_put_failure;
+	}
+	if (nla_put_u32(msg, SWITCH_LINK_SPEED, link->speed))
+		goto nla_put_failure;
+	if (link->eee & ADVERTISED_100baseT_Full) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_100BASET))
+			goto nla_put_failure;
+	}
+	if (link->eee & ADVERTISED_1000baseT_Full) {
+		if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_1000BASET))
+			goto nla_put_failure;
+	}
+	nla_nest_end(msg, p);
+
+	return err;
+
+nla_put_failure:
+	nla_nest_cancel(msg, p);
+	return -1;
+}
+
+static int
+swconfig_get_attr(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
+	const struct switch_attr *attr;
+	struct switch_dev *dev;
+	struct sk_buff *msg = NULL;
+	struct switch_val val;
+	int err = -EINVAL;
+	int cmd = hdr->cmd;
+
+	dev = swconfig_get_dev(info);
+	if (!dev)
+		return -EINVAL;
+
+	memset(&val, 0, sizeof(val));
+	attr = swconfig_lookup_attr(dev, info, &val);
+	if (!attr || !attr->get)
+		goto error;
+
+	if (attr->type == SWITCH_TYPE_PORTS) {
+		val.value.ports = dev->portbuf;
+		memset(dev->portbuf, 0,
+			sizeof(struct switch_port) * dev->ports);
+	} else if (attr->type == SWITCH_TYPE_LINK) {
+		val.value.link = &dev->linkbuf;
+		memset(&dev->linkbuf, 0, sizeof(struct switch_port_link));
+	}
+
+	err = attr->get(dev, attr, &val);
+	if (err)
+		goto error;
+
+	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (!msg)
+		goto error;
+
+	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam,
+			0, cmd);
+	if (IS_ERR(hdr))
+		goto nla_put_failure;
+
+	switch (attr->type) {
+	case SWITCH_TYPE_INT:
+		if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i))
+			goto nla_put_failure;
+		break;
+	case SWITCH_TYPE_STRING:
+		if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s))
+			goto nla_put_failure;
+		break;
+	case SWITCH_TYPE_PORTS:
+		err = swconfig_send_ports(&msg, info,
+				SWITCH_ATTR_OP_VALUE_PORTS, &val);
+		if (err < 0)
+			goto nla_put_failure;
+		break;
+	case SWITCH_TYPE_LINK:
+		err = swconfig_send_link(msg, info,
+					 SWITCH_ATTR_OP_VALUE_LINK, val.value.link);
+		if (err < 0)
+			goto nla_put_failure;
+		break;
+	default:
+		pr_debug("invalid type in attribute\n");
+		err = -EINVAL;
+		goto nla_put_failure;
+	}
+	genlmsg_end(msg, hdr);
+	err = msg->len;
+	if (err < 0)
+		goto nla_put_failure;
+
+	swconfig_put_dev(dev);
+	return genlmsg_reply(msg, info);
+
+nla_put_failure:
+	if (msg)
+		nlmsg_free(msg);
+error:
+	swconfig_put_dev(dev);
+	if (!err)
+		err = -ENOMEM;
+	return err;
+}
+
+static int
+swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags,
+		const struct switch_dev *dev)
+{
+	struct nlattr *p = NULL, *m = NULL;
+	void *hdr;
+	int i;
+
+	hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags,
+			SWITCH_CMD_NEW_ATTR);
+	if (IS_ERR(hdr))
+		return -1;
+
+	if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id))
+		goto nla_put_failure;
+	if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname))
+		goto nla_put_failure;
+	if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias))
+		goto nla_put_failure;
+	if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name))
+		goto nla_put_failure;
+	if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans))
+		goto nla_put_failure;
+	if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports))
+		goto nla_put_failure;
+	if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port))
+		goto nla_put_failure;
+
+	m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP);
+	if (!m)
+		goto nla_put_failure;
+	for (i = 0; i < dev->ports; i++) {
+		p = nla_nest_start(msg, SWITCH_ATTR_PORTS);
+		if (!p)
+			continue;
+		if (dev->portmap[i].s) {
+			if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT,
+						dev->portmap[i].s))
+				goto nla_put_failure;
+			if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT,
+						dev->portmap[i].virt))
+				goto nla_put_failure;
+		}
+		nla_nest_end(msg, p);
+	}
+	nla_nest_end(msg, m);
+	genlmsg_end(msg, hdr);
+	return msg->len;
+nla_put_failure:
+	genlmsg_cancel(msg, hdr);
+	return -EMSGSIZE;
+}
+
+static int swconfig_dump_switches(struct sk_buff *skb,
+		struct netlink_callback *cb)
+{
+	struct switch_dev *dev;
+	int start = cb->args[0];
+	int idx = 0;
+
+	swconfig_lock();
+	list_for_each_entry(dev, &swdevs, dev_list) {
+		if (++idx <= start)
+			continue;
+		if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid,
+				cb->nlh->nlmsg_seq, NLM_F_MULTI,
+				dev) < 0)
+			break;
+	}
+	swconfig_unlock();
+	cb->args[0] = idx;
+
+	return skb->len;
+}
+
+static int
+swconfig_done(struct netlink_callback *cb)
+{
+	return 0;
+}
+
+static struct genl_ops swconfig_ops[] = {
+	{
+		.cmd = SWITCH_CMD_LIST_GLOBAL,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.doit = swconfig_list_attrs,
+	},
+	{
+		.cmd = SWITCH_CMD_LIST_VLAN,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.doit = swconfig_list_attrs,
+	},
+	{
+		.cmd = SWITCH_CMD_LIST_PORT,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.doit = swconfig_list_attrs,
+	},
+	{
+		.cmd = SWITCH_CMD_GET_GLOBAL,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.doit = swconfig_get_attr,
+	},
+	{
+		.cmd = SWITCH_CMD_GET_VLAN,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.doit = swconfig_get_attr,
+	},
+	{
+		.cmd = SWITCH_CMD_GET_PORT,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.doit = swconfig_get_attr,
+	},
+	{
+		.cmd = SWITCH_CMD_SET_GLOBAL,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.flags = GENL_ADMIN_PERM,
+		.doit = swconfig_set_attr,
+	},
+	{
+		.cmd = SWITCH_CMD_SET_VLAN,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.flags = GENL_ADMIN_PERM,
+		.doit = swconfig_set_attr,
+	},
+	{
+		.cmd = SWITCH_CMD_SET_PORT,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.flags = GENL_ADMIN_PERM,
+		.doit = swconfig_set_attr,
+	},
+	{
+		.cmd = SWITCH_CMD_GET_SWITCH,
+		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+		.dumpit = swconfig_dump_switches,
+		.done = swconfig_done,
+	}
+};
+
+static struct genl_family switch_fam = {
+	.name = "switch",
+	.hdrsize = 0,
+	.version = 1,
+	.maxattr = SWITCH_ATTR_MAX,
+	.policy = switch_policy,
+	.module = THIS_MODULE,
+	.ops = swconfig_ops,
+	.n_ops = ARRAY_SIZE(swconfig_ops),
+};
+
+#ifdef CONFIG_OF
+void
+of_switch_load_portmap(struct switch_dev *dev)
+{
+	struct device_node *port;
+
+	if (!dev->of_node)
+		return;
+
+	for_each_child_of_node(dev->of_node, port) {
+		const __be32 *prop;
+		const char *segment;
+		int size, phys;
+
+		if (!of_device_is_compatible(port, "swconfig,port"))
+			continue;
+
+		if (of_property_read_string(port, "swconfig,segment", &segment))
+			continue;
+
+		prop = of_get_property(port, "swconfig,portmap", &size);
+		if (!prop)
+			continue;
+
+		if (size != (2 * sizeof(*prop))) {
+			pr_err("%s: failed to parse port mapping\n",
+					port->name);
+			continue;
+		}
+
+		phys = be32_to_cpup(prop++);
+		if ((phys < 0) | (phys >= dev->ports)) {
+			pr_err("%s: physical port index out of range\n",
+					port->name);
+			continue;
+		}
+
+		dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL);
+		dev->portmap[phys].virt = be32_to_cpup(prop);
+		pr_debug("Found port: %s, physical: %d, virtual: %d\n",
+			segment, phys, dev->portmap[phys].virt);
+	}
+}
+#endif
+
+int
+register_switch(struct switch_dev *dev, struct net_device *netdev)
+{
+	struct switch_dev *sdev;
+	const int max_switches = 8 * sizeof(unsigned long);
+	unsigned long in_use = 0;
+	int err;
+	int i;
+
+	INIT_LIST_HEAD(&dev->dev_list);
+	if (netdev) {
+		dev->netdev = netdev;
+		if (!dev->alias)
+			dev->alias = netdev->name;
+	}
+	BUG_ON(!dev->alias);
+
+	/* Make sure swdev_id doesn't overflow */
+	if (swdev_id == INT_MAX) {
+		return -ENOMEM;
+	}
+
+	if (dev->ports > 0) {
+		dev->portbuf = kzalloc(sizeof(struct switch_port) *
+				dev->ports, GFP_KERNEL);
+		if (!dev->portbuf)
+			return -ENOMEM;
+		dev->portmap = kzalloc(sizeof(struct switch_portmap) *
+				dev->ports, GFP_KERNEL);
+		if (!dev->portmap) {
+			kfree(dev->portbuf);
+			return -ENOMEM;
+		}
+	}
+	swconfig_defaults_init(dev);
+	mutex_init(&dev->sw_mutex);
+	swconfig_lock();
+	dev->id = ++swdev_id;
+
+	list_for_each_entry(sdev, &swdevs, dev_list) {
+		if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i))
+			continue;
+		if (i < 0 || i > max_switches)
+			continue;
+
+		set_bit(i, &in_use);
+	}
+	i = find_first_zero_bit(&in_use, max_switches);
+
+	if (i == max_switches) {
+		swconfig_unlock();
+		return -ENFILE;
+	}
+
+#ifdef CONFIG_OF
+	if (dev->ports)
+		of_switch_load_portmap(dev);
+#endif
+
+	/* fill device name */
+	snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i);
+
+	list_add_tail(&dev->dev_list, &swdevs);
+	swconfig_unlock();
+
+	err = swconfig_create_led_trigger(dev);
+	if (err)
+		return err;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(register_switch);
+
+void
+unregister_switch(struct switch_dev *dev)
+{
+	swconfig_destroy_led_trigger(dev);
+	kfree(dev->portbuf);
+	mutex_lock(&dev->sw_mutex);
+	swconfig_lock();
+	list_del(&dev->dev_list);
+	swconfig_unlock();
+	mutex_unlock(&dev->sw_mutex);
+}
+EXPORT_SYMBOL_GPL(unregister_switch);
+
+int
+switch_generic_set_link(struct switch_dev *dev, int port,
+			struct switch_port_link *link)
+{
+	if (WARN_ON(!dev->ops->phy_write16))
+		return -ENOTSUPP;
+
+	/* Generic implementation */
+	if (link->aneg) {
+		dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000);
+		dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
+	} else {
+		u16 bmcr = 0;
+
+		if (link->duplex)
+			bmcr |= BMCR_FULLDPLX;
+
+		switch (link->speed) {
+		case SWITCH_PORT_SPEED_10:
+			break;
+		case SWITCH_PORT_SPEED_100:
+			bmcr |= BMCR_SPEED100;
+			break;
+		case SWITCH_PORT_SPEED_1000:
+			bmcr |= BMCR_SPEED1000;
+			break;
+		default:
+			return -ENOTSUPP;
+		}
+
+		dev->ops->phy_write16(dev, port, MII_BMCR, bmcr);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(switch_generic_set_link);
+
+static int __init
+swconfig_init(void)
+{
+	INIT_LIST_HEAD(&swdevs);
+
+	return genl_register_family(&switch_fam);
+}
+
+static void __exit
+swconfig_exit(void)
+{
+	genl_unregister_family(&switch_fam);
+}
+
+module_init(swconfig_init);
+module_exit(swconfig_exit);
diff -Naur linux-orig/drivers/net/phy/swconfig_leds.c linux/drivers/net/phy/swconfig_leds.c
--- linux-orig/drivers/net/phy/swconfig_leds.c	1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/net/phy/swconfig_leds.c	2022-04-15 23:50:31.722484332 +0000
@@ -0,0 +1,555 @@
+/*
+ * swconfig_led.c: LED trigger support for the switch configuration API
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ */
+
+#ifdef CONFIG_SWCONFIG_LEDS
+
+#include <linux/leds.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+
+#define SWCONFIG_LED_TIMER_INTERVAL	(HZ / 10)
+#define SWCONFIG_LED_NUM_PORTS		32
+
+#define SWCONFIG_LED_PORT_SPEED_NA	0x01	/* unknown speed */
+#define SWCONFIG_LED_PORT_SPEED_10	0x02	/* 10 Mbps */
+#define SWCONFIG_LED_PORT_SPEED_100	0x04	/* 100 Mbps */
+#define SWCONFIG_LED_PORT_SPEED_1000	0x08	/* 1000 Mbps */
+#define SWCONFIG_LED_PORT_SPEED_ALL	(SWCONFIG_LED_PORT_SPEED_NA | \
+					 SWCONFIG_LED_PORT_SPEED_10 | \
+					 SWCONFIG_LED_PORT_SPEED_100 | \
+					 SWCONFIG_LED_PORT_SPEED_1000)
+
+#define SWCONFIG_LED_MODE_LINK		0x01
+#define SWCONFIG_LED_MODE_TX		0x02
+#define SWCONFIG_LED_MODE_RX		0x04
+#define SWCONFIG_LED_MODE_TXRX		(SWCONFIG_LED_MODE_TX   | \
+					 SWCONFIG_LED_MODE_RX)
+#define SWCONFIG_LED_MODE_ALL		(SWCONFIG_LED_MODE_LINK | \
+					 SWCONFIG_LED_MODE_TX   | \
+					 SWCONFIG_LED_MODE_RX)
+
+struct switch_led_trigger {
+	struct led_trigger trig;
+	struct switch_dev *swdev;
+
+	struct delayed_work sw_led_work;
+	u32 port_mask;
+	u32 port_link;
+	unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS];
+	unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS];
+	u8 link_speed[SWCONFIG_LED_NUM_PORTS];
+};
+
+struct swconfig_trig_data {
+	struct led_classdev *led_cdev;
+	struct switch_dev *swdev;
+
+	rwlock_t lock;
+	u32 port_mask;
+
+	bool prev_link;
+	unsigned long prev_traffic;
+	enum led_brightness prev_brightness;
+	u8 mode;
+	u8 speed_mask;
+};
+
+static void
+swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data,
+			     enum led_brightness brightness)
+{
+	led_set_brightness(trig_data->led_cdev, brightness);
+	trig_data->prev_brightness = brightness;
+}
+
+static void
+swconfig_trig_update_port_mask(struct led_trigger *trigger)
+{
+	struct list_head *entry;
+	struct switch_led_trigger *sw_trig;
+	u32 port_mask;
+
+	if (!trigger)
+		return;
+
+	sw_trig = (void *) trigger;
+
+	port_mask = 0;
+	read_lock(&trigger->leddev_list_lock);
+	list_for_each(entry, &trigger->led_cdevs) {
+		struct led_classdev *led_cdev;
+		struct swconfig_trig_data *trig_data;
+
+		led_cdev = list_entry(entry, struct led_classdev, trig_list);
+		trig_data = led_cdev->trigger_data;
+		if (trig_data) {
+			read_lock(&trig_data->lock);
+			port_mask |= trig_data->port_mask;
+			read_unlock(&trig_data->lock);
+		}
+	}
+	read_unlock(&trigger->leddev_list_lock);
+
+	sw_trig->port_mask = port_mask;
+
+	if (port_mask)
+		schedule_delayed_work(&sw_trig->sw_led_work,
+				      SWCONFIG_LED_TIMER_INTERVAL);
+	else
+		cancel_delayed_work_sync(&sw_trig->sw_led_work);
+}
+
+static ssize_t
+swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
+	unsigned long port_mask;
+	int ret;
+	bool changed;
+
+	ret = kstrtoul(buf, 0, &port_mask);
+	if (ret)
+		return ret;
+
+	write_lock(&trig_data->lock);
+	changed = (trig_data->port_mask != port_mask);
+	trig_data->port_mask = port_mask;
+	write_unlock(&trig_data->lock);
+
+	if (changed) {
+		if (port_mask == 0)
+			swconfig_trig_set_brightness(trig_data, LED_OFF);
+
+		swconfig_trig_update_port_mask(led_cdev->trigger);
+	}
+
+	return size;
+}
+
+static ssize_t
+swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
+	u32 port_mask;
+
+	read_lock(&trig_data->lock);
+	port_mask = trig_data->port_mask;
+	read_unlock(&trig_data->lock);
+
+	sprintf(buf, "%#x\n", port_mask);
+
+	return strlen(buf) + 1;
+}
+
+static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show,
+		   swconfig_trig_port_mask_store);
+
+/* speed_mask file handler - display value */
+static ssize_t swconfig_trig_speed_mask_show(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
+	u8 speed_mask;
+
+	read_lock(&trig_data->lock);
+	speed_mask = trig_data->speed_mask;
+	read_unlock(&trig_data->lock);
+
+	sprintf(buf, "%#x\n", speed_mask);
+
+	return strlen(buf) + 1;
+}
+
+/* speed_mask file handler - store value */
+static ssize_t swconfig_trig_speed_mask_store(struct device *dev,
+					      struct device_attribute *attr,
+					      const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
+	u8 speed_mask;
+	int ret;
+
+	ret = kstrtou8(buf, 0, &speed_mask);
+	if (ret)
+		return ret;
+
+	write_lock(&trig_data->lock);
+	trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL;
+	write_unlock(&trig_data->lock);
+
+	return size;
+}
+
+/* speed_mask special file */
+static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show,
+		   swconfig_trig_speed_mask_store);
+
+static ssize_t swconfig_trig_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
+	u8 mode;
+
+	read_lock(&trig_data->lock);
+	mode = trig_data->mode;
+	read_unlock(&trig_data->lock);
+
+	if (mode == 0) {
+		strcpy(buf, "none\n");
+	} else {
+		if (mode & SWCONFIG_LED_MODE_LINK)
+			strcat(buf, "link ");
+		if (mode & SWCONFIG_LED_MODE_TX)
+			strcat(buf, "tx ");
+		if (mode & SWCONFIG_LED_MODE_RX)
+			strcat(buf, "rx ");
+		strcat(buf, "\n");
+	}
+
+	return strlen(buf)+1;
+}
+
+static ssize_t swconfig_trig_mode_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
+	char copybuf[128];
+	int new_mode = -1;
+	char *p, *token;
+
+	/* take a copy since we don't want to trash the inbound buffer when using strsep */
+	strncpy(copybuf, buf, sizeof(copybuf));
+	copybuf[sizeof(copybuf) - 1] = 0;
+	p = copybuf;
+
+	while ((token = strsep(&p, " \t\n")) != NULL) {
+		if (!*token)
+			continue;
+
+		if (new_mode < 0)
+			new_mode = 0;
+
+		if (!strcmp(token, "none"))
+			new_mode = 0;
+		else if (!strcmp(token, "tx"))
+			new_mode |= SWCONFIG_LED_MODE_TX;
+		else if (!strcmp(token, "rx"))
+			new_mode |= SWCONFIG_LED_MODE_RX;
+		else if (!strcmp(token, "link"))
+			new_mode |= SWCONFIG_LED_MODE_LINK;
+		else
+			return -EINVAL;
+	}
+
+	if (new_mode < 0)
+		return -EINVAL;
+
+	write_lock(&trig_data->lock);
+	trig_data->mode = (u8)new_mode;
+	write_unlock(&trig_data->lock);
+
+	return size;
+}
+
+/* mode special file */
+static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show,
+		   swconfig_trig_mode_store);
+
+static int
+swconfig_trig_activate(struct led_classdev *led_cdev)
+{
+	struct switch_led_trigger *sw_trig;
+	struct swconfig_trig_data *trig_data;
+	int err;
+
+	trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL);
+	if (!trig_data)
+		return -ENOMEM;
+
+	sw_trig = (void *) led_cdev->trigger;
+
+	rwlock_init(&trig_data->lock);
+	trig_data->led_cdev = led_cdev;
+	trig_data->swdev = sw_trig->swdev;
+	trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL;
+	trig_data->mode = SWCONFIG_LED_MODE_ALL;
+	led_cdev->trigger_data = trig_data;
+
+	err = device_create_file(led_cdev->dev, &dev_attr_port_mask);
+	if (err)
+		goto err_free;
+
+	err = device_create_file(led_cdev->dev, &dev_attr_speed_mask);
+	if (err)
+		goto err_dev_free;
+
+	err = device_create_file(led_cdev->dev, &dev_attr_mode);
+	if (err)
+		goto err_mode_free;
+
+	return 0;
+
+err_mode_free:
+	device_remove_file(led_cdev->dev, &dev_attr_speed_mask);
+
+err_dev_free:
+	device_remove_file(led_cdev->dev, &dev_attr_port_mask);
+
+err_free:
+	led_cdev->trigger_data = NULL;
+	kfree(trig_data);
+
+	return err;
+}
+
+static void
+swconfig_trig_deactivate(struct led_classdev *led_cdev)
+{
+	struct swconfig_trig_data *trig_data;
+
+	swconfig_trig_update_port_mask(led_cdev->trigger);
+
+	trig_data = (void *) led_cdev->trigger_data;
+	if (trig_data) {
+		device_remove_file(led_cdev->dev, &dev_attr_port_mask);
+		device_remove_file(led_cdev->dev, &dev_attr_speed_mask);
+		device_remove_file(led_cdev->dev, &dev_attr_mode);
+		kfree(trig_data);
+	}
+}
+
+/*
+ * link off -> led off (can't be any other reason to turn it on)
+ * link on:
+ *	mode link: led on by default only if speed matches, else off
+ *	mode txrx: blink only if speed matches, else off
+ */
+static void
+swconfig_trig_led_event(struct switch_led_trigger *sw_trig,
+			struct led_classdev *led_cdev)
+{
+	struct swconfig_trig_data *trig_data;
+	u32 port_mask;
+	bool link;
+	u8 speed_mask, mode;
+	enum led_brightness led_base, led_blink;
+
+	trig_data = led_cdev->trigger_data;
+	if (!trig_data)
+		return;
+
+	read_lock(&trig_data->lock);
+	port_mask = trig_data->port_mask;
+	speed_mask = trig_data->speed_mask;
+	mode = trig_data->mode;
+	read_unlock(&trig_data->lock);
+
+	link = !!(sw_trig->port_link & port_mask);
+	if (!link) {
+		if (trig_data->prev_brightness != LED_OFF)
+			swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */
+	}
+	else {
+		unsigned long traffic;
+		int speedok;	/* link speed flag */
+		int i;
+
+		led_base = LED_FULL;
+		led_blink = LED_OFF;
+		traffic = 0;
+		speedok = 0;
+		for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
+			if (port_mask & (1 << i)) {
+				if (sw_trig->link_speed[i] & speed_mask) {
+					traffic += ((mode & SWCONFIG_LED_MODE_TX) ?
+						    sw_trig->port_tx_traffic[i] : 0) +
+						((mode & SWCONFIG_LED_MODE_RX) ?
+						 sw_trig->port_rx_traffic[i] : 0);
+					speedok = 1;
+				}
+			}
+		}
+
+		if (speedok) {
+			/* At least one port speed matches speed_mask */
+			if (!(mode & SWCONFIG_LED_MODE_LINK)) {
+				led_base = LED_OFF;
+				led_blink = LED_FULL;
+			}
+
+			if (trig_data->prev_brightness != led_base)
+				swconfig_trig_set_brightness(trig_data,
+							     led_base);
+			else if (traffic != trig_data->prev_traffic)
+				swconfig_trig_set_brightness(trig_data,
+							     led_blink);
+		} else if (trig_data->prev_brightness != LED_OFF)
+			swconfig_trig_set_brightness(trig_data, LED_OFF);
+
+		trig_data->prev_traffic = traffic;
+	}
+
+	trig_data->prev_link = link;
+}
+
+static void
+swconfig_trig_update_leds(struct switch_led_trigger *sw_trig)
+{
+	struct list_head *entry;
+	struct led_trigger *trigger;
+
+	trigger = &sw_trig->trig;
+	read_lock(&trigger->leddev_list_lock);
+	list_for_each(entry, &trigger->led_cdevs) {
+		struct led_classdev *led_cdev;
+
+		led_cdev = list_entry(entry, struct led_classdev, trig_list);
+		swconfig_trig_led_event(sw_trig, led_cdev);
+	}
+	read_unlock(&trigger->leddev_list_lock);
+}
+
+static void
+swconfig_led_work_func(struct work_struct *work)
+{
+	struct switch_led_trigger *sw_trig;
+	struct switch_dev *swdev;
+	u32 port_mask;
+	u32 link;
+	int i;
+
+	sw_trig = container_of(work, struct switch_led_trigger,
+			       sw_led_work.work);
+
+	port_mask = sw_trig->port_mask;
+	swdev = sw_trig->swdev;
+
+	link = 0;
+	for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
+		u32 port_bit;
+
+		sw_trig->link_speed[i] = 0;
+
+		port_bit = BIT(i);
+		if ((port_mask & port_bit) == 0)
+			continue;
+
+		if (swdev->ops->get_port_link) {
+			struct switch_port_link port_link;
+
+			memset(&port_link, '\0', sizeof(port_link));
+			swdev->ops->get_port_link(swdev, i, &port_link);
+
+			if (port_link.link) {
+				link |= port_bit;
+				switch (port_link.speed) {
+				case SWITCH_PORT_SPEED_UNKNOWN:
+					sw_trig->link_speed[i] =
+						SWCONFIG_LED_PORT_SPEED_NA;
+					break;
+				case SWITCH_PORT_SPEED_10:
+					sw_trig->link_speed[i] =
+						SWCONFIG_LED_PORT_SPEED_10;
+					break;
+				case SWITCH_PORT_SPEED_100:
+					sw_trig->link_speed[i] =
+						SWCONFIG_LED_PORT_SPEED_100;
+					break;
+				case SWITCH_PORT_SPEED_1000:
+					sw_trig->link_speed[i] =
+						SWCONFIG_LED_PORT_SPEED_1000;
+					break;
+				}
+			}
+		}
+
+		if (swdev->ops->get_port_stats) {
+			struct switch_port_stats port_stats;
+
+			memset(&port_stats, '\0', sizeof(port_stats));
+			swdev->ops->get_port_stats(swdev, i, &port_stats);
+			sw_trig->port_tx_traffic[i] = port_stats.tx_bytes;
+			sw_trig->port_rx_traffic[i] = port_stats.rx_bytes;
+		}
+	}
+
+	sw_trig->port_link = link;
+
+	swconfig_trig_update_leds(sw_trig);
+
+	schedule_delayed_work(&sw_trig->sw_led_work,
+			      SWCONFIG_LED_TIMER_INTERVAL);
+}
+
+static int
+swconfig_create_led_trigger(struct switch_dev *swdev)
+{
+	struct switch_led_trigger *sw_trig;
+	int err;
+
+	if (!swdev->ops->get_port_link)
+		return 0;
+
+	sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL);
+	if (!sw_trig)
+		return -ENOMEM;
+
+	sw_trig->swdev = swdev;
+	sw_trig->trig.name = swdev->devname;
+	sw_trig->trig.activate = swconfig_trig_activate;
+	sw_trig->trig.deactivate = swconfig_trig_deactivate;
+
+	INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func);
+
+	err = led_trigger_register(&sw_trig->trig);
+	if (err)
+		goto err_free;
+
+	swdev->led_trigger = sw_trig;
+
+	return 0;
+
+err_free:
+	kfree(sw_trig);
+	return err;
+}
+
+static void
+swconfig_destroy_led_trigger(struct switch_dev *swdev)
+{
+	struct switch_led_trigger *sw_trig;
+
+	sw_trig = swdev->led_trigger;
+	if (sw_trig) {
+		cancel_delayed_work_sync(&sw_trig->sw_led_work);
+		led_trigger_unregister(&sw_trig->trig);
+		kfree(sw_trig);
+	}
+}
+
+#else /* SWCONFIG_LEDS */
+static inline int
+swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; }
+
+static inline void
+swconfig_destroy_led_trigger(struct switch_dev *swdev) { }
+#endif /* CONFIG_SWCONFIG_LEDS */
diff -Naur linux-orig/include/linux/platform_data/b53.h linux/include/linux/platform_data/b53.h
--- linux-orig/include/linux/platform_data/b53.h	2021-11-18 15:52:35.000000000 +0000
+++ linux/include/linux/platform_data/b53.h	2022-04-15 23:51:16.673724613 +0000
@@ -29,6 +29,9 @@
 	u32 chip_id;
 	u16 enabled_ports;
 
+	/* allow to specify an ethX alias */
+	const char *alias;
+
 	/* only used by MMAP'd driver */
 	unsigned big_endian:1;
 	void __iomem *regs;
diff -Naur linux-orig/include/linux/rtl8366.h linux/include/linux/rtl8366.h
--- linux-orig/include/linux/rtl8366.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/include/linux/rtl8366.h	2022-04-15 23:51:16.681724477 +0000
@@ -0,0 +1,42 @@
+/*
+ * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef _RTL8366_H
+#define _RTL8366_H
+
+#define RTL8366_DRIVER_NAME	"rtl8366"
+#define RTL8366S_DRIVER_NAME	"rtl8366s"
+#define RTL8366RB_DRIVER_NAME	"rtl8366rb"
+
+struct rtl8366_smi;
+
+enum rtl8366_type {
+	RTL8366_TYPE_UNKNOWN,
+	RTL8366_TYPE_S,
+	RTL8366_TYPE_RB,
+};
+
+struct rtl8366_initval {
+	unsigned	reg;
+	u16		val;
+};
+
+struct rtl8366_platform_data {
+	unsigned	gpio_sda;
+	unsigned	gpio_sck;
+	void		(*hw_reset)(struct rtl8366_smi *smi, bool active);
+
+	unsigned	num_initvals;
+	struct rtl8366_initval *initvals;
+};
+
+enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata);
+
+#endif /*  _RTL8366_H */
diff -Naur linux-orig/include/linux/rtl8367.h linux/include/linux/rtl8367.h
--- linux-orig/include/linux/rtl8367.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/include/linux/rtl8367.h	2022-04-15 23:51:16.681724477 +0000
@@ -0,0 +1,63 @@
+/*
+ * Platform data definition for the Realtek RTL8367 ethernet switch driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef _RTL8367_H
+#define _RTL8367_H
+
+#define RTL8367_DRIVER_NAME	"rtl8367"
+#define RTL8367B_DRIVER_NAME	"rtl8367b"
+
+enum rtl8367_port_speed {
+	RTL8367_PORT_SPEED_10 = 0,
+	RTL8367_PORT_SPEED_100,
+	RTL8367_PORT_SPEED_1000,
+};
+
+struct rtl8367_port_ability {
+	int force_mode;
+	int nway;
+	int txpause;
+	int rxpause;
+	int link;
+	int duplex;
+	enum rtl8367_port_speed speed;
+};
+
+enum rtl8367_extif_mode {
+	RTL8367_EXTIF_MODE_DISABLED = 0,
+	RTL8367_EXTIF_MODE_RGMII,
+	RTL8367_EXTIF_MODE_MII_MAC,
+	RTL8367_EXTIF_MODE_MII_PHY,
+	RTL8367_EXTIF_MODE_TMII_MAC,
+	RTL8367_EXTIF_MODE_TMII_PHY,
+	RTL8367_EXTIF_MODE_GMII,
+	RTL8367_EXTIF_MODE_RGMII_33V,
+	RTL8367B_EXTIF_MODE_RMII_MAC = 7,
+	RTL8367B_EXTIF_MODE_RMII_PHY,
+	RTL8367B_EXTIF_MODE_RGMII_33V,
+};
+
+struct rtl8367_extif_config {
+	unsigned int txdelay;
+	unsigned int rxdelay;
+	enum rtl8367_extif_mode mode;
+	struct rtl8367_port_ability ability;
+};
+
+struct rtl8367_platform_data {
+	unsigned gpio_sda;
+	unsigned gpio_sck;
+	void (*hw_reset)(bool active);
+
+	struct rtl8367_extif_config *extif0_cfg;
+	struct rtl8367_extif_config *extif1_cfg;
+};
+
+#endif /*  _RTL8367_H */
diff -Naur linux-orig/include/linux/switch.h linux/include/linux/switch.h
--- linux-orig/include/linux/switch.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/include/linux/switch.h	2022-04-15 23:51:16.681724477 +0000
@@ -0,0 +1,179 @@
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _LINUX_SWITCH_H
+#define _LINUX_SWITCH_H
+
+#include <net/genetlink.h>
+#include <uapi/linux/switch.h>
+
+struct switch_dev;
+struct switch_op;
+struct switch_val;
+struct switch_attr;
+struct switch_attrlist;
+struct switch_led_trigger;
+
+int register_switch(struct switch_dev *dev, struct net_device *netdev);
+void unregister_switch(struct switch_dev *dev);
+
+/**
+ * struct switch_attrlist - attribute list
+ *
+ * @n_attr: number of attributes
+ * @attr: pointer to the attributes array
+ */
+struct switch_attrlist {
+	int n_attr;
+	const struct switch_attr *attr;
+};
+
+enum switch_port_speed {
+	SWITCH_PORT_SPEED_UNKNOWN = 0,
+	SWITCH_PORT_SPEED_10 = 10,
+	SWITCH_PORT_SPEED_100 = 100,
+	SWITCH_PORT_SPEED_1000 = 1000,
+};
+
+struct switch_port_link {
+	bool link;
+	bool duplex;
+	bool aneg;
+	bool tx_flow;
+	bool rx_flow;
+	enum switch_port_speed speed;
+	/* in ethtool adv_t format */
+	u32 eee;
+};
+
+struct switch_port_stats {
+	unsigned long long tx_bytes;
+	unsigned long long rx_bytes;
+};
+
+/**
+ * struct switch_dev_ops - switch driver operations
+ *
+ * @attr_global: global switch attribute list
+ * @attr_port: port attribute list
+ * @attr_vlan: vlan attribute list
+ *
+ * Callbacks:
+ *
+ * @get_vlan_ports: read the port list of a VLAN
+ * @set_vlan_ports: set the port list of a VLAN
+ *
+ * @get_port_pvid: get the primary VLAN ID of a port
+ * @set_port_pvid: set the primary VLAN ID of a port
+ *
+ * @apply_config: apply all changed settings to the switch
+ * @reset_switch: resetting the switch
+ */
+struct switch_dev_ops {
+	struct switch_attrlist attr_global, attr_port, attr_vlan;
+
+	int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val);
+	int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val);
+
+	int (*get_port_pvid)(struct switch_dev *dev, int port, int *val);
+	int (*set_port_pvid)(struct switch_dev *dev, int port, int val);
+
+	int (*apply_config)(struct switch_dev *dev);
+	int (*reset_switch)(struct switch_dev *dev);
+
+	int (*get_port_link)(struct switch_dev *dev, int port,
+			     struct switch_port_link *link);
+	int (*set_port_link)(struct switch_dev *dev, int port,
+			     struct switch_port_link *link);
+	int (*get_port_stats)(struct switch_dev *dev, int port,
+			      struct switch_port_stats *stats);
+
+	int (*phy_read16)(struct switch_dev *dev, int addr, u8 reg, u16 *value);
+	int (*phy_write16)(struct switch_dev *dev, int addr, u8 reg, u16 value);
+};
+
+struct switch_dev {
+	struct device_node *of_node;
+	const struct switch_dev_ops *ops;
+	/* will be automatically filled */
+	char devname[IFNAMSIZ];
+
+	const char *name;
+	/* NB: either alias or netdev must be set */
+	const char *alias;
+	struct net_device *netdev;
+
+	unsigned int ports;
+	unsigned int vlans;
+	unsigned int cpu_port;
+
+	/* the following fields are internal for swconfig */
+	unsigned int id;
+	struct list_head dev_list;
+	unsigned long def_global, def_port, def_vlan;
+
+	struct mutex sw_mutex;
+	struct switch_port *portbuf;
+	struct switch_portmap *portmap;
+	struct switch_port_link linkbuf;
+
+	char buf[128];
+
+#ifdef CONFIG_SWCONFIG_LEDS
+	struct switch_led_trigger *led_trigger;
+#endif
+};
+
+struct switch_port {
+	u32 id;
+	u32 flags;
+};
+
+struct switch_portmap {
+	u32 virt;
+	const char *s;
+};
+
+struct switch_val {
+	const struct switch_attr *attr;
+	unsigned int port_vlan;
+	unsigned int len;
+	union {
+		const char *s;
+		u32 i;
+		struct switch_port *ports;
+		struct switch_port_link *link;
+	} value;
+};
+
+struct switch_attr {
+	int disabled;
+	int type;
+	const char *name;
+	const char *description;
+
+	int (*set)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val);
+	int (*get)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val);
+
+	/* for driver internal use */
+	int id;
+	int ofs;
+	int max;
+};
+
+int switch_generic_set_link(struct switch_dev *dev, int port,
+			    struct switch_port_link *link);
+
+#endif /* _LINUX_SWITCH_H */
diff -Naur linux-orig/include/uapi/linux/switch.h linux/include/uapi/linux/switch.h
--- linux-orig/include/uapi/linux/switch.h	1970-01-01 00:00:00.000000000 +0000
+++ linux/include/uapi/linux/switch.h	2022-04-15 23:51:16.681724477 +0000
@@ -0,0 +1,119 @@
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _UAPI_LINUX_SWITCH_H
+#define _UAPI_LINUX_SWITCH_H
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#ifndef __KERNEL__
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#endif
+
+/* main attributes */
+enum {
+	SWITCH_ATTR_UNSPEC,
+	/* global */
+	SWITCH_ATTR_TYPE,
+	/* device */
+	SWITCH_ATTR_ID,
+	SWITCH_ATTR_DEV_NAME,
+	SWITCH_ATTR_ALIAS,
+	SWITCH_ATTR_NAME,
+	SWITCH_ATTR_VLANS,
+	SWITCH_ATTR_PORTS,
+	SWITCH_ATTR_PORTMAP,
+	SWITCH_ATTR_CPU_PORT,
+	/* attributes */
+	SWITCH_ATTR_OP_ID,
+	SWITCH_ATTR_OP_TYPE,
+	SWITCH_ATTR_OP_NAME,
+	SWITCH_ATTR_OP_PORT,
+	SWITCH_ATTR_OP_VLAN,
+	SWITCH_ATTR_OP_VALUE_INT,
+	SWITCH_ATTR_OP_VALUE_STR,
+	SWITCH_ATTR_OP_VALUE_PORTS,
+	SWITCH_ATTR_OP_VALUE_LINK,
+	SWITCH_ATTR_OP_DESCRIPTION,
+	/* port lists */
+	SWITCH_ATTR_PORT,
+	SWITCH_ATTR_MAX
+};
+
+enum {
+	/* port map */
+	SWITCH_PORTMAP_PORTS,
+	SWITCH_PORTMAP_SEGMENT,
+	SWITCH_PORTMAP_VIRT,
+	SWITCH_PORTMAP_MAX
+};
+
+/* commands */
+enum {
+	SWITCH_CMD_UNSPEC,
+	SWITCH_CMD_GET_SWITCH,
+	SWITCH_CMD_NEW_ATTR,
+	SWITCH_CMD_LIST_GLOBAL,
+	SWITCH_CMD_GET_GLOBAL,
+	SWITCH_CMD_SET_GLOBAL,
+	SWITCH_CMD_LIST_PORT,
+	SWITCH_CMD_GET_PORT,
+	SWITCH_CMD_SET_PORT,
+	SWITCH_CMD_LIST_VLAN,
+	SWITCH_CMD_GET_VLAN,
+	SWITCH_CMD_SET_VLAN
+};
+
+/* data types */
+enum switch_val_type {
+	SWITCH_TYPE_UNSPEC,
+	SWITCH_TYPE_INT,
+	SWITCH_TYPE_STRING,
+	SWITCH_TYPE_PORTS,
+	SWITCH_TYPE_LINK,
+	SWITCH_TYPE_NOVAL,
+};
+
+/* port nested attributes */
+enum {
+	SWITCH_PORT_UNSPEC,
+	SWITCH_PORT_ID,
+	SWITCH_PORT_FLAG_TAGGED,
+	SWITCH_PORT_ATTR_MAX
+};
+
+/* link nested attributes */
+enum {
+	SWITCH_LINK_UNSPEC,
+	SWITCH_LINK_FLAG_LINK,
+	SWITCH_LINK_FLAG_DUPLEX,
+	SWITCH_LINK_FLAG_ANEG,
+	SWITCH_LINK_FLAG_TX_FLOW,
+	SWITCH_LINK_FLAG_RX_FLOW,
+	SWITCH_LINK_SPEED,
+	SWITCH_LINK_FLAG_EEE_100BASET,
+	SWITCH_LINK_FLAG_EEE_1000BASET,
+	SWITCH_LINK_ATTR_MAX,
+};
+
+#define SWITCH_ATTR_DEFAULTS_OFFSET	0x1000
+
+
+#endif /* _UAPI_LINUX_SWITCH_H */
diff -Naur linux-orig/scripts/dtc/include-prefixes/arm64/broadcom/Makefile linux/scripts/dtc/include-prefixes/arm64/broadcom/Makefile
--- linux-orig/scripts/dtc/include-prefixes/arm64/broadcom/Makefile	2021-11-18 15:52:35.000000000 +0000
+++ linux/scripts/dtc/include-prefixes/arm64/broadcom/Makefile	2022-04-15 23:51:16.697724207 +0000
@@ -1,3 +1,4 @@
+dtb-y += interceptor.dtb
 # SPDX-License-Identifier: GPL-2.0
 dtb-$(CONFIG_ARCH_BCM2835) += bcm2837-rpi-3-a-plus.dtb \
 			      bcm2837-rpi-3-b.dtb \
diff -Naur linux-orig/scripts/dtc/include-prefixes/arm64/broadcom/interceptor.dts linux/scripts/dtc/include-prefixes/arm64/broadcom/interceptor.dts
--- linux-orig/scripts/dtc/include-prefixes/arm64/broadcom/interceptor.dts	1970-01-01 00:00:00.000000000 +0000
+++ linux/scripts/dtc/include-prefixes/arm64/broadcom/interceptor.dts	2022-04-15 23:51:16.685724410 +0000
@@ -0,0 +1,21 @@
+#include "arm/bcm2711-rpi-cm4.dts"
+
+/ {
+  rtl8367rb: rtl8367rb {
+    compatible = "realtek,rtl8367c";
+    gpio-sda = <&gpio 44 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+    gpio-sck = <&gpio 45 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+  };
+};
+
+&i2c0 {
+  rv3028@52 {
+    compatible = "microcrystal,rv3028";
+    reg = <0x52>;
+  };
+};
+
+&usb {
+  // dwc2 driver appears to be broken on mainline, using xhci instead
+  status = "disabled";
+};
