[Feature] add GA346 baseline version

Change-Id: Ic62933698569507dcf98240cdf5d9931ae34348f
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/Kconfig b/src/kernel/linux/v4.19/drivers/net/phy/Kconfig
new file mode 100644
index 0000000..5d6eb39
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/Kconfig
@@ -0,0 +1,547 @@
+#
+# PHY Layer Configuration
+#
+
+menuconfig MDIO_DEVICE
+	tristate "MDIO bus device drivers"
+	help
+	  MDIO devices and driver infrastructure code.
+
+config MDIO_BUS
+	tristate
+	default m if PHYLIB=m
+	default MDIO_DEVICE
+	help
+	  This internal symbol is used for link time dependencies and it
+	  reflects whether the mdio_bus/mdio_device code is built as a
+	  loadable module or built-in.
+
+if MDIO_BUS
+
+config MDIO_BCM_IPROC
+	tristate "Broadcom iProc MDIO bus controller"
+	depends on ARCH_BCM_IPROC || COMPILE_TEST
+	depends on HAS_IOMEM && OF_MDIO
+	help
+	  This module provides a driver for the MDIO busses found in the
+	  Broadcom iProc SoC's.
+
+config MDIO_BCM_UNIMAC
+	tristate "Broadcom UniMAC MDIO bus controller"
+	depends on HAS_IOMEM
+	help
+	  This module provides a driver for the Broadcom UniMAC MDIO busses.
+	  This hardware can be found in the Broadcom GENET Ethernet MAC
+	  controllers as well as some Broadcom Ethernet switches such as the
+	  Starfighter 2 switches.
+
+config MDIO_BITBANG
+	tristate "Bitbanged MDIO buses"
+	help
+	  This module implements the MDIO bus protocol in software,
+	  for use by low level drivers that export the ability to
+	  drive the relevant pins.
+
+	  If in doubt, say N.
+
+config MDIO_BUS_MUX
+	tristate
+	depends on OF_MDIO
+	help
+	  This module provides a driver framework for MDIO bus
+	  multiplexers which connect one of several child MDIO busses
+	  to a parent bus.  Switching between child busses is done by
+	  device specific drivers.
+
+config MDIO_BUS_MUX_BCM_IPROC
+	tristate "Broadcom iProc based MDIO bus multiplexers"
+	depends on OF && OF_MDIO && (ARCH_BCM_IPROC || COMPILE_TEST)
+	select MDIO_BUS_MUX
+	default ARCH_BCM_IPROC
+	help
+	  This module provides a driver for MDIO bus multiplexers found in
+	  iProc based Broadcom SoCs. This multiplexer connects one of several
+	  child MDIO bus to a parent bus. Buses could be internal as well as
+	  external and selection logic lies inside the same multiplexer.
+
+config MDIO_BUS_MUX_GPIO
+	tristate "GPIO controlled MDIO bus multiplexers"
+	depends on OF_GPIO && OF_MDIO
+	select MDIO_BUS_MUX
+	help
+	  This module provides a driver for MDIO bus multiplexers that
+	  are controlled via GPIO lines.  The multiplexer connects one of
+	  several child MDIO busses to a parent bus.  Child bus
+	  selection is under the control of GPIO lines.
+
+config MDIO_BUS_MUX_MMIOREG
+	tristate "MMIO device-controlled MDIO bus multiplexers"
+	depends on OF_MDIO && HAS_IOMEM
+	select MDIO_BUS_MUX
+	help
+	  This module provides a driver for MDIO bus multiplexers that
+	  are controlled via a simple memory-mapped device, like an FPGA.
+	  The multiplexer connects one of several child MDIO busses to a
+	  parent bus.  Child bus selection is under the control of one of
+	  the FPGA's registers.
+
+	  Currently, only 8/16/32 bits registers are supported.
+
+config MDIO_CAVIUM
+	tristate
+
+config MDIO_GPIO
+	tristate "GPIO lib-based bitbanged MDIO buses"
+	depends on MDIO_BITBANG
+	depends on GPIOLIB || COMPILE_TEST
+	---help---
+	  Supports GPIO lib-based MDIO busses.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called mdio-gpio.
+
+config MDIO_HISI_FEMAC
+	tristate "Hisilicon FEMAC MDIO bus controller"
+	depends on HAS_IOMEM && OF_MDIO
+	help
+	  This module provides a driver for the MDIO busses found in the
+	  Hisilicon SoC that have an Fast Ethernet MAC.
+
+config MDIO_I2C
+	tristate
+	depends on I2C
+	help
+	  Support I2C based PHYs.  This provides a MDIO bus bridged
+	  to I2C to allow PHYs connected in I2C mode to be accessed
+	  using the existing infrastructure.
+
+	  This is library mode.
+
+config MDIO_MOXART
+	tristate "MOXA ART MDIO interface support"
+	depends on ARCH_MOXART || COMPILE_TEST
+	help
+	  This driver supports the MDIO interface found in the network
+	  interface units of the MOXA ART SoC
+
+config MDIO_MSCC_MIIM
+	tristate "Microsemi MIIM interface support"
+	depends on HAS_IOMEM
+	help
+	  This driver supports the MIIM (MDIO) interface found in the network
+	  switches of the Microsemi SoCs
+
+config MDIO_OCTEON
+	tristate "Octeon and some ThunderX SOCs MDIO buses"
+	depends on 64BIT
+	depends on HAS_IOMEM && OF_MDIO
+	select MDIO_CAVIUM
+	help
+	  This module provides a driver for the Octeon and ThunderX MDIO
+	  buses. It is required by the Octeon and ThunderX ethernet device
+	  drivers on some systems.
+
+config MDIO_SUN4I
+	tristate "Allwinner sun4i MDIO interface support"
+	depends on ARCH_SUNXI || COMPILE_TEST
+	help
+	  This driver supports the MDIO interface found in the network
+	  interface units of the Allwinner SoC that have an EMAC (A10,
+	  A12, A10s, etc.)
+
+config MDIO_THUNDER
+	tristate "ThunderX SOCs MDIO buses"
+	depends on 64BIT
+	depends on PCI
+	select MDIO_CAVIUM
+	help
+	  This driver supports the MDIO interfaces found on Cavium
+	  ThunderX SoCs when the MDIO bus device appears as a PCI
+	  device.
+
+config MDIO_XGENE
+	tristate "APM X-Gene SoC MDIO bus controller"
+	depends on ARCH_XGENE || COMPILE_TEST
+	help
+	  This module provides a driver for the MDIO busses found in the
+	  APM X-Gene SoC's.
+
+endif
+
+config PHYLINK
+	tristate
+	depends on NETDEVICES
+	select PHYLIB
+	select SWPHY
+	help
+	  PHYlink models the link between the PHY and MAC, allowing fixed
+	  configuration links, PHYs, and Serdes links with MAC level
+	  autonegotiation modes.
+
+menuconfig PHYLIB
+	tristate "PHY Device support and infrastructure"
+	depends on NETDEVICES
+	select MDIO_DEVICE
+	help
+	  Ethernet controllers are usually attached to PHY
+	  devices.  This option provides infrastructure for
+	  managing PHY devices.
+
+if PHYLIB
+
+config SWPHY
+	bool
+
+config LED_TRIGGER_PHY
+	bool "Support LED triggers for tracking link state"
+	depends on LEDS_TRIGGERS
+	---help---
+	  Adds support for a set of LED trigger events per-PHY.  Link
+	  state change will trigger the events, for consumption by an
+	  LED class driver.  There are triggers for each link speed currently
+	  supported by the PHY and also a one common "link" trigger as a
+	  logical-or of all the link speed ones.
+	  All these triggers are named according to the following pattern:
+	      <mii bus id>:<phy>:<speed>
+
+	  Where speed is in the form:
+		<Speed in megabits>Mbps OR <Speed in gigabits>Gbps OR link
+		for any speed known to the PHY.
+
+
+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 ETHERNET_PACKET_MANGLE
+	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 MVSWITCH_PHY
+	tristate "Driver for Marvell 88E6060 switches"
+	select ETHERNET_PACKET_MANGLE
+
+config MVSW61XX_PHY
+	tristate "Driver for Marvell 88E6171/6172 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 SFP
+	tristate "SFP cage support"
+	depends on I2C && PHYLINK
+	depends on HWMON || HWMON=n
+	select MDIO_I2C
+
+config AMD_PHY
+	tristate "AMD PHYs"
+	---help---
+	  Currently supports the am79c874
+
+config AQUANTIA_PHY
+	tristate "Aquantia PHYs"
+	---help---
+	  Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
+
+config AX88796B_PHY
+	tristate "Asix PHYs"
+	help
+	  Currently supports the Asix Electronics PHY found in the X-Surf 100
+	  AX88796B package.
+
+config AT803X_PHY
+	tristate "AT803X PHYs"
+	---help---
+	  Currently supports the AT8030 and AT8035 model
+
+config BCM63XX_PHY
+	tristate "Broadcom 63xx SOCs internal PHY"
+	depends on BCM63XX
+	select BCM_NET_PHYLIB
+	---help---
+	  Currently supports the 6348 and 6358 PHYs.
+
+config BCM7XXX_PHY
+	tristate "Broadcom 7xxx SOCs internal PHYs"
+	select BCM_NET_PHYLIB
+	---help---
+	  Currently supports the BCM7366, BCM7439, BCM7445, and
+	  40nm and 65nm generation of BCM7xxx Set Top Box SoCs.
+
+config BCM87XX_PHY
+	tristate "Broadcom BCM8706 and BCM8727 PHYs"
+	help
+	  Currently supports the BCM8706 and BCM8727 10G Ethernet PHYs.
+
+config BCM_CYGNUS_PHY
+	tristate "Broadcom Cygnus SoC internal PHY"
+	depends on ARCH_BCM_CYGNUS || COMPILE_TEST
+	depends on MDIO_BCM_IPROC
+	select BCM_NET_PHYLIB
+	---help---
+	  This PHY driver is for the 1G internal PHYs of the Broadcom
+	  Cygnus Family SoC.
+
+	  Currently supports internal PHY's used in the BCM11300,
+	  BCM11320, BCM11350, BCM11360, BCM58300, BCM58302,
+	  BCM58303 & BCM58305 Broadcom Cygnus SoCs.
+
+config BCM_NET_PHYLIB
+	tristate
+
+config BROADCOM_PHY
+	tristate "Broadcom PHYs"
+	select BCM_NET_PHYLIB
+	---help---
+	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
+	  BCM5481, BCM54810 and BCM5482 PHYs.
+
+config BCM84881_PHY
+	tristate "Broadcom BCM84881 PHY"
+	---help---
+	  Support the Broadcom BCM84881 PHY.
+
+config CICADA_PHY
+	tristate "Cicada PHYs"
+	---help---
+	  Currently supports the cis8204
+
+config CORTINA_PHY
+	tristate "Cortina EDC CDR 10G Ethernet PHY"
+	---help---
+	  Currently supports the CS4340 phy.
+
+config DAVICOM_PHY
+	tristate "Davicom PHYs"
+	---help---
+	  Currently supports dm9161e and dm9131
+
+config DP83822_PHY
+	tristate "Texas Instruments DP83822 PHY"
+	---help---
+	  Supports the DP83822 PHY.
+
+config DP83TC811_PHY
+	tristate "Texas Instruments DP83TC822 PHY"
+	---help---
+	  Supports the DP83TC822 PHY.
+
+config DP83848_PHY
+	tristate "Texas Instruments DP83848 PHY"
+	---help---
+	  Supports the DP83848 PHY.
+
+config DP83867_PHY
+	tristate "Texas Instruments DP83867 Gigabit PHY"
+	---help---
+	  Currently supports the DP83867 PHY.
+
+config FIXED_PHY
+	tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
+	depends on PHYLIB
+	select SWPHY
+	---help---
+	  Adds the platform "fixed" MDIO Bus to cover the boards that use
+	  PHYs that are not connected to the real MDIO bus.
+
+	  Currently tested with mpc866ads and mpc8349e-mitx.
+
+config ICPLUS_PHY
+	tristate "ICPlus PHYs"
+	---help---
+	  Currently supports the IP175C and IP1001 PHYs.
+
+config INTEL_XWAY_PHY
+	tristate "Intel XWAY PHYs"
+	---help---
+	  Supports the Intel XWAY (former Lantiq) 11G and 22E PHYs.
+	  These PHYs are marked as standalone chips under the names
+	  PEF 7061, PEF 7071 and PEF 7072 or integrated into the Intel
+	  SoCs xRX200, xRX300, xRX330, xRX350 and xRX550.
+
+config LSI_ET1011C_PHY
+	tristate "LSI ET1011C PHY"
+	---help---
+	  Supports the LSI ET1011C PHY.
+
+config LXT_PHY
+	tristate "Intel LXT PHYs"
+	---help---
+	  Currently supports the lxt970, lxt971
+
+config MARVELL_PHY
+	tristate "Marvell PHYs"
+	---help---
+	  Currently has a driver for the 88E1011S
+
+config MARVELL_10G_PHY
+	tristate "Marvell Alaska 10Gbit PHYs"
+	---help---
+	  Support for the Marvell Alaska MV88X3310 and compatible PHYs.
+
+config MARVELL_88Q_PHY
+	tristate "Marvell 88Q PHYs"
+	help
+	  Currently supports the 88Q2110 PHYs.
+
+config MESON_GXL_PHY
+	tristate "Amlogic Meson GXL Internal PHY"
+	depends on ARCH_MESON || COMPILE_TEST
+	---help---
+	  Currently has a driver for the Amlogic Meson GXL Internal PHY
+
+config MICREL_PHY
+	tristate "Micrel PHYs"
+	---help---
+	  Supports the KSZ9021, VSC8201, KS8001 PHYs.
+
+config MICROCHIP_PHY
+	tristate "Microchip PHYs"
+	help
+	  Supports the LAN88XX PHYs.
+
+config TJA110X_PHY
+	tristate "Tja110x PHYs"
+	help
+	  Supports the Tja110x PHYs.
+
+config MICROCHIP_T1_PHY
+	tristate "Microchip T1 PHYs"
+	---help---
+	  Supports the LAN87XX PHYs.
+
+config MICROSEMI_PHY
+	tristate "Microsemi PHYs"
+	---help---
+	  Currently supports VSC8530, VSC8531, VSC8540 and VSC8541 PHYs
+
+config NATIONAL_PHY
+	tristate "National Semiconductor PHYs"
+	---help---
+	  Currently supports the DP83865 PHY.
+
+config NXP_TJA1100_PHY
+	tristate "NXP TJA1100 PHY"
+	help
+	  Currently supports the TJA1100 PHY.
+
+config QSEMI_PHY
+	tristate "Quality Semiconductor PHYs"
+	---help---
+	  Currently supports the qs6612
+
+config REALTEK_PHY
+	tristate "Realtek PHYs"
+	---help---
+	  Supports the Realtek 821x PHY.
+
+config RENESAS_PHY
+	tristate "Driver for Renesas PHYs"
+	---help---
+	  Supports the Renesas PHYs uPD60620 and uPD60620A.
+
+config ROCKCHIP_PHY
+        tristate "Driver for Rockchip Ethernet PHYs"
+        ---help---
+          Currently supports the integrated Ethernet PHY.
+
+config SMSC_PHY
+	tristate "SMSC PHYs"
+	---help---
+	  Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs
+
+config STE10XP
+	tristate "STMicroelectronics STe10Xp PHYs"
+	---help---
+	  This is the driver for the STe100p and STe101p PHYs.
+
+config TERANETICS_PHY
+	tristate "Teranetics PHYs"
+	---help---
+	  Currently supports the Teranetics TN2020
+
+config VITESSE_PHY
+	tristate "Vitesse PHYs"
+	---help---
+	  Currently supports the vsc8244
+
+config XILINX_GMII2RGMII
+	tristate "Xilinx GMII2RGMII converter driver"
+	---help---
+	  This driver support xilinx GMII to RGMII IP core it provides
+	  the Reduced Gigabit Media Independent Interface(RGMII) between
+	  Ethernet physical media devices and the Gigabit Ethernet controller.
+
+source "drivers/net/phy/mtk/mt753x/Kconfig"
+endif # PHYLIB
+
+config MICREL_KS8995MA
+	tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch"
+	depends on SPI
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/Makefile b/src/kernel/linux/v4.19/drivers/net/phy/Makefile
new file mode 100644
index 0000000..a1c12b0
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/Makefile
@@ -0,0 +1,106 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for Linux PHY drivers and MDIO bus drivers
+
+libphy-y			:= phy.o phy-c45.o phy-core.o phy_device.o
+mdio-bus-y			+= mdio_bus.o mdio_device.o
+
+ifdef CONFIG_MDIO_DEVICE
+obj-y				+= mdio-boardinfo.o
+endif
+
+# PHYLIB implies MDIO_DEVICE, in that case, we have a bunch of circular
+# dependencies that does not make it possible to split mdio-bus objects into a
+# dedicated loadable module, so we bundle them all together into libphy.ko
+ifdef CONFIG_PHYLIB
+libphy-y			+= $(mdio-bus-y)
+else
+obj-$(CONFIG_MDIO_DEVICE)	+= mdio-bus.o
+endif
+libphy-$(CONFIG_SWPHY)		+= swphy.o
+libphy-$(CONFIG_LED_TRIGGER_PHY)	+= phy_led_triggers.o
+
+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_MVSWITCH_PHY)	+= mvswitch.o
+obj-$(CONFIG_MVSW61XX_PHY)	+= mvsw61xx.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_MDIO_BCM_IPROC)	+= mdio-bcm-iproc.o
+obj-$(CONFIG_MDIO_BCM_UNIMAC)	+= mdio-bcm-unimac.o
+obj-$(CONFIG_MDIO_BITBANG)	+= mdio-bitbang.o
+obj-$(CONFIG_MDIO_BUS_MUX)	+= mdio-mux.o
+obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC)	+= mdio-mux-bcm-iproc.o
+obj-$(CONFIG_MDIO_BUS_MUX_GPIO)	+= mdio-mux-gpio.o
+obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
+obj-$(CONFIG_MDIO_CAVIUM)	+= mdio-cavium.o
+obj-$(CONFIG_MDIO_GPIO)		+= mdio-gpio.o
+obj-$(CONFIG_MDIO_HISI_FEMAC)	+= mdio-hisi-femac.o
+obj-$(CONFIG_MDIO_I2C)		+= mdio-i2c.o
+obj-$(CONFIG_MDIO_MOXART)	+= mdio-moxart.o
+obj-$(CONFIG_MDIO_MSCC_MIIM)	+= mdio-mscc-miim.o
+obj-$(CONFIG_MDIO_OCTEON)	+= mdio-octeon.o
+obj-$(CONFIG_MDIO_SUN4I)	+= mdio-sun4i.o
+obj-$(CONFIG_MDIO_THUNDER)	+= mdio-thunder.o
+obj-$(CONFIG_MDIO_XGENE)	+= mdio-xgene.o
+
+obj-$(CONFIG_SFP)		+= sfp.o
+sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
+obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
+
+obj-$(CONFIG_AMD_PHY)		+= amd.o
+obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
+obj-$(CONFIG_AX88796B_PHY)	+= ax88796b.o
+obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+obj-$(CONFIG_BCM63XX_PHY)	+= bcm63xx.o
+obj-$(CONFIG_BCM7XXX_PHY)	+= bcm7xxx.o
+obj-$(CONFIG_BCM87XX_PHY)	+= bcm87xx.o
+obj-$(CONFIG_BCM_CYGNUS_PHY)	+= bcm-cygnus.o
+obj-$(CONFIG_BCM_NET_PHYLIB)	+= bcm-phy-lib.o
+obj-$(CONFIG_BROADCOM_PHY)	+= broadcom.o
+obj-$(CONFIG_BCM84881_PHY)	+= bcm84881.o
+obj-$(CONFIG_CICADA_PHY)	+= cicada.o
+obj-$(CONFIG_CORTINA_PHY)	+= cortina.o
+obj-$(CONFIG_DAVICOM_PHY)	+= davicom.o
+obj-$(CONFIG_DP83640_PHY)	+= dp83640.o
+obj-$(CONFIG_DP83822_PHY)	+= dp83822.o
+obj-$(CONFIG_DP83TC811_PHY)	+= dp83tc811.o
+obj-$(CONFIG_DP83848_PHY)	+= dp83848.o
+obj-$(CONFIG_DP83867_PHY)	+= dp83867.o
+obj-$(CONFIG_FIXED_PHY)		+= fixed_phy.o
+obj-$(CONFIG_ICPLUS_PHY)	+= icplus.o
+obj-$(CONFIG_INTEL_XWAY_PHY)	+= intel-xway.o
+obj-$(CONFIG_LSI_ET1011C_PHY)	+= et1011c.o
+obj-$(CONFIG_LXT_PHY)		+= lxt.o
+obj-$(CONFIG_MARVELL_PHY)	+= marvell.o
+obj-$(CONFIG_MARVELL_88Q_PHY)	+= marvell-88q.o
+obj-$(CONFIG_MARVELL_10G_PHY)	+= marvell10g.o
+obj-$(CONFIG_MESON_GXL_PHY)	+= meson-gxl.o
+obj-$(CONFIG_MICREL_KS8995MA)	+= spi_ks8995.o
+obj-$(CONFIG_MICREL_PHY)	+= micrel.o
+obj-$(CONFIG_MICROCHIP_PHY)	+= microchip.o
+obj-$(CONFIG_MICROCHIP_T1_PHY)	+= microchip_t1.o
+obj-$(CONFIG_MICROSEMI_PHY)	+= mscc.o
+obj-$(CONFIG_NATIONAL_PHY)	+= national.o
+obj-$(CONFIG_NXP_TJA1100_PHY)	+= nxp-tja1100.o
+obj-$(CONFIG_QSEMI_PHY)		+= qsemi.o
+obj-y += realtek.o
+obj-$(CONFIG_RENESAS_PHY)	+= uPD60620.o
+obj-$(CONFIG_ROCKCHIP_PHY)	+= rockchip.o
+obj-$(CONFIG_SMSC_PHY)		+= smsc.o
+obj-$(CONFIG_STE10XP)		+= ste10Xp.o
+obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
+obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
+obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
+obj-$(CONFIG_MT753X_GSW)	+= mtk/mt753x/
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/adm6996.c b/src/kernel/linux/v4.19/drivers/net/phy/adm6996.c
new file mode 100644
index 0000000..42928ba
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/adm6996.c
@@ -0,0 +1,1241 @@
+/*
+ * 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 <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;
+
+	pdev->supported = ADVERTISED_100baseT_Full;
+	pdev->advertising = ADVERTISED_100baseT_Full;
+
+	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 --git a/src/kernel/linux/v4.19/drivers/net/phy/adm6996.h b/src/kernel/linux/v4.19/drivers/net/phy/adm6996.h
new file mode 100644
index 0000000..6fd460a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/adm6996.h
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/amd.c b/src/kernel/linux/v4.19/drivers/net/phy/amd.c
new file mode 100644
index 0000000..6fe5dc9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/amd.c
@@ -0,0 +1,82 @@
+/*
+ * Driver for AMD am79c PHYs
+ *
+ * Author: Heiko Schocher <hs@denx.de>
+ *
+ * Copyright (c) 2011 DENX Software Engineering GmbH
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+#define PHY_ID_AM79C874		0x0022561b
+
+#define MII_AM79C_IR		17	/* Interrupt Status/Control Register */
+#define MII_AM79C_IR_EN_LINK	0x0400	/* IR enable Linkstate */
+#define MII_AM79C_IR_EN_ANEG	0x0100	/* IR enable Aneg Complete */
+#define MII_AM79C_IR_IMASK_INIT	(MII_AM79C_IR_EN_LINK | MII_AM79C_IR_EN_ANEG)
+
+MODULE_DESCRIPTION("AMD PHY driver");
+MODULE_AUTHOR("Heiko Schocher <hs@denx.de>");
+MODULE_LICENSE("GPL");
+
+static int am79c_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, MII_BMSR);
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_AM79C_IR);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int am79c_config_init(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int am79c_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, MII_AM79C_IR, MII_AM79C_IR_IMASK_INIT);
+	else
+		err = phy_write(phydev, MII_AM79C_IR, 0);
+
+	return err;
+}
+
+static struct phy_driver am79c_driver[] = { {
+	.phy_id		= PHY_ID_AM79C874,
+	.name		= "AM79C874",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= am79c_config_init,
+	.ack_interrupt	= am79c_ack_interrupt,
+	.config_intr	= am79c_config_intr,
+} };
+
+module_phy_driver(am79c_driver);
+
+static struct mdio_device_id __maybe_unused amd_tbl[] = {
+	{ PHY_ID_AM79C874, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, amd_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/aquantia.c b/src/kernel/linux/v4.19/drivers/net/phy/aquantia.c
new file mode 100644
index 0000000..319edc9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/aquantia.c
@@ -0,0 +1,204 @@
+/*
+ * Driver for Aquantia PHY
+ *
+ * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
+ *
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/mdio.h>
+
+#define PHY_ID_AQ1202	0x03a1b445
+#define PHY_ID_AQ2104	0x03a1b460
+#define PHY_ID_AQR105	0x03a1b4a2
+#define PHY_ID_AQR106	0x03a1b4d0
+#define PHY_ID_AQR107	0x03a1b4e0
+#define PHY_ID_AQR405	0x03a1b4b0
+
+#define PHY_AQUANTIA_FEATURES	(SUPPORTED_10000baseT_Full | \
+				 SUPPORTED_1000baseT_Full | \
+				 SUPPORTED_100baseT_Full | \
+				 PHY_DEFAULT_FEATURES)
+
+static int aquantia_config_aneg(struct phy_device *phydev)
+{
+	phydev->supported = PHY_AQUANTIA_FEATURES;
+	phydev->advertising = phydev->supported;
+
+	return 0;
+}
+
+static int aquantia_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 1);
+		if (err < 0)
+			return err;
+
+		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 1);
+		if (err < 0)
+			return err;
+
+		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0x1001);
+	} else {
+		err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 0);
+		if (err < 0)
+			return err;
+
+		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 0);
+		if (err < 0)
+			return err;
+
+		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0);
+	}
+
+	return err;
+}
+
+static int aquantia_ack_interrupt(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xcc01);
+	return (reg < 0) ? reg : 0;
+}
+
+static int aquantia_read_status(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (reg & MDIO_STAT1_LSTATUS)
+		phydev->link = 1;
+	else
+		phydev->link = 0;
+
+	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800);
+	mdelay(10);
+	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800);
+
+	switch (reg) {
+	case 0x9:
+		phydev->speed = SPEED_2500;
+		break;
+	case 0x5:
+		phydev->speed = SPEED_1000;
+		break;
+	case 0x3:
+		phydev->speed = SPEED_100;
+		break;
+	case 0x7:
+	default:
+		phydev->speed = SPEED_10000;
+		break;
+	}
+	phydev->duplex = DUPLEX_FULL;
+
+	return 0;
+}
+
+static struct phy_driver aquantia_driver[] = {
+{
+	.phy_id		= PHY_ID_AQ1202,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Aquantia AQ1202",
+	.features	= PHY_AQUANTIA_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.aneg_done	= genphy_c45_aneg_done,
+	.config_aneg    = aquantia_config_aneg,
+	.config_intr	= aquantia_config_intr,
+	.ack_interrupt	= aquantia_ack_interrupt,
+	.read_status	= aquantia_read_status,
+},
+{
+	.phy_id		= PHY_ID_AQ2104,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Aquantia AQ2104",
+	.features	= PHY_AQUANTIA_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.aneg_done	= genphy_c45_aneg_done,
+	.config_aneg    = aquantia_config_aneg,
+	.config_intr	= aquantia_config_intr,
+	.ack_interrupt	= aquantia_ack_interrupt,
+	.read_status	= aquantia_read_status,
+},
+{
+	.phy_id		= PHY_ID_AQR105,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Aquantia AQR105",
+	.features	= PHY_AQUANTIA_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.aneg_done	= genphy_c45_aneg_done,
+	.config_aneg    = aquantia_config_aneg,
+	.config_intr	= aquantia_config_intr,
+	.ack_interrupt	= aquantia_ack_interrupt,
+	.read_status	= aquantia_read_status,
+},
+{
+	.phy_id		= PHY_ID_AQR106,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Aquantia AQR106",
+	.features	= PHY_AQUANTIA_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.aneg_done	= genphy_c45_aneg_done,
+	.config_aneg    = aquantia_config_aneg,
+	.config_intr	= aquantia_config_intr,
+	.ack_interrupt	= aquantia_ack_interrupt,
+	.read_status	= aquantia_read_status,
+},
+{
+	.phy_id		= PHY_ID_AQR107,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Aquantia AQR107",
+	.features	= PHY_AQUANTIA_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.aneg_done	= genphy_c45_aneg_done,
+	.config_aneg    = aquantia_config_aneg,
+	.config_intr	= aquantia_config_intr,
+	.ack_interrupt	= aquantia_ack_interrupt,
+	.read_status	= aquantia_read_status,
+},
+{
+	.phy_id		= PHY_ID_AQR405,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Aquantia AQR405",
+	.features	= PHY_AQUANTIA_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.aneg_done	= genphy_c45_aneg_done,
+	.config_aneg    = aquantia_config_aneg,
+	.config_intr	= aquantia_config_intr,
+	.ack_interrupt	= aquantia_ack_interrupt,
+	.read_status	= aquantia_read_status,
+},
+};
+
+module_phy_driver(aquantia_driver);
+
+static struct mdio_device_id __maybe_unused aquantia_tbl[] = {
+	{ PHY_ID_AQ1202, 0xfffffff0 },
+	{ PHY_ID_AQ2104, 0xfffffff0 },
+	{ PHY_ID_AQR105, 0xfffffff0 },
+	{ PHY_ID_AQR106, 0xfffffff0 },
+	{ PHY_ID_AQR107, 0xfffffff0 },
+	{ PHY_ID_AQR405, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, aquantia_tbl);
+
+MODULE_DESCRIPTION("Aquantia PHY driver");
+MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/ar8216.c b/src/kernel/linux/v4.19/drivers/net/phy/ar8216.c
new file mode 100644
index 0000000..5ace4e0
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ar8216.c
@@ -0,0 +1,2904 @@
+/*
+ * 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;
+	}
+}
+
+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;
+}
+
+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)
+{
+	int phy_if_mode;
+
+	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);
+
+	phy_if_mode = of_get_phy_mode(priv->pdev->of_node);
+
+	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/Multicast frames transmitted to the CPU */
+	ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK,
+		       AR8229_FLOOD_MASK_BC_DP(0) |
+		       AR8229_FLOOD_MASK_MC_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,
+		       AR8236_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 cpu port to receive multicast and broadcast frames */
+	ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK,
+		   AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN);
+
+	/* 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;
+
+	/* 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;
+	}
+
+	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 */
+	if (phydev->state == PHY_CHANGELINK)
+		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 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) {
+		if (ar8xxx_has_gige(priv)) {
+			phydev->supported = SUPPORTED_1000baseT_Full;
+			phydev->advertising = ADVERTISED_1000baseT_Full;
+		} else {
+			phydev->supported = SUPPORTED_100baseT_Full;
+			phydev->advertising = ADVERTISED_100baseT_Full;
+		}
+
+		if (priv->chip->config_at_probe) {
+			priv->phy = phydev;
+
+			ret = ar8xxx_start(priv);
+			if (ret)
+				goto err_unregister_switch;
+		}
+	} else {
+		if (ar8xxx_has_gige(priv)) {
+			phydev->supported |= SUPPORTED_1000baseT_Full;
+			phydev->advertising |= ADVERTISED_1000baseT_Full;
+		}
+		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;
+
+	dev->phy_ptr = NULL;
+	dev->priv_flags &= ~IFF_NO_IP_ALIGN;
+	dev->eth_mangle_rx = NULL;
+	dev->eth_mangle_tx = NULL;
+}
+
+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 int
+ar8xxx_phy_soft_reset(struct phy_device *phydev)
+{
+	/* we don't need an extra reset */
+	return 0;
+}
+
+static struct phy_driver ar8xxx_phy_driver[] = {
+	{
+		.phy_id		= 0x004d0000,
+		.name		= "Atheros AR8216/AR8236/AR8316",
+		.phy_id_mask	= 0xffff0000,
+		.features	= PHY_BASIC_FEATURES,
+		.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,
+		.soft_reset	= ar8xxx_phy_soft_reset,
+	}
+};
+
+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 --git a/src/kernel/linux/v4.19/drivers/net/phy/ar8216.h b/src/kernel/linux/v4.19/drivers/net/phy/ar8216.h
new file mode 100644
index 0000000..bf34fdb
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ar8216.h
@@ -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   AR8229_FLOOD_MASK_MC_DP(_p)	BIT(16 + (_p))
+#define   AR8229_FLOOD_MASK_BC_DP(_p)	BIT(25 + (_p))
+#define   AR8236_FM_CPU_BROADCAST_EN	BIT(26)
+#define   AR8236_FM_CPU_BCAST_FWD_EN	BIT(25)
+
+#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 --git a/src/kernel/linux/v4.19/drivers/net/phy/ar8327.c b/src/kernel/linux/v4.19/drivers/net/phy/ar8327.c
new file mode 100644
index 0000000..4cbfa4d
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ar8327.c
@@ -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_STRIP);
+	new_pos = pos;
+
+	led_cfg = pdata->led_cfg;
+	if (led_cfg) {
+		if (led_cfg->open_drain)
+			new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN;
+		else
+			new_pos &= ~AR8327_POWER_ON_STRIP_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_STRIP_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_STRIP_SERDES_AEN;
+		else
+			new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN;
+	}
+
+	ar8xxx_write(priv, AR8327_REG_POWER_ON_STRIP, 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 --git a/src/kernel/linux/v4.19/drivers/net/phy/ar8327.h b/src/kernel/linux/v4.19/drivers/net/phy/ar8327.h
new file mode 100644
index 0000000..a59d306
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ar8327.h
@@ -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_STRIP		0x010
+#define   AR8327_POWER_ON_STRIP_POWER_ON_SEL	BIT(31)
+#define   AR8327_POWER_ON_STRIP_LED_OPEN_EN	BIT(24)
+#define   AR8327_POWER_ON_STRIP_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 --git a/src/kernel/linux/v4.19/drivers/net/phy/at803x.c b/src/kernel/linux/v4.19/drivers/net/phy/at803x.c
new file mode 100644
index 0000000..eff7b0b
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/at803x.c
@@ -0,0 +1,471 @@
+/*
+ * drivers/net/phy/at803x.c
+ *
+ * Driver for Atheros 803x PHY
+ *
+ * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/phy.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio/consumer.h>
+
+#define AT803X_INTR_ENABLE			0x12
+#define AT803X_INTR_ENABLE_AUTONEG_ERR		BIT(15)
+#define AT803X_INTR_ENABLE_SPEED_CHANGED	BIT(14)
+#define AT803X_INTR_ENABLE_DUPLEX_CHANGED	BIT(13)
+#define AT803X_INTR_ENABLE_PAGE_RECEIVED	BIT(12)
+#define AT803X_INTR_ENABLE_LINK_FAIL		BIT(11)
+#define AT803X_INTR_ENABLE_LINK_SUCCESS		BIT(10)
+#define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE	BIT(5)
+#define AT803X_INTR_ENABLE_POLARITY_CHANGED	BIT(1)
+#define AT803X_INTR_ENABLE_WOL			BIT(0)
+
+#define AT803X_INTR_STATUS			0x13
+
+#define AT803X_SMART_SPEED			0x14
+#define AT803X_LED_CONTROL			0x18
+
+#define AT803X_DEVICE_ADDR			0x03
+#define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
+#define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
+#define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
+#define AT803X_MMD_ACCESS_CONTROL		0x0D
+#define AT803X_MMD_ACCESS_CONTROL_DATA		0x0E
+#define AT803X_FUNC_DATA			0x4003
+#define AT803X_REG_CHIP_CONFIG			0x1f
+#define AT803X_BT_BX_REG_SEL			0x8000
+#define AT803X_SGMII_ANEG_EN			0x1000
+
+#define AT803X_DEBUG_ADDR			0x1D
+#define AT803X_DEBUG_DATA			0x1E
+
+#define AT803X_MODE_CFG_MASK			0x0F
+#define AT803X_MODE_CFG_SGMII			0x01
+
+#define AT803X_PSSR			0x11	/*PHY-Specific Status Register*/
+#define AT803X_PSSR_MR_AN_COMPLETE	0x0200
+
+#define AT803X_DEBUG_REG_0			0x00
+#define AT803X_DEBUG_RX_CLK_DLY_EN		BIT(15)
+
+#define AT803X_DEBUG_REG_5			0x05
+#define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
+
+#define ATH8030_PHY_ID 0x004dd076
+#define ATH8031_PHY_ID 0x004dd074
+#define ATH8032_PHY_ID 0x004dd023
+#define ATH8035_PHY_ID 0x004dd072
+#define AT803X_PHY_ID_MASK			0xffffffef
+#define AT8032_PHY_ID_MASK			0xffffffff
+
+MODULE_DESCRIPTION("Atheros 803x PHY driver");
+MODULE_AUTHOR("Matus Ujhelyi");
+MODULE_LICENSE("GPL");
+
+struct at803x_priv {
+	bool phy_reset:1;
+};
+
+struct at803x_context {
+	u16 bmcr;
+	u16 advertise;
+	u16 control1000;
+	u16 int_enable;
+	u16 smart_speed;
+	u16 led_control;
+};
+
+static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
+{
+	int ret;
+
+	ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+	if (ret < 0)
+		return ret;
+
+	return phy_read(phydev, AT803X_DEBUG_DATA);
+}
+
+static int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,
+				 u16 clear, u16 set)
+{
+	u16 val;
+	int ret;
+
+	ret = at803x_debug_reg_read(phydev, reg);
+	if (ret < 0)
+		return ret;
+
+	val = ret & 0xffff;
+	val &= ~clear;
+	val |= set;
+
+	return phy_write(phydev, AT803X_DEBUG_DATA, val);
+}
+
+static inline int at803x_enable_rx_delay(struct phy_device *phydev)
+{
+	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_0, 0,
+					AT803X_DEBUG_RX_CLK_DLY_EN);
+}
+
+static inline int at803x_enable_tx_delay(struct phy_device *phydev)
+{
+	return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_5, 0,
+					AT803X_DEBUG_TX_CLK_DLY_EN);
+}
+
+/* save relevant PHY registers to private copy */
+static void at803x_context_save(struct phy_device *phydev,
+				struct at803x_context *context)
+{
+	context->bmcr = phy_read(phydev, MII_BMCR);
+	context->advertise = phy_read(phydev, MII_ADVERTISE);
+	context->control1000 = phy_read(phydev, MII_CTRL1000);
+	context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE);
+	context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED);
+	context->led_control = phy_read(phydev, AT803X_LED_CONTROL);
+}
+
+/* restore relevant PHY registers from private copy */
+static void at803x_context_restore(struct phy_device *phydev,
+				   const struct at803x_context *context)
+{
+	phy_write(phydev, MII_BMCR, context->bmcr);
+	phy_write(phydev, MII_ADVERTISE, context->advertise);
+	phy_write(phydev, MII_CTRL1000, context->control1000);
+	phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable);
+	phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed);
+	phy_write(phydev, AT803X_LED_CONTROL, context->led_control);
+}
+
+static int at803x_set_wol(struct phy_device *phydev,
+			  struct ethtool_wolinfo *wol)
+{
+	struct net_device *ndev = phydev->attached_dev;
+	const u8 *mac;
+	int ret;
+	u32 value;
+	unsigned int i, offsets[] = {
+		AT803X_LOC_MAC_ADDR_32_47_OFFSET,
+		AT803X_LOC_MAC_ADDR_16_31_OFFSET,
+		AT803X_LOC_MAC_ADDR_0_15_OFFSET,
+	};
+
+	if (!ndev)
+		return -ENODEV;
+
+	if (wol->wolopts & WAKE_MAGIC) {
+		mac = (const u8 *) ndev->dev_addr;
+
+		if (!is_valid_ether_addr(mac))
+			return -EINVAL;
+
+		for (i = 0; i < 3; i++) {
+			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
+				  AT803X_DEVICE_ADDR);
+			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
+				  offsets[i]);
+			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
+				  AT803X_FUNC_DATA);
+			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
+				  mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+		}
+
+		value = phy_read(phydev, AT803X_INTR_ENABLE);
+		value |= AT803X_INTR_ENABLE_WOL;
+		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
+		if (ret)
+			return ret;
+		value = phy_read(phydev, AT803X_INTR_STATUS);
+	} else {
+		value = phy_read(phydev, AT803X_INTR_ENABLE);
+		value &= (~AT803X_INTR_ENABLE_WOL);
+		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
+		if (ret)
+			return ret;
+		value = phy_read(phydev, AT803X_INTR_STATUS);
+	}
+
+	return ret;
+}
+
+static void at803x_get_wol(struct phy_device *phydev,
+			   struct ethtool_wolinfo *wol)
+{
+	u32 value;
+
+	wol->supported = WAKE_MAGIC;
+	wol->wolopts = 0;
+
+	value = phy_read(phydev, AT803X_INTR_ENABLE);
+	if (value & AT803X_INTR_ENABLE_WOL)
+		wol->wolopts |= WAKE_MAGIC;
+}
+
+static int at803x_suspend(struct phy_device *phydev)
+{
+	int value;
+	int wol_enabled;
+
+	value = phy_read(phydev, AT803X_INTR_ENABLE);
+	wol_enabled = value & AT803X_INTR_ENABLE_WOL;
+
+	if (wol_enabled)
+		value = BMCR_ISOLATE;
+	else
+		value = BMCR_PDOWN;
+
+	phy_modify(phydev, MII_BMCR, 0, value);
+
+	return 0;
+}
+
+static int at803x_resume(struct phy_device *phydev)
+{
+	return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0);
+}
+
+static int at803x_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct at803x_priv *priv;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static int at803x_config_init(struct phy_device *phydev)
+{
+	int ret;
+	u32 v;
+
+	if (phydev->drv->phy_id == ATH8031_PHY_ID &&
+		phydev->interface == PHY_INTERFACE_MODE_SGMII)
+	{
+		v = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+		/* select SGMII/fiber page */
+		ret = phy_write(phydev, AT803X_REG_CHIP_CONFIG,
+						v & ~AT803X_BT_BX_REG_SEL);
+		if (ret)
+			return ret;
+		/* enable SGMII autonegotiation */
+		ret = phy_write(phydev, MII_BMCR, AT803X_SGMII_ANEG_EN);
+		if (ret)
+			return ret;
+		/* select copper page */
+		ret = phy_write(phydev, AT803X_REG_CHIP_CONFIG,
+						v | AT803X_BT_BX_REG_SEL);
+		if (ret)
+			return ret;
+	}
+
+	ret = genphy_config_init(phydev);
+	if (ret < 0)
+		return ret;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID ||
+			phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
+		ret = at803x_enable_rx_delay(phydev);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID ||
+			phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
+		ret = at803x_enable_tx_delay(phydev);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int at803x_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, AT803X_INTR_STATUS);
+
+	return (err < 0) ? err : 0;
+}
+
+static int at803x_config_intr(struct phy_device *phydev)
+{
+	int err;
+	int value;
+
+	value = phy_read(phydev, AT803X_INTR_ENABLE);
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		value |= AT803X_INTR_ENABLE_AUTONEG_ERR;
+		value |= AT803X_INTR_ENABLE_SPEED_CHANGED;
+		value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
+		value |= AT803X_INTR_ENABLE_LINK_FAIL;
+		value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+
+		err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+	}
+	else
+		err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
+
+	return err;
+}
+
+static void at803x_link_change_notify(struct phy_device *phydev)
+{
+	struct at803x_priv *priv = phydev->priv;
+
+	/*
+	 * Conduct a hardware reset for AT8030/2 every time a link loss is
+	 * signalled. This is necessary to circumvent a hardware bug that
+	 * occurs when the cable is unplugged while TX packets are pending
+	 * in the FIFO. In such cases, the FIFO enters an error mode it
+	 * cannot recover from by software.
+	 */
+	if (phydev->state == PHY_NOLINK) {
+		if (phydev->mdio.reset && !priv->phy_reset) {
+			struct at803x_context context;
+
+			at803x_context_save(phydev, &context);
+
+			phy_device_reset(phydev, 1);
+			msleep(1);
+			phy_device_reset(phydev, 0);
+			msleep(1);
+
+			at803x_context_restore(phydev, &context);
+
+			phydev_dbg(phydev, "%s(): phy was reset\n",
+				   __func__);
+			priv->phy_reset = true;
+		}
+	} else {
+		priv->phy_reset = false;
+	}
+}
+
+static int at803x_aneg_done(struct phy_device *phydev)
+{
+	int ccr;
+
+	int aneg_done = genphy_aneg_done(phydev);
+	if (aneg_done != BMSR_ANEGCOMPLETE)
+		return aneg_done;
+
+	/*
+	 * in SGMII mode, if copper side autoneg is successful,
+	 * also check SGMII side autoneg result
+	 */
+	ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+	if ((ccr & AT803X_MODE_CFG_MASK) != AT803X_MODE_CFG_SGMII)
+		return aneg_done;
+
+	/* switch to SGMII/fiber page */
+	phy_write(phydev, AT803X_REG_CHIP_CONFIG, ccr & ~AT803X_BT_BX_REG_SEL);
+
+	/* check if the SGMII link is OK. */
+	if (!(phy_read(phydev, AT803X_PSSR) & AT803X_PSSR_MR_AN_COMPLETE)) {
+		pr_warn("803x_aneg_done: SGMII link is not ok\n");
+		aneg_done = 0;
+	}
+	/* switch back to copper page */
+	phy_write(phydev, AT803X_REG_CHIP_CONFIG, ccr | AT803X_BT_BX_REG_SEL);
+
+	return aneg_done;
+}
+
+static struct phy_driver at803x_driver[] = {
+{
+	/* ATHEROS 8035 */
+	.phy_id			= ATH8035_PHY_ID,
+	.name			= "Atheros 8035 ethernet",
+	.phy_id_mask		= AT803X_PHY_ID_MASK,
+	.probe			= at803x_probe,
+	.config_init		= at803x_config_init,
+	.set_wol		= at803x_set_wol,
+	.get_wol		= at803x_get_wol,
+	.suspend		= at803x_suspend,
+	.resume			= at803x_resume,
+	.features		= PHY_GBIT_FEATURES,
+	.flags			= PHY_HAS_INTERRUPT,
+	.ack_interrupt		= at803x_ack_interrupt,
+	.config_intr		= at803x_config_intr,
+}, {
+	/* ATHEROS 8030 */
+	.phy_id			= ATH8030_PHY_ID,
+	.name			= "Atheros 8030 ethernet",
+	.phy_id_mask		= AT803X_PHY_ID_MASK,
+	.probe			= at803x_probe,
+	.config_init		= at803x_config_init,
+	.link_change_notify	= at803x_link_change_notify,
+	.set_wol		= at803x_set_wol,
+	.get_wol		= at803x_get_wol,
+	.suspend		= at803x_suspend,
+	.resume			= at803x_resume,
+	.features		= PHY_BASIC_FEATURES,
+	.flags			= PHY_HAS_INTERRUPT,
+	.ack_interrupt		= at803x_ack_interrupt,
+	.config_intr		= at803x_config_intr,
+}, {
+	/* ATHEROS 8031 */
+	.phy_id			= ATH8031_PHY_ID,
+	.name			= "Atheros 8031 ethernet",
+	.phy_id_mask		= AT803X_PHY_ID_MASK,
+	.probe			= at803x_probe,
+	.config_init		= at803x_config_init,
+	.set_wol		= at803x_set_wol,
+	.get_wol		= at803x_get_wol,
+	.suspend		= at803x_suspend,
+	.resume			= at803x_resume,
+	.features		= PHY_GBIT_FEATURES,
+	.flags			= PHY_HAS_INTERRUPT,
+	.aneg_done		= at803x_aneg_done,
+	.ack_interrupt		= &at803x_ack_interrupt,
+	.config_intr		= &at803x_config_intr,
+}, {
+	/* ATHEROS 8032 */
+	.phy_id			= ATH8032_PHY_ID,
+	.name			= "Atheros 8032 ethernet",
+	.phy_id_mask		= AT8032_PHY_ID_MASK,
+	.probe			= at803x_probe,
+	.config_init		= at803x_config_init,
+	.link_change_notify	= at803x_link_change_notify,
+	.set_wol		= at803x_set_wol,
+	.get_wol		= at803x_get_wol,
+	.suspend		= at803x_suspend,
+	.resume			= at803x_resume,
+	.features		= PHY_BASIC_FEATURES,
+	.flags			= PHY_HAS_INTERRUPT,
+	.config_aneg		= genphy_config_aneg,
+	.read_status		= genphy_read_status,
+	.ack_interrupt		= at803x_ack_interrupt,
+	.config_intr		= at803x_config_intr,
+} };
+
+module_phy_driver(at803x_driver);
+
+static struct mdio_device_id __maybe_unused atheros_tbl[] = {
+	{ ATH8030_PHY_ID, AT803X_PHY_ID_MASK },
+	{ ATH8031_PHY_ID, AT803X_PHY_ID_MASK },
+	{ ATH8032_PHY_ID, AT8032_PHY_ID_MASK },
+	{ ATH8035_PHY_ID, AT803X_PHY_ID_MASK },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, atheros_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/ax88796b.c b/src/kernel/linux/v4.19/drivers/net/phy/ax88796b.c
new file mode 100644
index 0000000..8ebe7f5
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ax88796b.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for Asix PHYs
+ *
+ * Author: Michael Schmitz <schmitzmic@gmail.com>
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+#define PHY_ID_ASIX_AX88796B		0x003b1841
+
+MODULE_DESCRIPTION("Asix PHY driver");
+MODULE_AUTHOR("Michael Schmitz <schmitzmic@gmail.com>");
+MODULE_LICENSE("GPL");
+
+/**
+ * asix_soft_reset - software reset the PHY via BMCR_RESET bit
+ * @phydev: target phy_device struct
+ *
+ * Description: Perform a software PHY reset using the standard
+ * BMCR_RESET bit and poll for the reset bit to be cleared.
+ * Toggle BMCR_RESET bit off to accommodate broken AX8796B PHY implementation
+ * such as used on the Individual Computers' X-Surf 100 Zorro card.
+ *
+ * Returns: 0 on success, < 0 on failure
+ */
+static int asix_soft_reset(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Asix PHY won't reset unless reset bit toggles */
+	ret = phy_write(phydev, MII_BMCR, 0);
+	if (ret < 0)
+		return ret;
+
+	return genphy_soft_reset(phydev);
+}
+
+static struct phy_driver asix_driver[] = { {
+	.phy_id		= PHY_ID_ASIX_AX88796B,
+	.name		= "Asix Electronics AX88796B",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.soft_reset	= asix_soft_reset,
+} };
+
+module_phy_driver(asix_driver);
+
+static struct mdio_device_id __maybe_unused asix_tbl[] = {
+	{ PHY_ID_ASIX_AX88796B, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, asix_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/Kconfig b/src/kernel/linux/v4.19/drivers/net/phy/b53/Kconfig
new file mode 100644
index 0000000..08287e7
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/Kconfig
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/Makefile b/src/kernel/linux/v4.19/drivers/net/phy/b53/Makefile
new file mode 100644
index 0000000..13ff366
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/Makefile
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_common.c b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_common.c
new file mode 100644
index 0000000..670588c
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_common.c
@@ -0,0 +1,1722 @@
+/*
+ * 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;
+			int mode = of_get_phy_mode(pn);
+
+			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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_mdio.c b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_mdio.c
new file mode 100644
index 0000000..5675232
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_mdio.c
@@ -0,0 +1,477 @@
+/*
+ * 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;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
+	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);
+#else
+	if (is5325(dev) || is5365(dev))
+		phydev->supported = SUPPORTED_100baseT_Full;
+	else
+		phydev->supported = SUPPORTED_1000baseT_Full;
+
+	phydev->advertising = phydev->supported;
+#endif
+
+	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		= 0x00406000,
+	.name		= "Broadcom B53 (3)",
+	.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 = "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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_mmap.c b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_mmap.c
new file mode 100644
index 0000000..ab1895e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_mmap.c
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_phy_fixup.c b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_phy_fixup.c
new file mode 100644
index 0000000..e2f8a39
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_phy_fixup.c
@@ -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	0x00406000
+
+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 & 0xfffffc00) == 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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_priv.h b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_priv.h
new file mode 100644
index 0000000..a9296c9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_priv.h
@@ -0,0 +1,341 @@
+/*
+ * 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>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
+#include <linux/bcm47xx_nvram.h>
+#endif
+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
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
+	return bcm47xx_nvram_gpio_pin("robo_reset");
+#else
+	return -ENOENT;
+#endif
+}
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_regs.h b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_regs.h
new file mode 100644
index 0000000..f0bf674
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_regs.h
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_spi.c b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_spi.c
new file mode 100644
index 0000000..efc8f7e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_spi.c
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_srab.c b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_srab.c
new file mode 100644
index 0000000..012daa3
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/b53/b53_srab.c
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm-cygnus.c b/src/kernel/linux/v4.19/drivers/net/phy/bcm-cygnus.c
new file mode 100644
index 0000000..e757b09
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm-cygnus.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* Broadcom Cygnus SoC internal transceivers support. */
+#include "bcm-phy-lib.h"
+#include <linux/brcmphy.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+
+/* Broadcom Cygnus Phy specific registers */
+#define MII_BCM_CYGNUS_AFE_VDAC_ICTRL_0  0x91E5 /* VDAL Control register */
+
+static int bcm_cygnus_afe_config(struct phy_device *phydev)
+{
+	int rc;
+
+	/* ensure smdspclk is enabled */
+	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, 0x0c30);
+	if (rc < 0)
+		return rc;
+
+	/* AFE_VDAC_ICTRL_0 bit 7:4 Iq=1100 for 1g 10bt, normal modes */
+	rc = bcm_phy_write_misc(phydev, 0x39, 0x01, 0xA7C8);
+	if (rc < 0)
+		return rc;
+
+	/* AFE_HPF_TRIM_OTHERS bit11=1, short cascode enable for all modes*/
+	rc = bcm_phy_write_misc(phydev, 0x3A, 0x00, 0x0803);
+	if (rc < 0)
+		return rc;
+
+	/* AFE_TX_CONFIG_1 bit 7:4 Iq=1100 for test modes */
+	rc = bcm_phy_write_misc(phydev, 0x3A, 0x01, 0xA740);
+	if (rc < 0)
+		return rc;
+
+	/* AFE TEMPSEN_OTHERS rcal_HT, rcal_LT 10000 */
+	rc = bcm_phy_write_misc(phydev, 0x3A, 0x03, 0x8400);
+	if (rc < 0)
+		return rc;
+
+	/* AFE_FUTURE_RSV bit 2:0 rccal <2:0>=100 */
+	rc = bcm_phy_write_misc(phydev, 0x3B, 0x00, 0x0004);
+	if (rc < 0)
+		return rc;
+
+	/* Adjust bias current trim to overcome digital offSet */
+	rc = phy_write(phydev, MII_BRCM_CORE_BASE1E, 0x02);
+	if (rc < 0)
+		return rc;
+
+	/* make rcal=100, since rdb default is 000 */
+	rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB1, 0x10);
+	if (rc < 0)
+		return rc;
+
+	/* CORE_EXPB0, Reset R_CAL/RC_CAL Engine */
+	rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, 0x10);
+	if (rc < 0)
+		return rc;
+
+	/* CORE_EXPB0, Disable Reset R_CAL/RC_CAL Engine */
+	rc = bcm_phy_write_exp_sel(phydev, MII_BRCM_CORE_EXPB0, 0x00);
+
+	return 0;
+}
+
+static int bcm_cygnus_config_init(struct phy_device *phydev)
+{
+	int reg, rc;
+
+	reg = phy_read(phydev, MII_BCM54XX_ECR);
+	if (reg < 0)
+		return reg;
+
+	/* Mask interrupts globally. */
+	reg |= MII_BCM54XX_ECR_IM;
+	rc = phy_write(phydev, MII_BCM54XX_ECR, reg);
+	if (rc)
+		return rc;
+
+	/* Unmask events of interest */
+	reg = ~(MII_BCM54XX_INT_DUPLEX |
+		MII_BCM54XX_INT_SPEED |
+		MII_BCM54XX_INT_LINK);
+	rc = phy_write(phydev, MII_BCM54XX_IMR, reg);
+	if (rc)
+		return rc;
+
+	/* Apply AFE settings for the PHY */
+	rc = bcm_cygnus_afe_config(phydev);
+	if (rc)
+		return rc;
+
+	/* Advertise EEE */
+	rc = bcm_phy_set_eee(phydev, true);
+	if (rc)
+		return rc;
+
+	/* Enable APD */
+	return bcm_phy_enable_apd(phydev, false);
+}
+
+static int bcm_cygnus_resume(struct phy_device *phydev)
+{
+	int rc;
+
+	genphy_resume(phydev);
+
+	/* Re-initialize the PHY to apply AFE work-arounds and
+	 * configurations when coming out of suspend.
+	 */
+	rc = bcm_cygnus_config_init(phydev);
+	if (rc)
+		return rc;
+
+	/* restart auto negotiation with the new settings */
+	return genphy_config_aneg(phydev);
+}
+
+static struct phy_driver bcm_cygnus_phy_driver[] = {
+{
+	.phy_id        = PHY_ID_BCM_CYGNUS,
+	.phy_id_mask   = 0xfffffff0,
+	.name          = "Broadcom Cygnus PHY",
+	.features      = PHY_GBIT_FEATURES,
+	.config_init   = bcm_cygnus_config_init,
+	.ack_interrupt = bcm_phy_ack_intr,
+	.config_intr   = bcm_phy_config_intr,
+	.suspend       = genphy_suspend,
+	.resume        = bcm_cygnus_resume,
+} };
+
+static struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = {
+	{ PHY_ID_BCM_CYGNUS, 0xfffffff0, },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, bcm_cygnus_phy_tbl);
+
+module_phy_driver(bcm_cygnus_phy_driver);
+
+MODULE_DESCRIPTION("Broadcom Cygnus internal PHY driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Broadcom Corporation");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm-phy-lib.c b/src/kernel/linux/v4.19/drivers/net/phy/bcm-phy-lib.c
new file mode 100644
index 0000000..e10e7b5
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm-phy-lib.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2015-2017 Broadcom
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "bcm-phy-lib.h"
+#include <linux/brcmphy.h>
+#include <linux/export.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/ethtool.h>
+
+#define MII_BCM_CHANNEL_WIDTH     0x2000
+#define BCM_CL45VEN_EEE_ADV       0x3c
+
+int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
+{
+	int rc;
+
+	rc = phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
+	if (rc < 0)
+		return rc;
+
+	return phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_write_exp);
+
+int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
+{
+	int val;
+
+	val = phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
+	if (val < 0)
+		return val;
+
+	val = phy_read(phydev, MII_BCM54XX_EXP_DATA);
+
+	/* Restore default value.  It's O.K. if this write fails. */
+	phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
+
+	return val;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_read_exp);
+
+int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum)
+{
+	/* The register must be written to both the Shadow Register Select and
+	 * the Shadow Read Register Selector
+	 */
+	phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK |
+		  regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT);
+	return phy_read(phydev, MII_BCM54XX_AUX_CTL);
+}
+EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read);
+
+int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
+{
+	return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
+}
+EXPORT_SYMBOL(bcm54xx_auxctl_write);
+
+int bcm_phy_write_misc(struct phy_device *phydev,
+		       u16 reg, u16 chl, u16 val)
+{
+	int rc;
+	int tmp;
+
+	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
+		       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+	if (rc < 0)
+		return rc;
+
+	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
+	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
+	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
+	if (rc < 0)
+		return rc;
+
+	tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
+	rc = bcm_phy_write_exp(phydev, tmp, val);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_write_misc);
+
+int bcm_phy_read_misc(struct phy_device *phydev,
+		      u16 reg, u16 chl)
+{
+	int rc;
+	int tmp;
+
+	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
+		       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+	if (rc < 0)
+		return rc;
+
+	tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
+	tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
+	rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
+	if (rc < 0)
+		return rc;
+
+	tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
+	rc = bcm_phy_read_exp(phydev, tmp);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_read_misc);
+
+int bcm_phy_ack_intr(struct phy_device *phydev)
+{
+	int reg;
+
+	/* Clear pending interrupts.  */
+	reg = phy_read(phydev, MII_BCM54XX_ISR);
+	if (reg < 0)
+		return reg;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_ack_intr);
+
+int bcm_phy_config_intr(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = phy_read(phydev, MII_BCM54XX_ECR);
+	if (reg < 0)
+		return reg;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		reg &= ~MII_BCM54XX_ECR_IM;
+	else
+		reg |= MII_BCM54XX_ECR_IM;
+
+	return phy_write(phydev, MII_BCM54XX_ECR, reg);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_config_intr);
+
+int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow)
+{
+	phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow));
+	return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD));
+}
+EXPORT_SYMBOL_GPL(bcm_phy_read_shadow);
+
+int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow,
+			 u16 val)
+{
+	return phy_write(phydev, MII_BCM54XX_SHD,
+			 MII_BCM54XX_SHD_WRITE |
+			 MII_BCM54XX_SHD_VAL(shadow) |
+			 MII_BCM54XX_SHD_DATA(val));
+}
+EXPORT_SYMBOL_GPL(bcm_phy_write_shadow);
+
+int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down)
+{
+	int val;
+
+	if (dll_pwr_down) {
+		val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
+		if (val < 0)
+			return val;
+
+		val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
+		bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val);
+	}
+
+	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD);
+	if (val < 0)
+		return val;
+
+	/* Clear APD bits */
+	val &= BCM_APD_CLR_MASK;
+
+	if (phydev->autoneg == AUTONEG_ENABLE)
+		val |= BCM54XX_SHD_APD_EN;
+	else
+		val |= BCM_NO_ANEG_APD_EN;
+
+	/* Enable energy detect single link pulse for easy wakeup */
+	val |= BCM_APD_SINGLELP_EN;
+
+	/* Enable Auto Power-Down (APD) for the PHY */
+	return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_enable_apd);
+
+int bcm_phy_set_eee(struct phy_device *phydev, bool enable)
+{
+	int val;
+
+	/* Enable EEE at PHY level */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL);
+	if (val < 0)
+		return val;
+
+	if (enable)
+		val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X;
+	else
+		val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X);
+
+	phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val);
+
+	/* Advertise EEE */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV);
+	if (val < 0)
+		return val;
+
+	if (enable)
+		val |= (MDIO_EEE_100TX | MDIO_EEE_1000T);
+	else
+		val &= ~(MDIO_EEE_100TX | MDIO_EEE_1000T);
+
+	phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_set_eee);
+
+int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count)
+{
+	int val;
+
+	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+	if (val < 0)
+		return val;
+
+	/* Check if wirespeed is enabled or not */
+	if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) {
+		*count = DOWNSHIFT_DEV_DISABLE;
+		return 0;
+	}
+
+	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
+	if (val < 0)
+		return val;
+
+	/* Downgrade after one link attempt */
+	if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) {
+		*count = 1;
+	} else {
+		/* Downgrade after configured retry count */
+		val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
+		val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK;
+		*count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_downshift_get);
+
+int bcm_phy_downshift_set(struct phy_device *phydev, u8 count)
+{
+	int val = 0, ret = 0;
+
+	/* Range check the number given */
+	if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET >
+	    BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK &&
+	    count != DOWNSHIFT_DEV_DEFAULT_COUNT) {
+		return -ERANGE;
+	}
+
+	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+	if (val < 0)
+		return val;
+
+	/* Se the write enable bit */
+	val |= MII_BCM54XX_AUXCTL_MISC_WREN;
+
+	if (count == DOWNSHIFT_DEV_DISABLE) {
+		val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
+		return bcm54xx_auxctl_write(phydev,
+					    MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
+					    val);
+	} else {
+		val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
+		ret = bcm54xx_auxctl_write(phydev,
+					   MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
+					   val);
+		if (ret < 0)
+			return ret;
+	}
+
+	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
+	val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK <<
+		 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT |
+		 BCM54XX_SHD_SCR2_WSPD_RTRY_DIS);
+
+	switch (count) {
+	case 1:
+		val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS;
+		break;
+	case DOWNSHIFT_DEV_DEFAULT_COUNT:
+		val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
+		break;
+	default:
+		val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) <<
+			BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
+		break;
+	}
+
+	return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_downshift_set);
+
+struct bcm_phy_hw_stat {
+	const char *string;
+	u8 reg;
+	u8 shift;
+	u8 bits;
+};
+
+/* Counters freeze at either 0xffff or 0xff, better than nothing */
+static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = {
+	{ "phy_receive_errors", MII_BRCM_CORE_BASE12, 0, 16 },
+	{ "phy_serdes_ber_errors", MII_BRCM_CORE_BASE13, 8, 8 },
+	{ "phy_false_carrier_sense_errors", MII_BRCM_CORE_BASE13, 0, 8 },
+	{ "phy_local_rcvr_nok", MII_BRCM_CORE_BASE14, 8, 8 },
+	{ "phy_remote_rcv_nok", MII_BRCM_CORE_BASE14, 0, 8 },
+};
+
+int bcm_phy_get_sset_count(struct phy_device *phydev)
+{
+	return ARRAY_SIZE(bcm_phy_hw_stats);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count);
+
+void bcm_phy_get_strings(struct phy_device *phydev, u8 *data)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
+		strlcpy(data + i * ETH_GSTRING_LEN,
+			bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_get_strings);
+
+/* Caller is supposed to provide appropriate storage for the library code to
+ * access the shadow copy
+ */
+static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow,
+			    unsigned int i)
+{
+	struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i];
+	int val;
+	u64 ret;
+
+	val = phy_read(phydev, stat.reg);
+	if (val < 0) {
+		ret = U64_MAX;
+	} else {
+		val >>= stat.shift;
+		val = val & ((1 << stat.bits) - 1);
+		shadow[i] += val;
+		ret = shadow[i];
+	}
+
+	return ret;
+}
+
+void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow,
+		       struct ethtool_stats *stats, u64 *data)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
+		data[i] = bcm_phy_get_stat(phydev, shadow, i);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_get_stats);
+
+MODULE_DESCRIPTION("Broadcom PHY Library");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Broadcom Corporation");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm-phy-lib.h b/src/kernel/linux/v4.19/drivers/net/phy/bcm-phy-lib.h
new file mode 100644
index 0000000..81cceaa
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm-phy-lib.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _LINUX_BCM_PHY_LIB_H
+#define _LINUX_BCM_PHY_LIB_H
+
+#include <linux/brcmphy.h>
+#include <linux/phy.h>
+
+int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val);
+int bcm_phy_read_exp(struct phy_device *phydev, u16 reg);
+
+static inline int bcm_phy_write_exp_sel(struct phy_device *phydev,
+					u16 reg, u16 val)
+{
+	return bcm_phy_write_exp(phydev, reg | MII_BCM54XX_EXP_SEL_ER, val);
+}
+
+int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val);
+int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum);
+
+int bcm_phy_write_misc(struct phy_device *phydev,
+		       u16 reg, u16 chl, u16 value);
+int bcm_phy_read_misc(struct phy_device *phydev,
+		      u16 reg, u16 chl);
+
+int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow,
+			 u16 val);
+int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow);
+
+int bcm_phy_ack_intr(struct phy_device *phydev);
+int bcm_phy_config_intr(struct phy_device *phydev);
+
+int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down);
+
+int bcm_phy_set_eee(struct phy_device *phydev, bool enable);
+
+int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count);
+
+int bcm_phy_downshift_set(struct phy_device *phydev, u8 count);
+
+int bcm_phy_get_sset_count(struct phy_device *phydev);
+void bcm_phy_get_strings(struct phy_device *phydev, u8 *data);
+void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow,
+		       struct ethtool_stats *stats, u64 *data);
+
+#endif /* _LINUX_BCM_PHY_LIB_H */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm63xx.c b/src/kernel/linux/v4.19/drivers/net/phy/bcm63xx.c
new file mode 100644
index 0000000..cf14613
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm63xx.c
@@ -0,0 +1,94 @@
+/*
+ *	Driver for Broadcom 63xx SOCs integrated PHYs
+ *
+ *	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.
+ */
+#include "bcm-phy-lib.h"
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define MII_BCM63XX_IR		0x1a	/* interrupt register */
+#define MII_BCM63XX_IR_EN	0x4000	/* global interrupt enable */
+#define MII_BCM63XX_IR_DUPLEX	0x0800	/* duplex changed */
+#define MII_BCM63XX_IR_SPEED	0x0400	/* speed changed */
+#define MII_BCM63XX_IR_LINK	0x0200	/* link changed */
+#define MII_BCM63XX_IR_GMASK	0x0100	/* global interrupt mask */
+
+MODULE_DESCRIPTION("Broadcom 63xx internal PHY driver");
+MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
+MODULE_LICENSE("GPL");
+
+static int bcm63xx_config_intr(struct phy_device *phydev)
+{
+	int reg, err;
+
+	reg = phy_read(phydev, MII_BCM63XX_IR);
+	if (reg < 0)
+		return reg;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		reg &= ~MII_BCM63XX_IR_GMASK;
+	else
+		reg |= MII_BCM63XX_IR_GMASK;
+
+	err = phy_write(phydev, MII_BCM63XX_IR, reg);
+	return err;
+}
+
+static int bcm63xx_config_init(struct phy_device *phydev)
+{
+	int reg, err;
+
+	reg = phy_read(phydev, MII_BCM63XX_IR);
+	if (reg < 0)
+		return reg;
+
+	/* Mask interrupts globally.  */
+	reg |= MII_BCM63XX_IR_GMASK;
+	err = phy_write(phydev, MII_BCM63XX_IR, reg);
+	if (err < 0)
+		return err;
+
+	/* Unmask events we are interested in  */
+	reg = ~(MII_BCM63XX_IR_DUPLEX |
+		MII_BCM63XX_IR_SPEED |
+		MII_BCM63XX_IR_LINK) |
+		MII_BCM63XX_IR_EN;
+	return phy_write(phydev, MII_BCM63XX_IR, reg);
+}
+
+static struct phy_driver bcm63xx_driver[] = {
+{
+	.phy_id		= 0x00406000,
+	.phy_id_mask	= 0xfffffc00,
+	.name		= "Broadcom BCM63XX (1)",
+	/* ASYM_PAUSE bit is marked RO in datasheet, so don't cheat */
+	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause),
+	.flags		= PHY_HAS_INTERRUPT | PHY_IS_INTERNAL,
+	.config_init	= bcm63xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm63xx_config_intr,
+}, {
+	/* same phy as above, with just a different OUI */
+	.phy_id		= 0x002bdc00,
+	.phy_id_mask	= 0xfffffc00,
+	.name		= "Broadcom BCM63XX (2)",
+	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause),
+	.flags		= PHY_HAS_INTERRUPT | PHY_IS_INTERNAL,
+	.config_init	= bcm63xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm63xx_config_intr,
+} };
+
+module_phy_driver(bcm63xx_driver);
+
+static struct mdio_device_id __maybe_unused bcm63xx_tbl[] = {
+	{ 0x00406000, 0xfffffc00 },
+	{ 0x002bdc00, 0xfffffc00 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, bcm63xx_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm7xxx.c b/src/kernel/linux/v4.19/drivers/net/phy/bcm7xxx.c
new file mode 100644
index 0000000..acaf072
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm7xxx.c
@@ -0,0 +1,697 @@
+/*
+ * Broadcom BCM7xxx internal transceivers support.
+ *
+ * Copyright (C) 2014-2017 Broadcom
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/delay.h>
+#include "bcm-phy-lib.h"
+#include <linux/bitops.h>
+#include <linux/brcmphy.h>
+#include <linux/mdio.h>
+
+/* Broadcom BCM7xxx internal PHY registers */
+
+/* EPHY only register definitions */
+#define MII_BCM7XXX_100TX_AUX_CTL	0x10
+#define MII_BCM7XXX_100TX_FALSE_CAR	0x13
+#define MII_BCM7XXX_100TX_DISC		0x14
+#define MII_BCM7XXX_AUX_MODE		0x1d
+#define  MII_BCM7XXX_64CLK_MDIO		BIT(12)
+#define MII_BCM7XXX_TEST		0x1f
+#define  MII_BCM7XXX_SHD_MODE_2		BIT(2)
+#define MII_BCM7XXX_SHD_2_ADDR_CTRL	0xe
+#define MII_BCM7XXX_SHD_2_CTRL_STAT	0xf
+#define MII_BCM7XXX_SHD_2_BIAS_TRIM	0x1a
+#define MII_BCM7XXX_SHD_3_AN_EEE_ADV	0x3
+#define MII_BCM7XXX_SHD_3_PCS_CTRL_2	0x6
+#define  MII_BCM7XXX_PCS_CTRL_2_DEF	0x4400
+#define MII_BCM7XXX_SHD_3_AN_STAT	0xb
+#define  MII_BCM7XXX_AN_NULL_MSG_EN	BIT(0)
+#define  MII_BCM7XXX_AN_EEE_EN		BIT(1)
+#define MII_BCM7XXX_SHD_3_EEE_THRESH	0xe
+#define  MII_BCM7XXX_EEE_THRESH_DEF	0x50
+#define MII_BCM7XXX_SHD_3_TL4		0x23
+#define  MII_BCM7XXX_TL4_RST_MSK	(BIT(2) | BIT(1))
+
+/* 28nm only register definitions */
+#define MISC_ADDR(base, channel)	base, channel
+
+#define DSP_TAP10			MISC_ADDR(0x0a, 0)
+#define PLL_PLLCTRL_1			MISC_ADDR(0x32, 1)
+#define PLL_PLLCTRL_2			MISC_ADDR(0x32, 2)
+#define PLL_PLLCTRL_4			MISC_ADDR(0x33, 0)
+
+#define AFE_RXCONFIG_0			MISC_ADDR(0x38, 0)
+#define AFE_RXCONFIG_1			MISC_ADDR(0x38, 1)
+#define AFE_RXCONFIG_2			MISC_ADDR(0x38, 2)
+#define AFE_RX_LP_COUNTER		MISC_ADDR(0x38, 3)
+#define AFE_TX_CONFIG			MISC_ADDR(0x39, 0)
+#define AFE_VDCA_ICTRL_0		MISC_ADDR(0x39, 1)
+#define AFE_VDAC_OTHERS_0		MISC_ADDR(0x39, 3)
+#define AFE_HPF_TRIM_OTHERS		MISC_ADDR(0x3a, 0)
+
+struct bcm7xxx_phy_priv {
+	u64	*stats;
+};
+
+static void r_rc_cal_reset(struct phy_device *phydev)
+{
+	/* Reset R_CAL/RC_CAL Engine */
+	bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0010);
+
+	/* Disable Reset R_AL/RC_CAL Engine */
+	bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0000);
+}
+
+static int bcm7xxx_28nm_b0_afe_config_init(struct phy_device *phydev)
+{
+	/* Increase VCO range to prevent unlocking problem of PLL at low
+	 * temp
+	 */
+	bcm_phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048);
+
+	/* Change Ki to 011 */
+	bcm_phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b);
+
+	/* Disable loading of TVCO buffer to bandgap, set bandgap trim
+	 * to 111
+	 */
+	bcm_phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20);
+
+	/* Adjust bias current trim by -3 */
+	bcm_phy_write_misc(phydev, DSP_TAP10, 0x690b);
+
+	/* Switch to CORE_BASE1E */
+	phy_write(phydev, MII_BRCM_CORE_BASE1E, 0xd);
+
+	r_rc_cal_reset(phydev);
+
+	/* write AFE_RXCONFIG_0 */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19);
+
+	/* write AFE_RXCONFIG_1 */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f);
+
+	/* write AFE_RX_LP_COUNTER */
+	bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
+
+	/* write AFE_HPF_TRIM_OTHERS */
+	bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b);
+
+	/* write AFTE_TX_CONFIG */
+	bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800);
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_d0_afe_config_init(struct phy_device *phydev)
+{
+	/* AFE_RXCONFIG_0 */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb15);
+
+	/* AFE_RXCONFIG_1 */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9b2f);
+
+	/* AFE_RXCONFIG_2, set rCal offset for HT=0 code and LT=-2 code */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_2, 0x2003);
+
+	/* AFE_RX_LP_COUNTER, set RX bandwidth to maximum */
+	bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
+
+	/* AFE_TX_CONFIG, set 100BT Cfeed=011 to improve rise/fall time */
+	bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x431);
+
+	/* AFE_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */
+	bcm_phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da);
+
+	/* AFE_VDAC_OTHERS_0, set 1000BT Cidac=010 for all ports */
+	bcm_phy_write_misc(phydev, AFE_VDAC_OTHERS_0, 0xa020);
+
+	/* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal
+	 * offset for HT=0 code
+	 */
+	bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3);
+
+	/* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */
+	phy_write(phydev, MII_BRCM_CORE_BASE1E, 0x0010);
+
+	/* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */
+	bcm_phy_write_misc(phydev, DSP_TAP10, 0x011b);
+
+	/* Reset R_CAL/RC_CAL engine */
+	r_rc_cal_reset(phydev);
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_e0_plus_afe_config_init(struct phy_device *phydev)
+{
+	/* AFE_RXCONFIG_1, provide more margin for INL/DNL measurement */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9b2f);
+
+	/* AFE_TX_CONFIG, set 100BT Cfeed=011 to improve rise/fall time */
+	bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x431);
+
+	/* AFE_VDCA_ICTRL_0, set Iq=1101 instead of 0111 for AB symmetry */
+	bcm_phy_write_misc(phydev, AFE_VDCA_ICTRL_0, 0xa7da);
+
+	/* AFE_HPF_TRIM_OTHERS, set 100Tx/10BT to -4.5% swing and set rCal
+	 * offset for HT=0 code
+	 */
+	bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x00e3);
+
+	/* CORE_BASE1E, force trim to overwrite and set I_ext trim to 0000 */
+	phy_write(phydev, MII_BRCM_CORE_BASE1E, 0x0010);
+
+	/* DSP_TAP10, adjust bias current trim (+0% swing, +0 tick) */
+	bcm_phy_write_misc(phydev, DSP_TAP10, 0x011b);
+
+	/* Reset R_CAL/RC_CAL engine */
+	r_rc_cal_reset(phydev);
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_a0_patch_afe_config_init(struct phy_device *phydev)
+{
+	/* +1 RC_CAL codes for RL centering for both LT and HT conditions */
+	bcm_phy_write_misc(phydev, AFE_RXCONFIG_2, 0xd003);
+
+	/* Cut master bias current by 2% to compensate for RC_CAL offset */
+	bcm_phy_write_misc(phydev, DSP_TAP10, 0x791b);
+
+	/* Improve hybrid leakage */
+	bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x10e3);
+
+	/* Change rx_on_tune 8 to 0xf */
+	bcm_phy_write_misc(phydev, 0x21, 0x2, 0x87f6);
+
+	/* Change 100Tx EEE bandwidth */
+	bcm_phy_write_misc(phydev, 0x22, 0x2, 0x017d);
+
+	/* Enable ffe zero detection for Vitesse interoperability */
+	bcm_phy_write_misc(phydev, 0x26, 0x2, 0x0015);
+
+	r_rc_cal_reset(phydev);
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_config_init(struct phy_device *phydev)
+{
+	u8 rev = PHY_BRCM_7XXX_REV(phydev->dev_flags);
+	u8 patch = PHY_BRCM_7XXX_PATCH(phydev->dev_flags);
+	u8 count;
+	int ret = 0;
+
+	/* Newer devices have moved the revision information back into a
+	 * standard location in MII_PHYS_ID[23]
+	 */
+	if (rev == 0)
+		rev = phydev->phy_id & ~phydev->drv->phy_id_mask;
+
+	pr_info_once("%s: %s PHY revision: 0x%02x, patch: %d\n",
+		     phydev_name(phydev), phydev->drv->name, rev, patch);
+
+	/* Dummy read to a register to workaround an issue upon reset where the
+	 * internal inverter may not allow the first MDIO transaction to pass
+	 * the MDIO management controller and make us return 0xffff for such
+	 * reads.
+	 */
+	phy_read(phydev, MII_BMSR);
+
+	switch (rev) {
+	case 0xa0:
+	case 0xb0:
+		ret = bcm7xxx_28nm_b0_afe_config_init(phydev);
+		break;
+	case 0xd0:
+		ret = bcm7xxx_28nm_d0_afe_config_init(phydev);
+		break;
+	case 0xe0:
+	case 0xf0:
+	/* Rev G0 introduces a roll over */
+	case 0x10:
+		ret = bcm7xxx_28nm_e0_plus_afe_config_init(phydev);
+		break;
+	case 0x01:
+		ret = bcm7xxx_28nm_a0_patch_afe_config_init(phydev);
+		break;
+	default:
+		break;
+	}
+
+	if (ret)
+		return ret;
+
+	ret = bcm_phy_downshift_get(phydev, &count);
+	if (ret)
+		return ret;
+
+	/* Only enable EEE if Wirespeed/downshift is disabled */
+	ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE);
+	if (ret)
+		return ret;
+
+	return bcm_phy_enable_apd(phydev, true);
+}
+
+static int bcm7xxx_28nm_resume(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Re-apply workarounds coming out suspend/resume */
+	ret = bcm7xxx_28nm_config_init(phydev);
+	if (ret)
+		return ret;
+
+	/* 28nm Gigabit PHYs come out of reset without any half-duplex
+	 * or "hub" compliant advertised mode, fix that. This does not
+	 * cause any problems with the PHY library since genphy_config_aneg()
+	 * gracefully handles auto-negotiated and forced modes.
+	 */
+	return genphy_config_aneg(phydev);
+}
+
+static int phy_set_clr_bits(struct phy_device *dev, int location,
+					int set_mask, int clr_mask)
+{
+	int v, ret;
+
+	v = phy_read(dev, location);
+	if (v < 0)
+		return v;
+
+	v &= ~clr_mask;
+	v |= set_mask;
+
+	ret = phy_write(dev, location, v);
+	if (ret < 0)
+		return ret;
+
+	return v;
+}
+
+static int bcm7xxx_28nm_ephy_01_afe_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	/* set shadow mode 2 */
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+			       MII_BCM7XXX_SHD_MODE_2, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Set current trim values INT_trim = -1, Ext_trim =0 */
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_BIAS_TRIM, 0x3BE0);
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+	/* Cal reset */
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+			MII_BCM7XXX_SHD_3_TL4);
+	if (ret < 0)
+		goto reset_shadow_mode;
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+			       MII_BCM7XXX_TL4_RST_MSK, 0);
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+	/* Cal reset disable */
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+			MII_BCM7XXX_SHD_3_TL4);
+	if (ret < 0)
+		goto reset_shadow_mode;
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+			       0, MII_BCM7XXX_TL4_RST_MSK);
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+reset_shadow_mode:
+	/* reset shadow mode 2 */
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0,
+			       MII_BCM7XXX_SHD_MODE_2);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/* The 28nm EPHY does not support Clause 45 (MMD) used by bcm-phy-lib */
+static int bcm7xxx_28nm_ephy_apd_enable(struct phy_device *phydev)
+{
+	int ret;
+
+	/* set shadow mode 1 */
+	ret = phy_set_clr_bits(phydev, MII_BRCM_FET_BRCMTEST,
+			       MII_BRCM_FET_BT_SRE, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Enable auto-power down */
+	ret = phy_set_clr_bits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2,
+			       MII_BRCM_FET_SHDW_AS2_APDE, 0);
+	if (ret < 0)
+		return ret;
+
+	/* reset shadow mode 1 */
+	ret = phy_set_clr_bits(phydev, MII_BRCM_FET_BRCMTEST, 0,
+			       MII_BRCM_FET_BT_SRE);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_ephy_eee_enable(struct phy_device *phydev)
+{
+	int ret;
+
+	/* set shadow mode 2 */
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+			       MII_BCM7XXX_SHD_MODE_2, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Advertise supported modes */
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+			MII_BCM7XXX_SHD_3_AN_EEE_ADV);
+	if (ret < 0)
+		goto reset_shadow_mode;
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+			MDIO_EEE_100TX);
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+	/* Restore Defaults */
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+			MII_BCM7XXX_SHD_3_PCS_CTRL_2);
+	if (ret < 0)
+		goto reset_shadow_mode;
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+			MII_BCM7XXX_PCS_CTRL_2_DEF);
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+			MII_BCM7XXX_SHD_3_EEE_THRESH);
+	if (ret < 0)
+		goto reset_shadow_mode;
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+			MII_BCM7XXX_EEE_THRESH_DEF);
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+	/* Enable EEE autonegotiation */
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+			MII_BCM7XXX_SHD_3_AN_STAT);
+	if (ret < 0)
+		goto reset_shadow_mode;
+	ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+			(MII_BCM7XXX_AN_NULL_MSG_EN | MII_BCM7XXX_AN_EEE_EN));
+	if (ret < 0)
+		goto reset_shadow_mode;
+
+reset_shadow_mode:
+	/* reset shadow mode 2 */
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0,
+			       MII_BCM7XXX_SHD_MODE_2);
+	if (ret < 0)
+		return ret;
+
+	/* Restart autoneg */
+	phy_write(phydev, MII_BMCR,
+		  (BMCR_SPEED100 | BMCR_ANENABLE | BMCR_ANRESTART));
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_ephy_config_init(struct phy_device *phydev)
+{
+	u8 rev = phydev->phy_id & ~phydev->drv->phy_id_mask;
+	int ret = 0;
+
+	pr_info_once("%s: %s PHY revision: 0x%02x\n",
+		     phydev_name(phydev), phydev->drv->name, rev);
+
+	/* Dummy read to a register to workaround a possible issue upon reset
+	 * where the internal inverter may not allow the first MDIO transaction
+	 * to pass the MDIO management controller and make us return 0xffff for
+	 * such reads.
+	 */
+	phy_read(phydev, MII_BMSR);
+
+	/* Apply AFE software work-around if necessary */
+	if (rev == 0x01) {
+		ret = bcm7xxx_28nm_ephy_01_afe_config_init(phydev);
+		if (ret)
+			return ret;
+	}
+
+	ret = bcm7xxx_28nm_ephy_eee_enable(phydev);
+	if (ret)
+		return ret;
+
+	return bcm7xxx_28nm_ephy_apd_enable(phydev);
+}
+
+static int bcm7xxx_28nm_ephy_resume(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Re-apply workarounds coming out suspend/resume */
+	ret = bcm7xxx_28nm_ephy_config_init(phydev);
+	if (ret)
+		return ret;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int bcm7xxx_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Enable 64 clock MDIO */
+	phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XXX_64CLK_MDIO);
+	phy_read(phydev, MII_BCM7XXX_AUX_MODE);
+
+	/* set shadow mode 2 */
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+			MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2);
+	if (ret < 0)
+		return ret;
+
+	/* set iddq_clkbias */
+	phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00);
+	udelay(10);
+
+	/* reset iddq_clkbias */
+	phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00);
+
+	phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555);
+
+	/* reset shadow mode 2 */
+	ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0, MII_BCM7XXX_SHD_MODE_2);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/* Workaround for putting the PHY in IDDQ mode, required
+ * for all BCM7XXX 40nm and 65nm PHYs
+ */
+static int bcm7xxx_suspend(struct phy_device *phydev)
+{
+	int ret;
+	static const struct bcm7xxx_regs {
+		int reg;
+		u16 value;
+	} bcm7xxx_suspend_cfg[] = {
+		{ MII_BCM7XXX_TEST, 0x008b },
+		{ MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 },
+		{ MII_BCM7XXX_100TX_DISC, 0x7000 },
+		{ MII_BCM7XXX_TEST, 0x000f },
+		{ MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 },
+		{ MII_BCM7XXX_TEST, 0x000b },
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) {
+		ret = phy_write(phydev,
+				bcm7xxx_suspend_cfg[i].reg,
+				bcm7xxx_suspend_cfg[i].value);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int bcm7xxx_28nm_get_tunable(struct phy_device *phydev,
+				    struct ethtool_tunable *tuna,
+				    void *data)
+{
+	switch (tuna->id) {
+	case ETHTOOL_PHY_DOWNSHIFT:
+		return bcm_phy_downshift_get(phydev, (u8 *)data);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int bcm7xxx_28nm_set_tunable(struct phy_device *phydev,
+				    struct ethtool_tunable *tuna,
+				    const void *data)
+{
+	u8 count = *(u8 *)data;
+	int ret;
+
+	switch (tuna->id) {
+	case ETHTOOL_PHY_DOWNSHIFT:
+		ret = bcm_phy_downshift_set(phydev, count);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	if (ret)
+		return ret;
+
+	/* Disable EEE advertisement since this prevents the PHY
+	 * from successfully linking up, trigger auto-negotiation restart
+	 * to let the MAC decide what to do.
+	 */
+	ret = bcm_phy_set_eee(phydev, count == DOWNSHIFT_DEV_DISABLE);
+	if (ret)
+		return ret;
+
+	return genphy_restart_aneg(phydev);
+}
+
+static void bcm7xxx_28nm_get_phy_stats(struct phy_device *phydev,
+				       struct ethtool_stats *stats, u64 *data)
+{
+	struct bcm7xxx_phy_priv *priv = phydev->priv;
+
+	bcm_phy_get_stats(phydev, priv->stats, stats, data);
+}
+
+static int bcm7xxx_28nm_probe(struct phy_device *phydev)
+{
+	struct bcm7xxx_phy_priv *priv;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	priv->stats = devm_kcalloc(&phydev->mdio.dev,
+				   bcm_phy_get_sset_count(phydev), sizeof(u64),
+				   GFP_KERNEL);
+	if (!priv->stats)
+		return -ENOMEM;
+
+	return 0;
+}
+
+#define BCM7XXX_28NM_GPHY(_oui, _name)					\
+{									\
+	.phy_id		= (_oui),					\
+	.phy_id_mask	= 0xfffffff0,					\
+	.name		= _name,					\
+	.features	= PHY_GBIT_FEATURES,				\
+	.flags		= PHY_IS_INTERNAL,				\
+	.config_init	= bcm7xxx_28nm_config_init,			\
+	.resume		= bcm7xxx_28nm_resume,				\
+	.get_tunable	= bcm7xxx_28nm_get_tunable,			\
+	.set_tunable	= bcm7xxx_28nm_set_tunable,			\
+	.get_sset_count	= bcm_phy_get_sset_count,			\
+	.get_strings	= bcm_phy_get_strings,				\
+	.get_stats	= bcm7xxx_28nm_get_phy_stats,			\
+	.probe		= bcm7xxx_28nm_probe,				\
+}
+
+#define BCM7XXX_28NM_EPHY(_oui, _name)					\
+{									\
+	.phy_id		= (_oui),					\
+	.phy_id_mask	= 0xfffffff0,					\
+	.name		= _name,					\
+	.features	= PHY_BASIC_FEATURES,				\
+	.flags		= PHY_IS_INTERNAL,				\
+	.config_init	= bcm7xxx_28nm_ephy_config_init,		\
+	.resume		= bcm7xxx_28nm_ephy_resume,			\
+	.get_sset_count	= bcm_phy_get_sset_count,			\
+	.get_strings	= bcm_phy_get_strings,				\
+	.get_stats	= bcm7xxx_28nm_get_phy_stats,			\
+	.probe		= bcm7xxx_28nm_probe,				\
+}
+
+#define BCM7XXX_40NM_EPHY(_oui, _name)					\
+{									\
+	.phy_id         = (_oui),					\
+	.phy_id_mask    = 0xfffffff0,					\
+	.name           = _name,					\
+	.features       = PHY_BASIC_FEATURES,				\
+	.flags          = PHY_IS_INTERNAL,				\
+	.soft_reset	= genphy_soft_reset,				\
+	.config_init    = bcm7xxx_config_init,				\
+	.suspend        = bcm7xxx_suspend,				\
+	.resume         = bcm7xxx_config_init,				\
+}
+
+static struct phy_driver bcm7xxx_driver[] = {
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"),
+	BCM7XXX_28NM_EPHY(PHY_ID_BCM7260, "Broadcom BCM7260"),
+	BCM7XXX_28NM_EPHY(PHY_ID_BCM7268, "Broadcom BCM7268"),
+	BCM7XXX_28NM_EPHY(PHY_ID_BCM7271, "Broadcom BCM7271"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7278, "Broadcom BCM7278"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7366, "Broadcom BCM7366"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM74371, "Broadcom BCM74371"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7439_2, "Broadcom BCM7439 (2)"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM7445, "Broadcom BCM7445"),
+	BCM7XXX_28NM_GPHY(PHY_ID_BCM_OMEGA, "Broadcom Omega Combo GPHY"),
+	BCM7XXX_40NM_EPHY(PHY_ID_BCM7346, "Broadcom BCM7346"),
+	BCM7XXX_40NM_EPHY(PHY_ID_BCM7362, "Broadcom BCM7362"),
+	BCM7XXX_40NM_EPHY(PHY_ID_BCM7425, "Broadcom BCM7425"),
+	BCM7XXX_40NM_EPHY(PHY_ID_BCM7429, "Broadcom BCM7429"),
+	BCM7XXX_40NM_EPHY(PHY_ID_BCM7435, "Broadcom BCM7435"),
+};
+
+static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
+	{ PHY_ID_BCM7250, 0xfffffff0, },
+	{ PHY_ID_BCM7260, 0xfffffff0, },
+	{ PHY_ID_BCM7268, 0xfffffff0, },
+	{ PHY_ID_BCM7271, 0xfffffff0, },
+	{ PHY_ID_BCM7278, 0xfffffff0, },
+	{ PHY_ID_BCM7364, 0xfffffff0, },
+	{ PHY_ID_BCM7366, 0xfffffff0, },
+	{ PHY_ID_BCM7346, 0xfffffff0, },
+	{ PHY_ID_BCM7362, 0xfffffff0, },
+	{ PHY_ID_BCM7425, 0xfffffff0, },
+	{ PHY_ID_BCM7429, 0xfffffff0, },
+	{ PHY_ID_BCM74371, 0xfffffff0, },
+	{ PHY_ID_BCM7439, 0xfffffff0, },
+	{ PHY_ID_BCM7435, 0xfffffff0, },
+	{ PHY_ID_BCM7445, 0xfffffff0, },
+	{ }
+};
+
+module_phy_driver(bcm7xxx_driver);
+
+MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl);
+
+MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Broadcom Corporation");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm84881.c b/src/kernel/linux/v4.19/drivers/net/phy/bcm84881.c
new file mode 100644
index 0000000..4ece4c8
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm84881.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+// Broadcom BCM84881 NBASE-T PHY driver, as found on a SFP+ module.
+// Copyright (C) 2019 Russell King, Deep Blue Solutions Ltd.
+//
+// Like the Marvell 88x3310, the Broadcom 84881 changes its host-side
+// interface according to the operating speed between 10GBASE-R,
+// 2500BASE-X and SGMII (but unlike the 88x3310, without the control
+// word).
+//
+// This driver only supports those aspects of the PHY that I'm able to
+// observe and test with the SFP+ module, which is an incomplete subset
+// of what this PHY is able to support. For example, I only assume it
+// supports a single lane Serdes connection, but it may be that the PHY
+// is able to support more than that.
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+enum {
+	MDIO_AN_C22 = 0xffe0,
+};
+
+static int bcm84881_wait_init(struct phy_device *phydev)
+{
+	unsigned int tries = 20;
+	int ret, val;
+
+	do {
+		val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+		if (val < 0) {
+			ret = val;
+			break;
+		}
+		if (!(val & MDIO_CTRL1_RESET)) {
+			ret = 0;
+			break;
+		}
+		if (!--tries) {
+			ret = -ETIMEDOUT;
+			break;
+		}
+		msleep(100);
+	} while (1);
+
+	if (ret)
+		phydev_err(phydev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int bcm84881_config_init(struct phy_device *phydev)
+{
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_2500BASEX:
+	case PHY_INTERFACE_MODE_10GKR:
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int bcm84881_probe(struct phy_device *phydev)
+{
+	/* This driver requires PMAPMD and AN blocks */
+	const u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+	if (!phydev->is_c45 ||
+	    (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int genphy_c45_an_config_aneg(struct phy_device *phydev)
+{
+	bool changed = false;
+	u32 advertising;
+	int ret;
+
+	phydev->advertising &= phydev->supported;
+	advertising = phydev->advertising;
+
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE,
+				     ADVERTISE_ALL | ADVERTISE_100BASE4 |
+				     ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+				     ethtool_adv_to_mii_adv_t(advertising));
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+				     MDIO_AN_10GBT_CTRL_ADV10G,
+				     advertising & ADVERTISED_10000baseT_Full ?
+					MDIO_AN_10GBT_CTRL_ADV10G : 0);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int bcm84881_config_aneg(struct phy_device *phydev)
+{
+	bool changed = false;
+	u32 adv;
+	int ret;
+
+	/* Wait for the PHY to finish initialising, otherwise our
+	 * advertisement may be overwritten.
+	 */
+	ret = bcm84881_wait_init(phydev);
+	if (ret)
+		return ret;
+
+	/* We don't support manual MDI control */
+	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+	/* disabled autoneg doesn't seem to work with this PHY */
+	if (phydev->autoneg == AUTONEG_DISABLE)
+		return -EINVAL;
+
+	ret = genphy_c45_an_config_aneg(phydev);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	adv = ethtool_adv_to_mii_ctrl1000_t(phydev->advertising);
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+				     MDIO_AN_C22 + MII_CTRL1000,
+				     ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+				     adv);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int bcm84881_aneg_done(struct phy_device *phydev)
+{
+	int bmsr, val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
+	if (bmsr < 0)
+		return val;
+
+	return !!(val & MDIO_AN_STAT1_COMPLETE) &&
+	       !!(bmsr & BMSR_ANEGCOMPLETE);
+}
+
+static int bcm84881_read_status(struct phy_device *phydev)
+{
+	bool autoneg_complete;
+	unsigned int mode;
+	int bmsr, val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_CTRL1_RESTART) {
+		phydev->link = 0;
+		return 0;
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
+	if (bmsr < 0)
+		return val;
+
+	autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) &&
+			   !!(bmsr & BMSR_ANEGCOMPLETE);
+	phydev->link = !!(val & MDIO_STAT1_LSTATUS) &&
+		       !!(bmsr & BMSR_LSTATUS);
+	if (phydev->autoneg == AUTONEG_ENABLE && !autoneg_complete)
+		phydev->link = false;
+
+	if (!phydev->link)
+		return 0;
+
+	phydev->lp_advertising = 0;
+	phydev->speed = SPEED_UNKNOWN;
+	phydev->duplex = DUPLEX_UNKNOWN;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+	phydev->mdix = 0;
+
+	if (autoneg_complete) {
+		val = genphy_c45_read_lpa(phydev);
+		if (val < 0)
+			return val;
+
+		val = phy_read_mmd(phydev, MDIO_MMD_AN,
+				   MDIO_AN_C22 + MII_STAT1000);
+		if (val < 0)
+			return val;
+
+		phydev->lp_advertising |= mii_stat1000_to_ethtool_lpa_t(val);
+
+		if (phydev->autoneg == AUTONEG_ENABLE)
+			phy_resolve_aneg_linkmode(phydev);
+	}
+
+	if (phydev->autoneg == AUTONEG_DISABLE) {
+		/* disabled autoneg doesn't seem to work, so force the link
+		 * down.
+		 */
+		phydev->link = 0;
+		return 0;
+	}
+
+	/* Set the host link mode - we set the phy interface mode and
+	 * the speed according to this register so that downshift works.
+	 * We leave the duplex setting as per the resolution from the
+	 * above.
+	 */
+	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x4011);
+	mode = (val & 0x1e) >> 1;
+	if (mode == 1 || mode == 2)
+		phydev->interface = PHY_INTERFACE_MODE_SGMII;
+	else if (mode == 3)
+		phydev->interface = PHY_INTERFACE_MODE_10GKR;
+	else if (mode == 4)
+		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+	switch (mode & 7) {
+	case 1:
+		phydev->speed = SPEED_100;
+		break;
+	case 2:
+		phydev->speed = SPEED_1000;
+		break;
+	case 3:
+		phydev->speed = SPEED_10000;
+		break;
+	case 4:
+		phydev->speed = SPEED_2500;
+		break;
+	case 5:
+		phydev->speed = SPEED_5000;
+		break;
+	}
+
+	return genphy_c45_read_mdix(phydev);
+}
+
+static struct phy_driver bcm84881_drivers[] = {
+	{
+		.phy_id		= 0xae025150,
+		.phy_id_mask	= 0xfffffff0,
+		.name		= "Broadcom BCM84881",
+		.features	= SUPPORTED_100baseT_Full |
+				  SUPPORTED_100baseT_Half |
+				  SUPPORTED_1000baseT_Full |
+				  SUPPORTED_Autoneg |
+				  SUPPORTED_TP |
+				  SUPPORTED_FIBRE |
+				  SUPPORTED_10000baseT_Full |
+				  SUPPORTED_Backplane,
+		.config_init	= bcm84881_config_init,
+		.probe		= bcm84881_probe,
+		.config_aneg	= bcm84881_config_aneg,
+		.aneg_done	= bcm84881_aneg_done,
+		.read_status	= bcm84881_read_status,
+	},
+};
+
+module_phy_driver(bcm84881_drivers);
+
+/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */
+static struct mdio_device_id __maybe_unused bcm84881_tbl[] = {
+	{ 0xae025150, 0xfffffff0 },
+	{ },
+};
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Broadcom BCM84881 PHY driver");
+MODULE_DEVICE_TABLE(mdio, bcm84881_tbl);
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/bcm87xx.c b/src/kernel/linux/v4.19/drivers/net/phy/bcm87xx.c
new file mode 100644
index 0000000..f7ebdcf
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/bcm87xx.c
@@ -0,0 +1,220 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2011 - 2012 Cavium, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#define PHY_ID_BCM8706	0x0143bdc1
+#define PHY_ID_BCM8727	0x0143bff0
+
+#define BCM87XX_PMD_RX_SIGNAL_DETECT	(MII_ADDR_C45 | 0x1000a)
+#define BCM87XX_10GBASER_PCS_STATUS	(MII_ADDR_C45 | 0x30020)
+#define BCM87XX_XGXS_LANE_STATUS	(MII_ADDR_C45 | 0x40018)
+
+#define BCM87XX_LASI_CONTROL (MII_ADDR_C45 | 0x39002)
+#define BCM87XX_LASI_STATUS (MII_ADDR_C45 | 0x39005)
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+/* Set and/or override some configuration registers based on the
+ * broadcom,c45-reg-init property stored in the of_node for the phydev.
+ *
+ * broadcom,c45-reg-init = <devid reg mask value>,...;
+ *
+ * There may be one or more sets of <devid reg mask value>:
+ *
+ * devid: which sub-device to use.
+ * reg: the register.
+ * mask: if non-zero, ANDed with existing register value.
+ * value: ORed with the masked value and written to the regiser.
+ *
+ */
+static int bcm87xx_of_reg_init(struct phy_device *phydev)
+{
+	const __be32 *paddr;
+	const __be32 *paddr_end;
+	int len, ret;
+
+	if (!phydev->mdio.dev.of_node)
+		return 0;
+
+	paddr = of_get_property(phydev->mdio.dev.of_node,
+				"broadcom,c45-reg-init", &len);
+	if (!paddr)
+		return 0;
+
+	paddr_end = paddr + (len /= sizeof(*paddr));
+
+	ret = 0;
+
+	while (paddr + 3 < paddr_end) {
+		u16 devid	= be32_to_cpup(paddr++);
+		u16 reg		= be32_to_cpup(paddr++);
+		u16 mask	= be32_to_cpup(paddr++);
+		u16 val_bits	= be32_to_cpup(paddr++);
+		int val;
+		u32 regnum = MII_ADDR_C45 | (devid << 16) | reg;
+		val = 0;
+		if (mask) {
+			val = phy_read(phydev, regnum);
+			if (val < 0) {
+				ret = val;
+				goto err;
+			}
+			val &= mask;
+		}
+		val |= val_bits;
+
+		ret = phy_write(phydev, regnum, val);
+		if (ret < 0)
+			goto err;
+	}
+err:
+	return ret;
+}
+#else
+static int bcm87xx_of_reg_init(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int bcm87xx_config_init(struct phy_device *phydev)
+{
+	phydev->supported = SUPPORTED_10000baseR_FEC;
+	phydev->advertising = ADVERTISED_10000baseR_FEC;
+	phydev->state = PHY_NOLINK;
+	phydev->autoneg = AUTONEG_DISABLE;
+
+	bcm87xx_of_reg_init(phydev);
+
+	return 0;
+}
+
+static int bcm87xx_config_aneg(struct phy_device *phydev)
+{
+	return -EINVAL;
+}
+
+static int bcm87xx_read_status(struct phy_device *phydev)
+{
+	int rx_signal_detect;
+	int pcs_status;
+	int xgxs_lane_status;
+
+	rx_signal_detect = phy_read(phydev, BCM87XX_PMD_RX_SIGNAL_DETECT);
+	if (rx_signal_detect < 0)
+		return rx_signal_detect;
+
+	if ((rx_signal_detect & 1) == 0)
+		goto no_link;
+
+	pcs_status = phy_read(phydev, BCM87XX_10GBASER_PCS_STATUS);
+	if (pcs_status < 0)
+		return pcs_status;
+
+	if ((pcs_status & 1) == 0)
+		goto no_link;
+
+	xgxs_lane_status = phy_read(phydev, BCM87XX_XGXS_LANE_STATUS);
+	if (xgxs_lane_status < 0)
+		return xgxs_lane_status;
+
+	if ((xgxs_lane_status & 0x1000) == 0)
+		goto no_link;
+
+	phydev->speed = 10000;
+	phydev->link = 1;
+	phydev->duplex = 1;
+	return 0;
+
+no_link:
+	phydev->link = 0;
+	return 0;
+}
+
+static int bcm87xx_config_intr(struct phy_device *phydev)
+{
+	int reg, err;
+
+	reg = phy_read(phydev, BCM87XX_LASI_CONTROL);
+
+	if (reg < 0)
+		return reg;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		reg |= 1;
+	else
+		reg &= ~1;
+
+	err = phy_write(phydev, BCM87XX_LASI_CONTROL, reg);
+	return err;
+}
+
+static int bcm87xx_did_interrupt(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = phy_read(phydev, BCM87XX_LASI_STATUS);
+
+	if (reg < 0) {
+		phydev_err(phydev,
+			   "Error: Read of BCM87XX_LASI_STATUS failed: %d\n",
+			   reg);
+		return 0;
+	}
+	return (reg & 1) != 0;
+}
+
+static int bcm87xx_ack_interrupt(struct phy_device *phydev)
+{
+	/* Reading the LASI status clears it. */
+	bcm87xx_did_interrupt(phydev);
+	return 0;
+}
+
+static int bcm8706_match_phy_device(struct phy_device *phydev)
+{
+	return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706;
+}
+
+static int bcm8727_match_phy_device(struct phy_device *phydev)
+{
+	return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727;
+}
+
+static struct phy_driver bcm87xx_driver[] = {
+{
+	.phy_id		= PHY_ID_BCM8706,
+	.phy_id_mask	= 0xffffffff,
+	.name		= "Broadcom BCM8706",
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm87xx_config_init,
+	.config_aneg	= bcm87xx_config_aneg,
+	.read_status	= bcm87xx_read_status,
+	.ack_interrupt	= bcm87xx_ack_interrupt,
+	.config_intr	= bcm87xx_config_intr,
+	.did_interrupt	= bcm87xx_did_interrupt,
+	.match_phy_device = bcm8706_match_phy_device,
+}, {
+	.phy_id		= PHY_ID_BCM8727,
+	.phy_id_mask	= 0xffffffff,
+	.name		= "Broadcom BCM8727",
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm87xx_config_init,
+	.config_aneg	= bcm87xx_config_aneg,
+	.read_status	= bcm87xx_read_status,
+	.ack_interrupt	= bcm87xx_ack_interrupt,
+	.config_intr	= bcm87xx_config_intr,
+	.did_interrupt	= bcm87xx_did_interrupt,
+	.match_phy_device = bcm8727_match_phy_device,
+} };
+
+module_phy_driver(bcm87xx_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/broadcom.c b/src/kernel/linux/v4.19/drivers/net/phy/broadcom.c
new file mode 100644
index 0000000..e86ea10
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/broadcom.c
@@ -0,0 +1,769 @@
+/*
+ *	drivers/net/phy/broadcom.c
+ *
+ *	Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet
+ *	transceivers.
+ *
+ *	Copyright (c) 2006  Maciej W. Rozycki
+ *
+ *	Inspired by code written by Amy Fong.
+ *
+ *	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.
+ */
+
+#include "bcm-phy-lib.h"
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/brcmphy.h>
+#include <linux/of.h>
+
+#define BRCM_PHY_MODEL(phydev) \
+	((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask)
+
+#define BRCM_PHY_REV(phydev) \
+	((phydev)->drv->phy_id & ~((phydev)->drv->phy_id_mask))
+
+MODULE_DESCRIPTION("Broadcom PHY driver");
+MODULE_AUTHOR("Maciej W. Rozycki");
+MODULE_LICENSE("GPL");
+
+static int bcm54210e_config_init(struct phy_device *phydev)
+{
+	int val;
+
+	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+	val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;
+	val |= MII_BCM54XX_AUXCTL_MISC_WREN;
+	bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC, val);
+
+	val = bcm_phy_read_shadow(phydev, BCM54810_SHD_CLK_CTL);
+	val &= ~BCM54810_SHD_CLK_CTL_GTXCLK_EN;
+	bcm_phy_write_shadow(phydev, BCM54810_SHD_CLK_CTL, val);
+
+	if (phydev->dev_flags & PHY_BRCM_EN_MASTER_MODE) {
+		val = phy_read(phydev, MII_CTRL1000);
+		val |= CTL1000_AS_MASTER | CTL1000_ENABLE_MASTER;
+		phy_write(phydev, MII_CTRL1000, val);
+	}
+
+	return 0;
+}
+
+static int bcm54612e_config_init(struct phy_device *phydev)
+{
+	int reg;
+
+	/* Clear TX internal delay unless requested. */
+	if ((phydev->interface != PHY_INTERFACE_MODE_RGMII_ID) &&
+	    (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID)) {
+		/* Disable TXD to GTXCLK clock delay (default set) */
+		/* Bit 9 is the only field in shadow register 00011 */
+		bcm_phy_write_shadow(phydev, 0x03, 0);
+	}
+
+	/* Clear RX internal delay unless requested. */
+	if ((phydev->interface != PHY_INTERFACE_MODE_RGMII_ID) &&
+	    (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID)) {
+		reg = bcm54xx_auxctl_read(phydev,
+					  MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+		/* Disable RXD to RXC delay (default set) */
+		reg &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;
+		/* Clear shadow selector field */
+		reg &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MASK;
+		bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
+				     MII_BCM54XX_AUXCTL_MISC_WREN | reg);
+	}
+
+	/* Enable CLK125 MUX on LED4 if ref clock is enabled. */
+	if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) {
+		int err;
+
+		reg = bcm_phy_read_exp(phydev, BCM54612E_EXP_SPARE0);
+		err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0,
+					BCM54612E_LED4_CLK125OUT_EN | reg);
+
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int bcm5481x_config(struct phy_device *phydev)
+{
+	int rc, val;
+
+	/* handling PHY's internal RX clock delay */
+	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+	val |= MII_BCM54XX_AUXCTL_MISC_WREN;
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+		/* Disable RGMII RXC-RXD skew */
+		val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;
+	}
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+		/* Enable RGMII RXC-RXD skew */
+		val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;
+	}
+	rc = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
+				  val);
+	if (rc < 0)
+		return rc;
+
+	/* handling PHY's internal TX clock delay */
+	val = bcm_phy_read_shadow(phydev, BCM54810_SHD_CLK_CTL);
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+		/* Disable internal TX clock delay */
+		val &= ~BCM54810_SHD_CLK_CTL_GTXCLK_EN;
+	}
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+		/* Enable internal TX clock delay */
+		val |= BCM54810_SHD_CLK_CTL_GTXCLK_EN;
+	}
+	rc = bcm_phy_write_shadow(phydev, BCM54810_SHD_CLK_CTL, val);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/* Needs SMDSP clock enabled via bcm54xx_phydsp_config() */
+static int bcm50610_a0_workaround(struct phy_device *phydev)
+{
+	int err;
+
+	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_AADJ1CH0,
+				MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN |
+				MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF);
+	if (err < 0)
+		return err;
+
+	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_AADJ1CH3,
+				MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ);
+	if (err < 0)
+		return err;
+
+	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP75,
+				MII_BCM54XX_EXP_EXP75_VDACCTRL);
+	if (err < 0)
+		return err;
+
+	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP96,
+				MII_BCM54XX_EXP_EXP96_MYST);
+	if (err < 0)
+		return err;
+
+	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP97,
+				MII_BCM54XX_EXP_EXP97_MYST);
+
+	return err;
+}
+
+static int bcm54xx_phydsp_config(struct phy_device *phydev)
+{
+	int err, err2;
+
+	/* Enable the SMDSP clock */
+	err = bcm54xx_auxctl_write(phydev,
+				   MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
+				   MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA |
+				   MII_BCM54XX_AUXCTL_ACTL_TX_6DB);
+	if (err < 0)
+		return err;
+
+	if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||
+	    BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) {
+		/* Clear bit 9 to fix a phy interop issue. */
+		err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08,
+					MII_BCM54XX_EXP_EXP08_RJCT_2MHZ);
+		if (err < 0)
+			goto error;
+
+		if (phydev->drv->phy_id == PHY_ID_BCM50610) {
+			err = bcm50610_a0_workaround(phydev);
+			if (err < 0)
+				goto error;
+		}
+	}
+
+	if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM57780) {
+		int val;
+
+		val = bcm_phy_read_exp(phydev, MII_BCM54XX_EXP_EXP75);
+		if (val < 0)
+			goto error;
+
+		val |= MII_BCM54XX_EXP_EXP75_CM_OSC;
+		err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP75, val);
+	}
+
+error:
+	/* Disable the SMDSP clock */
+	err2 = bcm54xx_auxctl_write(phydev,
+				    MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
+				    MII_BCM54XX_AUXCTL_ACTL_TX_6DB);
+
+	/* Return the first error reported. */
+	return err ? err : err2;
+}
+
+static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev)
+{
+	u32 orig;
+	int val;
+	bool clk125en = true;
+
+	/* Abort if we are using an untested phy. */
+	if (BRCM_PHY_MODEL(phydev) != PHY_ID_BCM57780 &&
+	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610 &&
+	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M)
+		return;
+
+	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
+	if (val < 0)
+		return;
+
+	orig = val;
+
+	if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||
+	     BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) &&
+	    BRCM_PHY_REV(phydev) >= 0x3) {
+		/*
+		 * Here, bit 0 _disables_ CLK125 when set.
+		 * This bit is set by default.
+		 */
+		clk125en = false;
+	} else {
+		if (phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) {
+			/* Here, bit 0 _enables_ CLK125 when set */
+			val &= ~BCM54XX_SHD_SCR3_DEF_CLK125;
+			clk125en = false;
+		}
+	}
+
+	if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
+		val &= ~BCM54XX_SHD_SCR3_DLLAPD_DIS;
+	else
+		val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
+
+	if (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY)
+		val |= BCM54XX_SHD_SCR3_TRDDAPD;
+
+	if (orig != val)
+		bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val);
+
+	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD);
+	if (val < 0)
+		return;
+
+	orig = val;
+
+	if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
+		val |= BCM54XX_SHD_APD_EN;
+	else
+		val &= ~BCM54XX_SHD_APD_EN;
+
+	if (orig != val)
+		bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);
+}
+
+static int bcm54xx_config_init(struct phy_device *phydev)
+{
+	int reg, err, val;
+
+	reg = phy_read(phydev, MII_BCM54XX_ECR);
+	if (reg < 0)
+		return reg;
+
+	/* Mask interrupts globally.  */
+	reg |= MII_BCM54XX_ECR_IM;
+	err = phy_write(phydev, MII_BCM54XX_ECR, reg);
+	if (err < 0)
+		return err;
+
+	/* Unmask events we are interested in.  */
+	reg = ~(MII_BCM54XX_INT_DUPLEX |
+		MII_BCM54XX_INT_SPEED |
+		MII_BCM54XX_INT_LINK);
+	err = phy_write(phydev, MII_BCM54XX_IMR, reg);
+	if (err < 0)
+		return err;
+
+	if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||
+	     BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) &&
+	    (phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE))
+		bcm_phy_write_shadow(phydev, BCM54XX_SHD_RGMII_MODE, 0);
+
+	if ((phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) ||
+	    (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) ||
+	    (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
+		bcm54xx_adjust_rxrefclk(phydev);
+
+	if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54210E) {
+		err = bcm54210e_config_init(phydev);
+		if (err)
+			return err;
+	} else if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54612E) {
+		err = bcm54612e_config_init(phydev);
+		if (err)
+			return err;
+	} else if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54810) {
+		/* For BCM54810, we need to disable BroadR-Reach function */
+		val = bcm_phy_read_exp(phydev,
+				       BCM54810_EXP_BROADREACH_LRE_MISC_CTL);
+		val &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;
+		err = bcm_phy_write_exp(phydev,
+					BCM54810_EXP_BROADREACH_LRE_MISC_CTL,
+					val);
+		if (err < 0)
+			return err;
+	}
+
+	bcm54xx_phydsp_config(phydev);
+
+	return 0;
+}
+
+static int bcm5482_config_init(struct phy_device *phydev)
+{
+	int err, reg;
+
+	err = bcm54xx_config_init(phydev);
+
+	if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {
+		/*
+		 * Enable secondary SerDes and its use as an LED source
+		 */
+		reg = bcm_phy_read_shadow(phydev, BCM5482_SHD_SSD);
+		bcm_phy_write_shadow(phydev, BCM5482_SHD_SSD,
+				     reg |
+				     BCM5482_SHD_SSD_LEDM |
+				     BCM5482_SHD_SSD_EN);
+
+		/*
+		 * Enable SGMII slave mode and auto-detection
+		 */
+		reg = BCM5482_SSD_SGMII_SLAVE | MII_BCM54XX_EXP_SEL_SSD;
+		err = bcm_phy_read_exp(phydev, reg);
+		if (err < 0)
+			return err;
+		err = bcm_phy_write_exp(phydev, reg, err |
+					BCM5482_SSD_SGMII_SLAVE_EN |
+					BCM5482_SSD_SGMII_SLAVE_AD);
+		if (err < 0)
+			return err;
+
+		/*
+		 * Disable secondary SerDes powerdown
+		 */
+		reg = BCM5482_SSD_1000BX_CTL | MII_BCM54XX_EXP_SEL_SSD;
+		err = bcm_phy_read_exp(phydev, reg);
+		if (err < 0)
+			return err;
+		err = bcm_phy_write_exp(phydev, reg,
+					err & ~BCM5482_SSD_1000BX_CTL_PWRDOWN);
+		if (err < 0)
+			return err;
+
+		/*
+		 * Select 1000BASE-X register set (primary SerDes)
+		 */
+		reg = bcm_phy_read_shadow(phydev, BCM5482_SHD_MODE);
+		bcm_phy_write_shadow(phydev, BCM5482_SHD_MODE,
+				     reg | BCM5482_SHD_MODE_1000BX);
+
+		/*
+		 * LED1=ACTIVITYLED, LED3=LINKSPD[2]
+		 * (Use LED1 as secondary SerDes ACTIVITY LED)
+		 */
+		bcm_phy_write_shadow(phydev, BCM5482_SHD_LEDS1,
+			BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_ACTIVITYLED) |
+			BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_LINKSPD2));
+
+		/*
+		 * Auto-negotiation doesn't seem to work quite right
+		 * in this mode, so we disable it and force it to the
+		 * right speed/duplex setting.  Only 'link status'
+		 * is important.
+		 */
+		phydev->autoneg = AUTONEG_DISABLE;
+		phydev->speed = SPEED_1000;
+		phydev->duplex = DUPLEX_FULL;
+	}
+
+	return err;
+}
+
+static int bcm5482_read_status(struct phy_device *phydev)
+{
+	int err;
+
+	err = genphy_read_status(phydev);
+
+	if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {
+		/*
+		 * Only link status matters for 1000Base-X mode, so force
+		 * 1000 Mbit/s full-duplex status
+		 */
+		if (phydev->link) {
+			phydev->speed = SPEED_1000;
+			phydev->duplex = DUPLEX_FULL;
+		}
+	}
+
+	return err;
+}
+
+static int bcm5481_config_aneg(struct phy_device *phydev)
+{
+	struct device_node *np = phydev->mdio.dev.of_node;
+	int ret;
+
+	/* Aneg firsly. */
+	ret = genphy_config_aneg(phydev);
+
+	/* Then we can set up the delay. */
+	bcm5481x_config(phydev);
+
+	if (of_property_read_bool(np, "enet-phy-lane-swap")) {
+		/* Lane Swap - Undocumented register...magic! */
+		ret = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_SEL_ER + 0x9,
+					0x11B);
+		if (ret < 0)
+			return ret;
+	}
+
+	return ret;
+}
+
+static int brcm_phy_setbits(struct phy_device *phydev, int reg, int set)
+{
+	int val;
+
+	val = phy_read(phydev, reg);
+	if (val < 0)
+		return val;
+
+	return phy_write(phydev, reg, val | set);
+}
+
+static int brcm_fet_config_init(struct phy_device *phydev)
+{
+	int reg, err, err2, brcmtest;
+
+	/* Reset the PHY to bring it to a known state. */
+	err = phy_write(phydev, MII_BMCR, BMCR_RESET);
+	if (err < 0)
+		return err;
+
+	reg = phy_read(phydev, MII_BRCM_FET_INTREG);
+	if (reg < 0)
+		return reg;
+
+	/* Unmask events we are interested in and mask interrupts globally. */
+	reg = MII_BRCM_FET_IR_DUPLEX_EN |
+	      MII_BRCM_FET_IR_SPEED_EN |
+	      MII_BRCM_FET_IR_LINK_EN |
+	      MII_BRCM_FET_IR_ENABLE |
+	      MII_BRCM_FET_IR_MASK;
+
+	err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);
+	if (err < 0)
+		return err;
+
+	/* Enable shadow register access */
+	brcmtest = phy_read(phydev, MII_BRCM_FET_BRCMTEST);
+	if (brcmtest < 0)
+		return brcmtest;
+
+	reg = brcmtest | MII_BRCM_FET_BT_SRE;
+
+	err = phy_write(phydev, MII_BRCM_FET_BRCMTEST, reg);
+	if (err < 0)
+		return err;
+
+	/* Set the LED mode */
+	reg = phy_read(phydev, MII_BRCM_FET_SHDW_AUXMODE4);
+	if (reg < 0) {
+		err = reg;
+		goto done;
+	}
+
+	reg &= ~MII_BRCM_FET_SHDW_AM4_LED_MASK;
+	reg |= MII_BRCM_FET_SHDW_AM4_LED_MODE1;
+
+	err = phy_write(phydev, MII_BRCM_FET_SHDW_AUXMODE4, reg);
+	if (err < 0)
+		goto done;
+
+	/* Enable auto MDIX */
+	err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_MISCCTRL,
+				       MII_BRCM_FET_SHDW_MC_FAME);
+	if (err < 0)
+		goto done;
+
+	if (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE) {
+		/* Enable auto power down */
+		err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2,
+					       MII_BRCM_FET_SHDW_AS2_APDE);
+	}
+
+done:
+	/* Disable shadow register access */
+	err2 = phy_write(phydev, MII_BRCM_FET_BRCMTEST, brcmtest);
+	if (!err)
+		err = err2;
+
+	return err;
+}
+
+static int brcm_fet_ack_interrupt(struct phy_device *phydev)
+{
+	int reg;
+
+	/* Clear pending interrupts.  */
+	reg = phy_read(phydev, MII_BRCM_FET_INTREG);
+	if (reg < 0)
+		return reg;
+
+	return 0;
+}
+
+static int brcm_fet_config_intr(struct phy_device *phydev)
+{
+	int reg, err;
+
+	reg = phy_read(phydev, MII_BRCM_FET_INTREG);
+	if (reg < 0)
+		return reg;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		reg &= ~MII_BRCM_FET_IR_MASK;
+	else
+		reg |= MII_BRCM_FET_IR_MASK;
+
+	err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);
+	return err;
+}
+
+struct bcm53xx_phy_priv {
+	u64	*stats;
+};
+
+static int bcm53xx_phy_probe(struct phy_device *phydev)
+{
+	struct bcm53xx_phy_priv *priv;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	priv->stats = devm_kcalloc(&phydev->mdio.dev,
+				   bcm_phy_get_sset_count(phydev), sizeof(u64),
+				   GFP_KERNEL);
+	if (!priv->stats)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void bcm53xx_phy_get_stats(struct phy_device *phydev,
+				  struct ethtool_stats *stats, u64 *data)
+{
+	struct bcm53xx_phy_priv *priv = phydev->priv;
+
+	bcm_phy_get_stats(phydev, priv->stats, stats, data);
+}
+
+static struct phy_driver broadcom_drivers[] = {
+{
+	.phy_id		= PHY_ID_BCM5411,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5411",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5421,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5421",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM54210E,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM54210E",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5461,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5461",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM54612E,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM54612E",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM54616S,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM54616S",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5464,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5464",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5481,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5481",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.config_aneg	= bcm5481_config_aneg,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id         = PHY_ID_BCM54810,
+	.phy_id_mask    = 0xfffffff0,
+	.name           = "Broadcom BCM54810",
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = bcm54xx_config_init,
+	.config_aneg    = bcm5481_config_aneg,
+	.ack_interrupt  = bcm_phy_ack_intr,
+	.config_intr    = bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5482,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5482",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm5482_config_init,
+	.read_status	= bcm5482_read_status,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM50610,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM50610",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM50610M,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM50610M",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM57780,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM57780",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= bcm54xx_config_init,
+	.ack_interrupt	= bcm_phy_ack_intr,
+	.config_intr	= bcm_phy_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCMAC131,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCMAC131",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= brcm_fet_config_init,
+	.ack_interrupt	= brcm_fet_ack_interrupt,
+	.config_intr	= brcm_fet_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5241,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5241",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= brcm_fet_config_init,
+	.ack_interrupt	= brcm_fet_ack_interrupt,
+	.config_intr	= brcm_fet_config_intr,
+}, {
+	.phy_id		= PHY_ID_BCM5395,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Broadcom BCM5395",
+	.flags		= PHY_IS_INTERNAL,
+	.features	= PHY_GBIT_FEATURES,
+	.get_sset_count	= bcm_phy_get_sset_count,
+	.get_strings	= bcm_phy_get_strings,
+	.get_stats	= bcm53xx_phy_get_stats,
+	.probe		= bcm53xx_phy_probe,
+}, {
+	.phy_id         = PHY_ID_BCM89610,
+	.phy_id_mask    = 0xfffffff0,
+	.name           = "Broadcom BCM89610",
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = bcm54xx_config_init,
+	.ack_interrupt  = bcm_phy_ack_intr,
+	.config_intr    = bcm_phy_config_intr,
+} };
+
+module_phy_driver(broadcom_drivers);
+
+static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
+	{ PHY_ID_BCM5411, 0xfffffff0 },
+	{ PHY_ID_BCM5421, 0xfffffff0 },
+	{ PHY_ID_BCM54210E, 0xfffffff0 },
+	{ PHY_ID_BCM5461, 0xfffffff0 },
+	{ PHY_ID_BCM54612E, 0xfffffff0 },
+	{ PHY_ID_BCM54616S, 0xfffffff0 },
+	{ PHY_ID_BCM5464, 0xfffffff0 },
+	{ PHY_ID_BCM5481, 0xfffffff0 },
+	{ PHY_ID_BCM54810, 0xfffffff0 },
+	{ PHY_ID_BCM5482, 0xfffffff0 },
+	{ PHY_ID_BCM50610, 0xfffffff0 },
+	{ PHY_ID_BCM50610M, 0xfffffff0 },
+	{ PHY_ID_BCM57780, 0xfffffff0 },
+	{ PHY_ID_BCMAC131, 0xfffffff0 },
+	{ PHY_ID_BCM5241, 0xfffffff0 },
+	{ PHY_ID_BCM5395, 0xfffffff0 },
+	{ PHY_ID_BCM89610, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, broadcom_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/cicada.c b/src/kernel/linux/v4.19/drivers/net/phy/cicada.c
new file mode 100644
index 0000000..c05af00
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/cicada.c
@@ -0,0 +1,134 @@
+/*
+ * drivers/net/phy/cicada.c
+ *
+ * Driver for Cicada PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.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/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+/* Cicada Extended Control Register 1 */
+#define MII_CIS8201_EXT_CON1           0x17
+#define MII_CIS8201_EXTCON1_INIT       0x0000
+
+/* Cicada Interrupt Mask Register */
+#define MII_CIS8201_IMASK		0x19
+#define MII_CIS8201_IMASK_IEN		0x8000
+#define MII_CIS8201_IMASK_SPEED	0x4000
+#define MII_CIS8201_IMASK_LINK		0x2000
+#define MII_CIS8201_IMASK_DUPLEX	0x1000
+#define MII_CIS8201_IMASK_MASK		0xf000
+
+/* Cicada Interrupt Status Register */
+#define MII_CIS8201_ISTAT		0x1a
+#define MII_CIS8201_ISTAT_STATUS	0x8000
+#define MII_CIS8201_ISTAT_SPEED	0x4000
+#define MII_CIS8201_ISTAT_LINK		0x2000
+#define MII_CIS8201_ISTAT_DUPLEX	0x1000
+
+/* Cicada Auxiliary Control/Status Register */
+#define MII_CIS8201_AUX_CONSTAT        0x1c
+#define MII_CIS8201_AUXCONSTAT_INIT    0x0004
+#define MII_CIS8201_AUXCONSTAT_DUPLEX  0x0020
+#define MII_CIS8201_AUXCONSTAT_SPEED   0x0018
+#define MII_CIS8201_AUXCONSTAT_GBIT    0x0010
+#define MII_CIS8201_AUXCONSTAT_100     0x0008
+
+MODULE_DESCRIPTION("Cicadia PHY driver");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+static int cis820x_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, MII_CIS8201_AUX_CONSTAT,
+			MII_CIS8201_AUXCONSTAT_INIT);
+
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, MII_CIS8201_EXT_CON1,
+			MII_CIS8201_EXTCON1_INIT);
+
+	return err;
+}
+
+static int cis820x_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, MII_CIS8201_ISTAT);
+
+	return (err < 0) ? err : 0;
+}
+
+static int cis820x_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, MII_CIS8201_IMASK,
+				MII_CIS8201_IMASK_MASK);
+	else
+		err = phy_write(phydev, MII_CIS8201_IMASK, 0);
+
+	return err;
+}
+
+/* Cicada 8201, a.k.a Vitesse VSC8201 */
+static struct phy_driver cis820x_driver[] = {
+{
+	.phy_id		= 0x000fc410,
+	.name		= "Cicada Cis8201",
+	.phy_id_mask	= 0x000ffff0,
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= &cis820x_config_init,
+	.ack_interrupt	= &cis820x_ack_interrupt,
+	.config_intr	= &cis820x_config_intr,
+}, {
+	.phy_id		= 0x000fc440,
+	.name		= "Cicada Cis8204",
+	.phy_id_mask	= 0x000fffc0,
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= &cis820x_config_init,
+	.ack_interrupt	= &cis820x_ack_interrupt,
+	.config_intr	= &cis820x_config_intr,
+} };
+
+module_phy_driver(cis820x_driver);
+
+static struct mdio_device_id __maybe_unused cicada_tbl[] = {
+	{ 0x000fc410, 0x000ffff0 },
+	{ 0x000fc440, 0x000fffc0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, cicada_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/cortina.c b/src/kernel/linux/v4.19/drivers/net/phy/cortina.c
new file mode 100644
index 0000000..8022cd3
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/cortina.c
@@ -0,0 +1,110 @@
+/*
+ *    Copyright 2017 NXP
+ *
+ *    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.
+ *
+ *    CORTINA is a registered trademark of Cortina Systems, Inc.
+ *
+ */
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define PHY_ID_CS4340	0x13e51002
+
+#define VILLA_GLOBAL_CHIP_ID_LSB			0x0
+#define VILLA_GLOBAL_CHIP_ID_MSB			0x1
+
+#define VILLA_GLOBAL_GPIO_1_INTS			0x017
+
+static int cortina_read_reg(struct phy_device *phydev, u16 regnum)
+{
+	return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr,
+			    MII_ADDR_C45 | regnum);
+}
+
+static int cortina_read_status(struct phy_device *phydev)
+{
+	int gpio_int_status, ret = 0;
+
+	gpio_int_status = cortina_read_reg(phydev, VILLA_GLOBAL_GPIO_1_INTS);
+	if (gpio_int_status < 0) {
+		ret = gpio_int_status;
+		goto err;
+	}
+
+	if (gpio_int_status & 0x8) {
+		/* up when edc_convergedS set */
+		phydev->speed = SPEED_10000;
+		phydev->duplex = DUPLEX_FULL;
+		phydev->link = 1;
+	} else {
+		phydev->link = 0;
+	}
+
+err:
+	return ret;
+}
+
+static int cortina_probe(struct phy_device *phydev)
+{
+	u32 phy_id = 0;
+	int id_lsb = 0, id_msb = 0;
+
+	/* Read device id from phy registers. */
+	id_lsb = cortina_read_reg(phydev, VILLA_GLOBAL_CHIP_ID_LSB);
+	if (id_lsb < 0)
+		return -ENXIO;
+
+	phy_id = id_lsb << 16;
+
+	id_msb = cortina_read_reg(phydev, VILLA_GLOBAL_CHIP_ID_MSB);
+	if (id_msb < 0)
+		return -ENXIO;
+
+	phy_id |= id_msb;
+
+	/* Make sure the device tree binding matched the driver with the
+	 * right device.
+	 */
+	if (phy_id != phydev->drv->phy_id) {
+		phydev_err(phydev, "Error matching phy with %s driver\n",
+			   phydev->drv->name);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static struct phy_driver cortina_driver[] = {
+{
+	.phy_id		= PHY_ID_CS4340,
+	.phy_id_mask	= 0xffffffff,
+	.name		= "Cortina CS4340",
+	.config_init	= gen10g_config_init,
+	.config_aneg	= gen10g_config_aneg,
+	.read_status	= cortina_read_status,
+	.soft_reset	= gen10g_no_soft_reset,
+	.probe		= cortina_probe,
+},
+};
+
+module_phy_driver(cortina_driver);
+
+static struct mdio_device_id __maybe_unused cortina_tbl[] = {
+	{ PHY_ID_CS4340, 0xffffffff},
+	{},
+};
+
+MODULE_DEVICE_TABLE(mdio, cortina_tbl);
+
+MODULE_DESCRIPTION("Cortina EDC CDR 10G Ethernet PHY driver");
+MODULE_AUTHOR("NXP");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/davicom.c b/src/kernel/linux/v4.19/drivers/net/phy/davicom.c
new file mode 100644
index 0000000..5ee99b3
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/davicom.c
@@ -0,0 +1,198 @@
+/*
+ * drivers/net/phy/davicom.c
+ *
+ * Driver for Davicom PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.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 <asm/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+#define MII_DM9161_SCR		0x10
+#define MII_DM9161_SCR_INIT	0x0610
+#define MII_DM9161_SCR_RMII	0x0100
+
+/* DM9161 Interrupt Register */
+#define MII_DM9161_INTR	0x15
+#define MII_DM9161_INTR_PEND		0x8000
+#define MII_DM9161_INTR_DPLX_MASK	0x0800
+#define MII_DM9161_INTR_SPD_MASK	0x0400
+#define MII_DM9161_INTR_LINK_MASK	0x0200
+#define MII_DM9161_INTR_MASK		0x0100
+#define MII_DM9161_INTR_DPLX_CHANGE	0x0010
+#define MII_DM9161_INTR_SPD_CHANGE	0x0008
+#define MII_DM9161_INTR_LINK_CHANGE	0x0004
+#define MII_DM9161_INTR_INIT 		0x0000
+#define MII_DM9161_INTR_STOP	\
+(MII_DM9161_INTR_DPLX_MASK | MII_DM9161_INTR_SPD_MASK \
+ | MII_DM9161_INTR_LINK_MASK | MII_DM9161_INTR_MASK)
+
+/* DM9161 10BT Configuration/Status */
+#define MII_DM9161_10BTCSR	0x12
+#define MII_DM9161_10BTCSR_INIT	0x7800
+
+MODULE_DESCRIPTION("Davicom PHY driver");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+
+#define DM9161_DELAY 1
+static int dm9161_config_intr(struct phy_device *phydev)
+{
+	int temp;
+
+	temp = phy_read(phydev, MII_DM9161_INTR);
+
+	if (temp < 0)
+		return temp;
+
+	if (PHY_INTERRUPT_ENABLED == phydev->interrupts)
+		temp &= ~(MII_DM9161_INTR_STOP);
+	else
+		temp |= MII_DM9161_INTR_STOP;
+
+	temp = phy_write(phydev, MII_DM9161_INTR, temp);
+
+	return temp;
+}
+
+static int dm9161_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	/* Isolate the PHY */
+	err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE);
+
+	if (err < 0)
+		return err;
+
+	/* Configure the new settings */
+	err = genphy_config_aneg(phydev);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int dm9161_config_init(struct phy_device *phydev)
+{
+	int err, temp;
+
+	/* Isolate the PHY */
+	err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE);
+
+	if (err < 0)
+		return err;
+
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_MII:
+		temp = MII_DM9161_SCR_INIT;
+		break;
+	case PHY_INTERFACE_MODE_RMII:
+		temp =  MII_DM9161_SCR_INIT | MII_DM9161_SCR_RMII;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Do not bypass the scrambler/descrambler */
+	err = phy_write(phydev, MII_DM9161_SCR, temp);
+	if (err < 0)
+		return err;
+
+	/* Clear 10BTCSR to default */
+	err = phy_write(phydev, MII_DM9161_10BTCSR, MII_DM9161_10BTCSR_INIT);
+
+	if (err < 0)
+		return err;
+
+	/* Reconnect the PHY, and enable Autonegotiation */
+	return phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+}
+
+static int dm9161_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, MII_DM9161_INTR);
+
+	return (err < 0) ? err : 0;
+}
+
+static struct phy_driver dm91xx_driver[] = {
+{
+	.phy_id		= 0x0181b880,
+	.name		= "Davicom DM9161E",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= dm9161_config_init,
+	.config_aneg	= dm9161_config_aneg,
+	.ack_interrupt	= dm9161_ack_interrupt,
+	.config_intr	= dm9161_config_intr,
+}, {
+	.phy_id		= 0x0181b8b0,
+	.name		= "Davicom DM9161B/C",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= dm9161_config_init,
+	.config_aneg	= dm9161_config_aneg,
+	.ack_interrupt	= dm9161_ack_interrupt,
+	.config_intr	= dm9161_config_intr,
+}, {
+	.phy_id		= 0x0181b8a0,
+	.name		= "Davicom DM9161A",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= dm9161_config_init,
+	.config_aneg	= dm9161_config_aneg,
+	.ack_interrupt	= dm9161_ack_interrupt,
+	.config_intr	= dm9161_config_intr,
+}, {
+	.phy_id		= 0x00181b80,
+	.name		= "Davicom DM9131",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.ack_interrupt	= dm9161_ack_interrupt,
+	.config_intr	= dm9161_config_intr,
+} };
+
+module_phy_driver(dm91xx_driver);
+
+static struct mdio_device_id __maybe_unused davicom_tbl[] = {
+	{ 0x0181b880, 0x0ffffff0 },
+	{ 0x0181b8b0, 0x0ffffff0 },
+	{ 0x0181b8a0, 0x0ffffff0 },
+	{ 0x00181b80, 0x0ffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, davicom_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/dp83640.c b/src/kernel/linux/v4.19/drivers/net/phy/dp83640.c
new file mode 100644
index 0000000..59b3f1f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/dp83640.c
@@ -0,0 +1,1565 @@
+/*
+ * Driver for the National Semiconductor DP83640 PHYTER
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/crc32.h>
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+
+#include "dp83640_reg.h"
+
+#define DP83640_PHY_ID	0x20005ce1
+#define PAGESEL		0x13
+#define MAX_RXTS	64
+#define N_EXT_TS	6
+#define N_PER_OUT	7
+#define PSF_PTPVER	2
+#define PSF_EVNT	0x4000
+#define PSF_RX		0x2000
+#define PSF_TX		0x1000
+#define EXT_EVENT	1
+#define CAL_EVENT	7
+#define CAL_TRIGGER	1
+#define DP83640_N_PINS	12
+
+#define MII_DP83640_MICR 0x11
+#define MII_DP83640_MISR 0x12
+
+#define MII_DP83640_MICR_OE 0x1
+#define MII_DP83640_MICR_IE 0x2
+
+#define MII_DP83640_MISR_RHF_INT_EN 0x01
+#define MII_DP83640_MISR_FHF_INT_EN 0x02
+#define MII_DP83640_MISR_ANC_INT_EN 0x04
+#define MII_DP83640_MISR_DUP_INT_EN 0x08
+#define MII_DP83640_MISR_SPD_INT_EN 0x10
+#define MII_DP83640_MISR_LINK_INT_EN 0x20
+#define MII_DP83640_MISR_ED_INT_EN 0x40
+#define MII_DP83640_MISR_LQ_INT_EN 0x80
+
+/* phyter seems to miss the mark by 16 ns */
+#define ADJTIME_FIX	16
+
+#define SKB_TIMESTAMP_TIMEOUT	2 /* jiffies */
+
+#if defined(__BIG_ENDIAN)
+#define ENDIAN_FLAG	0
+#elif defined(__LITTLE_ENDIAN)
+#define ENDIAN_FLAG	PSF_ENDIAN
+#endif
+
+struct dp83640_skb_info {
+	int ptp_type;
+	unsigned long tmo;
+};
+
+struct phy_rxts {
+	u16 ns_lo;   /* ns[15:0] */
+	u16 ns_hi;   /* overflow[1:0], ns[29:16] */
+	u16 sec_lo;  /* sec[15:0] */
+	u16 sec_hi;  /* sec[31:16] */
+	u16 seqid;   /* sequenceId[15:0] */
+	u16 msgtype; /* messageType[3:0], hash[11:0] */
+};
+
+struct phy_txts {
+	u16 ns_lo;   /* ns[15:0] */
+	u16 ns_hi;   /* overflow[1:0], ns[29:16] */
+	u16 sec_lo;  /* sec[15:0] */
+	u16 sec_hi;  /* sec[31:16] */
+};
+
+struct rxts {
+	struct list_head list;
+	unsigned long tmo;
+	u64 ns;
+	u16 seqid;
+	u8  msgtype;
+	u16 hash;
+};
+
+struct dp83640_clock;
+
+struct dp83640_private {
+	struct list_head list;
+	struct dp83640_clock *clock;
+	struct phy_device *phydev;
+	struct delayed_work ts_work;
+	int hwts_tx_en;
+	int hwts_rx_en;
+	int layer;
+	int version;
+	/* remember state of cfg0 during calibration */
+	int cfg0;
+	/* remember the last event time stamp */
+	struct phy_txts edata;
+	/* list of rx timestamps */
+	struct list_head rxts;
+	struct list_head rxpool;
+	struct rxts rx_pool_data[MAX_RXTS];
+	/* protects above three fields from concurrent access */
+	spinlock_t rx_lock;
+	/* queues of incoming and outgoing packets */
+	struct sk_buff_head rx_queue;
+	struct sk_buff_head tx_queue;
+};
+
+struct dp83640_clock {
+	/* keeps the instance in the 'phyter_clocks' list */
+	struct list_head list;
+	/* we create one clock instance per MII bus */
+	struct mii_bus *bus;
+	/* protects extended registers from concurrent access */
+	struct mutex extreg_lock;
+	/* remembers which page was last selected */
+	int page;
+	/* our advertised capabilities */
+	struct ptp_clock_info caps;
+	/* protects the three fields below from concurrent access */
+	struct mutex clock_lock;
+	/* the one phyter from which we shall read */
+	struct dp83640_private *chosen;
+	/* list of the other attached phyters, not chosen */
+	struct list_head phylist;
+	/* reference to our PTP hardware clock */
+	struct ptp_clock *ptp_clock;
+};
+
+/* globals */
+
+enum {
+	CALIBRATE_GPIO,
+	PEROUT_GPIO,
+	EXTTS0_GPIO,
+	EXTTS1_GPIO,
+	EXTTS2_GPIO,
+	EXTTS3_GPIO,
+	EXTTS4_GPIO,
+	EXTTS5_GPIO,
+	GPIO_TABLE_SIZE
+};
+
+static int chosen_phy = -1;
+static ushort gpio_tab[GPIO_TABLE_SIZE] = {
+	1, 2, 3, 4, 8, 9, 10, 11
+};
+
+module_param(chosen_phy, int, 0444);
+module_param_array(gpio_tab, ushort, NULL, 0444);
+
+MODULE_PARM_DESC(chosen_phy, \
+	"The address of the PHY to use for the ancillary clock features");
+MODULE_PARM_DESC(gpio_tab, \
+	"Which GPIO line to use for which purpose: cal,perout,extts1,...,extts6");
+
+static void dp83640_gpio_defaults(struct ptp_pin_desc *pd)
+{
+	int i, index;
+
+	for (i = 0; i < DP83640_N_PINS; i++) {
+		snprintf(pd[i].name, sizeof(pd[i].name), "GPIO%d", 1 + i);
+		pd[i].index = i;
+	}
+
+	for (i = 0; i < GPIO_TABLE_SIZE; i++) {
+		if (gpio_tab[i] < 1 || gpio_tab[i] > DP83640_N_PINS) {
+			pr_err("gpio_tab[%d]=%hu out of range", i, gpio_tab[i]);
+			return;
+		}
+	}
+
+	index = gpio_tab[CALIBRATE_GPIO] - 1;
+	pd[index].func = PTP_PF_PHYSYNC;
+	pd[index].chan = 0;
+
+	index = gpio_tab[PEROUT_GPIO] - 1;
+	pd[index].func = PTP_PF_PEROUT;
+	pd[index].chan = 0;
+
+	for (i = EXTTS0_GPIO; i < GPIO_TABLE_SIZE; i++) {
+		index = gpio_tab[i] - 1;
+		pd[index].func = PTP_PF_EXTTS;
+		pd[index].chan = i - EXTTS0_GPIO;
+	}
+}
+
+/* a list of clocks and a mutex to protect it */
+static LIST_HEAD(phyter_clocks);
+static DEFINE_MUTEX(phyter_clocks_lock);
+
+static void rx_timestamp_work(struct work_struct *work);
+
+/* extended register access functions */
+
+#define BROADCAST_ADDR 31
+
+static inline int broadcast_write(struct phy_device *phydev, u32 regnum,
+				  u16 val)
+{
+	return mdiobus_write(phydev->mdio.bus, BROADCAST_ADDR, regnum, val);
+}
+
+/* Caller must hold extreg_lock. */
+static int ext_read(struct phy_device *phydev, int page, u32 regnum)
+{
+	struct dp83640_private *dp83640 = phydev->priv;
+	int val;
+
+	if (dp83640->clock->page != page) {
+		broadcast_write(phydev, PAGESEL, page);
+		dp83640->clock->page = page;
+	}
+	val = phy_read(phydev, regnum);
+
+	return val;
+}
+
+/* Caller must hold extreg_lock. */
+static void ext_write(int broadcast, struct phy_device *phydev,
+		      int page, u32 regnum, u16 val)
+{
+	struct dp83640_private *dp83640 = phydev->priv;
+
+	if (dp83640->clock->page != page) {
+		broadcast_write(phydev, PAGESEL, page);
+		dp83640->clock->page = page;
+	}
+	if (broadcast)
+		broadcast_write(phydev, regnum, val);
+	else
+		phy_write(phydev, regnum, val);
+}
+
+/* Caller must hold extreg_lock. */
+static int tdr_write(int bc, struct phy_device *dev,
+		     const struct timespec64 *ts, u16 cmd)
+{
+	ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_nsec & 0xffff);/* ns[15:0]  */
+	ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_nsec >> 16);   /* ns[31:16] */
+	ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_sec & 0xffff); /* sec[15:0] */
+	ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_sec >> 16);    /* sec[31:16]*/
+
+	ext_write(bc, dev, PAGE4, PTP_CTL, cmd);
+
+	return 0;
+}
+
+/* convert phy timestamps into driver timestamps */
+
+static void phy2rxts(struct phy_rxts *p, struct rxts *rxts)
+{
+	u32 sec;
+
+	sec = p->sec_lo;
+	sec |= p->sec_hi << 16;
+
+	rxts->ns = p->ns_lo;
+	rxts->ns |= (p->ns_hi & 0x3fff) << 16;
+	rxts->ns += ((u64)sec) * 1000000000ULL;
+	rxts->seqid = p->seqid;
+	rxts->msgtype = (p->msgtype >> 12) & 0xf;
+	rxts->hash = p->msgtype & 0x0fff;
+	rxts->tmo = jiffies + SKB_TIMESTAMP_TIMEOUT;
+}
+
+static u64 phy2txts(struct phy_txts *p)
+{
+	u64 ns;
+	u32 sec;
+
+	sec = p->sec_lo;
+	sec |= p->sec_hi << 16;
+
+	ns = p->ns_lo;
+	ns |= (p->ns_hi & 0x3fff) << 16;
+	ns += ((u64)sec) * 1000000000ULL;
+
+	return ns;
+}
+
+static int periodic_output(struct dp83640_clock *clock,
+			   struct ptp_clock_request *clkreq, bool on,
+			   int trigger)
+{
+	struct dp83640_private *dp83640 = clock->chosen;
+	struct phy_device *phydev = dp83640->phydev;
+	u32 sec, nsec, pwidth;
+	u16 gpio, ptp_trig, val;
+
+	if (on) {
+		gpio = 1 + ptp_find_pin(clock->ptp_clock, PTP_PF_PEROUT,
+					trigger);
+		if (gpio < 1)
+			return -EINVAL;
+	} else {
+		gpio = 0;
+	}
+
+	ptp_trig = TRIG_WR |
+		(trigger & TRIG_CSEL_MASK) << TRIG_CSEL_SHIFT |
+		(gpio & TRIG_GPIO_MASK) << TRIG_GPIO_SHIFT |
+		TRIG_PER |
+		TRIG_PULSE;
+
+	val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT;
+
+	if (!on) {
+		val |= TRIG_DIS;
+		mutex_lock(&clock->extreg_lock);
+		ext_write(0, phydev, PAGE5, PTP_TRIG, ptp_trig);
+		ext_write(0, phydev, PAGE4, PTP_CTL, val);
+		mutex_unlock(&clock->extreg_lock);
+		return 0;
+	}
+
+	sec = clkreq->perout.start.sec;
+	nsec = clkreq->perout.start.nsec;
+	pwidth = clkreq->perout.period.sec * 1000000000UL;
+	pwidth += clkreq->perout.period.nsec;
+	pwidth /= 2;
+
+	mutex_lock(&clock->extreg_lock);
+
+	ext_write(0, phydev, PAGE5, PTP_TRIG, ptp_trig);
+
+	/*load trigger*/
+	val |= TRIG_LOAD;
+	ext_write(0, phydev, PAGE4, PTP_CTL, val);
+	ext_write(0, phydev, PAGE4, PTP_TDR, nsec & 0xffff);   /* ns[15:0] */
+	ext_write(0, phydev, PAGE4, PTP_TDR, nsec >> 16);      /* ns[31:16] */
+	ext_write(0, phydev, PAGE4, PTP_TDR, sec & 0xffff);    /* sec[15:0] */
+	ext_write(0, phydev, PAGE4, PTP_TDR, sec >> 16);       /* sec[31:16] */
+	ext_write(0, phydev, PAGE4, PTP_TDR, pwidth & 0xffff); /* ns[15:0] */
+	ext_write(0, phydev, PAGE4, PTP_TDR, pwidth >> 16);    /* ns[31:16] */
+	/* Triggers 0 and 1 has programmable pulsewidth2 */
+	if (trigger < 2) {
+		ext_write(0, phydev, PAGE4, PTP_TDR, pwidth & 0xffff);
+		ext_write(0, phydev, PAGE4, PTP_TDR, pwidth >> 16);
+	}
+
+	/*enable trigger*/
+	val &= ~TRIG_LOAD;
+	val |= TRIG_EN;
+	ext_write(0, phydev, PAGE4, PTP_CTL, val);
+
+	mutex_unlock(&clock->extreg_lock);
+	return 0;
+}
+
+/* ptp clock methods */
+
+static int ptp_dp83640_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+	struct dp83640_clock *clock =
+		container_of(ptp, struct dp83640_clock, caps);
+	struct phy_device *phydev = clock->chosen->phydev;
+	u64 rate;
+	int neg_adj = 0;
+	u16 hi, lo;
+
+	if (scaled_ppm < 0) {
+		neg_adj = 1;
+		scaled_ppm = -scaled_ppm;
+	}
+	rate = scaled_ppm;
+	rate <<= 13;
+	rate = div_u64(rate, 15625);
+
+	hi = (rate >> 16) & PTP_RATE_HI_MASK;
+	if (neg_adj)
+		hi |= PTP_RATE_DIR;
+
+	lo = rate & 0xffff;
+
+	mutex_lock(&clock->extreg_lock);
+
+	ext_write(1, phydev, PAGE4, PTP_RATEH, hi);
+	ext_write(1, phydev, PAGE4, PTP_RATEL, lo);
+
+	mutex_unlock(&clock->extreg_lock);
+
+	return 0;
+}
+
+static int ptp_dp83640_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+	struct dp83640_clock *clock =
+		container_of(ptp, struct dp83640_clock, caps);
+	struct phy_device *phydev = clock->chosen->phydev;
+	struct timespec64 ts;
+	int err;
+
+	delta += ADJTIME_FIX;
+
+	ts = ns_to_timespec64(delta);
+
+	mutex_lock(&clock->extreg_lock);
+
+	err = tdr_write(1, phydev, &ts, PTP_STEP_CLK);
+
+	mutex_unlock(&clock->extreg_lock);
+
+	return err;
+}
+
+static int ptp_dp83640_gettime(struct ptp_clock_info *ptp,
+			       struct timespec64 *ts)
+{
+	struct dp83640_clock *clock =
+		container_of(ptp, struct dp83640_clock, caps);
+	struct phy_device *phydev = clock->chosen->phydev;
+	unsigned int val[4];
+
+	mutex_lock(&clock->extreg_lock);
+
+	ext_write(0, phydev, PAGE4, PTP_CTL, PTP_RD_CLK);
+
+	val[0] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[15:0] */
+	val[1] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[31:16] */
+	val[2] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[15:0] */
+	val[3] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[31:16] */
+
+	mutex_unlock(&clock->extreg_lock);
+
+	ts->tv_nsec = val[0] | (val[1] << 16);
+	ts->tv_sec  = val[2] | (val[3] << 16);
+
+	return 0;
+}
+
+static int ptp_dp83640_settime(struct ptp_clock_info *ptp,
+			       const struct timespec64 *ts)
+{
+	struct dp83640_clock *clock =
+		container_of(ptp, struct dp83640_clock, caps);
+	struct phy_device *phydev = clock->chosen->phydev;
+	int err;
+
+	mutex_lock(&clock->extreg_lock);
+
+	err = tdr_write(1, phydev, ts, PTP_LOAD_CLK);
+
+	mutex_unlock(&clock->extreg_lock);
+
+	return err;
+}
+
+static int ptp_dp83640_enable(struct ptp_clock_info *ptp,
+			      struct ptp_clock_request *rq, int on)
+{
+	struct dp83640_clock *clock =
+		container_of(ptp, struct dp83640_clock, caps);
+	struct phy_device *phydev = clock->chosen->phydev;
+	unsigned int index;
+	u16 evnt, event_num, gpio_num;
+
+	switch (rq->type) {
+	case PTP_CLK_REQ_EXTTS:
+		index = rq->extts.index;
+		if (index >= N_EXT_TS)
+			return -EINVAL;
+		event_num = EXT_EVENT + index;
+		evnt = EVNT_WR | (event_num & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+		if (on) {
+			gpio_num = 1 + ptp_find_pin(clock->ptp_clock,
+						    PTP_PF_EXTTS, index);
+			if (gpio_num < 1)
+				return -EINVAL;
+			evnt |= (gpio_num & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+			if (rq->extts.flags & PTP_FALLING_EDGE)
+				evnt |= EVNT_FALL;
+			else
+				evnt |= EVNT_RISE;
+		}
+		mutex_lock(&clock->extreg_lock);
+		ext_write(0, phydev, PAGE5, PTP_EVNT, evnt);
+		mutex_unlock(&clock->extreg_lock);
+		return 0;
+
+	case PTP_CLK_REQ_PEROUT:
+		if (rq->perout.index >= N_PER_OUT)
+			return -EINVAL;
+		return periodic_output(clock, rq, on, rq->perout.index);
+
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_verify(struct ptp_clock_info *ptp, unsigned int pin,
+			      enum ptp_pin_function func, unsigned int chan)
+{
+	struct dp83640_clock *clock =
+		container_of(ptp, struct dp83640_clock, caps);
+
+	if (clock->caps.pin_config[pin].func == PTP_PF_PHYSYNC &&
+	    !list_empty(&clock->phylist))
+		return 1;
+
+	if (func == PTP_PF_PHYSYNC)
+		return 1;
+
+	return 0;
+}
+
+static u8 status_frame_dst[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 };
+static u8 status_frame_src[6] = { 0x08, 0x00, 0x17, 0x0B, 0x6B, 0x0F };
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+	struct dp83640_private *dp83640 = phydev->priv;
+	struct dp83640_clock *clock = dp83640->clock;
+	u16 cfg0 = 0, ver;
+
+	if (on)
+		cfg0 = PSF_EVNT_EN | PSF_RXTS_EN | PSF_TXTS_EN | ENDIAN_FLAG;
+
+	ver = (PSF_PTPVER & VERSIONPTP_MASK) << VERSIONPTP_SHIFT;
+
+	mutex_lock(&clock->extreg_lock);
+
+	ext_write(0, phydev, PAGE5, PSF_CFG0, cfg0);
+	ext_write(0, phydev, PAGE6, PSF_CFG1, ver);
+
+	mutex_unlock(&clock->extreg_lock);
+
+	if (!phydev->attached_dev) {
+		pr_warn("expected to find an attached netdevice\n");
+		return;
+	}
+
+	if (on) {
+		if (dev_mc_add(phydev->attached_dev, status_frame_dst))
+			pr_warn("failed to add mc address\n");
+	} else {
+		if (dev_mc_del(phydev->attached_dev, status_frame_dst))
+			pr_warn("failed to delete mc address\n");
+	}
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+	struct ethhdr *h = eth_hdr(skb);
+
+	if (PTP_CLASS_V2_L2 == type &&
+	    !memcmp(h->h_source, status_frame_src, sizeof(status_frame_src)))
+		return true;
+	else
+		return false;
+}
+
+static int expired(struct rxts *rxts)
+{
+	return time_after(jiffies, rxts->tmo);
+}
+
+/* Caller must hold rx_lock. */
+static void prune_rx_ts(struct dp83640_private *dp83640)
+{
+	struct list_head *this, *next;
+	struct rxts *rxts;
+
+	list_for_each_safe(this, next, &dp83640->rxts) {
+		rxts = list_entry(this, struct rxts, list);
+		if (expired(rxts)) {
+			list_del_init(&rxts->list);
+			list_add(&rxts->list, &dp83640->rxpool);
+		}
+	}
+}
+
+/* synchronize the phyters so they act as one clock */
+
+static void enable_broadcast(struct phy_device *phydev, int init_page, int on)
+{
+	int val;
+	phy_write(phydev, PAGESEL, 0);
+	val = phy_read(phydev, PHYCR2);
+	if (on)
+		val |= BC_WRITE;
+	else
+		val &= ~BC_WRITE;
+	phy_write(phydev, PHYCR2, val);
+	phy_write(phydev, PAGESEL, init_page);
+}
+
+static void recalibrate(struct dp83640_clock *clock)
+{
+	s64 now, diff;
+	struct phy_txts event_ts;
+	struct timespec64 ts;
+	struct list_head *this;
+	struct dp83640_private *tmp;
+	struct phy_device *master = clock->chosen->phydev;
+	u16 cal_gpio, cfg0, evnt, ptp_trig, trigger, val;
+
+	trigger = CAL_TRIGGER;
+	cal_gpio = 1 + ptp_find_pin(clock->ptp_clock, PTP_PF_PHYSYNC, 0);
+	if (cal_gpio < 1) {
+		pr_err("PHY calibration pin not available - PHY is not calibrated.");
+		return;
+	}
+
+	mutex_lock(&clock->extreg_lock);
+
+	/*
+	 * enable broadcast, disable status frames, enable ptp clock
+	 */
+	list_for_each(this, &clock->phylist) {
+		tmp = list_entry(this, struct dp83640_private, list);
+		enable_broadcast(tmp->phydev, clock->page, 1);
+		tmp->cfg0 = ext_read(tmp->phydev, PAGE5, PSF_CFG0);
+		ext_write(0, tmp->phydev, PAGE5, PSF_CFG0, 0);
+		ext_write(0, tmp->phydev, PAGE4, PTP_CTL, PTP_ENABLE);
+	}
+	enable_broadcast(master, clock->page, 1);
+	cfg0 = ext_read(master, PAGE5, PSF_CFG0);
+	ext_write(0, master, PAGE5, PSF_CFG0, 0);
+	ext_write(0, master, PAGE4, PTP_CTL, PTP_ENABLE);
+
+	/*
+	 * enable an event timestamp
+	 */
+	evnt = EVNT_WR | EVNT_RISE | EVNT_SINGLE;
+	evnt |= (CAL_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+	evnt |= (cal_gpio & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+
+	list_for_each(this, &clock->phylist) {
+		tmp = list_entry(this, struct dp83640_private, list);
+		ext_write(0, tmp->phydev, PAGE5, PTP_EVNT, evnt);
+	}
+	ext_write(0, master, PAGE5, PTP_EVNT, evnt);
+
+	/*
+	 * configure a trigger
+	 */
+	ptp_trig = TRIG_WR | TRIG_IF_LATE | TRIG_PULSE;
+	ptp_trig |= (trigger  & TRIG_CSEL_MASK) << TRIG_CSEL_SHIFT;
+	ptp_trig |= (cal_gpio & TRIG_GPIO_MASK) << TRIG_GPIO_SHIFT;
+	ext_write(0, master, PAGE5, PTP_TRIG, ptp_trig);
+
+	/* load trigger */
+	val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT;
+	val |= TRIG_LOAD;
+	ext_write(0, master, PAGE4, PTP_CTL, val);
+
+	/* enable trigger */
+	val &= ~TRIG_LOAD;
+	val |= TRIG_EN;
+	ext_write(0, master, PAGE4, PTP_CTL, val);
+
+	/* disable trigger */
+	val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT;
+	val |= TRIG_DIS;
+	ext_write(0, master, PAGE4, PTP_CTL, val);
+
+	/*
+	 * read out and correct offsets
+	 */
+	val = ext_read(master, PAGE4, PTP_STS);
+	pr_info("master PTP_STS  0x%04hx\n", val);
+	val = ext_read(master, PAGE4, PTP_ESTS);
+	pr_info("master PTP_ESTS 0x%04hx\n", val);
+	event_ts.ns_lo  = ext_read(master, PAGE4, PTP_EDATA);
+	event_ts.ns_hi  = ext_read(master, PAGE4, PTP_EDATA);
+	event_ts.sec_lo = ext_read(master, PAGE4, PTP_EDATA);
+	event_ts.sec_hi = ext_read(master, PAGE4, PTP_EDATA);
+	now = phy2txts(&event_ts);
+
+	list_for_each(this, &clock->phylist) {
+		tmp = list_entry(this, struct dp83640_private, list);
+		val = ext_read(tmp->phydev, PAGE4, PTP_STS);
+		pr_info("slave  PTP_STS  0x%04hx\n", val);
+		val = ext_read(tmp->phydev, PAGE4, PTP_ESTS);
+		pr_info("slave  PTP_ESTS 0x%04hx\n", val);
+		event_ts.ns_lo  = ext_read(tmp->phydev, PAGE4, PTP_EDATA);
+		event_ts.ns_hi  = ext_read(tmp->phydev, PAGE4, PTP_EDATA);
+		event_ts.sec_lo = ext_read(tmp->phydev, PAGE4, PTP_EDATA);
+		event_ts.sec_hi = ext_read(tmp->phydev, PAGE4, PTP_EDATA);
+		diff = now - (s64) phy2txts(&event_ts);
+		pr_info("slave offset %lld nanoseconds\n", diff);
+		diff += ADJTIME_FIX;
+		ts = ns_to_timespec64(diff);
+		tdr_write(0, tmp->phydev, &ts, PTP_STEP_CLK);
+	}
+
+	/*
+	 * restore status frames
+	 */
+	list_for_each(this, &clock->phylist) {
+		tmp = list_entry(this, struct dp83640_private, list);
+		ext_write(0, tmp->phydev, PAGE5, PSF_CFG0, tmp->cfg0);
+	}
+	ext_write(0, master, PAGE5, PSF_CFG0, cfg0);
+
+	mutex_unlock(&clock->extreg_lock);
+}
+
+/* time stamping methods */
+
+static inline u16 exts_chan_to_edata(int ch)
+{
+	return 1 << ((ch + EXT_EVENT) * 2);
+}
+
+static int decode_evnt(struct dp83640_private *dp83640,
+		       void *data, int len, u16 ests)
+{
+	struct phy_txts *phy_txts;
+	struct ptp_clock_event event;
+	int i, parsed;
+	int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK;
+	u16 ext_status = 0;
+
+	/* calculate length of the event timestamp status message */
+	if (ests & MULT_EVNT)
+		parsed = (words + 2) * sizeof(u16);
+	else
+		parsed = (words + 1) * sizeof(u16);
+
+	/* check if enough data is available */
+	if (len < parsed)
+		return len;
+
+	if (ests & MULT_EVNT) {
+		ext_status = *(u16 *) data;
+		data += sizeof(ext_status);
+	}
+
+	phy_txts = data;
+
+	switch (words) {
+	case 3:
+		dp83640->edata.sec_hi = phy_txts->sec_hi;
+		/* fall through */
+	case 2:
+		dp83640->edata.sec_lo = phy_txts->sec_lo;
+		/* fall through */
+	case 1:
+		dp83640->edata.ns_hi = phy_txts->ns_hi;
+		/* fall through */
+	case 0:
+		dp83640->edata.ns_lo = phy_txts->ns_lo;
+	}
+
+	if (!ext_status) {
+		i = ((ests >> EVNT_NUM_SHIFT) & EVNT_NUM_MASK) - EXT_EVENT;
+		ext_status = exts_chan_to_edata(i);
+	}
+
+	event.type = PTP_CLOCK_EXTTS;
+	event.timestamp = phy2txts(&dp83640->edata);
+
+	/* Compensate for input path and synchronization delays */
+	event.timestamp -= 35;
+
+	for (i = 0; i < N_EXT_TS; i++) {
+		if (ext_status & exts_chan_to_edata(i)) {
+			event.index = i;
+			ptp_clock_event(dp83640->clock->ptp_clock, &event);
+		}
+	}
+
+	return parsed;
+}
+
+#define DP83640_PACKET_HASH_OFFSET	20
+#define DP83640_PACKET_HASH_LEN		10
+
+static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
+{
+	u16 *seqid, hash;
+	unsigned int offset = 0;
+	u8 *msgtype, *data = skb_mac_header(skb);
+
+	/* check sequenceID, messageType, 12 bit hash of offset 20-29 */
+
+	if (type & PTP_CLASS_VLAN)
+		offset += VLAN_HLEN;
+
+	switch (type & PTP_CLASS_PMASK) {
+	case PTP_CLASS_IPV4:
+		offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN;
+		break;
+	case PTP_CLASS_IPV6:
+		offset += ETH_HLEN + IP6_HLEN + UDP_HLEN;
+		break;
+	case PTP_CLASS_L2:
+		offset += ETH_HLEN;
+		break;
+	default:
+		return 0;
+	}
+
+	if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid))
+		return 0;
+
+	if (unlikely(type & PTP_CLASS_V1))
+		msgtype = data + offset + OFF_PTP_CONTROL;
+	else
+		msgtype = data + offset;
+	if (rxts->msgtype != (*msgtype & 0xf))
+		return 0;
+
+	seqid = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID);
+	if (rxts->seqid != ntohs(*seqid))
+		return 0;
+
+	hash = ether_crc(DP83640_PACKET_HASH_LEN,
+			 data + offset + DP83640_PACKET_HASH_OFFSET) >> 20;
+	if (rxts->hash != hash)
+		return 0;
+
+	return 1;
+}
+
+static void decode_rxts(struct dp83640_private *dp83640,
+			struct phy_rxts *phy_rxts)
+{
+	struct rxts *rxts;
+	struct skb_shared_hwtstamps *shhwtstamps = NULL;
+	struct sk_buff *skb;
+	unsigned long flags;
+	u8 overflow;
+
+	overflow = (phy_rxts->ns_hi >> 14) & 0x3;
+	if (overflow)
+		pr_debug("rx timestamp queue overflow, count %d\n", overflow);
+
+	spin_lock_irqsave(&dp83640->rx_lock, flags);
+
+	prune_rx_ts(dp83640);
+
+	if (list_empty(&dp83640->rxpool)) {
+		pr_debug("rx timestamp pool is empty\n");
+		goto out;
+	}
+	rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+	list_del_init(&rxts->list);
+	phy2rxts(phy_rxts, rxts);
+
+	spin_lock(&dp83640->rx_queue.lock);
+	skb_queue_walk(&dp83640->rx_queue, skb) {
+		struct dp83640_skb_info *skb_info;
+
+		skb_info = (struct dp83640_skb_info *)skb->cb;
+		if (match(skb, skb_info->ptp_type, rxts)) {
+			__skb_unlink(skb, &dp83640->rx_queue);
+			shhwtstamps = skb_hwtstamps(skb);
+			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+			shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns);
+			list_add(&rxts->list, &dp83640->rxpool);
+			break;
+		}
+	}
+	spin_unlock(&dp83640->rx_queue.lock);
+
+	if (!shhwtstamps)
+		list_add_tail(&rxts->list, &dp83640->rxts);
+out:
+	spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+
+	if (shhwtstamps)
+		netif_rx_ni(skb);
+}
+
+static void decode_txts(struct dp83640_private *dp83640,
+			struct phy_txts *phy_txts)
+{
+	struct skb_shared_hwtstamps shhwtstamps;
+	struct dp83640_skb_info *skb_info;
+	struct sk_buff *skb;
+	u8 overflow;
+	u64 ns;
+
+	/* We must already have the skb that triggered this. */
+again:
+	skb = skb_dequeue(&dp83640->tx_queue);
+	if (!skb) {
+		pr_debug("have timestamp but tx_queue empty\n");
+		return;
+	}
+
+	overflow = (phy_txts->ns_hi >> 14) & 0x3;
+	if (overflow) {
+		pr_debug("tx timestamp queue overflow, count %d\n", overflow);
+		while (skb) {
+			kfree_skb(skb);
+			skb = skb_dequeue(&dp83640->tx_queue);
+		}
+		return;
+	}
+	skb_info = (struct dp83640_skb_info *)skb->cb;
+	if (time_after(jiffies, skb_info->tmo)) {
+		kfree_skb(skb);
+		goto again;
+	}
+
+	ns = phy2txts(phy_txts);
+	memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+	shhwtstamps.hwtstamp = ns_to_ktime(ns);
+	skb_complete_tx_timestamp(skb, &shhwtstamps);
+}
+
+static void decode_status_frame(struct dp83640_private *dp83640,
+				struct sk_buff *skb)
+{
+	struct phy_rxts *phy_rxts;
+	struct phy_txts *phy_txts;
+	u8 *ptr;
+	int len, size;
+	u16 ests, type;
+
+	ptr = skb->data + 2;
+
+	for (len = skb_headlen(skb) - 2; len > sizeof(type); len -= size) {
+
+		type = *(u16 *)ptr;
+		ests = type & 0x0fff;
+		type = type & 0xf000;
+		len -= sizeof(type);
+		ptr += sizeof(type);
+
+		if (PSF_RX == type && len >= sizeof(*phy_rxts)) {
+
+			phy_rxts = (struct phy_rxts *) ptr;
+			decode_rxts(dp83640, phy_rxts);
+			size = sizeof(*phy_rxts);
+
+		} else if (PSF_TX == type && len >= sizeof(*phy_txts)) {
+
+			phy_txts = (struct phy_txts *) ptr;
+			decode_txts(dp83640, phy_txts);
+			size = sizeof(*phy_txts);
+
+		} else if (PSF_EVNT == type) {
+
+			size = decode_evnt(dp83640, ptr, len, ests);
+
+		} else {
+			size = 0;
+			break;
+		}
+		ptr += size;
+	}
+}
+
+static int is_sync(struct sk_buff *skb, int type)
+{
+	u8 *data = skb->data, *msgtype;
+	unsigned int offset = 0;
+
+	if (type & PTP_CLASS_VLAN)
+		offset += VLAN_HLEN;
+
+	switch (type & PTP_CLASS_PMASK) {
+	case PTP_CLASS_IPV4:
+		offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN;
+		break;
+	case PTP_CLASS_IPV6:
+		offset += ETH_HLEN + IP6_HLEN + UDP_HLEN;
+		break;
+	case PTP_CLASS_L2:
+		offset += ETH_HLEN;
+		break;
+	default:
+		return 0;
+	}
+
+	if (type & PTP_CLASS_V1)
+		offset += OFF_PTP_CONTROL;
+
+	if (skb->len < offset + 1)
+		return 0;
+
+	msgtype = data + offset;
+
+	return (*msgtype & 0xf) == 0;
+}
+
+static void dp83640_free_clocks(void)
+{
+	struct dp83640_clock *clock;
+	struct list_head *this, *next;
+
+	mutex_lock(&phyter_clocks_lock);
+
+	list_for_each_safe(this, next, &phyter_clocks) {
+		clock = list_entry(this, struct dp83640_clock, list);
+		if (!list_empty(&clock->phylist)) {
+			pr_warn("phy list non-empty while unloading\n");
+			BUG();
+		}
+		list_del(&clock->list);
+		mutex_destroy(&clock->extreg_lock);
+		mutex_destroy(&clock->clock_lock);
+		put_device(&clock->bus->dev);
+		kfree(clock->caps.pin_config);
+		kfree(clock);
+	}
+
+	mutex_unlock(&phyter_clocks_lock);
+}
+
+static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus)
+{
+	INIT_LIST_HEAD(&clock->list);
+	clock->bus = bus;
+	mutex_init(&clock->extreg_lock);
+	mutex_init(&clock->clock_lock);
+	INIT_LIST_HEAD(&clock->phylist);
+	clock->caps.owner = THIS_MODULE;
+	sprintf(clock->caps.name, "dp83640 timer");
+	clock->caps.max_adj	= 1953124;
+	clock->caps.n_alarm	= 0;
+	clock->caps.n_ext_ts	= N_EXT_TS;
+	clock->caps.n_per_out	= N_PER_OUT;
+	clock->caps.n_pins	= DP83640_N_PINS;
+	clock->caps.pps		= 0;
+	clock->caps.adjfine	= ptp_dp83640_adjfine;
+	clock->caps.adjtime	= ptp_dp83640_adjtime;
+	clock->caps.gettime64	= ptp_dp83640_gettime;
+	clock->caps.settime64	= ptp_dp83640_settime;
+	clock->caps.enable	= ptp_dp83640_enable;
+	clock->caps.verify	= ptp_dp83640_verify;
+	/*
+	 * Convert the module param defaults into a dynamic pin configuration.
+	 */
+	dp83640_gpio_defaults(clock->caps.pin_config);
+	/*
+	 * Get a reference to this bus instance.
+	 */
+	get_device(&bus->dev);
+}
+
+static int choose_this_phy(struct dp83640_clock *clock,
+			   struct phy_device *phydev)
+{
+	if (chosen_phy == -1 && !clock->chosen)
+		return 1;
+
+	if (chosen_phy == phydev->mdio.addr)
+		return 1;
+
+	return 0;
+}
+
+static struct dp83640_clock *dp83640_clock_get(struct dp83640_clock *clock)
+{
+	if (clock)
+		mutex_lock(&clock->clock_lock);
+	return clock;
+}
+
+/*
+ * Look up and lock a clock by bus instance.
+ * If there is no clock for this bus, then create it first.
+ */
+static struct dp83640_clock *dp83640_clock_get_bus(struct mii_bus *bus)
+{
+	struct dp83640_clock *clock = NULL, *tmp;
+	struct list_head *this;
+
+	mutex_lock(&phyter_clocks_lock);
+
+	list_for_each(this, &phyter_clocks) {
+		tmp = list_entry(this, struct dp83640_clock, list);
+		if (tmp->bus == bus) {
+			clock = tmp;
+			break;
+		}
+	}
+	if (clock)
+		goto out;
+
+	clock = kzalloc(sizeof(struct dp83640_clock), GFP_KERNEL);
+	if (!clock)
+		goto out;
+
+	clock->caps.pin_config = kcalloc(DP83640_N_PINS,
+					 sizeof(struct ptp_pin_desc),
+					 GFP_KERNEL);
+	if (!clock->caps.pin_config) {
+		kfree(clock);
+		clock = NULL;
+		goto out;
+	}
+	dp83640_clock_init(clock, bus);
+	list_add_tail(&phyter_clocks, &clock->list);
+out:
+	mutex_unlock(&phyter_clocks_lock);
+
+	return dp83640_clock_get(clock);
+}
+
+static void dp83640_clock_put(struct dp83640_clock *clock)
+{
+	mutex_unlock(&clock->clock_lock);
+}
+
+static int dp83640_probe(struct phy_device *phydev)
+{
+	struct dp83640_clock *clock;
+	struct dp83640_private *dp83640;
+	int err = -ENOMEM, i;
+
+	if (phydev->mdio.addr == BROADCAST_ADDR)
+		return 0;
+
+	clock = dp83640_clock_get_bus(phydev->mdio.bus);
+	if (!clock)
+		goto no_clock;
+
+	dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
+	if (!dp83640)
+		goto no_memory;
+
+	dp83640->phydev = phydev;
+	INIT_DELAYED_WORK(&dp83640->ts_work, rx_timestamp_work);
+
+	INIT_LIST_HEAD(&dp83640->rxts);
+	INIT_LIST_HEAD(&dp83640->rxpool);
+	for (i = 0; i < MAX_RXTS; i++)
+		list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
+
+	phydev->priv = dp83640;
+
+	spin_lock_init(&dp83640->rx_lock);
+	skb_queue_head_init(&dp83640->rx_queue);
+	skb_queue_head_init(&dp83640->tx_queue);
+
+	dp83640->clock = clock;
+
+	if (choose_this_phy(clock, phydev)) {
+		clock->chosen = dp83640;
+		clock->ptp_clock = ptp_clock_register(&clock->caps,
+						      &phydev->mdio.dev);
+		if (IS_ERR(clock->ptp_clock)) {
+			err = PTR_ERR(clock->ptp_clock);
+			goto no_register;
+		}
+	} else
+		list_add_tail(&dp83640->list, &clock->phylist);
+
+	dp83640_clock_put(clock);
+	return 0;
+
+no_register:
+	clock->chosen = NULL;
+	kfree(dp83640);
+no_memory:
+	dp83640_clock_put(clock);
+no_clock:
+	return err;
+}
+
+static void dp83640_remove(struct phy_device *phydev)
+{
+	struct dp83640_clock *clock;
+	struct list_head *this, *next;
+	struct dp83640_private *tmp, *dp83640 = phydev->priv;
+
+	if (phydev->mdio.addr == BROADCAST_ADDR)
+		return;
+
+	enable_status_frames(phydev, false);
+	cancel_delayed_work_sync(&dp83640->ts_work);
+
+	skb_queue_purge(&dp83640->rx_queue);
+	skb_queue_purge(&dp83640->tx_queue);
+
+	clock = dp83640_clock_get(dp83640->clock);
+
+	if (dp83640 == clock->chosen) {
+		ptp_clock_unregister(clock->ptp_clock);
+		clock->chosen = NULL;
+	} else {
+		list_for_each_safe(this, next, &clock->phylist) {
+			tmp = list_entry(this, struct dp83640_private, list);
+			if (tmp == dp83640) {
+				list_del_init(&tmp->list);
+				break;
+			}
+		}
+	}
+
+	dp83640_clock_put(clock);
+	kfree(dp83640);
+}
+
+static int dp83640_soft_reset(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_soft_reset(phydev);
+	if (ret < 0)
+		return ret;
+
+	/* From DP83640 datasheet: "Software driver code must wait 3 us
+	 * following a software reset before allowing further serial MII
+	 * operations with the DP83640."
+	 */
+	udelay(10);		/* Taking udelay inaccuracy into account */
+
+	return 0;
+}
+
+static int dp83640_config_init(struct phy_device *phydev)
+{
+	struct dp83640_private *dp83640 = phydev->priv;
+	struct dp83640_clock *clock = dp83640->clock;
+
+	if (clock->chosen && !list_empty(&clock->phylist))
+		recalibrate(clock);
+	else {
+		mutex_lock(&clock->extreg_lock);
+		enable_broadcast(phydev, clock->page, 1);
+		mutex_unlock(&clock->extreg_lock);
+	}
+
+	enable_status_frames(phydev, true);
+
+	mutex_lock(&clock->extreg_lock);
+	ext_write(0, phydev, PAGE4, PTP_CTL, PTP_ENABLE);
+	mutex_unlock(&clock->extreg_lock);
+
+	return 0;
+}
+
+static int dp83640_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, MII_DP83640_MISR);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int dp83640_config_intr(struct phy_device *phydev)
+{
+	int micr;
+	int misr;
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		misr = phy_read(phydev, MII_DP83640_MISR);
+		if (misr < 0)
+			return misr;
+		misr |=
+			(MII_DP83640_MISR_ANC_INT_EN |
+			MII_DP83640_MISR_DUP_INT_EN |
+			MII_DP83640_MISR_SPD_INT_EN |
+			MII_DP83640_MISR_LINK_INT_EN);
+		err = phy_write(phydev, MII_DP83640_MISR, misr);
+		if (err < 0)
+			return err;
+
+		micr = phy_read(phydev, MII_DP83640_MICR);
+		if (micr < 0)
+			return micr;
+		micr |=
+			(MII_DP83640_MICR_OE |
+			MII_DP83640_MICR_IE);
+		return phy_write(phydev, MII_DP83640_MICR, micr);
+	} else {
+		micr = phy_read(phydev, MII_DP83640_MICR);
+		if (micr < 0)
+			return micr;
+		micr &=
+			~(MII_DP83640_MICR_OE |
+			MII_DP83640_MICR_IE);
+		err = phy_write(phydev, MII_DP83640_MICR, micr);
+		if (err < 0)
+			return err;
+
+		misr = phy_read(phydev, MII_DP83640_MISR);
+		if (misr < 0)
+			return misr;
+		misr &=
+			~(MII_DP83640_MISR_ANC_INT_EN |
+			MII_DP83640_MISR_DUP_INT_EN |
+			MII_DP83640_MISR_SPD_INT_EN |
+			MII_DP83640_MISR_LINK_INT_EN);
+		return phy_write(phydev, MII_DP83640_MISR, misr);
+	}
+}
+
+static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
+{
+	struct dp83640_private *dp83640 = phydev->priv;
+	struct hwtstamp_config cfg;
+	u16 txcfg0, rxcfg0;
+
+	if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+		return -EFAULT;
+
+	if (cfg.flags) /* reserved for future extensions */
+		return -EINVAL;
+
+	if (cfg.tx_type < 0 || cfg.tx_type > HWTSTAMP_TX_ONESTEP_SYNC)
+		return -ERANGE;
+
+	dp83640->hwts_tx_en = cfg.tx_type;
+
+	switch (cfg.rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		dp83640->hwts_rx_en = 0;
+		dp83640->layer = 0;
+		dp83640->version = 0;
+		break;
+	case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+		dp83640->hwts_rx_en = 1;
+		dp83640->layer = PTP_CLASS_L4;
+		dp83640->version = PTP_CLASS_V1;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+		dp83640->hwts_rx_en = 1;
+		dp83640->layer = PTP_CLASS_L4;
+		dp83640->version = PTP_CLASS_V2;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+		dp83640->hwts_rx_en = 1;
+		dp83640->layer = PTP_CLASS_L2;
+		dp83640->version = PTP_CLASS_V2;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+		dp83640->hwts_rx_en = 1;
+		dp83640->layer = PTP_CLASS_L4 | PTP_CLASS_L2;
+		dp83640->version = PTP_CLASS_V2;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	txcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+	rxcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+
+	if (dp83640->layer & PTP_CLASS_L2) {
+		txcfg0 |= TX_L2_EN;
+		rxcfg0 |= RX_L2_EN;
+	}
+	if (dp83640->layer & PTP_CLASS_L4) {
+		txcfg0 |= TX_IPV6_EN | TX_IPV4_EN;
+		rxcfg0 |= RX_IPV6_EN | RX_IPV4_EN;
+	}
+
+	if (dp83640->hwts_tx_en)
+		txcfg0 |= TX_TS_EN;
+
+	if (dp83640->hwts_tx_en == HWTSTAMP_TX_ONESTEP_SYNC)
+		txcfg0 |= SYNC_1STEP | CHK_1STEP;
+
+	if (dp83640->hwts_rx_en)
+		rxcfg0 |= RX_TS_EN;
+
+	mutex_lock(&dp83640->clock->extreg_lock);
+
+	ext_write(0, phydev, PAGE5, PTP_TXCFG0, txcfg0);
+	ext_write(0, phydev, PAGE5, PTP_RXCFG0, rxcfg0);
+
+	mutex_unlock(&dp83640->clock->extreg_lock);
+
+	return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static void rx_timestamp_work(struct work_struct *work)
+{
+	struct dp83640_private *dp83640 =
+		container_of(work, struct dp83640_private, ts_work.work);
+	struct sk_buff *skb;
+
+	/* Deliver expired packets. */
+	while ((skb = skb_dequeue(&dp83640->rx_queue))) {
+		struct dp83640_skb_info *skb_info;
+
+		skb_info = (struct dp83640_skb_info *)skb->cb;
+		if (!time_after(jiffies, skb_info->tmo)) {
+			skb_queue_head(&dp83640->rx_queue, skb);
+			break;
+		}
+
+		netif_rx_ni(skb);
+	}
+
+	if (!skb_queue_empty(&dp83640->rx_queue))
+		schedule_delayed_work(&dp83640->ts_work, SKB_TIMESTAMP_TIMEOUT);
+}
+
+static bool dp83640_rxtstamp(struct phy_device *phydev,
+			     struct sk_buff *skb, int type)
+{
+	struct dp83640_private *dp83640 = phydev->priv;
+	struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb;
+	struct list_head *this, *next;
+	struct rxts *rxts;
+	struct skb_shared_hwtstamps *shhwtstamps = NULL;
+	unsigned long flags;
+
+	if (is_status_frame(skb, type)) {
+		decode_status_frame(dp83640, skb);
+		kfree_skb(skb);
+		return true;
+	}
+
+	if (!dp83640->hwts_rx_en)
+		return false;
+
+	if ((type & dp83640->version) == 0 || (type & dp83640->layer) == 0)
+		return false;
+
+	spin_lock_irqsave(&dp83640->rx_lock, flags);
+	prune_rx_ts(dp83640);
+	list_for_each_safe(this, next, &dp83640->rxts) {
+		rxts = list_entry(this, struct rxts, list);
+		if (match(skb, type, rxts)) {
+			shhwtstamps = skb_hwtstamps(skb);
+			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+			shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns);
+			list_del_init(&rxts->list);
+			list_add(&rxts->list, &dp83640->rxpool);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+
+	if (!shhwtstamps) {
+		skb_info->ptp_type = type;
+		skb_info->tmo = jiffies + SKB_TIMESTAMP_TIMEOUT;
+		skb_queue_tail(&dp83640->rx_queue, skb);
+		schedule_delayed_work(&dp83640->ts_work, SKB_TIMESTAMP_TIMEOUT);
+	} else {
+		netif_rx_ni(skb);
+	}
+
+	return true;
+}
+
+static void dp83640_txtstamp(struct phy_device *phydev,
+			     struct sk_buff *skb, int type)
+{
+	struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb;
+	struct dp83640_private *dp83640 = phydev->priv;
+
+	switch (dp83640->hwts_tx_en) {
+
+	case HWTSTAMP_TX_ONESTEP_SYNC:
+		if (is_sync(skb, type)) {
+			kfree_skb(skb);
+			return;
+		}
+		/* fall through */
+	case HWTSTAMP_TX_ON:
+		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+		skb_info->tmo = jiffies + SKB_TIMESTAMP_TIMEOUT;
+		skb_queue_tail(&dp83640->tx_queue, skb);
+		break;
+
+	case HWTSTAMP_TX_OFF:
+	default:
+		kfree_skb(skb);
+		break;
+	}
+}
+
+static int dp83640_ts_info(struct phy_device *dev, struct ethtool_ts_info *info)
+{
+	struct dp83640_private *dp83640 = dev->priv;
+
+	info->so_timestamping =
+		SOF_TIMESTAMPING_TX_HARDWARE |
+		SOF_TIMESTAMPING_RX_HARDWARE |
+		SOF_TIMESTAMPING_RAW_HARDWARE;
+	info->phc_index = ptp_clock_index(dp83640->clock->ptp_clock);
+	info->tx_types =
+		(1 << HWTSTAMP_TX_OFF) |
+		(1 << HWTSTAMP_TX_ON) |
+		(1 << HWTSTAMP_TX_ONESTEP_SYNC);
+	info->rx_filters =
+		(1 << HWTSTAMP_FILTER_NONE) |
+		(1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
+		(1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
+		(1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
+		(1 << HWTSTAMP_FILTER_PTP_V2_EVENT);
+	return 0;
+}
+
+static struct phy_driver dp83640_driver = {
+	.phy_id		= DP83640_PHY_ID,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "NatSemi DP83640",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.probe		= dp83640_probe,
+	.remove		= dp83640_remove,
+	.soft_reset	= dp83640_soft_reset,
+	.config_init	= dp83640_config_init,
+	.ack_interrupt  = dp83640_ack_interrupt,
+	.config_intr    = dp83640_config_intr,
+	.ts_info	= dp83640_ts_info,
+	.hwtstamp	= dp83640_hwtstamp,
+	.rxtstamp	= dp83640_rxtstamp,
+	.txtstamp	= dp83640_txtstamp,
+};
+
+static int __init dp83640_init(void)
+{
+	return phy_driver_register(&dp83640_driver, THIS_MODULE);
+}
+
+static void __exit dp83640_exit(void)
+{
+	dp83640_free_clocks();
+	phy_driver_unregister(&dp83640_driver);
+}
+
+MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver");
+MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>");
+MODULE_LICENSE("GPL");
+
+module_init(dp83640_init);
+module_exit(dp83640_exit);
+
+static struct mdio_device_id __maybe_unused dp83640_tbl[] = {
+	{ DP83640_PHY_ID, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, dp83640_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/dp83640_reg.h b/src/kernel/linux/v4.19/drivers/net/phy/dp83640_reg.h
new file mode 100644
index 0000000..21aa24c
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/dp83640_reg.h
@@ -0,0 +1,268 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* dp83640_reg.h
+ * Generated by regen.tcl on Thu Feb 17 10:02:48 AM CET 2011
+ */
+#ifndef HAVE_DP83640_REGISTERS
+#define HAVE_DP83640_REGISTERS
+
+#define PAGE0                     0x0000
+#define PHYCR2                    0x001c /* PHY Control Register 2 */
+
+#define PAGE4                     0x0004
+#define PTP_CTL                   0x0014 /* PTP Control Register */
+#define PTP_TDR                   0x0015 /* PTP Time Data Register */
+#define PTP_STS                   0x0016 /* PTP Status Register */
+#define PTP_TSTS                  0x0017 /* PTP Trigger Status Register */
+#define PTP_RATEL                 0x0018 /* PTP Rate Low Register */
+#define PTP_RATEH                 0x0019 /* PTP Rate High Register */
+#define PTP_RDCKSUM               0x001a /* PTP Read Checksum */
+#define PTP_WRCKSUM               0x001b /* PTP Write Checksum */
+#define PTP_TXTS                  0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */
+#define PTP_RXTS                  0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */
+#define PTP_ESTS                  0x001e /* PTP Event Status Register */
+#define PTP_EDATA                 0x001f /* PTP Event Data Register */
+
+#define PAGE5                     0x0005
+#define PTP_TRIG                  0x0014 /* PTP Trigger Configuration Register */
+#define PTP_EVNT                  0x0015 /* PTP Event Configuration Register */
+#define PTP_TXCFG0                0x0016 /* PTP Transmit Configuration Register 0 */
+#define PTP_TXCFG1                0x0017 /* PTP Transmit Configuration Register 1 */
+#define PSF_CFG0                  0x0018 /* PHY Status Frame Configuration Register 0 */
+#define PTP_RXCFG0                0x0019 /* PTP Receive Configuration Register 0 */
+#define PTP_RXCFG1                0x001a /* PTP Receive Configuration Register 1 */
+#define PTP_RXCFG2                0x001b /* PTP Receive Configuration Register 2 */
+#define PTP_RXCFG3                0x001c /* PTP Receive Configuration Register 3 */
+#define PTP_RXCFG4                0x001d /* PTP Receive Configuration Register 4 */
+#define PTP_TRDL                  0x001e /* PTP Temporary Rate Duration Low Register */
+#define PTP_TRDH                  0x001f /* PTP Temporary Rate Duration High Register */
+
+#define PAGE6                     0x0006
+#define PTP_COC                   0x0014 /* PTP Clock Output Control Register */
+#define PSF_CFG1                  0x0015 /* PHY Status Frame Configuration Register 1 */
+#define PSF_CFG2                  0x0016 /* PHY Status Frame Configuration Register 2 */
+#define PSF_CFG3                  0x0017 /* PHY Status Frame Configuration Register 3 */
+#define PSF_CFG4                  0x0018 /* PHY Status Frame Configuration Register 4 */
+#define PTP_SFDCFG                0x0019 /* PTP SFD Configuration Register */
+#define PTP_INTCTL                0x001a /* PTP Interrupt Control Register */
+#define PTP_CLKSRC                0x001b /* PTP Clock Source Register */
+#define PTP_ETR                   0x001c /* PTP Ethernet Type Register */
+#define PTP_OFF                   0x001d /* PTP Offset Register */
+#define PTP_GPIOMON               0x001e /* PTP GPIO Monitor Register */
+#define PTP_RXHASH                0x001f /* PTP Receive Hash Register */
+
+/* Bit definitions for the PHYCR2 register */
+#define BC_WRITE                  (1<<11) /* Broadcast Write Enable */
+
+/* Bit definitions for the PTP_CTL register */
+#define TRIG_SEL_SHIFT            (10)    /* PTP Trigger Select */
+#define TRIG_SEL_MASK             (0x7)
+#define TRIG_DIS                  (1<<9)  /* Disable PTP Trigger */
+#define TRIG_EN                   (1<<8)  /* Enable PTP Trigger */
+#define TRIG_READ                 (1<<7)  /* Read PTP Trigger */
+#define TRIG_LOAD                 (1<<6)  /* Load PTP Trigger */
+#define PTP_RD_CLK                (1<<5)  /* Read PTP Clock */
+#define PTP_LOAD_CLK              (1<<4)  /* Load PTP Clock */
+#define PTP_STEP_CLK              (1<<3)  /* Step PTP Clock */
+#define PTP_ENABLE                (1<<2)  /* Enable PTP Clock */
+#define PTP_DISABLE               (1<<1)  /* Disable PTP Clock */
+#define PTP_RESET                 (1<<0)  /* Reset PTP Clock */
+
+/* Bit definitions for the PTP_STS register */
+#define TXTS_RDY                  (1<<11) /* Transmit Timestamp Ready */
+#define RXTS_RDY                  (1<<10) /* Receive Timestamp Ready */
+#define TRIG_DONE                 (1<<9)  /* PTP Trigger Done */
+#define EVENT_RDY                 (1<<8)  /* PTP Event Timestamp Ready */
+#define TXTS_IE                   (1<<3)  /* Transmit Timestamp Interrupt Enable */
+#define RXTS_IE                   (1<<2)  /* Receive Timestamp Interrupt Enable */
+#define TRIG_IE                   (1<<1)  /* Trigger Interrupt Enable */
+#define EVENT_IE                  (1<<0)  /* Event Interrupt Enable */
+
+/* Bit definitions for the PTP_TSTS register */
+#define TRIG7_ERROR               (1<<15) /* Trigger 7 Error */
+#define TRIG7_ACTIVE              (1<<14) /* Trigger 7 Active */
+#define TRIG6_ERROR               (1<<13) /* Trigger 6 Error */
+#define TRIG6_ACTIVE              (1<<12) /* Trigger 6 Active */
+#define TRIG5_ERROR               (1<<11) /* Trigger 5 Error */
+#define TRIG5_ACTIVE              (1<<10) /* Trigger 5 Active */
+#define TRIG4_ERROR               (1<<9)  /* Trigger 4 Error */
+#define TRIG4_ACTIVE              (1<<8)  /* Trigger 4 Active */
+#define TRIG3_ERROR               (1<<7)  /* Trigger 3 Error */
+#define TRIG3_ACTIVE              (1<<6)  /* Trigger 3 Active */
+#define TRIG2_ERROR               (1<<5)  /* Trigger 2 Error */
+#define TRIG2_ACTIVE              (1<<4)  /* Trigger 2 Active */
+#define TRIG1_ERROR               (1<<3)  /* Trigger 1 Error */
+#define TRIG1_ACTIVE              (1<<2)  /* Trigger 1 Active */
+#define TRIG0_ERROR               (1<<1)  /* Trigger 0 Error */
+#define TRIG0_ACTIVE              (1<<0)  /* Trigger 0 Active */
+
+/* Bit definitions for the PTP_RATEH register */
+#define PTP_RATE_DIR              (1<<15) /* PTP Rate Direction */
+#define PTP_TMP_RATE              (1<<14) /* PTP Temporary Rate */
+#define PTP_RATE_HI_SHIFT         (0)     /* PTP Rate High 10-bits */
+#define PTP_RATE_HI_MASK          (0x3ff)
+
+/* Bit definitions for the PTP_ESTS register */
+#define EVNTS_MISSED_SHIFT        (8)     /* Indicates number of events missed */
+#define EVNTS_MISSED_MASK         (0x7)
+#define EVNT_TS_LEN_SHIFT         (6)     /* Indicates length of the Timestamp field in 16-bit words minus 1 */
+#define EVNT_TS_LEN_MASK          (0x3)
+#define EVNT_RF                   (1<<5)  /* Indicates whether the event is a rise or falling event */
+#define EVNT_NUM_SHIFT            (2)     /* Indicates Event Timestamp Unit which detected an event */
+#define EVNT_NUM_MASK             (0x7)
+#define MULT_EVNT                 (1<<1)  /* Indicates multiple events were detected at the same time */
+#define EVENT_DET                 (1<<0)  /* PTP Event Detected */
+
+/* Bit definitions for the PTP_EDATA register */
+#define E7_RISE                   (1<<15) /* Indicates direction of Event 7 */
+#define E7_DET                    (1<<14) /* Indicates Event 7 detected */
+#define E6_RISE                   (1<<13) /* Indicates direction of Event 6 */
+#define E6_DET                    (1<<12) /* Indicates Event 6 detected */
+#define E5_RISE                   (1<<11) /* Indicates direction of Event 5 */
+#define E5_DET                    (1<<10) /* Indicates Event 5 detected */
+#define E4_RISE                   (1<<9)  /* Indicates direction of Event 4 */
+#define E4_DET                    (1<<8)  /* Indicates Event 4 detected */
+#define E3_RISE                   (1<<7)  /* Indicates direction of Event 3 */
+#define E3_DET                    (1<<6)  /* Indicates Event 3 detected */
+#define E2_RISE                   (1<<5)  /* Indicates direction of Event 2 */
+#define E2_DET                    (1<<4)  /* Indicates Event 2 detected */
+#define E1_RISE                   (1<<3)  /* Indicates direction of Event 1 */
+#define E1_DET                    (1<<2)  /* Indicates Event 1 detected */
+#define E0_RISE                   (1<<1)  /* Indicates direction of Event 0 */
+#define E0_DET                    (1<<0)  /* Indicates Event 0 detected */
+
+/* Bit definitions for the PTP_TRIG register */
+#define TRIG_PULSE                (1<<15) /* generate a Pulse rather than a single edge */
+#define TRIG_PER                  (1<<14) /* generate a periodic signal */
+#define TRIG_IF_LATE              (1<<13) /* trigger immediately if already past */
+#define TRIG_NOTIFY               (1<<12) /* Trigger Notification Enable */
+#define TRIG_GPIO_SHIFT           (8)     /* Trigger GPIO Connection, value 1-12 */
+#define TRIG_GPIO_MASK            (0xf)
+#define TRIG_TOGGLE               (1<<7)  /* Trigger Toggle Mode Enable */
+#define TRIG_CSEL_SHIFT           (1)     /* Trigger Configuration Select */
+#define TRIG_CSEL_MASK            (0x7)
+#define TRIG_WR                   (1<<0)  /* Trigger Configuration Write */
+
+/* Bit definitions for the PTP_EVNT register */
+#define EVNT_RISE                 (1<<14) /* Event Rise Detect Enable */
+#define EVNT_FALL                 (1<<13) /* Event Fall Detect Enable */
+#define EVNT_SINGLE               (1<<12) /* enable single event capture operation */
+#define EVNT_GPIO_SHIFT           (8)     /* Event GPIO Connection, value 1-12 */
+#define EVNT_GPIO_MASK            (0xf)
+#define EVNT_SEL_SHIFT            (1)     /* Event Select */
+#define EVNT_SEL_MASK             (0x7)
+#define EVNT_WR                   (1<<0)  /* Event Configuration Write */
+
+/* Bit definitions for the PTP_TXCFG0 register */
+#define SYNC_1STEP                (1<<15) /* insert timestamp into transmit Sync Messages */
+#define DR_INSERT                 (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */
+#define NTP_TS_EN                 (1<<12) /* Enable Timestamping of NTP Packets */
+#define IGNORE_2STEP              (1<<11) /* Ignore Two_Step flag for One-Step operation */
+#define CRC_1STEP                 (1<<10) /* Disable checking of CRC for One-Step operation */
+#define CHK_1STEP                 (1<<9)  /* Enable UDP Checksum correction for One-Step Operation */
+#define IP1588_EN                 (1<<8)  /* Enable IEEE 1588 defined IP address filter */
+#define TX_L2_EN                  (1<<7)  /* Layer2 Timestamp Enable */
+#define TX_IPV6_EN                (1<<6)  /* IPv6 Timestamp Enable */
+#define TX_IPV4_EN                (1<<5)  /* IPv4 Timestamp Enable */
+#define TX_PTP_VER_SHIFT          (1)     /* Enable Timestamp capture for IEEE 1588 version X */
+#define TX_PTP_VER_MASK           (0xf)
+#define TX_TS_EN                  (1<<0)  /* Transmit Timestamp Enable */
+
+/* Bit definitions for the PTP_TXCFG1 register */
+#define BYTE0_MASK_SHIFT          (8)     /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK           (0xff)
+#define BYTE0_DATA_SHIFT          (0)     /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK           (0xff)
+
+/* Bit definitions for the PSF_CFG0 register */
+#define MAC_SRC_ADD_SHIFT         (11)    /* Status Frame Mac Source Address */
+#define MAC_SRC_ADD_MASK          (0x3)
+#define MIN_PRE_SHIFT             (8)     /* Status Frame Minimum Preamble */
+#define MIN_PRE_MASK              (0x7)
+#define PSF_ENDIAN                (1<<7)  /* Status Frame Endian Control */
+#define PSF_IPV4                  (1<<6)  /* Status Frame IPv4 Enable */
+#define PSF_PCF_RD                (1<<5)  /* Control Frame Read PHY Status Frame Enable */
+#define PSF_ERR_EN                (1<<4)  /* Error PHY Status Frame Enable */
+#define PSF_TXTS_EN               (1<<3)  /* Transmit Timestamp PHY Status Frame Enable */
+#define PSF_RXTS_EN               (1<<2)  /* Receive Timestamp PHY Status Frame Enable */
+#define PSF_TRIG_EN               (1<<1)  /* Trigger PHY Status Frame Enable */
+#define PSF_EVNT_EN               (1<<0)  /* Event PHY Status Frame Enable */
+
+/* Bit definitions for the PTP_RXCFG0 register */
+#define DOMAIN_EN                 (1<<15) /* Domain Match Enable */
+#define ALT_MAST_DIS              (1<<14) /* Alternate Master Timestamp Disable */
+#define USER_IP_SEL               (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */
+#define USER_IP_EN                (1<<12) /* Enable User-programmed IP address filter */
+#define RX_SLAVE                  (1<<11) /* Receive Slave Only */
+#define IP1588_EN_SHIFT           (8)     /* Enable IEEE 1588 defined IP address filters */
+#define IP1588_EN_MASK            (0xf)
+#define RX_L2_EN                  (1<<7)  /* Layer2 Timestamp Enable */
+#define RX_IPV6_EN                (1<<6)  /* IPv6 Timestamp Enable */
+#define RX_IPV4_EN                (1<<5)  /* IPv4 Timestamp Enable */
+#define RX_PTP_VER_SHIFT          (1)     /* Enable Timestamp capture for IEEE 1588 version X */
+#define RX_PTP_VER_MASK           (0xf)
+#define RX_TS_EN                  (1<<0)  /* Receive Timestamp Enable */
+
+/* Bit definitions for the PTP_RXCFG1 register */
+#define BYTE0_MASK_SHIFT          (8)     /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK           (0xff)
+#define BYTE0_DATA_SHIFT          (0)     /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK           (0xff)
+
+/* Bit definitions for the PTP_RXCFG3 register */
+#define TS_MIN_IFG_SHIFT          (12)    /* Minimum Inter-frame Gap */
+#define TS_MIN_IFG_MASK           (0xf)
+#define ACC_UDP                   (1<<11) /* Record Timestamp if UDP Checksum Error */
+#define ACC_CRC                   (1<<10) /* Record Timestamp if CRC Error */
+#define TS_APPEND                 (1<<9)  /* Append Timestamp for L2 */
+#define TS_INSERT                 (1<<8)  /* Enable Timestamp Insertion */
+#define PTP_DOMAIN_SHIFT          (0)     /* PTP Message domainNumber field */
+#define PTP_DOMAIN_MASK           (0xff)
+
+/* Bit definitions for the PTP_RXCFG4 register */
+#define IPV4_UDP_MOD              (1<<15) /* Enable IPV4 UDP Modification */
+#define TS_SEC_EN                 (1<<14) /* Enable Timestamp Seconds */
+#define TS_SEC_LEN_SHIFT          (12)    /* Inserted Timestamp Seconds Length */
+#define TS_SEC_LEN_MASK           (0x3)
+#define RXTS_NS_OFF_SHIFT         (6)     /* Receive Timestamp Nanoseconds offset */
+#define RXTS_NS_OFF_MASK          (0x3f)
+#define RXTS_SEC_OFF_SHIFT        (0)     /* Receive Timestamp Seconds offset */
+#define RXTS_SEC_OFF_MASK         (0x3f)
+
+/* Bit definitions for the PTP_COC register */
+#define PTP_CLKOUT_EN             (1<<15) /* PTP Clock Output Enable */
+#define PTP_CLKOUT_SEL            (1<<14) /* PTP Clock Output Source Select */
+#define PTP_CLKOUT_SPEEDSEL       (1<<13) /* PTP Clock Output I/O Speed Select */
+#define PTP_CLKDIV_SHIFT          (0)     /* PTP Clock Divide-by Value */
+#define PTP_CLKDIV_MASK           (0xff)
+
+/* Bit definitions for the PSF_CFG1 register */
+#define PTPRESERVED_SHIFT         (12)    /* PTP v2 reserved field */
+#define PTPRESERVED_MASK          (0xf)
+#define VERSIONPTP_SHIFT          (8)     /* PTP v2 versionPTP field */
+#define VERSIONPTP_MASK           (0xf)
+#define TRANSPORT_SPECIFIC_SHIFT  (4)     /* PTP v2 Header transportSpecific field */
+#define TRANSPORT_SPECIFIC_MASK   (0xf)
+#define MESSAGETYPE_SHIFT         (0)     /* PTP v2 messageType field */
+#define MESSAGETYPE_MASK          (0xf)
+
+/* Bit definitions for the PTP_SFDCFG register */
+#define TX_SFD_GPIO_SHIFT         (4)     /* TX SFD GPIO Select, value 1-12 */
+#define TX_SFD_GPIO_MASK          (0xf)
+#define RX_SFD_GPIO_SHIFT         (0)     /* RX SFD GPIO Select, value 1-12 */
+#define RX_SFD_GPIO_MASK          (0xf)
+
+/* Bit definitions for the PTP_INTCTL register */
+#define PTP_INT_GPIO_SHIFT        (0)     /* PTP Interrupt GPIO Select */
+#define PTP_INT_GPIO_MASK         (0xf)
+
+/* Bit definitions for the PTP_CLKSRC register */
+#define CLK_SRC_SHIFT             (14)    /* PTP Clock Source Select */
+#define CLK_SRC_MASK              (0x3)
+#define CLK_SRC_PER_SHIFT         (0)     /* PTP Clock Source Period */
+#define CLK_SRC_PER_MASK          (0x7f)
+
+/* Bit definitions for the PTP_OFF register */
+#define PTP_OFFSET_SHIFT          (0)     /* PTP Message offset from preceding header */
+#define PTP_OFFSET_MASK           (0xff)
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/dp83822.c b/src/kernel/linux/v4.19/drivers/net/phy/dp83822.c
new file mode 100644
index 0000000..6e8a2a4
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/dp83822.c
@@ -0,0 +1,342 @@
+/*
+ * Driver for the Texas Instruments DP83822 PHY
+ *
+ * Copyright (C) 2017 Texas Instruments Inc.
+ *
+ * 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.
+ *
+ * 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/ethtool.h>
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+
+#define DP83822_PHY_ID	        0x2000a240
+#define DP83822_DEVADDR		0x1f
+
+#define MII_DP83822_PHYSCR	0x11
+#define MII_DP83822_MISR1	0x12
+#define MII_DP83822_MISR2	0x13
+#define MII_DP83822_RESET_CTRL	0x1f
+
+#define DP83822_HW_RESET	BIT(15)
+#define DP83822_SW_RESET	BIT(14)
+
+/* PHYSCR Register Fields */
+#define DP83822_PHYSCR_INT_OE		BIT(0) /* Interrupt Output Enable */
+#define DP83822_PHYSCR_INTEN		BIT(1) /* Interrupt Enable */
+
+/* MISR1 bits */
+#define DP83822_RX_ERR_HF_INT_EN	BIT(0)
+#define DP83822_FALSE_CARRIER_HF_INT_EN	BIT(1)
+#define DP83822_ANEG_COMPLETE_INT_EN	BIT(2)
+#define DP83822_DUP_MODE_CHANGE_INT_EN	BIT(3)
+#define DP83822_SPEED_CHANGED_INT_EN	BIT(4)
+#define DP83822_LINK_STAT_INT_EN	BIT(5)
+#define DP83822_ENERGY_DET_INT_EN	BIT(6)
+#define DP83822_LINK_QUAL_INT_EN	BIT(7)
+
+/* MISR2 bits */
+#define DP83822_JABBER_DET_INT_EN	BIT(0)
+#define DP83822_WOL_PKT_INT_EN		BIT(1)
+#define DP83822_SLEEP_MODE_INT_EN	BIT(2)
+#define DP83822_MDI_XOVER_INT_EN	BIT(3)
+#define DP83822_LB_FIFO_INT_EN		BIT(4)
+#define DP83822_PAGE_RX_INT_EN		BIT(5)
+#define DP83822_ANEG_ERR_INT_EN		BIT(6)
+#define DP83822_EEE_ERROR_CHANGE_INT_EN	BIT(7)
+
+/* INT_STAT1 bits */
+#define DP83822_WOL_INT_EN	BIT(4)
+#define DP83822_WOL_INT_STAT	BIT(12)
+
+#define MII_DP83822_RXSOP1	0x04a5
+#define	MII_DP83822_RXSOP2	0x04a6
+#define	MII_DP83822_RXSOP3	0x04a7
+
+/* WoL Registers */
+#define	MII_DP83822_WOL_CFG	0x04a0
+#define	MII_DP83822_WOL_STAT	0x04a1
+#define	MII_DP83822_WOL_DA1	0x04a2
+#define	MII_DP83822_WOL_DA2	0x04a3
+#define	MII_DP83822_WOL_DA3	0x04a4
+
+/* WoL bits */
+#define DP83822_WOL_MAGIC_EN	BIT(0)
+#define DP83822_WOL_SECURE_ON	BIT(5)
+#define DP83822_WOL_EN		BIT(7)
+#define DP83822_WOL_INDICATION_SEL BIT(8)
+#define DP83822_WOL_CLR_INDICATION BIT(11)
+
+static int dp83822_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, MII_DP83822_MISR1);
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_DP83822_MISR2);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int dp83822_set_wol(struct phy_device *phydev,
+			   struct ethtool_wolinfo *wol)
+{
+	struct net_device *ndev = phydev->attached_dev;
+	u16 value;
+	const u8 *mac;
+
+	if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) {
+		mac = (const u8 *)ndev->dev_addr;
+
+		if (!is_valid_ether_addr(mac))
+			return -EINVAL;
+
+		/* MAC addresses start with byte 5, but stored in mac[0].
+		 * 822 PHYs store bytes 4|5, 2|3, 0|1
+		 */
+		phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA1,
+			      (mac[1] << 8) | mac[0]);
+		phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA2,
+			      (mac[3] << 8) | mac[2]);
+		phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA3,
+			      (mac[5] << 8) | mac[4]);
+
+		value = phy_read_mmd(phydev, DP83822_DEVADDR,
+				     MII_DP83822_WOL_CFG);
+		if (wol->wolopts & WAKE_MAGIC)
+			value |= DP83822_WOL_MAGIC_EN;
+		else
+			value &= ~DP83822_WOL_MAGIC_EN;
+
+		if (wol->wolopts & WAKE_MAGICSECURE) {
+			phy_write_mmd(phydev, DP83822_DEVADDR,
+				      MII_DP83822_RXSOP1,
+				      (wol->sopass[1] << 8) | wol->sopass[0]);
+			phy_write_mmd(phydev, DP83822_DEVADDR,
+				      MII_DP83822_RXSOP2,
+				      (wol->sopass[3] << 8) | wol->sopass[2]);
+			phy_write_mmd(phydev, DP83822_DEVADDR,
+				      MII_DP83822_RXSOP3,
+				      (wol->sopass[5] << 8) | wol->sopass[4]);
+			value |= DP83822_WOL_SECURE_ON;
+		} else {
+			value &= ~DP83822_WOL_SECURE_ON;
+		}
+
+		value |= (DP83822_WOL_EN | DP83822_WOL_INDICATION_SEL |
+			  DP83822_WOL_CLR_INDICATION);
+		phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG,
+			      value);
+	} else {
+		value = phy_read_mmd(phydev, DP83822_DEVADDR,
+				     MII_DP83822_WOL_CFG);
+		value &= ~DP83822_WOL_EN;
+		phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG,
+			      value);
+	}
+
+	return 0;
+}
+
+static void dp83822_get_wol(struct phy_device *phydev,
+			    struct ethtool_wolinfo *wol)
+{
+	int value;
+	u16 sopass_val;
+
+	wol->supported = (WAKE_MAGIC | WAKE_MAGICSECURE);
+	wol->wolopts = 0;
+
+	value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG);
+
+	if (value & DP83822_WOL_MAGIC_EN)
+		wol->wolopts |= WAKE_MAGIC;
+
+	if (value & DP83822_WOL_SECURE_ON) {
+		sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR,
+					  MII_DP83822_RXSOP1);
+		wol->sopass[0] = (sopass_val & 0xff);
+		wol->sopass[1] = (sopass_val >> 8);
+
+		sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR,
+					  MII_DP83822_RXSOP2);
+		wol->sopass[2] = (sopass_val & 0xff);
+		wol->sopass[3] = (sopass_val >> 8);
+
+		sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR,
+					  MII_DP83822_RXSOP3);
+		wol->sopass[4] = (sopass_val & 0xff);
+		wol->sopass[5] = (sopass_val >> 8);
+
+		wol->wolopts |= WAKE_MAGICSECURE;
+	}
+
+	/* WoL is not enabled so set wolopts to 0 */
+	if (!(value & DP83822_WOL_EN))
+		wol->wolopts = 0;
+}
+
+static int dp83822_config_intr(struct phy_device *phydev)
+{
+	int misr_status;
+	int physcr_status;
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		misr_status = phy_read(phydev, MII_DP83822_MISR1);
+		if (misr_status < 0)
+			return misr_status;
+
+		misr_status |= (DP83822_RX_ERR_HF_INT_EN |
+				DP83822_FALSE_CARRIER_HF_INT_EN |
+				DP83822_ANEG_COMPLETE_INT_EN |
+				DP83822_DUP_MODE_CHANGE_INT_EN |
+				DP83822_SPEED_CHANGED_INT_EN |
+				DP83822_LINK_STAT_INT_EN |
+				DP83822_ENERGY_DET_INT_EN |
+				DP83822_LINK_QUAL_INT_EN);
+
+		err = phy_write(phydev, MII_DP83822_MISR1, misr_status);
+		if (err < 0)
+			return err;
+
+		misr_status = phy_read(phydev, MII_DP83822_MISR2);
+		if (misr_status < 0)
+			return misr_status;
+
+		misr_status |= (DP83822_JABBER_DET_INT_EN |
+				DP83822_WOL_PKT_INT_EN |
+				DP83822_SLEEP_MODE_INT_EN |
+				DP83822_MDI_XOVER_INT_EN |
+				DP83822_LB_FIFO_INT_EN |
+				DP83822_PAGE_RX_INT_EN |
+				DP83822_ANEG_ERR_INT_EN |
+				DP83822_EEE_ERROR_CHANGE_INT_EN);
+
+		err = phy_write(phydev, MII_DP83822_MISR2, misr_status);
+		if (err < 0)
+			return err;
+
+		physcr_status = phy_read(phydev, MII_DP83822_PHYSCR);
+		if (physcr_status < 0)
+			return physcr_status;
+
+		physcr_status |= DP83822_PHYSCR_INT_OE | DP83822_PHYSCR_INTEN;
+
+	} else {
+		err = phy_write(phydev, MII_DP83822_MISR1, 0);
+		if (err < 0)
+			return err;
+
+		err = phy_write(phydev, MII_DP83822_MISR1, 0);
+		if (err < 0)
+			return err;
+
+		physcr_status = phy_read(phydev, MII_DP83822_PHYSCR);
+		if (physcr_status < 0)
+			return physcr_status;
+
+		physcr_status &= ~DP83822_PHYSCR_INTEN;
+	}
+
+	return phy_write(phydev, MII_DP83822_PHYSCR, physcr_status);
+}
+
+static int dp83822_config_init(struct phy_device *phydev)
+{
+	int err;
+	int value;
+
+	err = genphy_config_init(phydev);
+	if (err < 0)
+		return err;
+
+	value = DP83822_WOL_MAGIC_EN | DP83822_WOL_SECURE_ON | DP83822_WOL_EN;
+
+	return phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG,
+	      value);
+}
+
+static int dp83822_phy_reset(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, MII_DP83822_RESET_CTRL, DP83822_HW_RESET);
+	if (err < 0)
+		return err;
+
+	dp83822_config_init(phydev);
+
+	return 0;
+}
+
+static int dp83822_suspend(struct phy_device *phydev)
+{
+	int value;
+
+	value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG);
+
+	if (!(value & DP83822_WOL_EN))
+		genphy_suspend(phydev);
+
+	return 0;
+}
+
+static int dp83822_resume(struct phy_device *phydev)
+{
+	int value;
+
+	genphy_resume(phydev);
+
+	value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG);
+
+	phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, value |
+		      DP83822_WOL_CLR_INDICATION);
+
+	return 0;
+}
+
+static struct phy_driver dp83822_driver[] = {
+	{
+		.phy_id = DP83822_PHY_ID,
+		.phy_id_mask = 0xfffffff0,
+		.name = "TI DP83822",
+		.features = PHY_BASIC_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.config_init = dp83822_config_init,
+		.soft_reset = dp83822_phy_reset,
+		.get_wol = dp83822_get_wol,
+		.set_wol = dp83822_set_wol,
+		.ack_interrupt = dp83822_ack_interrupt,
+		.config_intr = dp83822_config_intr,
+		.suspend = dp83822_suspend,
+		.resume = dp83822_resume,
+	 },
+};
+module_phy_driver(dp83822_driver);
+
+static struct mdio_device_id __maybe_unused dp83822_tbl[] = {
+	{ DP83822_PHY_ID, 0xfffffff0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(mdio, dp83822_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments DP83822 PHY driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/dp83848.c b/src/kernel/linux/v4.19/drivers/net/phy/dp83848.c
new file mode 100644
index 0000000..6e8e423
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/dp83848.c
@@ -0,0 +1,137 @@
+/*
+ * Driver for the Texas Instruments DP83848 PHY
+ *
+ * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * 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.
+ *
+ * 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/module.h>
+#include <linux/phy.h>
+
+#define TI_DP83848C_PHY_ID		0x20005ca0
+#define TI_DP83620_PHY_ID		0x20005ce0
+#define NS_DP83848C_PHY_ID		0x20005c90
+#define TLK10X_PHY_ID			0x2000a210
+
+/* Registers */
+#define DP83848_MICR			0x11 /* MII Interrupt Control Register */
+#define DP83848_MISR			0x12 /* MII Interrupt Status Register */
+
+/* MICR Register Fields */
+#define DP83848_MICR_INT_OE		BIT(0) /* Interrupt Output Enable */
+#define DP83848_MICR_INTEN		BIT(1) /* Interrupt Enable */
+
+/* MISR Register Fields */
+#define DP83848_MISR_RHF_INT_EN		BIT(0) /* Receive Error Counter */
+#define DP83848_MISR_FHF_INT_EN		BIT(1) /* False Carrier Counter */
+#define DP83848_MISR_ANC_INT_EN		BIT(2) /* Auto-negotiation complete */
+#define DP83848_MISR_DUP_INT_EN		BIT(3) /* Duplex Status */
+#define DP83848_MISR_SPD_INT_EN		BIT(4) /* Speed status */
+#define DP83848_MISR_LINK_INT_EN	BIT(5) /* Link status */
+#define DP83848_MISR_ED_INT_EN		BIT(6) /* Energy detect */
+#define DP83848_MISR_LQM_INT_EN		BIT(7) /* Link Quality Monitor */
+
+#define DP83848_INT_EN_MASK		\
+	(DP83848_MISR_ANC_INT_EN |	\
+	 DP83848_MISR_DUP_INT_EN |	\
+	 DP83848_MISR_SPD_INT_EN |	\
+	 DP83848_MISR_LINK_INT_EN)
+
+static int dp83848_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, DP83848_MISR);
+
+	return err < 0 ? err : 0;
+}
+
+static int dp83848_config_intr(struct phy_device *phydev)
+{
+	int control, ret;
+
+	control = phy_read(phydev, DP83848_MICR);
+	if (control < 0)
+		return control;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		control |= DP83848_MICR_INT_OE;
+		control |= DP83848_MICR_INTEN;
+
+		ret = phy_write(phydev, DP83848_MISR, DP83848_INT_EN_MASK);
+		if (ret < 0)
+			return ret;
+	} else {
+		control &= ~DP83848_MICR_INTEN;
+	}
+
+	return phy_write(phydev, DP83848_MICR, control);
+}
+
+static int dp83848_config_init(struct phy_device *phydev)
+{
+	int err;
+	int val;
+
+	err = genphy_config_init(phydev);
+	if (err < 0)
+		return err;
+
+	/* DP83620 always reports Auto Negotiation Ability on BMSR. Instead,
+	 * we check initial value of BMCR Auto negotiation enable bit
+	 */
+	val = phy_read(phydev, MII_BMCR);
+	if (!(val & BMCR_ANENABLE))
+		phydev->autoneg = AUTONEG_DISABLE;
+
+	return 0;
+}
+
+static struct mdio_device_id __maybe_unused dp83848_tbl[] = {
+	{ TI_DP83848C_PHY_ID, 0xfffffff0 },
+	{ NS_DP83848C_PHY_ID, 0xfffffff0 },
+	{ TI_DP83620_PHY_ID, 0xfffffff0 },
+	{ TLK10X_PHY_ID, 0xfffffff0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, dp83848_tbl);
+
+#define DP83848_PHY_DRIVER(_id, _name, _config_init)		\
+	{							\
+		.phy_id		= _id,				\
+		.phy_id_mask	= 0xfffffff0,			\
+		.name		= _name,			\
+		.features	= PHY_BASIC_FEATURES,		\
+		.flags		= PHY_HAS_INTERRUPT,		\
+								\
+		.soft_reset	= genphy_soft_reset,		\
+		.config_init	= _config_init,			\
+		.suspend	= genphy_suspend,		\
+		.resume		= genphy_resume,		\
+								\
+		/* IRQ related */				\
+		.ack_interrupt	= dp83848_ack_interrupt,	\
+		.config_intr	= dp83848_config_intr,		\
+	}
+
+static struct phy_driver dp83848_driver[] = {
+	DP83848_PHY_DRIVER(TI_DP83848C_PHY_ID, "TI DP83848C 10/100 Mbps PHY",
+			   genphy_config_init),
+	DP83848_PHY_DRIVER(NS_DP83848C_PHY_ID, "NS DP83848C 10/100 Mbps PHY",
+			   genphy_config_init),
+	DP83848_PHY_DRIVER(TI_DP83620_PHY_ID, "TI DP83620 10/100 Mbps PHY",
+			   dp83848_config_init),
+	DP83848_PHY_DRIVER(TLK10X_PHY_ID, "TI TLK10X 10/100 Mbps PHY",
+			   genphy_config_init),
+};
+module_phy_driver(dp83848_driver);
+
+MODULE_DESCRIPTION("Texas Instruments DP83848 PHY driver");
+MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/dp83867.c b/src/kernel/linux/v4.19/drivers/net/phy/dp83867.c
new file mode 100644
index 0000000..879096d
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/dp83867.c
@@ -0,0 +1,401 @@
+/*
+ * Driver for the Texas Instruments DP83867 PHY
+ *
+ * Copyright (C) 2015 Texas Instruments Inc.
+ *
+ * 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.
+ *
+ * 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/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+
+#include <dt-bindings/net/ti-dp83867.h>
+
+#define DP83867_PHY_ID		0x2000a231
+#define DP83867_DEVADDR		0x1f
+
+#define MII_DP83867_PHYCTRL	0x10
+#define MII_DP83867_MICR	0x12
+#define MII_DP83867_ISR		0x13
+#define DP83867_CTRL		0x1f
+#define DP83867_CFG3		0x1e
+
+/* Extended Registers */
+#define DP83867_CFG4            0x0031
+#define DP83867_CFG4_SGMII_ANEG_MASK (BIT(5) | BIT(6))
+#define DP83867_CFG4_SGMII_ANEG_TIMER_11MS   (3 << 5)
+#define DP83867_CFG4_SGMII_ANEG_TIMER_800US  (2 << 5)
+#define DP83867_CFG4_SGMII_ANEG_TIMER_2US    (1 << 5)
+#define DP83867_CFG4_SGMII_ANEG_TIMER_16MS   (0 << 5)
+
+#define DP83867_RGMIICTL	0x0032
+#define DP83867_STRAP_STS1	0x006E
+#define DP83867_RGMIIDCTL	0x0086
+#define DP83867_IO_MUX_CFG	0x0170
+#define DP83867_10M_SGMII_CFG   0x016F
+#define DP83867_10M_SGMII_RATE_ADAPT_MASK BIT(7)
+
+#define DP83867_SW_RESET	BIT(15)
+#define DP83867_SW_RESTART	BIT(14)
+
+/* MICR Interrupt bits */
+#define MII_DP83867_MICR_AN_ERR_INT_EN		BIT(15)
+#define MII_DP83867_MICR_SPEED_CHNG_INT_EN	BIT(14)
+#define MII_DP83867_MICR_DUP_MODE_CHNG_INT_EN	BIT(13)
+#define MII_DP83867_MICR_PAGE_RXD_INT_EN	BIT(12)
+#define MII_DP83867_MICR_AUTONEG_COMP_INT_EN	BIT(11)
+#define MII_DP83867_MICR_LINK_STS_CHNG_INT_EN	BIT(10)
+#define MII_DP83867_MICR_FALSE_CARRIER_INT_EN	BIT(8)
+#define MII_DP83867_MICR_SLEEP_MODE_CHNG_INT_EN	BIT(4)
+#define MII_DP83867_MICR_WOL_INT_EN		BIT(3)
+#define MII_DP83867_MICR_XGMII_ERR_INT_EN	BIT(2)
+#define MII_DP83867_MICR_POL_CHNG_INT_EN	BIT(1)
+#define MII_DP83867_MICR_JABBER_INT_EN		BIT(0)
+
+/* RGMIICTL bits */
+#define DP83867_RGMII_TX_CLK_DELAY_EN		BIT(1)
+#define DP83867_RGMII_RX_CLK_DELAY_EN		BIT(0)
+
+/* STRAP_STS1 bits */
+#define DP83867_STRAP_STS1_RESERVED		BIT(11)
+
+/* PHY CTRL bits */
+#define DP83867_PHYCR_FIFO_DEPTH_SHIFT		14
+#define DP83867_PHYCR_FIFO_DEPTH_MASK		(3 << 14)
+#define DP83867_PHYCR_RESERVED_MASK		BIT(11)
+
+/* RGMIIDCTL bits */
+#define DP83867_RGMII_TX_CLK_DELAY_SHIFT	4
+
+/* IO_MUX_CFG bits */
+#define DP83867_IO_MUX_CFG_IO_IMPEDANCE_CTRL	0x1f
+
+#define DP83867_IO_MUX_CFG_IO_IMPEDANCE_MAX	0x0
+#define DP83867_IO_MUX_CFG_IO_IMPEDANCE_MIN	0x1f
+#define DP83867_IO_MUX_CFG_CLK_O_SEL_MASK	(0x1f << 8)
+#define DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT	8
+
+/* CFG3 bits */
+#define DP83867_CFG3_INT_OE			BIT(7)
+#define DP83867_CFG3_ROBUST_AUTO_MDIX		BIT(9)
+
+/* CFG4 bits */
+#define DP83867_CFG4_PORT_MIRROR_EN              BIT(0)
+
+enum {
+	DP83867_PORT_MIRROING_KEEP,
+	DP83867_PORT_MIRROING_EN,
+	DP83867_PORT_MIRROING_DIS,
+};
+
+struct dp83867_private {
+	int rx_id_delay;
+	int tx_id_delay;
+	int fifo_depth;
+	int io_impedance;
+	int port_mirroring;
+	bool rxctrl_strap_quirk;
+	int clk_output_sel;
+};
+
+static int dp83867_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, MII_DP83867_ISR);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int dp83867_config_intr(struct phy_device *phydev)
+{
+	int micr_status;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		micr_status = phy_read(phydev, MII_DP83867_MICR);
+		if (micr_status < 0)
+			return micr_status;
+
+		micr_status |=
+			(MII_DP83867_MICR_AN_ERR_INT_EN |
+			MII_DP83867_MICR_SPEED_CHNG_INT_EN |
+			MII_DP83867_MICR_AUTONEG_COMP_INT_EN |
+			MII_DP83867_MICR_LINK_STS_CHNG_INT_EN |
+			MII_DP83867_MICR_DUP_MODE_CHNG_INT_EN |
+			MII_DP83867_MICR_SLEEP_MODE_CHNG_INT_EN);
+
+		return phy_write(phydev, MII_DP83867_MICR, micr_status);
+	}
+
+	micr_status = 0x0;
+	return phy_write(phydev, MII_DP83867_MICR, micr_status);
+}
+
+static int dp83867_config_port_mirroring(struct phy_device *phydev)
+{
+	struct dp83867_private *dp83867 =
+		(struct dp83867_private *)phydev->priv;
+	u16 val;
+
+	val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4);
+
+	if (dp83867->port_mirroring == DP83867_PORT_MIRROING_EN)
+		val |= DP83867_CFG4_PORT_MIRROR_EN;
+	else
+		val &= ~DP83867_CFG4_PORT_MIRROR_EN;
+
+	phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4, val);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF_MDIO
+static int dp83867_of_init(struct phy_device *phydev)
+{
+	struct dp83867_private *dp83867 = phydev->priv;
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	int ret;
+
+	if (!of_node)
+		return -ENODEV;
+
+	dp83867->io_impedance = -EINVAL;
+
+	/* Optional configuration */
+	ret = of_property_read_u32(of_node, "ti,clk-output-sel",
+				   &dp83867->clk_output_sel);
+	if (ret || dp83867->clk_output_sel > DP83867_CLK_O_SEL_REF_CLK)
+		/* Keep the default value if ti,clk-output-sel is not set
+		 * or too high
+		 */
+		dp83867->clk_output_sel = DP83867_CLK_O_SEL_REF_CLK;
+
+	if (of_property_read_bool(of_node, "ti,max-output-impedance"))
+		dp83867->io_impedance = DP83867_IO_MUX_CFG_IO_IMPEDANCE_MAX;
+	else if (of_property_read_bool(of_node, "ti,min-output-impedance"))
+		dp83867->io_impedance = DP83867_IO_MUX_CFG_IO_IMPEDANCE_MIN;
+
+	dp83867->rxctrl_strap_quirk = of_property_read_bool(of_node,
+					"ti,dp83867-rxctrl-strap-quirk");
+
+	ret = of_property_read_u32(of_node, "ti,rx-internal-delay",
+				   &dp83867->rx_id_delay);
+	if (ret &&
+	    (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+	     phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID))
+		return ret;
+
+	ret = of_property_read_u32(of_node, "ti,tx-internal-delay",
+				   &dp83867->tx_id_delay);
+	if (ret &&
+	    (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+	     phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID))
+		return ret;
+
+	if (of_property_read_bool(of_node, "enet-phy-lane-swap"))
+		dp83867->port_mirroring = DP83867_PORT_MIRROING_EN;
+
+	if (of_property_read_bool(of_node, "enet-phy-lane-no-swap"))
+		dp83867->port_mirroring = DP83867_PORT_MIRROING_DIS;
+
+	return of_property_read_u32(of_node, "ti,fifo-depth",
+				   &dp83867->fifo_depth);
+}
+#else
+static int dp83867_of_init(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int dp83867_config_init(struct phy_device *phydev)
+{
+	struct dp83867_private *dp83867;
+	int ret, val, bs;
+	u16 delay;
+
+	if (!phydev->priv) {
+		dp83867 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83867),
+				       GFP_KERNEL);
+		if (!dp83867)
+			return -ENOMEM;
+
+		phydev->priv = dp83867;
+		ret = dp83867_of_init(phydev);
+		if (ret)
+			return ret;
+	} else {
+		dp83867 = (struct dp83867_private *)phydev->priv;
+	}
+
+	/* RX_DV/RX_CTRL strapped in mode 1 or mode 2 workaround */
+	if (dp83867->rxctrl_strap_quirk) {
+		val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4);
+		val &= ~BIT(7);
+		phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4, val);
+	}
+
+	if (phy_interface_is_rgmii(phydev)) {
+		val = phy_read(phydev, MII_DP83867_PHYCTRL);
+		if (val < 0)
+			return val;
+		val &= ~DP83867_PHYCR_FIFO_DEPTH_MASK;
+		val |= (dp83867->fifo_depth << DP83867_PHYCR_FIFO_DEPTH_SHIFT);
+
+		/* The code below checks if "port mirroring" N/A MODE4 has been
+		 * enabled during power on bootstrap.
+		 *
+		 * Such N/A mode enabled by mistake can put PHY IC in some
+		 * internal testing mode and disable RGMII transmission.
+		 *
+		 * In this particular case one needs to check STRAP_STS1
+		 * register's bit 11 (marked as RESERVED).
+		 */
+
+		bs = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_STRAP_STS1);
+		if (bs & DP83867_STRAP_STS1_RESERVED)
+			val &= ~DP83867_PHYCR_RESERVED_MASK;
+
+		ret = phy_write(phydev, MII_DP83867_PHYCTRL, val);
+		if (ret)
+			return ret;
+
+		/* Set up RGMII delays */
+		val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL);
+
+		if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+			val |= (DP83867_RGMII_TX_CLK_DELAY_EN | DP83867_RGMII_RX_CLK_DELAY_EN);
+
+		if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+			val |= DP83867_RGMII_TX_CLK_DELAY_EN;
+
+		if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+			val |= DP83867_RGMII_RX_CLK_DELAY_EN;
+
+		phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL, val);
+
+		delay = (dp83867->rx_id_delay |
+			(dp83867->tx_id_delay << DP83867_RGMII_TX_CLK_DELAY_SHIFT));
+
+		phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIIDCTL,
+			      delay);
+
+		if (dp83867->io_impedance >= 0) {
+			val = phy_read_mmd(phydev, DP83867_DEVADDR,
+					   DP83867_IO_MUX_CFG);
+
+			val &= ~DP83867_IO_MUX_CFG_IO_IMPEDANCE_CTRL;
+			val |= dp83867->io_impedance &
+			       DP83867_IO_MUX_CFG_IO_IMPEDANCE_CTRL;
+
+			phy_write_mmd(phydev, DP83867_DEVADDR,
+				      DP83867_IO_MUX_CFG, val);
+		}
+	}
+
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+		/* For support SPEED_10 in SGMII mode
+		 * DP83867_10M_SGMII_RATE_ADAPT bit
+		 * has to be cleared by software. That
+		 * does not affect SPEED_100 and
+		 * SPEED_1000.
+		 */
+		val = phy_read_mmd(phydev, DP83867_DEVADDR,
+				   DP83867_10M_SGMII_CFG);
+		val &= ~DP83867_10M_SGMII_RATE_ADAPT_MASK;
+		ret = phy_write_mmd(phydev, DP83867_DEVADDR,
+				    DP83867_10M_SGMII_CFG, val);
+
+		if (ret)
+			return ret;
+
+		/* After reset SGMII Autoneg timer is set to 2us (bits 6 and 5
+		 * are 01). That is not enough to finalize autoneg on some
+		 * devices. Increase this timer duration to maximum 16ms.
+		 */
+		val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4);
+		val &= ~DP83867_CFG4_SGMII_ANEG_MASK;
+		val |= DP83867_CFG4_SGMII_ANEG_TIMER_16MS;
+		ret = phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4, val);
+
+		if (ret)
+			return ret;
+	}
+
+	val = phy_read(phydev, DP83867_CFG3);
+	/* Enable Interrupt output INT_OE in CFG3 register */
+	if (phy_interrupt_is_valid(phydev))
+		val |= DP83867_CFG3_INT_OE;
+
+	val |= DP83867_CFG3_ROBUST_AUTO_MDIX;
+	phy_write(phydev, DP83867_CFG3, val);
+
+	if (dp83867->port_mirroring != DP83867_PORT_MIRROING_KEEP)
+		dp83867_config_port_mirroring(phydev);
+
+	/* Clock output selection if muxing property is set */
+	if (dp83867->clk_output_sel != DP83867_CLK_O_SEL_REF_CLK) {
+		val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_IO_MUX_CFG);
+		val &= ~DP83867_IO_MUX_CFG_CLK_O_SEL_MASK;
+		val |= (dp83867->clk_output_sel << DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT);
+		phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_IO_MUX_CFG, val);
+	}
+
+	return 0;
+}
+
+static int dp83867_phy_reset(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, DP83867_CTRL, DP83867_SW_RESET);
+	if (err < 0)
+		return err;
+
+	return dp83867_config_init(phydev);
+}
+
+static struct phy_driver dp83867_driver[] = {
+	{
+		.phy_id		= DP83867_PHY_ID,
+		.phy_id_mask	= 0xfffffff0,
+		.name		= "TI DP83867",
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+
+		.config_init	= dp83867_config_init,
+		.soft_reset	= dp83867_phy_reset,
+
+		/* IRQ related */
+		.ack_interrupt	= dp83867_ack_interrupt,
+		.config_intr	= dp83867_config_intr,
+
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	},
+};
+module_phy_driver(dp83867_driver);
+
+static struct mdio_device_id __maybe_unused dp83867_tbl[] = {
+	{ DP83867_PHY_ID, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, dp83867_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments DP83867 PHY driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/dp83tc811.c b/src/kernel/linux/v4.19/drivers/net/phy/dp83tc811.c
new file mode 100644
index 0000000..78cad13
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/dp83tc811.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DP83TC811 PHY
+ *
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ */
+
+#include <linux/ethtool.h>
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+
+#define DP83TC811_PHY_ID	0x2000a253
+#define DP83811_DEVADDR		0x1f
+
+#define MII_DP83811_SGMII_CTRL	0x09
+#define MII_DP83811_INT_STAT1	0x12
+#define MII_DP83811_INT_STAT2	0x13
+#define MII_DP83811_INT_STAT3	0x18
+#define MII_DP83811_RESET_CTRL	0x1f
+
+#define DP83811_HW_RESET	BIT(15)
+#define DP83811_SW_RESET	BIT(14)
+
+/* INT_STAT1 bits */
+#define DP83811_RX_ERR_HF_INT_EN	BIT(0)
+#define DP83811_MS_TRAINING_INT_EN	BIT(1)
+#define DP83811_ANEG_COMPLETE_INT_EN	BIT(2)
+#define DP83811_ESD_EVENT_INT_EN	BIT(3)
+#define DP83811_WOL_INT_EN		BIT(4)
+#define DP83811_LINK_STAT_INT_EN	BIT(5)
+#define DP83811_ENERGY_DET_INT_EN	BIT(6)
+#define DP83811_LINK_QUAL_INT_EN	BIT(7)
+
+/* INT_STAT2 bits */
+#define DP83811_JABBER_DET_INT_EN	BIT(0)
+#define DP83811_POLARITY_INT_EN		BIT(1)
+#define DP83811_SLEEP_MODE_INT_EN	BIT(2)
+#define DP83811_OVERTEMP_INT_EN		BIT(3)
+#define DP83811_OVERVOLTAGE_INT_EN	BIT(6)
+#define DP83811_UNDERVOLTAGE_INT_EN	BIT(7)
+
+/* INT_STAT3 bits */
+#define DP83811_LPS_INT_EN	BIT(0)
+#define DP83811_NO_FRAME_INT_EN	BIT(3)
+#define DP83811_POR_DONE_INT_EN	BIT(4)
+
+#define MII_DP83811_RXSOP1	0x04a5
+#define MII_DP83811_RXSOP2	0x04a6
+#define MII_DP83811_RXSOP3	0x04a7
+
+/* WoL Registers */
+#define MII_DP83811_WOL_CFG	0x04a0
+#define MII_DP83811_WOL_STAT	0x04a1
+#define MII_DP83811_WOL_DA1	0x04a2
+#define MII_DP83811_WOL_DA2	0x04a3
+#define MII_DP83811_WOL_DA3	0x04a4
+
+/* WoL bits */
+#define DP83811_WOL_MAGIC_EN	BIT(0)
+#define DP83811_WOL_SECURE_ON	BIT(5)
+#define DP83811_WOL_EN		BIT(7)
+#define DP83811_WOL_INDICATION_SEL BIT(8)
+#define DP83811_WOL_CLR_INDICATION BIT(11)
+
+/* SGMII CTRL bits */
+#define DP83811_TDR_AUTO		BIT(8)
+#define DP83811_SGMII_EN		BIT(12)
+#define DP83811_SGMII_AUTO_NEG_EN	BIT(13)
+#define DP83811_SGMII_TX_ERR_DIS	BIT(14)
+#define DP83811_SGMII_SOFT_RESET	BIT(15)
+
+static int dp83811_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, MII_DP83811_INT_STAT1);
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_DP83811_INT_STAT2);
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_DP83811_INT_STAT3);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int dp83811_set_wol(struct phy_device *phydev,
+			   struct ethtool_wolinfo *wol)
+{
+	struct net_device *ndev = phydev->attached_dev;
+	const u8 *mac;
+	u16 value;
+
+	if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) {
+		mac = (const u8 *)ndev->dev_addr;
+
+		if (!is_valid_ether_addr(mac))
+			return -EINVAL;
+
+		/* MAC addresses start with byte 5, but stored in mac[0].
+		 * 811 PHYs store bytes 4|5, 2|3, 0|1
+		 */
+		phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_DA1,
+			      (mac[1] << 8) | mac[0]);
+		phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_DA2,
+			      (mac[3] << 8) | mac[2]);
+		phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_DA3,
+			      (mac[5] << 8) | mac[4]);
+
+		value = phy_read_mmd(phydev, DP83811_DEVADDR,
+				     MII_DP83811_WOL_CFG);
+		if (wol->wolopts & WAKE_MAGIC)
+			value |= DP83811_WOL_MAGIC_EN;
+		else
+			value &= ~DP83811_WOL_MAGIC_EN;
+
+		if (wol->wolopts & WAKE_MAGICSECURE) {
+			phy_write_mmd(phydev, DP83811_DEVADDR,
+				      MII_DP83811_RXSOP1,
+				      (wol->sopass[1] << 8) | wol->sopass[0]);
+			phy_write_mmd(phydev, DP83811_DEVADDR,
+				      MII_DP83811_RXSOP2,
+				      (wol->sopass[3] << 8) | wol->sopass[2]);
+			phy_write_mmd(phydev, DP83811_DEVADDR,
+				      MII_DP83811_RXSOP3,
+				      (wol->sopass[5] << 8) | wol->sopass[4]);
+			value |= DP83811_WOL_SECURE_ON;
+		} else {
+			value &= ~DP83811_WOL_SECURE_ON;
+		}
+
+		value |= (DP83811_WOL_EN | DP83811_WOL_INDICATION_SEL |
+			  DP83811_WOL_CLR_INDICATION);
+		phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG,
+			      value);
+	} else {
+		value = phy_read_mmd(phydev, DP83811_DEVADDR,
+				     MII_DP83811_WOL_CFG);
+		value &= ~DP83811_WOL_EN;
+		phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG,
+			      value);
+	}
+
+	return 0;
+}
+
+static void dp83811_get_wol(struct phy_device *phydev,
+			    struct ethtool_wolinfo *wol)
+{
+	u16 sopass_val;
+	int value;
+
+	wol->supported = (WAKE_MAGIC | WAKE_MAGICSECURE);
+	wol->wolopts = 0;
+
+	value = phy_read_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG);
+
+	if (value & DP83811_WOL_MAGIC_EN)
+		wol->wolopts |= WAKE_MAGIC;
+
+	if (value & DP83811_WOL_SECURE_ON) {
+		sopass_val = phy_read_mmd(phydev, DP83811_DEVADDR,
+					  MII_DP83811_RXSOP1);
+		wol->sopass[0] = (sopass_val & 0xff);
+		wol->sopass[1] = (sopass_val >> 8);
+
+		sopass_val = phy_read_mmd(phydev, DP83811_DEVADDR,
+					  MII_DP83811_RXSOP2);
+		wol->sopass[2] = (sopass_val & 0xff);
+		wol->sopass[3] = (sopass_val >> 8);
+
+		sopass_val = phy_read_mmd(phydev, DP83811_DEVADDR,
+					  MII_DP83811_RXSOP3);
+		wol->sopass[4] = (sopass_val & 0xff);
+		wol->sopass[5] = (sopass_val >> 8);
+
+		wol->wolopts |= WAKE_MAGICSECURE;
+	}
+
+	/* WoL is not enabled so set wolopts to 0 */
+	if (!(value & DP83811_WOL_EN))
+		wol->wolopts = 0;
+}
+
+static int dp83811_config_intr(struct phy_device *phydev)
+{
+	int misr_status, err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		misr_status = phy_read(phydev, MII_DP83811_INT_STAT1);
+		if (misr_status < 0)
+			return misr_status;
+
+		misr_status |= (DP83811_RX_ERR_HF_INT_EN |
+				DP83811_MS_TRAINING_INT_EN |
+				DP83811_ANEG_COMPLETE_INT_EN |
+				DP83811_ESD_EVENT_INT_EN |
+				DP83811_WOL_INT_EN |
+				DP83811_LINK_STAT_INT_EN |
+				DP83811_ENERGY_DET_INT_EN |
+				DP83811_LINK_QUAL_INT_EN);
+
+		err = phy_write(phydev, MII_DP83811_INT_STAT1, misr_status);
+		if (err < 0)
+			return err;
+
+		misr_status = phy_read(phydev, MII_DP83811_INT_STAT2);
+		if (misr_status < 0)
+			return misr_status;
+
+		misr_status |= (DP83811_JABBER_DET_INT_EN |
+				DP83811_POLARITY_INT_EN |
+				DP83811_SLEEP_MODE_INT_EN |
+				DP83811_OVERTEMP_INT_EN |
+				DP83811_OVERVOLTAGE_INT_EN |
+				DP83811_UNDERVOLTAGE_INT_EN);
+
+		err = phy_write(phydev, MII_DP83811_INT_STAT2, misr_status);
+		if (err < 0)
+			return err;
+
+		misr_status = phy_read(phydev, MII_DP83811_INT_STAT3);
+		if (misr_status < 0)
+			return misr_status;
+
+		misr_status |= (DP83811_LPS_INT_EN |
+				DP83811_NO_FRAME_INT_EN |
+				DP83811_POR_DONE_INT_EN);
+
+		err = phy_write(phydev, MII_DP83811_INT_STAT3, misr_status);
+
+	} else {
+		err = phy_write(phydev, MII_DP83811_INT_STAT1, 0);
+		if (err < 0)
+			return err;
+
+		err = phy_write(phydev, MII_DP83811_INT_STAT2, 0);
+		if (err < 0)
+			return err;
+
+		err = phy_write(phydev, MII_DP83811_INT_STAT3, 0);
+	}
+
+	return err;
+}
+
+static int dp83811_config_aneg(struct phy_device *phydev)
+{
+	int value, err;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+		value = phy_read(phydev, MII_DP83811_SGMII_CTRL);
+		if (phydev->autoneg == AUTONEG_ENABLE) {
+			err = phy_write(phydev, MII_DP83811_SGMII_CTRL,
+					(DP83811_SGMII_AUTO_NEG_EN | value));
+			if (err < 0)
+				return err;
+		} else {
+			err = phy_write(phydev, MII_DP83811_SGMII_CTRL,
+					(~DP83811_SGMII_AUTO_NEG_EN & value));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return genphy_config_aneg(phydev);
+}
+
+static int dp83811_config_init(struct phy_device *phydev)
+{
+	int value, err;
+
+	err = genphy_config_init(phydev);
+	if (err < 0)
+		return err;
+
+	value = phy_read(phydev, MII_DP83811_SGMII_CTRL);
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+		err = phy_write(phydev, MII_DP83811_SGMII_CTRL,
+					(DP83811_SGMII_EN | value));
+	} else {
+		err = phy_write(phydev, MII_DP83811_SGMII_CTRL,
+				(~DP83811_SGMII_EN & value));
+	}
+
+	if (err < 0)
+
+		return err;
+
+	value = DP83811_WOL_MAGIC_EN | DP83811_WOL_SECURE_ON | DP83811_WOL_EN;
+
+	return phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG,
+	      value);
+}
+
+static int dp83811_phy_reset(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, MII_DP83811_RESET_CTRL, DP83811_HW_RESET);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int dp83811_suspend(struct phy_device *phydev)
+{
+	int value;
+
+	value = phy_read_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG);
+
+	if (!(value & DP83811_WOL_EN))
+		genphy_suspend(phydev);
+
+	return 0;
+}
+
+static int dp83811_resume(struct phy_device *phydev)
+{
+	int value;
+
+	genphy_resume(phydev);
+
+	value = phy_read_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG);
+
+	phy_write_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG, value |
+		      DP83811_WOL_CLR_INDICATION);
+
+	return 0;
+}
+
+static struct phy_driver dp83811_driver[] = {
+	{
+		.phy_id = DP83TC811_PHY_ID,
+		.phy_id_mask = 0xfffffff0,
+		.name = "TI DP83TC811",
+		.features = PHY_BASIC_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.config_init = dp83811_config_init,
+		.config_aneg = dp83811_config_aneg,
+		.soft_reset = dp83811_phy_reset,
+		.get_wol = dp83811_get_wol,
+		.set_wol = dp83811_set_wol,
+		.ack_interrupt = dp83811_ack_interrupt,
+		.config_intr = dp83811_config_intr,
+		.suspend = dp83811_suspend,
+		.resume = dp83811_resume,
+	 },
+};
+module_phy_driver(dp83811_driver);
+
+static struct mdio_device_id __maybe_unused dp83811_tbl[] = {
+	{ DP83TC811_PHY_ID, 0xfffffff0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(mdio, dp83811_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments DP83TC811 PHY driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/et1011c.c b/src/kernel/linux/v4.19/drivers/net/phy/et1011c.c
new file mode 100644
index 0000000..a9a4edf
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/et1011c.c
@@ -0,0 +1,107 @@
+/*
+ * drivers/net/phy/et1011c.c
+ *
+ * Driver for LSI ET1011C PHYs
+ *
+ * Author: Chaithrika U S
+ *
+ * Copyright (c) 2008 Texas Instruments
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.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/io.h>
+#include <linux/uaccess.h>
+#include <asm/irq.h>
+
+#define ET1011C_STATUS_REG	(0x1A)
+#define ET1011C_CONFIG_REG	(0x16)
+#define ET1011C_SPEED_MASK		(0x0300)
+#define ET1011C_GIGABIT_SPEED		(0x0200)
+#define ET1011C_TX_FIFO_MASK		(0x3000)
+#define ET1011C_TX_FIFO_DEPTH_8		(0x0000)
+#define ET1011C_TX_FIFO_DEPTH_16	(0x1000)
+#define ET1011C_INTERFACE_MASK		(0x0007)
+#define ET1011C_GMII_INTERFACE		(0x0002)
+#define ET1011C_SYS_CLK_EN		(0x01 << 4)
+
+
+MODULE_DESCRIPTION("LSI ET1011C PHY driver");
+MODULE_AUTHOR("Chaithrika U S");
+MODULE_LICENSE("GPL");
+
+static int et1011c_config_aneg(struct phy_device *phydev)
+{
+	int ctl = 0;
+	ctl = phy_read(phydev, MII_BMCR);
+	if (ctl < 0)
+		return ctl;
+	ctl &= ~(BMCR_FULLDPLX | BMCR_SPEED100 | BMCR_SPEED1000 |
+		 BMCR_ANENABLE);
+	/* First clear the PHY */
+	phy_write(phydev, MII_BMCR, ctl | BMCR_RESET);
+
+	return genphy_config_aneg(phydev);
+}
+
+static int et1011c_read_status(struct phy_device *phydev)
+{
+	int ret;
+	u32 val;
+	static int speed;
+	ret = genphy_read_status(phydev);
+
+	if (speed != phydev->speed) {
+		speed = phydev->speed;
+		val = phy_read(phydev, ET1011C_STATUS_REG);
+		if ((val & ET1011C_SPEED_MASK) ==
+					ET1011C_GIGABIT_SPEED) {
+			val = phy_read(phydev, ET1011C_CONFIG_REG);
+			val &= ~ET1011C_TX_FIFO_MASK;
+			phy_write(phydev, ET1011C_CONFIG_REG, val\
+					| ET1011C_GMII_INTERFACE\
+					| ET1011C_SYS_CLK_EN\
+					| ET1011C_TX_FIFO_DEPTH_16);
+
+		}
+	}
+	return ret;
+}
+
+static struct phy_driver et1011c_driver[] = { {
+	.phy_id		= 0x0282f014,
+	.name		= "ET1011C",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= (PHY_BASIC_FEATURES | SUPPORTED_1000baseT_Full),
+	.flags		= PHY_POLL,
+	.config_aneg	= et1011c_config_aneg,
+	.read_status	= et1011c_read_status,
+} };
+
+module_phy_driver(et1011c_driver);
+
+static struct mdio_device_id __maybe_unused et1011c_tbl[] = {
+	{ 0x0282f014, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, et1011c_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/fixed_phy.c b/src/kernel/linux/v4.19/drivers/net/phy/fixed_phy.c
new file mode 100644
index 0000000..67b2608
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/fixed_phy.c
@@ -0,0 +1,311 @@
+/*
+ * Fixed MDIO bus (MDIO bus emulation with fixed PHYs)
+ *
+ * Author: Vitaly Bordug <vbordug@ru.mvista.com>
+ *         Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * Copyright (c) 2006-2007 MontaVista Software, Inc.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/gpio.h>
+#include <linux/seqlock.h>
+#include <linux/idr.h>
+
+#include "swphy.h"
+
+struct fixed_mdio_bus {
+	struct mii_bus *mii_bus;
+	struct list_head phys;
+};
+
+struct fixed_phy {
+	int addr;
+	struct phy_device *phydev;
+	seqcount_t seqcount;
+	struct fixed_phy_status status;
+	int (*link_update)(struct net_device *, struct fixed_phy_status *);
+	struct list_head node;
+	int link_gpio;
+};
+
+static struct platform_device *pdev;
+static struct fixed_mdio_bus platform_fmb = {
+	.phys = LIST_HEAD_INIT(platform_fmb.phys),
+};
+
+static void fixed_phy_update(struct fixed_phy *fp)
+{
+	if (gpio_is_valid(fp->link_gpio))
+		fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
+}
+
+static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
+{
+	struct fixed_mdio_bus *fmb = bus->priv;
+	struct fixed_phy *fp;
+
+	list_for_each_entry(fp, &fmb->phys, node) {
+		if (fp->addr == phy_addr) {
+			struct fixed_phy_status state;
+			int s;
+
+			do {
+				s = read_seqcount_begin(&fp->seqcount);
+				/* Issue callback if user registered it. */
+				if (fp->link_update) {
+					fp->link_update(fp->phydev->attached_dev,
+							&fp->status);
+					fixed_phy_update(fp);
+				}
+				state = fp->status;
+			} while (read_seqcount_retry(&fp->seqcount, s));
+
+			return swphy_read_reg(reg_num, &state);
+		}
+	}
+
+	return 0xFFFF;
+}
+
+static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num,
+			    u16 val)
+{
+	return 0;
+}
+
+/*
+ * If something weird is required to be done with link/speed,
+ * network driver is able to assign a function to implement this.
+ * May be useful for PHY's that need to be software-driven.
+ */
+int fixed_phy_set_link_update(struct phy_device *phydev,
+			      int (*link_update)(struct net_device *,
+						 struct fixed_phy_status *))
+{
+	struct fixed_mdio_bus *fmb = &platform_fmb;
+	struct fixed_phy *fp;
+
+	if (!phydev || !phydev->mdio.bus)
+		return -EINVAL;
+
+	list_for_each_entry(fp, &fmb->phys, node) {
+		if (fp->addr == phydev->mdio.addr) {
+			fp->link_update = link_update;
+			fp->phydev = phydev;
+			return 0;
+		}
+	}
+
+	return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(fixed_phy_set_link_update);
+
+int fixed_phy_add(unsigned int irq, int phy_addr,
+		  struct fixed_phy_status *status,
+		  int link_gpio)
+{
+	int ret;
+	struct fixed_mdio_bus *fmb = &platform_fmb;
+	struct fixed_phy *fp;
+
+	ret = swphy_validate_state(status);
+	if (ret < 0)
+		return ret;
+
+	fp = kzalloc(sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return -ENOMEM;
+
+	seqcount_init(&fp->seqcount);
+
+	if (irq != PHY_POLL)
+		fmb->mii_bus->irq[phy_addr] = irq;
+
+	fp->addr = phy_addr;
+	fp->status = *status;
+	fp->link_gpio = link_gpio;
+
+	if (gpio_is_valid(fp->link_gpio)) {
+		ret = gpio_request_one(fp->link_gpio, GPIOF_DIR_IN,
+				       "fixed-link-gpio-link");
+		if (ret)
+			goto err_regs;
+	}
+
+	fixed_phy_update(fp);
+
+	list_add_tail(&fp->node, &fmb->phys);
+
+	return 0;
+
+err_regs:
+	kfree(fp);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(fixed_phy_add);
+
+static DEFINE_IDA(phy_fixed_ida);
+
+static void fixed_phy_del(int phy_addr)
+{
+	struct fixed_mdio_bus *fmb = &platform_fmb;
+	struct fixed_phy *fp, *tmp;
+
+	list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
+		if (fp->addr == phy_addr) {
+			list_del(&fp->node);
+			if (gpio_is_valid(fp->link_gpio))
+				gpio_free(fp->link_gpio);
+			kfree(fp);
+			ida_simple_remove(&phy_fixed_ida, phy_addr);
+			return;
+		}
+	}
+}
+
+struct phy_device *fixed_phy_register(unsigned int irq,
+				      struct fixed_phy_status *status,
+				      int link_gpio,
+				      struct device_node *np)
+{
+	struct fixed_mdio_bus *fmb = &platform_fmb;
+	struct phy_device *phy;
+	int phy_addr;
+	int ret;
+
+	if (!fmb->mii_bus || fmb->mii_bus->state != MDIOBUS_REGISTERED)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	/* Get the next available PHY address, up to PHY_MAX_ADDR */
+	phy_addr = ida_simple_get(&phy_fixed_ida, 0, PHY_MAX_ADDR, GFP_KERNEL);
+	if (phy_addr < 0)
+		return ERR_PTR(phy_addr);
+
+	ret = fixed_phy_add(irq, phy_addr, status, link_gpio);
+	if (ret < 0) {
+		ida_simple_remove(&phy_fixed_ida, phy_addr);
+		return ERR_PTR(ret);
+	}
+
+	phy = get_phy_device(fmb->mii_bus, phy_addr, false);
+	if (IS_ERR(phy)) {
+		fixed_phy_del(phy_addr);
+		return ERR_PTR(-EINVAL);
+	}
+
+	/* propagate the fixed link values to struct phy_device */
+	phy->link = status->link;
+	if (status->link) {
+		phy->speed = status->speed;
+		phy->duplex = status->duplex;
+		phy->pause = status->pause;
+		phy->asym_pause = status->asym_pause;
+	}
+
+	of_node_get(np);
+	phy->mdio.dev.of_node = np;
+	phy->is_pseudo_fixed_link = true;
+
+	switch (status->speed) {
+	case SPEED_1000:
+		phy->supported = PHY_1000BT_FEATURES;
+		break;
+	case SPEED_100:
+		phy->supported = PHY_100BT_FEATURES;
+		break;
+	case SPEED_10:
+	default:
+		phy->supported = PHY_10BT_FEATURES;
+	}
+
+	ret = phy_device_register(phy);
+	if (ret) {
+		phy_device_free(phy);
+		of_node_put(np);
+		fixed_phy_del(phy_addr);
+		return ERR_PTR(ret);
+	}
+
+	return phy;
+}
+EXPORT_SYMBOL_GPL(fixed_phy_register);
+
+void fixed_phy_unregister(struct phy_device *phy)
+{
+	phy_device_remove(phy);
+	of_node_put(phy->mdio.dev.of_node);
+	fixed_phy_del(phy->mdio.addr);
+}
+EXPORT_SYMBOL_GPL(fixed_phy_unregister);
+
+static int __init fixed_mdio_bus_init(void)
+{
+	struct fixed_mdio_bus *fmb = &platform_fmb;
+	int ret;
+
+	pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	fmb->mii_bus = mdiobus_alloc();
+	if (fmb->mii_bus == NULL) {
+		ret = -ENOMEM;
+		goto err_mdiobus_reg;
+	}
+
+	snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0");
+	fmb->mii_bus->name = "Fixed MDIO Bus";
+	fmb->mii_bus->priv = fmb;
+	fmb->mii_bus->parent = &pdev->dev;
+	fmb->mii_bus->read = &fixed_mdio_read;
+	fmb->mii_bus->write = &fixed_mdio_write;
+
+	ret = mdiobus_register(fmb->mii_bus);
+	if (ret)
+		goto err_mdiobus_alloc;
+
+	return 0;
+
+err_mdiobus_alloc:
+	mdiobus_free(fmb->mii_bus);
+err_mdiobus_reg:
+	platform_device_unregister(pdev);
+	return ret;
+}
+module_init(fixed_mdio_bus_init);
+
+static void __exit fixed_mdio_bus_exit(void)
+{
+	struct fixed_mdio_bus *fmb = &platform_fmb;
+	struct fixed_phy *fp, *tmp;
+
+	mdiobus_unregister(fmb->mii_bus);
+	mdiobus_free(fmb->mii_bus);
+	platform_device_unregister(pdev);
+
+	list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
+		list_del(&fp->node);
+		kfree(fp);
+	}
+	ida_destroy(&phy_fixed_ida);
+}
+module_exit(fixed_mdio_bus_exit);
+
+MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)");
+MODULE_AUTHOR("Vitaly Bordug");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/icplus.c b/src/kernel/linux/v4.19/drivers/net/phy/icplus.c
new file mode 100644
index 0000000..791587a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/icplus.c
@@ -0,0 +1,253 @@
+/*
+ * Driver for ICPlus PHYs
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.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 <asm/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+MODULE_DESCRIPTION("ICPlus IP175C/IP101A/IP101G/IC1001 PHY drivers");
+MODULE_AUTHOR("Michael Barkowski");
+MODULE_LICENSE("GPL");
+
+/* IP101A/G - IP1001 */
+#define IP10XX_SPEC_CTRL_STATUS		16	/* Spec. Control Register */
+#define IP1001_RXPHASE_SEL		(1<<0)	/* Add delay on RX_CLK */
+#define IP1001_TXPHASE_SEL		(1<<1)	/* Add delay on TX_CLK */
+#define IP1001_SPEC_CTRL_STATUS_2	20	/* IP1001 Spec. Control Reg 2 */
+#define IP1001_APS_ON			11	/* IP1001 APS Mode  bit */
+#define IP101A_G_APS_ON			2	/* IP101A/G APS Mode bit */
+#define IP101A_G_IRQ_CONF_STATUS	0x11	/* Conf Info IRQ & Status Reg */
+#define	IP101A_G_IRQ_PIN_USED		(1<<15) /* INTR pin used */
+#define	IP101A_G_IRQ_DEFAULT		IP101A_G_IRQ_PIN_USED
+
+static int ip175c_config_init(struct phy_device *phydev)
+{
+	int err, i;
+	static int full_reset_performed;
+
+	if (full_reset_performed == 0) {
+
+		/* master reset */
+		err = mdiobus_write(phydev->mdio.bus, 30, 0, 0x175c);
+		if (err < 0)
+			return err;
+
+		/* ensure no bus delays overlap reset period */
+		err = mdiobus_read(phydev->mdio.bus, 30, 0);
+
+		/* data sheet specifies reset period is 2 msec */
+		mdelay(2);
+
+		/* enable IP175C mode */
+		err = mdiobus_write(phydev->mdio.bus, 29, 31, 0x175c);
+		if (err < 0)
+			return err;
+
+		/* Set MII0 speed and duplex (in PHY mode) */
+		err = mdiobus_write(phydev->mdio.bus, 29, 22, 0x420);
+		if (err < 0)
+			return err;
+
+		/* reset switch ports */
+		for (i = 0; i < 5; i++) {
+			err = mdiobus_write(phydev->mdio.bus, i,
+					    MII_BMCR, BMCR_RESET);
+			if (err < 0)
+				return err;
+		}
+
+		for (i = 0; i < 5; i++)
+			err = mdiobus_read(phydev->mdio.bus, i, MII_BMCR);
+
+		mdelay(2);
+
+		full_reset_performed = 1;
+	}
+
+	if (phydev->mdio.addr != 4) {
+		phydev->state = PHY_RUNNING;
+		phydev->speed = SPEED_100;
+		phydev->duplex = DUPLEX_FULL;
+		phydev->link = 1;
+		netif_carrier_on(phydev->attached_dev);
+	}
+
+	return 0;
+}
+
+static int ip1xx_reset(struct phy_device *phydev)
+{
+	int bmcr;
+
+	/* Software Reset PHY */
+	bmcr = phy_read(phydev, MII_BMCR);
+	if (bmcr < 0)
+		return bmcr;
+	bmcr |= BMCR_RESET;
+	bmcr = phy_write(phydev, MII_BMCR, bmcr);
+	if (bmcr < 0)
+		return bmcr;
+
+	do {
+		bmcr = phy_read(phydev, MII_BMCR);
+		if (bmcr < 0)
+			return bmcr;
+	} while (bmcr & BMCR_RESET);
+
+	return 0;
+}
+
+static int ip1001_config_init(struct phy_device *phydev)
+{
+	int c;
+
+	c = ip1xx_reset(phydev);
+	if (c < 0)
+		return c;
+
+	/* Enable Auto Power Saving mode */
+	c = phy_read(phydev, IP1001_SPEC_CTRL_STATUS_2);
+	if (c < 0)
+		return c;
+	c |= IP1001_APS_ON;
+	c = phy_write(phydev, IP1001_SPEC_CTRL_STATUS_2, c);
+	if (c < 0)
+		return c;
+
+	if (phy_interface_is_rgmii(phydev)) {
+
+		c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
+		if (c < 0)
+			return c;
+
+		c &= ~(IP1001_RXPHASE_SEL | IP1001_TXPHASE_SEL);
+
+		if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+			c |= (IP1001_RXPHASE_SEL | IP1001_TXPHASE_SEL);
+		else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+			c |= IP1001_RXPHASE_SEL;
+		else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+			c |= IP1001_TXPHASE_SEL;
+
+		c = phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c);
+		if (c < 0)
+			return c;
+	}
+
+	return 0;
+}
+
+static int ip101a_g_config_init(struct phy_device *phydev)
+{
+	int c;
+
+	c = ip1xx_reset(phydev);
+	if (c < 0)
+		return c;
+
+	/* INTR pin used: speed/link/duplex will cause an interrupt */
+	c = phy_write(phydev, IP101A_G_IRQ_CONF_STATUS, IP101A_G_IRQ_DEFAULT);
+	if (c < 0)
+		return c;
+
+	/* Enable Auto Power Saving mode */
+	c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
+	c |= IP101A_G_APS_ON;
+
+	return phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c);
+}
+
+static int ip175c_read_status(struct phy_device *phydev)
+{
+	if (phydev->mdio.addr == 4) /* WAN port */
+		genphy_read_status(phydev);
+	else
+		/* Don't need to read status for switch ports */
+		phydev->irq = PHY_IGNORE_INTERRUPT;
+
+	return 0;
+}
+
+static int ip175c_config_aneg(struct phy_device *phydev)
+{
+	if (phydev->mdio.addr == 4) /* WAN port */
+		genphy_config_aneg(phydev);
+
+	return 0;
+}
+
+static int ip101a_g_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, IP101A_G_IRQ_CONF_STATUS);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static struct phy_driver icplus_driver[] = {
+{
+	.phy_id		= 0x02430d80,
+	.name		= "ICPlus IP175C",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.config_init	= &ip175c_config_init,
+	.config_aneg	= &ip175c_config_aneg,
+	.read_status	= &ip175c_read_status,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x02430d90,
+	.name		= "ICPlus IP1001",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_GBIT_FEATURES,
+	.config_init	= &ip1001_config_init,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x02430c54,
+	.name		= "ICPlus IP101A/G",
+	.phy_id_mask	= 0x0ffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.ack_interrupt	= ip101a_g_ack_interrupt,
+	.config_init	= &ip101a_g_config_init,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+} };
+
+module_phy_driver(icplus_driver);
+
+static struct mdio_device_id __maybe_unused icplus_tbl[] = {
+	{ 0x02430d80, 0x0ffffff0 },
+	{ 0x02430d90, 0x0ffffff0 },
+	{ 0x02430c54, 0x0ffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, icplus_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/intel-xway.c b/src/kernel/linux/v4.19/drivers/net/phy/intel-xway.c
new file mode 100644
index 0000000..7d936fb
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/intel-xway.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2012 Daniel Schwierzeck <daniel.schwierzeck@googlemail.com>
+ * Copyright (C) 2016 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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/mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#define XWAY_MDIO_IMASK			0x19	/* interrupt mask */
+#define XWAY_MDIO_ISTAT			0x1A	/* interrupt status */
+
+#define XWAY_MDIO_INIT_WOL		BIT(15)	/* Wake-On-LAN */
+#define XWAY_MDIO_INIT_MSRE		BIT(14)
+#define XWAY_MDIO_INIT_NPRX		BIT(13)
+#define XWAY_MDIO_INIT_NPTX		BIT(12)
+#define XWAY_MDIO_INIT_ANE		BIT(11)	/* Auto-Neg error */
+#define XWAY_MDIO_INIT_ANC		BIT(10)	/* Auto-Neg complete */
+#define XWAY_MDIO_INIT_ADSC		BIT(5)	/* Link auto-downspeed detect */
+#define XWAY_MDIO_INIT_MPIPC		BIT(4)
+#define XWAY_MDIO_INIT_MDIXC		BIT(3)
+#define XWAY_MDIO_INIT_DXMC		BIT(2)	/* Duplex mode change */
+#define XWAY_MDIO_INIT_LSPC		BIT(1)	/* Link speed change */
+#define XWAY_MDIO_INIT_LSTC		BIT(0)	/* Link state change */
+#define XWAY_MDIO_INIT_MASK		(XWAY_MDIO_INIT_LSTC | \
+					 XWAY_MDIO_INIT_ADSC)
+
+#define ADVERTISED_MPD			BIT(10)	/* Multi-port device */
+
+/* LED Configuration */
+#define XWAY_MMD_LEDCH			0x01E0
+/* Inverse of SCAN Function */
+#define  XWAY_MMD_LEDCH_NACS_NONE	0x0000
+#define  XWAY_MMD_LEDCH_NACS_LINK	0x0001
+#define  XWAY_MMD_LEDCH_NACS_PDOWN	0x0002
+#define  XWAY_MMD_LEDCH_NACS_EEE	0x0003
+#define  XWAY_MMD_LEDCH_NACS_ANEG	0x0004
+#define  XWAY_MMD_LEDCH_NACS_ABIST	0x0005
+#define  XWAY_MMD_LEDCH_NACS_CDIAG	0x0006
+#define  XWAY_MMD_LEDCH_NACS_TEST	0x0007
+/* Slow Blink Frequency */
+#define  XWAY_MMD_LEDCH_SBF_F02HZ	0x0000
+#define  XWAY_MMD_LEDCH_SBF_F04HZ	0x0010
+#define  XWAY_MMD_LEDCH_SBF_F08HZ	0x0020
+#define  XWAY_MMD_LEDCH_SBF_F16HZ	0x0030
+/* Fast Blink Frequency */
+#define  XWAY_MMD_LEDCH_FBF_F02HZ	0x0000
+#define  XWAY_MMD_LEDCH_FBF_F04HZ	0x0040
+#define  XWAY_MMD_LEDCH_FBF_F08HZ	0x0080
+#define  XWAY_MMD_LEDCH_FBF_F16HZ	0x00C0
+/* LED Configuration */
+#define XWAY_MMD_LEDCL			0x01E1
+/* Complex Blinking Configuration */
+#define  XWAY_MMD_LEDCH_CBLINK_NONE	0x0000
+#define  XWAY_MMD_LEDCH_CBLINK_LINK	0x0001
+#define  XWAY_MMD_LEDCH_CBLINK_PDOWN	0x0002
+#define  XWAY_MMD_LEDCH_CBLINK_EEE	0x0003
+#define  XWAY_MMD_LEDCH_CBLINK_ANEG	0x0004
+#define  XWAY_MMD_LEDCH_CBLINK_ABIST	0x0005
+#define  XWAY_MMD_LEDCH_CBLINK_CDIAG	0x0006
+#define  XWAY_MMD_LEDCH_CBLINK_TEST	0x0007
+/* Complex SCAN Configuration */
+#define  XWAY_MMD_LEDCH_SCAN_NONE	0x0000
+#define  XWAY_MMD_LEDCH_SCAN_LINK	0x0010
+#define  XWAY_MMD_LEDCH_SCAN_PDOWN	0x0020
+#define  XWAY_MMD_LEDCH_SCAN_EEE	0x0030
+#define  XWAY_MMD_LEDCH_SCAN_ANEG	0x0040
+#define  XWAY_MMD_LEDCH_SCAN_ABIST	0x0050
+#define  XWAY_MMD_LEDCH_SCAN_CDIAG	0x0060
+#define  XWAY_MMD_LEDCH_SCAN_TEST	0x0070
+/* Configuration for LED Pin x */
+#define XWAY_MMD_LED0H			0x01E2
+/* Fast Blinking Configuration */
+#define  XWAY_MMD_LEDxH_BLINKF_MASK	0x000F
+#define  XWAY_MMD_LEDxH_BLINKF_NONE	0x0000
+#define  XWAY_MMD_LEDxH_BLINKF_LINK10	0x0001
+#define  XWAY_MMD_LEDxH_BLINKF_LINK100	0x0002
+#define  XWAY_MMD_LEDxH_BLINKF_LINK10X	0x0003
+#define  XWAY_MMD_LEDxH_BLINKF_LINK1000	0x0004
+#define  XWAY_MMD_LEDxH_BLINKF_LINK10_0	0x0005
+#define  XWAY_MMD_LEDxH_BLINKF_LINK100X	0x0006
+#define  XWAY_MMD_LEDxH_BLINKF_LINK10XX	0x0007
+#define  XWAY_MMD_LEDxH_BLINKF_PDOWN	0x0008
+#define  XWAY_MMD_LEDxH_BLINKF_EEE	0x0009
+#define  XWAY_MMD_LEDxH_BLINKF_ANEG	0x000A
+#define  XWAY_MMD_LEDxH_BLINKF_ABIST	0x000B
+#define  XWAY_MMD_LEDxH_BLINKF_CDIAG	0x000C
+/* Constant On Configuration */
+#define  XWAY_MMD_LEDxH_CON_MASK	0x00F0
+#define  XWAY_MMD_LEDxH_CON_NONE	0x0000
+#define  XWAY_MMD_LEDxH_CON_LINK10	0x0010
+#define  XWAY_MMD_LEDxH_CON_LINK100	0x0020
+#define  XWAY_MMD_LEDxH_CON_LINK10X	0x0030
+#define  XWAY_MMD_LEDxH_CON_LINK1000	0x0040
+#define  XWAY_MMD_LEDxH_CON_LINK10_0	0x0050
+#define  XWAY_MMD_LEDxH_CON_LINK100X	0x0060
+#define  XWAY_MMD_LEDxH_CON_LINK10XX	0x0070
+#define  XWAY_MMD_LEDxH_CON_PDOWN	0x0080
+#define  XWAY_MMD_LEDxH_CON_EEE		0x0090
+#define  XWAY_MMD_LEDxH_CON_ANEG	0x00A0
+#define  XWAY_MMD_LEDxH_CON_ABIST	0x00B0
+#define  XWAY_MMD_LEDxH_CON_CDIAG	0x00C0
+#define  XWAY_MMD_LEDxH_CON_COPPER	0x00D0
+#define  XWAY_MMD_LEDxH_CON_FIBER	0x00E0
+/* Configuration for LED Pin x */
+#define XWAY_MMD_LED0L			0x01E3
+/* Pulsing Configuration */
+#define  XWAY_MMD_LEDxL_PULSE_MASK	0x000F
+#define  XWAY_MMD_LEDxL_PULSE_NONE	0x0000
+#define  XWAY_MMD_LEDxL_PULSE_TXACT	0x0001
+#define  XWAY_MMD_LEDxL_PULSE_RXACT	0x0002
+#define  XWAY_MMD_LEDxL_PULSE_COL	0x0004
+/* Slow Blinking Configuration */
+#define  XWAY_MMD_LEDxL_BLINKS_MASK	0x00F0
+#define  XWAY_MMD_LEDxL_BLINKS_NONE	0x0000
+#define  XWAY_MMD_LEDxL_BLINKS_LINK10	0x0010
+#define  XWAY_MMD_LEDxL_BLINKS_LINK100	0x0020
+#define  XWAY_MMD_LEDxL_BLINKS_LINK10X	0x0030
+#define  XWAY_MMD_LEDxL_BLINKS_LINK1000	0x0040
+#define  XWAY_MMD_LEDxL_BLINKS_LINK10_0	0x0050
+#define  XWAY_MMD_LEDxL_BLINKS_LINK100X	0x0060
+#define  XWAY_MMD_LEDxL_BLINKS_LINK10XX	0x0070
+#define  XWAY_MMD_LEDxL_BLINKS_PDOWN	0x0080
+#define  XWAY_MMD_LEDxL_BLINKS_EEE	0x0090
+#define  XWAY_MMD_LEDxL_BLINKS_ANEG	0x00A0
+#define  XWAY_MMD_LEDxL_BLINKS_ABIST	0x00B0
+#define  XWAY_MMD_LEDxL_BLINKS_CDIAG	0x00C0
+#define XWAY_MMD_LED1H			0x01E4
+#define XWAY_MMD_LED1L			0x01E5
+#define XWAY_MMD_LED2H			0x01E6
+#define XWAY_MMD_LED2L			0x01E7
+#define XWAY_MMD_LED3H			0x01E8
+#define XWAY_MMD_LED3L			0x01E9
+
+#define PHY_ID_PHY11G_1_3		0x030260D1
+#define PHY_ID_PHY22F_1_3		0x030260E1
+#define PHY_ID_PHY11G_1_4		0xD565A400
+#define PHY_ID_PHY22F_1_4		0xD565A410
+#define PHY_ID_PHY11G_1_5		0xD565A401
+#define PHY_ID_PHY22F_1_5		0xD565A411
+#define PHY_ID_PHY11G_VR9_1_1		0xD565A408
+#define PHY_ID_PHY22F_VR9_1_1		0xD565A418
+#define PHY_ID_PHY11G_VR9_1_2		0xD565A409
+#define PHY_ID_PHY22F_VR9_1_2		0xD565A419
+
+static int xway_gphy_config_init(struct phy_device *phydev)
+{
+	int err;
+	u32 ledxh;
+	u32 ledxl;
+
+	/* Mask all interrupts */
+	err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
+	if (err)
+		return err;
+
+	/* Clear all pending interrupts */
+	phy_read(phydev, XWAY_MDIO_ISTAT);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDCH,
+		      XWAY_MMD_LEDCH_NACS_NONE |
+		      XWAY_MMD_LEDCH_SBF_F02HZ |
+		      XWAY_MMD_LEDCH_FBF_F16HZ);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDCL,
+		      XWAY_MMD_LEDCH_CBLINK_NONE |
+		      XWAY_MMD_LEDCH_SCAN_NONE);
+
+	/**
+	 * In most cases only one LED is connected to this phy, so
+	 * configure them all to constant on and pulse mode. LED3 is
+	 * only available in some packages, leave it in its reset
+	 * configuration.
+	 */
+	ledxh = XWAY_MMD_LEDxH_BLINKF_NONE | XWAY_MMD_LEDxH_CON_LINK10XX;
+	ledxl = XWAY_MMD_LEDxL_PULSE_TXACT | XWAY_MMD_LEDxL_PULSE_RXACT |
+		XWAY_MMD_LEDxL_BLINKS_NONE;
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED0H, ledxh);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED0L, ledxl);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED1H, ledxh);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED1L, ledxl);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2H, ledxh);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2L, ledxl);
+
+	return 0;
+}
+
+static int xway_gphy14_config_aneg(struct phy_device *phydev)
+{
+	int reg, err;
+
+	/* Advertise as multi-port device, see IEEE802.3-2002 40.5.1.1 */
+	/* This is a workaround for an errata in rev < 1.5 devices */
+	reg = phy_read(phydev, MII_CTRL1000);
+	reg |= ADVERTISED_MPD;
+	err = phy_write(phydev, MII_CTRL1000, reg);
+	if (err)
+		return err;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int xway_gphy_ack_interrupt(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = phy_read(phydev, XWAY_MDIO_ISTAT);
+	return (reg < 0) ? reg : 0;
+}
+
+static int xway_gphy_did_interrupt(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = phy_read(phydev, XWAY_MDIO_ISTAT);
+	return reg & XWAY_MDIO_INIT_MASK;
+}
+
+static int xway_gphy_config_intr(struct phy_device *phydev)
+{
+	u16 mask = 0;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		mask = XWAY_MDIO_INIT_MASK;
+
+	return phy_write(phydev, XWAY_MDIO_IMASK, mask);
+}
+
+static struct phy_driver xway_gphy[] = {
+	{
+		.phy_id		= PHY_ID_PHY11G_1_3,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY11G (PEF 7071/PEF 7072) v1.3",
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.config_aneg	= xway_gphy14_config_aneg,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY22F_1_3,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY22F (PEF 7061) v1.3",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.config_aneg	= xway_gphy14_config_aneg,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY11G_1_4,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY11G (PEF 7071/PEF 7072) v1.4",
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.config_aneg	= xway_gphy14_config_aneg,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY22F_1_4,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY22F (PEF 7061) v1.4",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.config_aneg	= xway_gphy14_config_aneg,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY11G_1_5,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY11G (PEF 7071/PEF 7072) v1.5 / v1.6",
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY22F_1_5,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY22F (PEF 7061) v1.5 / v1.6",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY11G_VR9_1_1,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY11G (xRX v1.1 integrated)",
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY22F_VR9_1_1,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY22F (xRX v1.1 integrated)",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY11G_VR9_1_2,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY11G (xRX v1.2 integrated)",
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= PHY_ID_PHY22F_VR9_1_2,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "Intel XWAY PHY22F (xRX v1.2 integrated)",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= xway_gphy_config_init,
+		.ack_interrupt	= xway_gphy_ack_interrupt,
+		.did_interrupt	= xway_gphy_did_interrupt,
+		.config_intr	= xway_gphy_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	},
+};
+module_phy_driver(xway_gphy);
+
+static struct mdio_device_id __maybe_unused xway_gphy_tbl[] = {
+	{ PHY_ID_PHY11G_1_3, 0xffffffff },
+	{ PHY_ID_PHY22F_1_3, 0xffffffff },
+	{ PHY_ID_PHY11G_1_4, 0xffffffff },
+	{ PHY_ID_PHY22F_1_4, 0xffffffff },
+	{ PHY_ID_PHY11G_1_5, 0xffffffff },
+	{ PHY_ID_PHY22F_1_5, 0xffffffff },
+	{ PHY_ID_PHY11G_VR9_1_1, 0xffffffff },
+	{ PHY_ID_PHY22F_VR9_1_1, 0xffffffff },
+	{ PHY_ID_PHY11G_VR9_1_2, 0xffffffff },
+	{ PHY_ID_PHY22F_VR9_1_2, 0xffffffff },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, xway_gphy_tbl);
+
+MODULE_DESCRIPTION("Intel XWAY PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/ip17xx.c b/src/kernel/linux/v4.19/drivers/net/phy/ip17xx.c
new file mode 100644
index 0000000..85a9617
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ip17xx.c
@@ -0,0 +1,1377 @@
+/*
+ * 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_warning("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_warning("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_warning("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_update_link(struct phy_device *pdev)
+{
+	pdev->link = 1;
+	return 0;
+}
+
+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,
+		.update_link	= ip17xx_update_link,
+		.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 --git a/src/kernel/linux/v4.19/drivers/net/phy/lxt.c b/src/kernel/linux/v4.19/drivers/net/phy/lxt.c
new file mode 100644
index 0000000..c14b254
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/lxt.c
@@ -0,0 +1,300 @@
+/*
+ * drivers/net/phy/lxt.c
+ *
+ * Driver for Intel LXT PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.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 <asm/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+/* The Level one LXT970 is used by many boards				     */
+
+#define MII_LXT970_IER       17  /* Interrupt Enable Register */
+
+#define MII_LXT970_IER_IEN	0x0002
+
+#define MII_LXT970_ISR       18  /* Interrupt Status Register */
+
+#define MII_LXT970_CONFIG    19  /* Configuration Register    */
+
+/* ------------------------------------------------------------------------- */
+/* The Level one LXT971 is used on some of my custom boards                  */
+
+/* register definitions for the 971 */
+#define MII_LXT971_IER		18  /* Interrupt Enable Register */
+#define MII_LXT971_IER_IEN	0x00f2
+
+#define MII_LXT971_ISR		19  /* Interrupt Status Register */
+
+/* register definitions for the 973 */
+#define MII_LXT973_PCR 16 /* Port Configuration Register */
+#define PCR_FIBER_SELECT 1
+
+MODULE_DESCRIPTION("Intel LXT PHY driver");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+static int lxt970_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, MII_BMSR);
+
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_LXT970_ISR);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int lxt970_config_intr(struct phy_device *phydev)
+{
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		return phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN);
+	else
+		return phy_write(phydev, MII_LXT970_IER, 0);
+}
+
+static int lxt970_config_init(struct phy_device *phydev)
+{
+	return phy_write(phydev, MII_LXT970_CONFIG, 0);
+}
+
+
+static int lxt971_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, MII_LXT971_ISR);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int lxt971_config_intr(struct phy_device *phydev)
+{
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		return phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN);
+	else
+		return phy_write(phydev, MII_LXT971_IER, 0);
+}
+
+/*
+ * A2 version of LXT973 chip has an ERRATA: it randomly return the contents
+ * of the previous even register when you read a odd register regularly
+ */
+
+static int lxt973a2_update_link(struct phy_device *phydev)
+{
+	int status;
+	int control;
+	int retry = 8; /* we try 8 times */
+
+	/* Do a fake read */
+	status = phy_read(phydev, MII_BMSR);
+
+	if (status < 0)
+		return status;
+
+	control = phy_read(phydev, MII_BMCR);
+	if (control < 0)
+		return control;
+
+	do {
+		/* Read link and autonegotiation status */
+		status = phy_read(phydev, MII_BMSR);
+	} while (status >= 0 && retry-- && status == control);
+
+	if (status < 0)
+		return status;
+
+	if ((status & BMSR_LSTATUS) == 0)
+		phydev->link = 0;
+	else
+		phydev->link = 1;
+
+	return 0;
+}
+
+static int lxt973a2_read_status(struct phy_device *phydev)
+{
+	int adv;
+	int err;
+	int lpa;
+
+	/* Update the link, but return if there was an error */
+	err = lxt973a2_update_link(phydev);
+	if (err)
+		return err;
+
+	if (AUTONEG_ENABLE == phydev->autoneg) {
+		int retry = 1;
+
+		adv = phy_read(phydev, MII_ADVERTISE);
+
+		if (adv < 0)
+			return adv;
+
+		do {
+			lpa = phy_read(phydev, MII_LPA);
+
+			if (lpa < 0)
+				return lpa;
+
+			/* If both registers are equal, it is suspect but not
+			* impossible, hence a new try
+			*/
+		} while (lpa == adv && retry--);
+
+		phydev->lp_advertising = mii_lpa_to_ethtool_lpa_t(lpa);
+
+		lpa &= adv;
+
+		phydev->speed = SPEED_10;
+		phydev->duplex = DUPLEX_HALF;
+		phydev->pause = phydev->asym_pause = 0;
+
+		if (lpa & (LPA_100FULL | LPA_100HALF)) {
+			phydev->speed = SPEED_100;
+
+			if (lpa & LPA_100FULL)
+				phydev->duplex = DUPLEX_FULL;
+		} else {
+			if (lpa & LPA_10FULL)
+				phydev->duplex = DUPLEX_FULL;
+		}
+
+		if (phydev->duplex == DUPLEX_FULL) {
+			phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0;
+			phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0;
+		}
+	} else {
+		int bmcr = phy_read(phydev, MII_BMCR);
+
+		if (bmcr < 0)
+			return bmcr;
+
+		if (bmcr & BMCR_FULLDPLX)
+			phydev->duplex = DUPLEX_FULL;
+		else
+			phydev->duplex = DUPLEX_HALF;
+
+		if (bmcr & BMCR_SPEED1000)
+			phydev->speed = SPEED_1000;
+		else if (bmcr & BMCR_SPEED100)
+			phydev->speed = SPEED_100;
+		else
+			phydev->speed = SPEED_10;
+
+		phydev->pause = phydev->asym_pause = 0;
+		phydev->lp_advertising = 0;
+	}
+
+	return 0;
+}
+
+static int lxt973_probe(struct phy_device *phydev)
+{
+	int val = phy_read(phydev, MII_LXT973_PCR);
+
+	if (val & PCR_FIBER_SELECT) {
+		/*
+		 * If fiber is selected, then the only correct setting
+		 * is 100Mbps, full duplex, and auto negotiation off.
+		 */
+		val = phy_read(phydev, MII_BMCR);
+		val |= (BMCR_SPEED100 | BMCR_FULLDPLX);
+		val &= ~BMCR_ANENABLE;
+		phy_write(phydev, MII_BMCR, val);
+		/* Remember that the port is in fiber mode. */
+		phydev->priv = lxt973_probe;
+	} else {
+		phydev->priv = NULL;
+	}
+	return 0;
+}
+
+static int lxt973_config_aneg(struct phy_device *phydev)
+{
+	/* Do nothing if port is in fiber mode. */
+	return phydev->priv ? 0 : genphy_config_aneg(phydev);
+}
+
+static struct phy_driver lxt97x_driver[] = {
+{
+	.phy_id		= 0x78100000,
+	.name		= "LXT970",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= lxt970_config_init,
+	.ack_interrupt	= lxt970_ack_interrupt,
+	.config_intr	= lxt970_config_intr,
+}, {
+	.phy_id		= 0x001378e0,
+	.name		= "LXT971",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.ack_interrupt	= lxt971_ack_interrupt,
+	.config_intr	= lxt971_config_intr,
+}, {
+	.phy_id		= 0x00137a10,
+	.name		= "LXT973-A2",
+	.phy_id_mask	= 0xffffffff,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= 0,
+	.probe		= lxt973_probe,
+	.config_aneg	= lxt973_config_aneg,
+	.read_status	= lxt973a2_read_status,
+}, {
+	.phy_id		= 0x00137a10,
+	.name		= "LXT973",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= 0,
+	.probe		= lxt973_probe,
+	.config_aneg	= lxt973_config_aneg,
+} };
+
+module_phy_driver(lxt97x_driver);
+
+static struct mdio_device_id __maybe_unused lxt_tbl[] = {
+	{ 0x78100000, 0xfffffff0 },
+	{ 0x001378e0, 0xfffffff0 },
+	{ 0x00137a10, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, lxt_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/marvell-88q.c b/src/kernel/linux/v4.19/drivers/net/phy/marvell-88q.c
new file mode 100644
index 0000000..6fb740a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/marvell-88q.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#define PHY_ID_88Q2110			0x002b0980
+
+#define Q2110_PMA_PMD_CTRL		(MII_ADDR_C45 | 0x10000)
+/* 1 = PMA/PMD reset, 0 = Normal */
+#define Q2110_PMA_PMD_RST		BIT(15)
+
+#define Q2110_T1_PMA_PMD_CTRL		(MII_ADDR_C45 | 0x10834)
+/* 1 = PHY as master, 0 = PHY as slave */
+#define Q2110_T1_MASTER_SLAVE_CFG	BIT(14)
+/* 0 = 100BASE-T1, 1 = 1000BASE-T1 */
+#define Q2110_T1_LINK_TYPE		BIT(0)
+
+#define Q2110_RST_CTRL			(MII_ADDR_C45 | 0x038000)
+/* software reset of T-unit */
+#define Q2110_RGMII_SW_RESET		BIT(15)
+
+#define Q2110_PCS_CTRL			(MII_ADDR_C45 | 0x030900)
+#define Q2110_LOOPBACK			BIT(14)
+
+#define Q2110_T1_AN_STATUS		(MII_ADDR_C45 | 0x070201)
+/* 1 = Link Up, 0 = Link Down */
+#define Q2110_T1_LINK_STATUS		BIT(2)
+
+#define Q2110_COM_PORT_CTRL		(MII_ADDR_C45 | 0x1f8001)
+/* Add delay on TX_CLK */
+#define Q2110_RGMII_TX_TIMING_CTRL	BIT(15)
+/* Add delay on RX_CLK */
+#define Q2110_RGMII_RX_TIMING_CTRL	BIT(14)
+
+/* Set and/or override some configuration registers based on the
+ * marvell,88q2110 property stored in the of_node for the phydev.
+ * marvell,88q2110 = <speed master>,...;
+ * speed: 1000Mbps or 100Mbps.
+ * master: 1-master, 0-slave.
+ */
+static int q2110_dts_init(struct phy_device *phydev)
+{
+	const __be32 *paddr;
+	u32 speed;
+	u32 master;
+	int val, len;
+
+	if (!phydev->mdio.dev.of_node)
+		return 0;
+
+	paddr = of_get_property(phydev->mdio.dev.of_node,
+				"marvell,88q2110", &len);
+	if (!paddr)
+		return 0;
+
+	speed = be32_to_cpup(paddr);
+	master = be32_to_cpup(paddr + 1);
+
+	val = phy_read(phydev, Q2110_T1_PMA_PMD_CTRL);
+	if (val < 0)
+		return val;
+	val &= ~(Q2110_T1_MASTER_SLAVE_CFG | Q2110_T1_LINK_TYPE);
+	if (speed == SPEED_1000)
+		val |= Q2110_T1_LINK_TYPE;
+	if (master)
+		val |= Q2110_T1_MASTER_SLAVE_CFG;
+	val = phy_write(phydev, Q2110_T1_PMA_PMD_CTRL, val);
+	if (val < 0)
+		return val;
+
+	/* Software Reset PHY */
+	val = phy_read(phydev, Q2110_PMA_PMD_CTRL);
+	if (val < 0)
+		return val;
+	val |= Q2110_PMA_PMD_RST;
+	val = phy_write(phydev, Q2110_PMA_PMD_CTRL, val);
+	if (val < 0)
+		return val;
+
+	do {
+		val = phy_read(phydev, Q2110_PMA_PMD_CTRL);
+		if (val < 0)
+			return val;
+	} while (val & Q2110_PMA_PMD_RST);
+
+	return 0;
+}
+
+static int q2110_timing_init(struct phy_device *phydev)
+{
+	int val;
+
+	if (phy_interface_is_rgmii(phydev)) {
+		val = phy_read(phydev, Q2110_COM_PORT_CTRL);
+		if (val < 0)
+			return val;
+
+		val &= ~(Q2110_RGMII_TX_TIMING_CTRL |
+			 Q2110_RGMII_RX_TIMING_CTRL);
+
+		if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+			val |= (Q2110_RGMII_TX_TIMING_CTRL |
+				Q2110_RGMII_RX_TIMING_CTRL);
+		else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+			val |= Q2110_RGMII_RX_TIMING_CTRL;
+		else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+			val |= Q2110_RGMII_TX_TIMING_CTRL;
+
+		val = phy_write(phydev, Q2110_COM_PORT_CTRL, val);
+		if (val < 0)
+			return val;
+	}
+
+	/* Software Reset of T-unit */
+	val = phy_read(phydev, Q2110_RST_CTRL);
+	if (val < 0)
+		return val;
+	val |= Q2110_RGMII_SW_RESET;
+	val = phy_write(phydev, Q2110_RST_CTRL, val);
+	if (val < 0)
+		return val;
+
+	/* not self-clearing */
+	val = phy_read(phydev, Q2110_RST_CTRL);
+	if (val < 0)
+		return val;
+	val &= ~Q2110_RGMII_SW_RESET;
+	val = phy_write(phydev, Q2110_RST_CTRL, val);
+	if (val < 0)
+		return val;
+
+	return 0;
+}
+
+static int q2110_config_init(struct phy_device *phydev)
+{
+	phydev->supported =
+		SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full;
+	phydev->advertising =
+		SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full;
+	phydev->state = PHY_NOLINK;
+	phydev->autoneg = AUTONEG_DISABLE;
+
+	q2110_dts_init(phydev);
+	q2110_timing_init(phydev);
+
+	return 0;
+}
+
+static int q2110_loopback(struct phy_device *phydev, bool enable)
+{
+	int val;
+
+	val = phy_read(phydev, Q2110_PCS_CTRL);
+	if (val < 0)
+		return val;
+
+	val &= ~Q2110_LOOPBACK;
+	if (enable)
+		val |= Q2110_LOOPBACK;
+
+	val = phy_write(phydev, Q2110_PCS_CTRL, val);
+	if (val < 0)
+		return val;
+
+	return 0;
+}
+
+
+static int q2110_read_status(struct phy_device *phydev)
+{
+	int val;
+
+	phydev->duplex = 1;
+	phydev->pause = 0;
+
+	val = phy_read(phydev, Q2110_T1_AN_STATUS);
+	if (val < 0)
+		return val;
+
+	if (val & Q2110_T1_LINK_STATUS)
+		phydev->link = 1;
+	else
+		phydev->link = 0;
+
+	val = phy_read(phydev, Q2110_T1_PMA_PMD_CTRL);
+	if (val < 0)
+		return val;
+
+	if (val & Q2110_T1_LINK_TYPE)
+		phydev->speed = SPEED_1000;
+	else
+		phydev->speed = SPEED_100;
+
+	return 0;
+}
+
+static int q2110_match_phy_device(struct phy_device *phydev)
+{
+	return (phydev->c45_ids.device_ids[1] & 0xfffffff0) == PHY_ID_88Q2110;
+}
+
+static struct phy_driver marvell_88q_driver[] = {
+	{
+		.phy_id		= PHY_ID_88Q2110,
+		.phy_id_mask	= 0xfffffff0,
+		.name		= "Marvell 88Q2110",
+		.config_init	= q2110_config_init,
+		.match_phy_device = q2110_match_phy_device,
+		.set_loopback   = &q2110_loopback,
+		.read_status	= q2110_read_status,
+	}
+};
+
+module_phy_driver(marvell_88q_driver);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/marvell.c b/src/kernel/linux/v4.19/drivers/net/phy/marvell.c
new file mode 100644
index 0000000..bb6107f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/marvell.c
@@ -0,0 +1,2375 @@
+/*
+ * drivers/net/phy/marvell.c
+ *
+ * Driver for Marvell PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * Copyright (c) 2013 Michael Stapelberg <michael@stapelberg.de>
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/hwmon.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/marvell_phy.h>
+#include <linux/of.h>
+
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+#define MII_MARVELL_PHY_PAGE		22
+#define MII_MARVELL_COPPER_PAGE		0x00
+#define MII_MARVELL_FIBER_PAGE		0x01
+#define MII_MARVELL_MSCR_PAGE		0x02
+#define MII_MARVELL_LED_PAGE		0x03
+#define MII_MARVELL_MISC_TEST_PAGE	0x06
+#define MII_MARVELL_WOL_PAGE		0x11
+
+#define MII_M1011_IEVENT		0x13
+#define MII_M1011_IEVENT_CLEAR		0x0000
+
+#define MII_M1011_IMASK			0x12
+#define MII_M1011_IMASK_INIT		0x6400
+#define MII_M1011_IMASK_CLEAR		0x0000
+
+#define MII_M1011_PHY_SCR			0x10
+#define MII_M1011_PHY_SCR_DOWNSHIFT_EN		BIT(11)
+#define MII_M1011_PHY_SCR_DOWNSHIFT_SHIFT	12
+#define MII_M1011_PHY_SRC_DOWNSHIFT_MASK	0x7800
+#define MII_M1011_PHY_SCR_MDI			(0x0 << 5)
+#define MII_M1011_PHY_SCR_MDI_X			(0x1 << 5)
+#define MII_M1011_PHY_SCR_AUTO_CROSS		(0x3 << 5)
+
+#define MII_M1111_PHY_LED_CONTROL	0x18
+#define MII_M1111_PHY_LED_DIRECT	0x4100
+#define MII_M1111_PHY_LED_COMBINE	0x411c
+#define MII_M1111_PHY_EXT_CR		0x14
+#define MII_M1111_RGMII_RX_DELAY	BIT(7)
+#define MII_M1111_RGMII_TX_DELAY	BIT(1)
+#define MII_M1111_PHY_EXT_SR		0x1b
+
+#define MII_M1111_HWCFG_MODE_MASK		0xf
+#define MII_M1111_HWCFG_MODE_FIBER_RGMII	0x3
+#define MII_M1111_HWCFG_MODE_SGMII_NO_CLK	0x4
+#define MII_M1111_HWCFG_MODE_RTBI		0x7
+#define MII_M1111_HWCFG_MODE_COPPER_RTBI	0x9
+#define MII_M1111_HWCFG_MODE_COPPER_RGMII	0xb
+#define MII_M1111_HWCFG_FIBER_COPPER_RES	BIT(13)
+#define MII_M1111_HWCFG_FIBER_COPPER_AUTO	BIT(15)
+
+#define MII_88E1121_PHY_MSCR_REG	21
+#define MII_88E1121_PHY_MSCR_RX_DELAY	BIT(5)
+#define MII_88E1121_PHY_MSCR_TX_DELAY	BIT(4)
+#define MII_88E1121_PHY_MSCR_DELAY_MASK	(BIT(5) | BIT(4))
+
+#define MII_88E1121_MISC_TEST				0x1a
+#define MII_88E1510_MISC_TEST_TEMP_THRESHOLD_MASK	0x1f00
+#define MII_88E1510_MISC_TEST_TEMP_THRESHOLD_SHIFT	8
+#define MII_88E1510_MISC_TEST_TEMP_IRQ_EN		BIT(7)
+#define MII_88E1510_MISC_TEST_TEMP_IRQ			BIT(6)
+#define MII_88E1121_MISC_TEST_TEMP_SENSOR_EN		BIT(5)
+#define MII_88E1121_MISC_TEST_TEMP_MASK			0x1f
+
+#define MII_88E1510_TEMP_SENSOR		0x1b
+#define MII_88E1510_TEMP_SENSOR_MASK	0xff
+
+#define MII_88E6390_MISC_TEST		0x1b
+#define MII_88E6390_MISC_TEST_SAMPLE_1S		0
+#define MII_88E6390_MISC_TEST_SAMPLE_10MS	BIT(14)
+#define MII_88E6390_MISC_TEST_SAMPLE_DISABLE	BIT(15)
+#define MII_88E6390_MISC_TEST_SAMPLE_ENABLE	0
+#define MII_88E6390_MISC_TEST_SAMPLE_MASK	(0x3 << 14)
+
+#define MII_88E6390_TEMP_SENSOR		0x1c
+#define MII_88E6390_TEMP_SENSOR_MASK	0xff
+#define MII_88E6390_TEMP_SENSOR_SAMPLES 10
+
+#define MII_88E1318S_PHY_MSCR1_REG	16
+#define MII_88E1318S_PHY_MSCR1_PAD_ODD	BIT(6)
+
+/* Copper Specific Interrupt Enable Register */
+#define MII_88E1318S_PHY_CSIER				0x12
+/* WOL Event Interrupt Enable */
+#define MII_88E1318S_PHY_CSIER_WOL_EIE			BIT(7)
+
+/* LED Timer Control Register */
+#define MII_88E1318S_PHY_LED_TCR			0x12
+#define MII_88E1318S_PHY_LED_TCR_FORCE_INT		BIT(15)
+#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE		BIT(7)
+#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW		BIT(11)
+
+/* Magic Packet MAC address registers */
+#define MII_88E1318S_PHY_MAGIC_PACKET_WORD2		0x17
+#define MII_88E1318S_PHY_MAGIC_PACKET_WORD1		0x18
+#define MII_88E1318S_PHY_MAGIC_PACKET_WORD0		0x19
+
+#define MII_88E1318S_PHY_WOL_CTRL				0x10
+#define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS		BIT(12)
+#define MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE	BIT(14)
+
+#define MII_PHY_LED_CTRL	        16
+#define MII_88E1121_PHY_LED_DEF		0x0030
+#define MII_88E1510_PHY_LED_DEF		0x1177
+
+#define MII_M1011_PHY_STATUS		0x11
+#define MII_M1011_PHY_STATUS_1000	0x8000
+#define MII_M1011_PHY_STATUS_100	0x4000
+#define MII_M1011_PHY_STATUS_SPD_MASK	0xc000
+#define MII_M1011_PHY_STATUS_FULLDUPLEX	0x2000
+#define MII_M1011_PHY_STATUS_RESOLVED	0x0800
+#define MII_M1011_PHY_STATUS_LINK	0x0400
+
+#define MII_88E3016_PHY_SPEC_CTRL	0x10
+#define MII_88E3016_DISABLE_SCRAMBLER	0x0200
+#define MII_88E3016_AUTO_MDIX_CROSSOVER	0x0030
+
+#define MII_88E1510_GEN_CTRL_REG_1		0x14
+#define MII_88E1510_GEN_CTRL_REG_1_MODE_MASK	0x7
+#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII	0x1	/* SGMII to copper */
+#define MII_88E1510_GEN_CTRL_REG_1_RESET	0x8000	/* Soft reset */
+
+#define LPA_FIBER_1000HALF	0x40
+#define LPA_FIBER_1000FULL	0x20
+
+#define LPA_PAUSE_FIBER		0x180
+#define LPA_PAUSE_ASYM_FIBER	0x100
+
+#define ADVERTISE_FIBER_1000HALF	0x40
+#define ADVERTISE_FIBER_1000FULL	0x20
+
+#define ADVERTISE_PAUSE_FIBER		0x180
+#define ADVERTISE_PAUSE_ASYM_FIBER	0x100
+
+#define REGISTER_LINK_STATUS	0x400
+#define NB_FIBER_STATS	1
+
+MODULE_DESCRIPTION("Marvell PHY driver");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+struct marvell_hw_stat {
+	const char *string;
+	u8 page;
+	u8 reg;
+	u8 bits;
+};
+
+static struct marvell_hw_stat marvell_hw_stats[] = {
+	{ "phy_receive_errors_copper", 0, 21, 16},
+	{ "phy_idle_errors", 0, 10, 8 },
+	{ "phy_receive_errors_fiber", 1, 21, 16},
+};
+
+struct marvell_priv {
+	u64 stats[ARRAY_SIZE(marvell_hw_stats)];
+	char *hwmon_name;
+	struct device *hwmon_dev;
+};
+
+static int marvell_read_page(struct phy_device *phydev)
+{
+	return __phy_read(phydev, MII_MARVELL_PHY_PAGE);
+}
+
+static int marvell_write_page(struct phy_device *phydev, int page)
+{
+	return __phy_write(phydev, MII_MARVELL_PHY_PAGE, page);
+}
+
+static int marvell_set_page(struct phy_device *phydev, int page)
+{
+	return phy_write(phydev, MII_MARVELL_PHY_PAGE, page);
+}
+
+static int marvell_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	/* Clear the interrupts by reading the reg */
+	err = phy_read(phydev, MII_M1011_IEVENT);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int marvell_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, MII_M1011_IMASK,
+				MII_M1011_IMASK_INIT);
+	else
+		err = phy_write(phydev, MII_M1011_IMASK,
+				MII_M1011_IMASK_CLEAR);
+
+	return err;
+}
+
+static int marvell_set_polarity(struct phy_device *phydev, int polarity)
+{
+	int reg;
+	int err;
+	int val;
+
+	/* get the current settings */
+	reg = phy_read(phydev, MII_M1011_PHY_SCR);
+	if (reg < 0)
+		return reg;
+
+	val = reg;
+	val &= ~MII_M1011_PHY_SCR_AUTO_CROSS;
+	switch (polarity) {
+	case ETH_TP_MDI:
+		val |= MII_M1011_PHY_SCR_MDI;
+		break;
+	case ETH_TP_MDI_X:
+		val |= MII_M1011_PHY_SCR_MDI_X;
+		break;
+	case ETH_TP_MDI_AUTO:
+	case ETH_TP_MDI_INVALID:
+	default:
+		val |= MII_M1011_PHY_SCR_AUTO_CROSS;
+		break;
+	}
+
+	if (val != reg) {
+		/* Set the new polarity value in the register */
+		err = phy_write(phydev, MII_M1011_PHY_SCR, val);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int marvell_set_downshift(struct phy_device *phydev, bool enable,
+				 u8 retries)
+{
+	int reg;
+
+	reg = phy_read(phydev, MII_M1011_PHY_SCR);
+	if (reg < 0)
+		return reg;
+
+	reg &= MII_M1011_PHY_SRC_DOWNSHIFT_MASK;
+	reg |= ((retries - 1) << MII_M1011_PHY_SCR_DOWNSHIFT_SHIFT);
+	if (enable)
+		reg |= MII_M1011_PHY_SCR_DOWNSHIFT_EN;
+
+	return phy_write(phydev, MII_M1011_PHY_SCR, reg);
+}
+
+static int marvell_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, MII_M1111_PHY_LED_CONTROL,
+			MII_M1111_PHY_LED_DIRECT);
+	if (err < 0)
+		return err;
+
+	err = genphy_config_aneg(phydev);
+	if (err < 0)
+		return err;
+
+	if (phydev->autoneg != AUTONEG_ENABLE) {
+		/* A write to speed/duplex bits (that is performed by
+		 * genphy_config_aneg() call above) must be followed by
+		 * a software reset. Otherwise, the write has no effect.
+		 */
+		err = genphy_soft_reset(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int m88e1101_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	/* This Marvell PHY has an errata which requires
+	 * that certain registers get written in order
+	 * to restart autonegotiation
+	 */
+	err = genphy_soft_reset(phydev);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1d, 0x1f);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1e, 0x200c);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1d, 0x5);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1e, 0);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1e, 0x100);
+	if (err < 0)
+		return err;
+
+	return marvell_config_aneg(phydev);
+}
+
+static int m88e1111_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	/* The Marvell PHY has an errata which requires
+	 * that certain registers get written in order
+	 * to restart autonegotiation
+	 */
+	err = genphy_soft_reset(phydev);
+
+	err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, MII_M1111_PHY_LED_CONTROL,
+			MII_M1111_PHY_LED_DIRECT);
+	if (err < 0)
+		return err;
+
+	err = genphy_config_aneg(phydev);
+	if (err < 0)
+		return err;
+
+	if (phydev->autoneg != AUTONEG_ENABLE) {
+		/* A write to speed/duplex bits (that is performed by
+		 * genphy_config_aneg() call above) must be followed by
+		 * a software reset. Otherwise, the write has no effect.
+		 */
+		err = genphy_soft_reset(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_OF_MDIO
+/* Set and/or override some configuration registers based on the
+ * marvell,reg-init property stored in the of_node for the phydev.
+ *
+ * marvell,reg-init = <reg-page reg mask value>,...;
+ *
+ * There may be one or more sets of <reg-page reg mask value>:
+ *
+ * reg-page: which register bank to use.
+ * reg: the register.
+ * mask: if non-zero, ANDed with existing register value.
+ * value: ORed with the masked value and written to the regiser.
+ *
+ */
+static int marvell_of_reg_init(struct phy_device *phydev)
+{
+	const __be32 *paddr;
+	int len, i, saved_page, current_page, ret = 0;
+
+	if (!phydev->mdio.dev.of_node)
+		return 0;
+
+	paddr = of_get_property(phydev->mdio.dev.of_node,
+				"marvell,reg-init", &len);
+	if (!paddr || len < (4 * sizeof(*paddr)))
+		return 0;
+
+	saved_page = phy_save_page(phydev);
+	if (saved_page < 0)
+		goto err;
+	current_page = saved_page;
+
+	len /= sizeof(*paddr);
+	for (i = 0; i < len - 3; i += 4) {
+		u16 page = be32_to_cpup(paddr + i);
+		u16 reg = be32_to_cpup(paddr + i + 1);
+		u16 mask = be32_to_cpup(paddr + i + 2);
+		u16 val_bits = be32_to_cpup(paddr + i + 3);
+		int val;
+
+		if (page != current_page) {
+			current_page = page;
+			ret = marvell_write_page(phydev, page);
+			if (ret < 0)
+				goto err;
+		}
+
+		val = 0;
+		if (mask) {
+			val = __phy_read(phydev, reg);
+			if (val < 0) {
+				ret = val;
+				goto err;
+			}
+			val &= mask;
+		}
+		val |= val_bits;
+
+		ret = __phy_write(phydev, reg, val);
+		if (ret < 0)
+			goto err;
+	}
+err:
+	return phy_restore_page(phydev, saved_page, ret);
+}
+#else
+static int marvell_of_reg_init(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int m88e1121_config_aneg_rgmii_delays(struct phy_device *phydev)
+{
+	int mscr;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+		mscr = MII_88E1121_PHY_MSCR_RX_DELAY |
+		       MII_88E1121_PHY_MSCR_TX_DELAY;
+	else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+		mscr = MII_88E1121_PHY_MSCR_RX_DELAY;
+	else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+		mscr = MII_88E1121_PHY_MSCR_TX_DELAY;
+	else
+		mscr = 0;
+
+	return phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE,
+				MII_88E1121_PHY_MSCR_REG,
+				MII_88E1121_PHY_MSCR_DELAY_MASK, mscr);
+}
+
+static int m88e1121_config_aneg(struct phy_device *phydev)
+{
+	int err = 0;
+
+	if (phy_interface_is_rgmii(phydev)) {
+		err = m88e1121_config_aneg_rgmii_delays(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	err = genphy_soft_reset(phydev);
+	if (err < 0)
+		return err;
+
+	err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+	if (err < 0)
+		return err;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int m88e1318_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE,
+			       MII_88E1318S_PHY_MSCR1_REG,
+			       0, MII_88E1318S_PHY_MSCR1_PAD_ODD);
+	if (err < 0)
+		return err;
+
+	return m88e1121_config_aneg(phydev);
+}
+
+/**
+ * ethtool_adv_to_fiber_adv_t
+ * @ethadv: the ethtool advertisement settings
+ *
+ * A small helper function that translates ethtool advertisement
+ * settings to phy autonegotiation advertisements for the
+ * MII_ADV register for fiber link.
+ */
+static inline u32 ethtool_adv_to_fiber_adv_t(u32 ethadv)
+{
+	u32 result = 0;
+
+	if (ethadv & ADVERTISED_1000baseT_Half)
+		result |= ADVERTISE_FIBER_1000HALF;
+	if (ethadv & ADVERTISED_1000baseT_Full)
+		result |= ADVERTISE_FIBER_1000FULL;
+
+	if ((ethadv & ADVERTISE_PAUSE_ASYM) && (ethadv & ADVERTISE_PAUSE_CAP))
+		result |= LPA_PAUSE_ASYM_FIBER;
+	else if (ethadv & ADVERTISE_PAUSE_CAP)
+		result |= (ADVERTISE_PAUSE_FIBER
+			   & (~ADVERTISE_PAUSE_ASYM_FIBER));
+
+	return result;
+}
+
+/**
+ * marvell_config_aneg_fiber - restart auto-negotiation or write BMCR
+ * @phydev: target phy_device struct
+ *
+ * Description: If auto-negotiation is enabled, we configure the
+ *   advertising, and then restart auto-negotiation.  If it is not
+ *   enabled, then we write the BMCR. Adapted for fiber link in
+ *   some Marvell's devices.
+ */
+static int marvell_config_aneg_fiber(struct phy_device *phydev)
+{
+	int changed = 0;
+	int err;
+	int adv, oldadv;
+	u32 advertise;
+
+	if (phydev->autoneg != AUTONEG_ENABLE)
+		return genphy_setup_forced(phydev);
+
+	/* Only allow advertising what this PHY supports */
+	phydev->advertising &= phydev->supported;
+	advertise = phydev->advertising;
+
+	/* Setup fiber advertisement */
+	adv = phy_read(phydev, MII_ADVERTISE);
+	if (adv < 0)
+		return adv;
+
+	oldadv = adv;
+	adv &= ~(ADVERTISE_FIBER_1000HALF | ADVERTISE_FIBER_1000FULL
+		| LPA_PAUSE_FIBER);
+	adv |= ethtool_adv_to_fiber_adv_t(advertise);
+
+	if (adv != oldadv) {
+		err = phy_write(phydev, MII_ADVERTISE, adv);
+		if (err < 0)
+			return err;
+
+		changed = 1;
+	}
+
+	if (changed == 0) {
+		/* Advertisement hasn't changed, but maybe aneg was never on to
+		 * begin with?	Or maybe phy was isolated?
+		 */
+		int ctl = phy_read(phydev, MII_BMCR);
+
+		if (ctl < 0)
+			return ctl;
+
+		if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE))
+			changed = 1; /* do restart aneg */
+	}
+
+	/* Only restart aneg if we are advertising something different
+	 * than we were before.
+	 */
+	if (changed > 0)
+		changed = genphy_restart_aneg(phydev);
+
+	return changed;
+}
+
+static int m88e1510_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	if (err < 0)
+		goto error;
+
+	/* Configure the copper link first */
+	err = m88e1318_config_aneg(phydev);
+	if (err < 0)
+		goto error;
+
+	/* Do not touch the fiber page if we're in copper->sgmii mode */
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII)
+		return 0;
+
+	/* Then the fiber link */
+	err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
+	if (err < 0)
+		goto error;
+
+	err = marvell_config_aneg_fiber(phydev);
+	if (err < 0)
+		goto error;
+
+	return marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+
+error:
+	marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	return err;
+}
+
+static void marvell_config_led(struct phy_device *phydev)
+{
+	u16 def_config;
+	int err;
+
+	switch (MARVELL_PHY_FAMILY_ID(phydev->phy_id)) {
+	/* Default PHY LED config: LED[0] .. Link, LED[1] .. Activity */
+	case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1121R):
+	case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1318S):
+		def_config = MII_88E1121_PHY_LED_DEF;
+		break;
+	/* Default PHY LED config:
+	 * LED[0] .. 1000Mbps Link
+	 * LED[1] .. 100Mbps Link
+	 * LED[2] .. Blink, Activity
+	 */
+	case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1510):
+		def_config = MII_88E1510_PHY_LED_DEF;
+		break;
+	default:
+		return;
+	}
+
+	err = phy_write_paged(phydev, MII_MARVELL_LED_PAGE, MII_PHY_LED_CTRL,
+			      def_config);
+	if (err < 0)
+		pr_warn("Fail to config marvell phy LED.\n");
+}
+
+static int marvell_config_init(struct phy_device *phydev)
+{
+	/* Set defalut LED */
+	marvell_config_led(phydev);
+
+	/* Set registers from marvell,reg-init DT property */
+	return marvell_of_reg_init(phydev);
+}
+
+static int m88e1116r_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	err = genphy_soft_reset(phydev);
+	if (err < 0)
+		return err;
+
+	msleep(500);
+
+	err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	if (err < 0)
+		return err;
+
+	err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+	if (err < 0)
+		return err;
+
+	err = marvell_set_downshift(phydev, true, 8);
+	if (err < 0)
+		return err;
+
+	if (phy_interface_is_rgmii(phydev)) {
+		err = m88e1121_config_aneg_rgmii_delays(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	err = genphy_soft_reset(phydev);
+	if (err < 0)
+		return err;
+
+	return marvell_config_init(phydev);
+}
+
+static int m88e3016_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Enable Scrambler and Auto-Crossover */
+	ret = phy_modify(phydev, MII_88E3016_PHY_SPEC_CTRL,
+			 MII_88E3016_DISABLE_SCRAMBLER,
+			 MII_88E3016_AUTO_MDIX_CROSSOVER);
+	if (ret < 0)
+		return ret;
+
+	return marvell_config_init(phydev);
+}
+
+static int m88e1111_config_init_hwcfg_mode(struct phy_device *phydev,
+					   u16 mode,
+					   int fibre_copper_auto)
+{
+	if (fibre_copper_auto)
+		mode |= MII_M1111_HWCFG_FIBER_COPPER_AUTO;
+
+	return phy_modify(phydev, MII_M1111_PHY_EXT_SR,
+			  MII_M1111_HWCFG_MODE_MASK |
+			  MII_M1111_HWCFG_FIBER_COPPER_AUTO |
+			  MII_M1111_HWCFG_FIBER_COPPER_RES,
+			  mode);
+}
+
+static int m88e1111_config_init_rgmii_delays(struct phy_device *phydev)
+{
+	int delay;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
+		delay = MII_M1111_RGMII_RX_DELAY | MII_M1111_RGMII_TX_DELAY;
+	} else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+		delay = MII_M1111_RGMII_RX_DELAY;
+	} else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+		delay = MII_M1111_RGMII_TX_DELAY;
+	} else {
+		delay = 0;
+	}
+
+	return phy_modify(phydev, MII_M1111_PHY_EXT_CR,
+			  MII_M1111_RGMII_RX_DELAY | MII_M1111_RGMII_TX_DELAY,
+			  delay);
+}
+
+static int m88e1111_config_init_rgmii(struct phy_device *phydev)
+{
+	int temp;
+	int err;
+
+	err = m88e1111_config_init_rgmii_delays(phydev);
+	if (err < 0)
+		return err;
+
+	temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+	if (temp < 0)
+		return temp;
+
+	temp &= ~(MII_M1111_HWCFG_MODE_MASK);
+
+	if (temp & MII_M1111_HWCFG_FIBER_COPPER_RES)
+		temp |= MII_M1111_HWCFG_MODE_FIBER_RGMII;
+	else
+		temp |= MII_M1111_HWCFG_MODE_COPPER_RGMII;
+
+	return phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
+}
+
+static int m88e1111_config_init_sgmii(struct phy_device *phydev)
+{
+	int err;
+
+	err = m88e1111_config_init_hwcfg_mode(
+		phydev,
+		MII_M1111_HWCFG_MODE_SGMII_NO_CLK,
+		MII_M1111_HWCFG_FIBER_COPPER_AUTO);
+	if (err < 0)
+		return err;
+
+	/* make sure copper is selected */
+	return marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+}
+
+static int m88e1111_config_init_rtbi(struct phy_device *phydev)
+{
+	int err;
+
+	err = m88e1111_config_init_rgmii_delays(phydev);
+	if (err < 0)
+		return err;
+
+	err = m88e1111_config_init_hwcfg_mode(
+		phydev,
+		MII_M1111_HWCFG_MODE_RTBI,
+		MII_M1111_HWCFG_FIBER_COPPER_AUTO);
+	if (err < 0)
+		return err;
+
+	/* soft reset */
+	err = genphy_soft_reset(phydev);
+	if (err < 0)
+		return err;
+
+	return m88e1111_config_init_hwcfg_mode(
+		phydev,
+		MII_M1111_HWCFG_MODE_RTBI,
+		MII_M1111_HWCFG_FIBER_COPPER_AUTO);
+}
+
+static int m88e1111_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	if (phy_interface_is_rgmii(phydev)) {
+		err = m88e1111_config_init_rgmii(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+		err = m88e1111_config_init_sgmii(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RTBI) {
+		err = m88e1111_config_init_rtbi(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	err = marvell_of_reg_init(phydev);
+	if (err < 0)
+		return err;
+
+	return genphy_soft_reset(phydev);
+}
+
+static int m88e1318_config_init(struct phy_device *phydev)
+{
+	if (phy_interrupt_is_valid(phydev)) {
+		int err = phy_modify_paged(
+			phydev, MII_MARVELL_LED_PAGE,
+			MII_88E1318S_PHY_LED_TCR,
+			MII_88E1318S_PHY_LED_TCR_FORCE_INT,
+			MII_88E1318S_PHY_LED_TCR_INTn_ENABLE |
+			MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW);
+		if (err < 0)
+			return err;
+	}
+
+	return marvell_config_init(phydev);
+}
+
+static int m88e1510_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	/* SGMII-to-Copper mode initialization */
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+		/* Select page 18 */
+		err = marvell_set_page(phydev, 18);
+		if (err < 0)
+			return err;
+
+		/* In reg 20, write MODE[2:0] = 0x1 (SGMII to Copper) */
+		err = phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1,
+				 MII_88E1510_GEN_CTRL_REG_1_MODE_MASK,
+				 MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII);
+		if (err < 0)
+			return err;
+
+		/* PHY reset is necessary after changing MODE[2:0] */
+		err = phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1, 0,
+				 MII_88E1510_GEN_CTRL_REG_1_RESET);
+		if (err < 0)
+			return err;
+
+		/* Reset page selection */
+		err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+		if (err < 0)
+			return err;
+	}
+
+	return m88e1318_config_init(phydev);
+}
+
+static int m88e1118_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	err = genphy_soft_reset(phydev);
+	if (err < 0)
+		return err;
+
+	err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+	if (err < 0)
+		return err;
+
+	err = genphy_config_aneg(phydev);
+	return 0;
+}
+
+static int m88e1118_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	/* Change address */
+	err = marvell_set_page(phydev, MII_MARVELL_MSCR_PAGE);
+	if (err < 0)
+		return err;
+
+	/* Enable 1000 Mbit */
+	err = phy_write(phydev, 0x15, 0x1070);
+	if (err < 0)
+		return err;
+
+	/* Change address */
+	err = marvell_set_page(phydev, MII_MARVELL_LED_PAGE);
+	if (err < 0)
+		return err;
+
+	/* Adjust LED Control */
+	if (phydev->dev_flags & MARVELL_PHY_M1118_DNS323_LEDS)
+		err = phy_write(phydev, 0x10, 0x1100);
+	else
+		err = phy_write(phydev, 0x10, 0x021e);
+	if (err < 0)
+		return err;
+
+	err = marvell_of_reg_init(phydev);
+	if (err < 0)
+		return err;
+
+	/* Reset address */
+	err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	if (err < 0)
+		return err;
+
+	return genphy_soft_reset(phydev);
+}
+
+static int m88e1149_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	/* Change address */
+	err = marvell_set_page(phydev, MII_MARVELL_MSCR_PAGE);
+	if (err < 0)
+		return err;
+
+	/* Enable 1000 Mbit */
+	err = phy_write(phydev, 0x15, 0x1048);
+	if (err < 0)
+		return err;
+
+	err = marvell_of_reg_init(phydev);
+	if (err < 0)
+		return err;
+
+	/* Reset address */
+	err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	if (err < 0)
+		return err;
+
+	return genphy_soft_reset(phydev);
+}
+
+static int m88e1145_config_init_rgmii(struct phy_device *phydev)
+{
+	int err;
+
+	err = m88e1111_config_init_rgmii_delays(phydev);
+	if (err < 0)
+		return err;
+
+	if (phydev->dev_flags & MARVELL_PHY_M1145_FLAGS_RESISTANCE) {
+		err = phy_write(phydev, 0x1d, 0x0012);
+		if (err < 0)
+			return err;
+
+		err = phy_modify(phydev, 0x1e, 0x0fc0,
+				 2 << 9 | /* 36 ohm */
+				 2 << 6); /* 39 ohm */
+		if (err < 0)
+			return err;
+
+		err = phy_write(phydev, 0x1d, 0x3);
+		if (err < 0)
+			return err;
+
+		err = phy_write(phydev, 0x1e, 0x8000);
+	}
+	return err;
+}
+
+static int m88e1145_config_init_sgmii(struct phy_device *phydev)
+{
+	return m88e1111_config_init_hwcfg_mode(
+		phydev, MII_M1111_HWCFG_MODE_SGMII_NO_CLK,
+		MII_M1111_HWCFG_FIBER_COPPER_AUTO);
+}
+
+static int m88e1145_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	/* Take care of errata E0 & E1 */
+	err = phy_write(phydev, 0x1d, 0x001b);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1e, 0x418f);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1d, 0x0016);
+	if (err < 0)
+		return err;
+
+	err = phy_write(phydev, 0x1e, 0xa2da);
+	if (err < 0)
+		return err;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
+		err = m88e1145_config_init_rgmii(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+		err = m88e1145_config_init_sgmii(phydev);
+		if (err < 0)
+			return err;
+	}
+
+	err = marvell_of_reg_init(phydev);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/* The VOD can be out of specification on link up. Poke an
+ * undocumented register, in an undocumented page, with a magic value
+ * to fix this.
+ */
+static int m88e6390_errata(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, MII_BMCR,
+			BMCR_ANENABLE | BMCR_SPEED1000 | BMCR_FULLDPLX);
+	if (err)
+		return err;
+
+	usleep_range(300, 400);
+
+	err = phy_write_paged(phydev, 0xf8, 0x08, 0x36);
+	if (err)
+		return err;
+
+	return genphy_soft_reset(phydev);
+}
+
+static int m88e6390_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	err = m88e6390_errata(phydev);
+	if (err)
+		return err;
+
+	return m88e1510_config_aneg(phydev);
+}
+
+/**
+ * fiber_lpa_to_ethtool_lpa_t
+ * @lpa: value of the MII_LPA register for fiber link
+ *
+ * A small helper function that translates MII_LPA
+ * bits to ethtool LP advertisement settings.
+ */
+static u32 fiber_lpa_to_ethtool_lpa_t(u32 lpa)
+{
+	u32 result = 0;
+
+	if (lpa & LPA_FIBER_1000HALF)
+		result |= ADVERTISED_1000baseT_Half;
+	if (lpa & LPA_FIBER_1000FULL)
+		result |= ADVERTISED_1000baseT_Full;
+
+	return result;
+}
+
+/**
+ * marvell_update_link - update link status in real time in @phydev
+ * @phydev: target phy_device struct
+ *
+ * Description: Update the value in phydev->link to reflect the
+ *   current link value.
+ */
+static int marvell_update_link(struct phy_device *phydev, int fiber)
+{
+	int status;
+
+	/* Use the generic register for copper link, or specific
+	 * register for fiber case
+	 */
+	if (fiber) {
+		status = phy_read(phydev, MII_M1011_PHY_STATUS);
+		if (status < 0)
+			return status;
+
+		if ((status & REGISTER_LINK_STATUS) == 0)
+			phydev->link = 0;
+		else
+			phydev->link = 1;
+	} else {
+		return genphy_update_link(phydev);
+	}
+
+	return 0;
+}
+
+static int marvell_read_status_page_an(struct phy_device *phydev,
+				       int fiber)
+{
+	int status;
+	int lpa;
+	int lpagb;
+
+	status = phy_read(phydev, MII_M1011_PHY_STATUS);
+	if (status < 0)
+		return status;
+
+	lpa = phy_read(phydev, MII_LPA);
+	if (lpa < 0)
+		return lpa;
+
+	lpagb = phy_read(phydev, MII_STAT1000);
+	if (lpagb < 0)
+		return lpagb;
+
+	if (status & MII_M1011_PHY_STATUS_FULLDUPLEX)
+		phydev->duplex = DUPLEX_FULL;
+	else
+		phydev->duplex = DUPLEX_HALF;
+
+	status = status & MII_M1011_PHY_STATUS_SPD_MASK;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+
+	switch (status) {
+	case MII_M1011_PHY_STATUS_1000:
+		phydev->speed = SPEED_1000;
+		break;
+
+	case MII_M1011_PHY_STATUS_100:
+		phydev->speed = SPEED_100;
+		break;
+
+	default:
+		phydev->speed = SPEED_10;
+		break;
+	}
+
+	if (!fiber) {
+		phydev->lp_advertising =
+			mii_stat1000_to_ethtool_lpa_t(lpagb) |
+			mii_lpa_to_ethtool_lpa_t(lpa);
+
+		if (phydev->duplex == DUPLEX_FULL) {
+			phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0;
+			phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0;
+		}
+	} else {
+		/* The fiber link is only 1000M capable */
+		phydev->lp_advertising = fiber_lpa_to_ethtool_lpa_t(lpa);
+
+		if (phydev->duplex == DUPLEX_FULL) {
+			if (!(lpa & LPA_PAUSE_FIBER)) {
+				phydev->pause = 0;
+				phydev->asym_pause = 0;
+			} else if ((lpa & LPA_PAUSE_ASYM_FIBER)) {
+				phydev->pause = 1;
+				phydev->asym_pause = 1;
+			} else {
+				phydev->pause = 1;
+				phydev->asym_pause = 0;
+			}
+		}
+	}
+	return 0;
+}
+
+static int marvell_read_status_page_fixed(struct phy_device *phydev)
+{
+	int bmcr = phy_read(phydev, MII_BMCR);
+
+	if (bmcr < 0)
+		return bmcr;
+
+	if (bmcr & BMCR_FULLDPLX)
+		phydev->duplex = DUPLEX_FULL;
+	else
+		phydev->duplex = DUPLEX_HALF;
+
+	if (bmcr & BMCR_SPEED1000)
+		phydev->speed = SPEED_1000;
+	else if (bmcr & BMCR_SPEED100)
+		phydev->speed = SPEED_100;
+	else
+		phydev->speed = SPEED_10;
+
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+	phydev->lp_advertising = 0;
+
+	return 0;
+}
+
+/* marvell_read_status_page
+ *
+ * Description:
+ *   Check the link, then figure out the current state
+ *   by comparing what we advertise with what the link partner
+ *   advertises.  Start by checking the gigabit possibilities,
+ *   then move on to 10/100.
+ */
+static int marvell_read_status_page(struct phy_device *phydev, int page)
+{
+	int fiber;
+	int err;
+
+	/* Detect and update the link, but return if there
+	 * was an error
+	 */
+	if (page == MII_MARVELL_FIBER_PAGE)
+		fiber = 1;
+	else
+		fiber = 0;
+
+	err = marvell_update_link(phydev, fiber);
+	if (err)
+		return err;
+
+	if (phydev->autoneg == AUTONEG_ENABLE)
+		err = marvell_read_status_page_an(phydev, fiber);
+	else
+		err = marvell_read_status_page_fixed(phydev);
+
+	return err;
+}
+
+/* marvell_read_status
+ *
+ * Some Marvell's phys have two modes: fiber and copper.
+ * Both need status checked.
+ * Description:
+ *   First, check the fiber link and status.
+ *   If the fiber link is down, check the copper link and status which
+ *   will be the default value if both link are down.
+ */
+static int marvell_read_status(struct phy_device *phydev)
+{
+	int err;
+
+	/* Check the fiber mode first */
+	if (phydev->supported & SUPPORTED_FIBRE &&
+	    phydev->interface != PHY_INTERFACE_MODE_SGMII) {
+		err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
+		if (err < 0)
+			goto error;
+
+		err = marvell_read_status_page(phydev, MII_MARVELL_FIBER_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* If the fiber link is up, it is the selected and
+		 * used link. In this case, we need to stay in the
+		 * fiber page. Please to be careful about that, avoid
+		 * to restore Copper page in other functions which
+		 * could break the behaviour for some fiber phy like
+		 * 88E1512.
+		 */
+		if (phydev->link)
+			return 0;
+
+		/* If fiber link is down, check and save copper mode state */
+		err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+		if (err < 0)
+			goto error;
+	}
+
+	return marvell_read_status_page(phydev, MII_MARVELL_COPPER_PAGE);
+
+error:
+	marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	return err;
+}
+
+/* marvell_suspend
+ *
+ * Some Marvell's phys have two modes: fiber and copper.
+ * Both need to be suspended
+ */
+static int marvell_suspend(struct phy_device *phydev)
+{
+	int err;
+
+	/* Suspend the fiber mode first */
+	if (!(phydev->supported & SUPPORTED_FIBRE)) {
+		err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* With the page set, use the generic suspend */
+		err = genphy_suspend(phydev);
+		if (err < 0)
+			goto error;
+
+		/* Then, the copper link */
+		err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+		if (err < 0)
+			goto error;
+	}
+
+	/* With the page set, use the generic suspend */
+	return genphy_suspend(phydev);
+
+error:
+	marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	return err;
+}
+
+/* marvell_resume
+ *
+ * Some Marvell's phys have two modes: fiber and copper.
+ * Both need to be resumed
+ */
+static int marvell_resume(struct phy_device *phydev)
+{
+	int err;
+
+	/* Resume the fiber mode first */
+	if (!(phydev->supported & SUPPORTED_FIBRE)) {
+		err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* With the page set, use the generic resume */
+		err = genphy_resume(phydev);
+		if (err < 0)
+			goto error;
+
+		/* Then, the copper link */
+		err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+		if (err < 0)
+			goto error;
+	}
+
+	/* With the page set, use the generic resume */
+	return genphy_resume(phydev);
+
+error:
+	marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+	return err;
+}
+
+static int marvell_aneg_done(struct phy_device *phydev)
+{
+	int retval = phy_read(phydev, MII_M1011_PHY_STATUS);
+
+	return (retval < 0) ? retval : (retval & MII_M1011_PHY_STATUS_RESOLVED);
+}
+
+static int m88e1121_did_interrupt(struct phy_device *phydev)
+{
+	int imask;
+
+	imask = phy_read(phydev, MII_M1011_IEVENT);
+
+	if (imask & MII_M1011_IMASK_INIT)
+		return 1;
+
+	return 0;
+}
+
+static void m88e1318_get_wol(struct phy_device *phydev,
+			     struct ethtool_wolinfo *wol)
+{
+	int oldpage, ret = 0;
+
+	wol->supported = WAKE_MAGIC;
+	wol->wolopts = 0;
+
+	oldpage = phy_select_page(phydev, MII_MARVELL_WOL_PAGE);
+	if (oldpage < 0)
+		goto error;
+
+	ret = __phy_read(phydev, MII_88E1318S_PHY_WOL_CTRL);
+	if (ret & MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE)
+		wol->wolopts |= WAKE_MAGIC;
+
+error:
+	phy_restore_page(phydev, oldpage, ret);
+}
+
+static int m88e1318_set_wol(struct phy_device *phydev,
+			    struct ethtool_wolinfo *wol)
+{
+	int err = 0, oldpage;
+
+	oldpage = phy_save_page(phydev);
+	if (oldpage < 0)
+		goto error;
+
+	if (wol->wolopts & WAKE_MAGIC) {
+		/* Explicitly switch to page 0x00, just to be sure */
+		err = marvell_write_page(phydev, MII_MARVELL_COPPER_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* If WOL event happened once, the LED[2] interrupt pin
+		 * will not be cleared unless we reading the interrupt status
+		 * register. If interrupts are in use, the normal interrupt
+		 * handling will clear the WOL event. Clear the WOL event
+		 * before enabling it if !phy_interrupt_is_valid()
+		 */
+		if (!phy_interrupt_is_valid(phydev))
+			__phy_read(phydev, MII_M1011_IEVENT);
+
+		/* Enable the WOL interrupt */
+		err = __phy_modify(phydev, MII_88E1318S_PHY_CSIER, 0,
+				   MII_88E1318S_PHY_CSIER_WOL_EIE);
+		if (err < 0)
+			goto error;
+
+		err = marvell_write_page(phydev, MII_MARVELL_LED_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* Setup LED[2] as interrupt pin (active low) */
+		err = __phy_modify(phydev, MII_88E1318S_PHY_LED_TCR,
+				   MII_88E1318S_PHY_LED_TCR_FORCE_INT,
+				   MII_88E1318S_PHY_LED_TCR_INTn_ENABLE |
+				   MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW);
+		if (err < 0)
+			goto error;
+
+		err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* Store the device address for the magic packet */
+		err = __phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD2,
+				((phydev->attached_dev->dev_addr[5] << 8) |
+				 phydev->attached_dev->dev_addr[4]));
+		if (err < 0)
+			goto error;
+		err = __phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD1,
+				((phydev->attached_dev->dev_addr[3] << 8) |
+				 phydev->attached_dev->dev_addr[2]));
+		if (err < 0)
+			goto error;
+		err = __phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD0,
+				((phydev->attached_dev->dev_addr[1] << 8) |
+				 phydev->attached_dev->dev_addr[0]));
+		if (err < 0)
+			goto error;
+
+		/* Clear WOL status and enable magic packet matching */
+		err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL, 0,
+				   MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS |
+				   MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE);
+		if (err < 0)
+			goto error;
+	} else {
+		err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
+		if (err < 0)
+			goto error;
+
+		/* Clear WOL status and disable magic packet matching */
+		err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL,
+				   MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE,
+				   MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
+		if (err < 0)
+			goto error;
+	}
+
+error:
+	return phy_restore_page(phydev, oldpage, err);
+}
+
+static int marvell_get_sset_count(struct phy_device *phydev)
+{
+	if (phydev->supported & SUPPORTED_FIBRE)
+		return ARRAY_SIZE(marvell_hw_stats);
+	else
+		return ARRAY_SIZE(marvell_hw_stats) - NB_FIBER_STATS;
+}
+
+static void marvell_get_strings(struct phy_device *phydev, u8 *data)
+{
+	int count = marvell_get_sset_count(phydev);
+	int i;
+
+	for (i = 0; i < count; i++) {
+		strlcpy(data + i * ETH_GSTRING_LEN,
+			marvell_hw_stats[i].string, ETH_GSTRING_LEN);
+	}
+}
+
+static u64 marvell_get_stat(struct phy_device *phydev, int i)
+{
+	struct marvell_hw_stat stat = marvell_hw_stats[i];
+	struct marvell_priv *priv = phydev->priv;
+	int val;
+	u64 ret;
+
+	val = phy_read_paged(phydev, stat.page, stat.reg);
+	if (val < 0) {
+		ret = U64_MAX;
+	} else {
+		val = val & ((1 << stat.bits) - 1);
+		priv->stats[i] += val;
+		ret = priv->stats[i];
+	}
+
+	return ret;
+}
+
+static void marvell_get_stats(struct phy_device *phydev,
+			      struct ethtool_stats *stats, u64 *data)
+{
+	int count = marvell_get_sset_count(phydev);
+	int i;
+
+	for (i = 0; i < count; i++)
+		data[i] = marvell_get_stat(phydev, i);
+}
+
+#ifdef CONFIG_HWMON
+static int m88e1121_get_temp(struct phy_device *phydev, long *temp)
+{
+	int oldpage;
+	int ret = 0;
+	int val;
+
+	*temp = 0;
+
+	oldpage = phy_select_page(phydev, MII_MARVELL_MISC_TEST_PAGE);
+	if (oldpage < 0)
+		goto error;
+
+	/* Enable temperature sensor */
+	ret = __phy_read(phydev, MII_88E1121_MISC_TEST);
+	if (ret < 0)
+		goto error;
+
+	ret = __phy_write(phydev, MII_88E1121_MISC_TEST,
+			  ret | MII_88E1121_MISC_TEST_TEMP_SENSOR_EN);
+	if (ret < 0)
+		goto error;
+
+	/* Wait for temperature to stabilize */
+	usleep_range(10000, 12000);
+
+	val = __phy_read(phydev, MII_88E1121_MISC_TEST);
+	if (val < 0) {
+		ret = val;
+		goto error;
+	}
+
+	/* Disable temperature sensor */
+	ret = __phy_write(phydev, MII_88E1121_MISC_TEST,
+			  ret & ~MII_88E1121_MISC_TEST_TEMP_SENSOR_EN);
+	if (ret < 0)
+		goto error;
+
+	*temp = ((val & MII_88E1121_MISC_TEST_TEMP_MASK) - 5) * 5000;
+
+error:
+	return phy_restore_page(phydev, oldpage, ret);
+}
+
+static int m88e1121_hwmon_read(struct device *dev,
+			       enum hwmon_sensor_types type,
+			       u32 attr, int channel, long *temp)
+{
+	struct phy_device *phydev = dev_get_drvdata(dev);
+	int err;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		err = m88e1121_get_temp(phydev, temp);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return err;
+}
+
+static umode_t m88e1121_hwmon_is_visible(const void *data,
+					 enum hwmon_sensor_types type,
+					 u32 attr, int channel)
+{
+	if (type != hwmon_temp)
+		return 0;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		return 0444;
+	default:
+		return 0;
+	}
+}
+
+static u32 m88e1121_hwmon_chip_config[] = {
+	HWMON_C_REGISTER_TZ,
+	0
+};
+
+static const struct hwmon_channel_info m88e1121_hwmon_chip = {
+	.type = hwmon_chip,
+	.config = m88e1121_hwmon_chip_config,
+};
+
+static u32 m88e1121_hwmon_temp_config[] = {
+	HWMON_T_INPUT,
+	0
+};
+
+static const struct hwmon_channel_info m88e1121_hwmon_temp = {
+	.type = hwmon_temp,
+	.config = m88e1121_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info *m88e1121_hwmon_info[] = {
+	&m88e1121_hwmon_chip,
+	&m88e1121_hwmon_temp,
+	NULL
+};
+
+static const struct hwmon_ops m88e1121_hwmon_hwmon_ops = {
+	.is_visible = m88e1121_hwmon_is_visible,
+	.read = m88e1121_hwmon_read,
+};
+
+static const struct hwmon_chip_info m88e1121_hwmon_chip_info = {
+	.ops = &m88e1121_hwmon_hwmon_ops,
+	.info = m88e1121_hwmon_info,
+};
+
+static int m88e1510_get_temp(struct phy_device *phydev, long *temp)
+{
+	int ret;
+
+	*temp = 0;
+
+	ret = phy_read_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+			     MII_88E1510_TEMP_SENSOR);
+	if (ret < 0)
+		return ret;
+
+	*temp = ((ret & MII_88E1510_TEMP_SENSOR_MASK) - 25) * 1000;
+
+	return 0;
+}
+
+static int m88e1510_get_temp_critical(struct phy_device *phydev, long *temp)
+{
+	int ret;
+
+	*temp = 0;
+
+	ret = phy_read_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+			     MII_88E1121_MISC_TEST);
+	if (ret < 0)
+		return ret;
+
+	*temp = (((ret & MII_88E1510_MISC_TEST_TEMP_THRESHOLD_MASK) >>
+		  MII_88E1510_MISC_TEST_TEMP_THRESHOLD_SHIFT) * 5) - 25;
+	/* convert to mC */
+	*temp *= 1000;
+
+	return 0;
+}
+
+static int m88e1510_set_temp_critical(struct phy_device *phydev, long temp)
+{
+	temp = temp / 1000;
+	temp = clamp_val(DIV_ROUND_CLOSEST(temp, 5) + 5, 0, 0x1f);
+
+	return phy_modify_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+				MII_88E1121_MISC_TEST,
+				MII_88E1510_MISC_TEST_TEMP_THRESHOLD_MASK,
+				temp << MII_88E1510_MISC_TEST_TEMP_THRESHOLD_SHIFT);
+}
+
+static int m88e1510_get_temp_alarm(struct phy_device *phydev, long *alarm)
+{
+	int ret;
+
+	*alarm = false;
+
+	ret = phy_read_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+			     MII_88E1121_MISC_TEST);
+	if (ret < 0)
+		return ret;
+
+	*alarm = !!(ret & MII_88E1510_MISC_TEST_TEMP_IRQ);
+
+	return 0;
+}
+
+static int m88e1510_hwmon_read(struct device *dev,
+			       enum hwmon_sensor_types type,
+			       u32 attr, int channel, long *temp)
+{
+	struct phy_device *phydev = dev_get_drvdata(dev);
+	int err;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		err = m88e1510_get_temp(phydev, temp);
+		break;
+	case hwmon_temp_crit:
+		err = m88e1510_get_temp_critical(phydev, temp);
+		break;
+	case hwmon_temp_max_alarm:
+		err = m88e1510_get_temp_alarm(phydev, temp);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return err;
+}
+
+static int m88e1510_hwmon_write(struct device *dev,
+				enum hwmon_sensor_types type,
+				u32 attr, int channel, long temp)
+{
+	struct phy_device *phydev = dev_get_drvdata(dev);
+	int err;
+
+	switch (attr) {
+	case hwmon_temp_crit:
+		err = m88e1510_set_temp_critical(phydev, temp);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+	return err;
+}
+
+static umode_t m88e1510_hwmon_is_visible(const void *data,
+					 enum hwmon_sensor_types type,
+					 u32 attr, int channel)
+{
+	if (type != hwmon_temp)
+		return 0;
+
+	switch (attr) {
+	case hwmon_temp_input:
+	case hwmon_temp_max_alarm:
+		return 0444;
+	case hwmon_temp_crit:
+		return 0644;
+	default:
+		return 0;
+	}
+}
+
+static u32 m88e1510_hwmon_temp_config[] = {
+	HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_MAX_ALARM,
+	0
+};
+
+static const struct hwmon_channel_info m88e1510_hwmon_temp = {
+	.type = hwmon_temp,
+	.config = m88e1510_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info *m88e1510_hwmon_info[] = {
+	&m88e1121_hwmon_chip,
+	&m88e1510_hwmon_temp,
+	NULL
+};
+
+static const struct hwmon_ops m88e1510_hwmon_hwmon_ops = {
+	.is_visible = m88e1510_hwmon_is_visible,
+	.read = m88e1510_hwmon_read,
+	.write = m88e1510_hwmon_write,
+};
+
+static const struct hwmon_chip_info m88e1510_hwmon_chip_info = {
+	.ops = &m88e1510_hwmon_hwmon_ops,
+	.info = m88e1510_hwmon_info,
+};
+
+static int m88e6390_get_temp(struct phy_device *phydev, long *temp)
+{
+	int sum = 0;
+	int oldpage;
+	int ret = 0;
+	int i;
+
+	*temp = 0;
+
+	oldpage = phy_select_page(phydev, MII_MARVELL_MISC_TEST_PAGE);
+	if (oldpage < 0)
+		goto error;
+
+	/* Enable temperature sensor */
+	ret = __phy_read(phydev, MII_88E6390_MISC_TEST);
+	if (ret < 0)
+		goto error;
+
+	ret = ret & ~MII_88E6390_MISC_TEST_SAMPLE_MASK;
+	ret |= MII_88E6390_MISC_TEST_SAMPLE_ENABLE |
+		MII_88E6390_MISC_TEST_SAMPLE_1S;
+
+	ret = __phy_write(phydev, MII_88E6390_MISC_TEST, ret);
+	if (ret < 0)
+		goto error;
+
+	/* Wait for temperature to stabilize */
+	usleep_range(10000, 12000);
+
+	/* Reading the temperature sense has an errata. You need to read
+	 * a number of times and take an average.
+	 */
+	for (i = 0; i < MII_88E6390_TEMP_SENSOR_SAMPLES; i++) {
+		ret = __phy_read(phydev, MII_88E6390_TEMP_SENSOR);
+		if (ret < 0)
+			goto error;
+		sum += ret & MII_88E6390_TEMP_SENSOR_MASK;
+	}
+
+	sum /= MII_88E6390_TEMP_SENSOR_SAMPLES;
+	*temp = (sum  - 75) * 1000;
+
+	/* Disable temperature sensor */
+	ret = __phy_read(phydev, MII_88E6390_MISC_TEST);
+	if (ret < 0)
+		goto error;
+
+	ret = ret & ~MII_88E6390_MISC_TEST_SAMPLE_MASK;
+	ret |= MII_88E6390_MISC_TEST_SAMPLE_DISABLE;
+
+	ret = __phy_write(phydev, MII_88E6390_MISC_TEST, ret);
+
+error:
+	phy_restore_page(phydev, oldpage, ret);
+
+	return ret;
+}
+
+static int m88e6390_hwmon_read(struct device *dev,
+			       enum hwmon_sensor_types type,
+			       u32 attr, int channel, long *temp)
+{
+	struct phy_device *phydev = dev_get_drvdata(dev);
+	int err;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		err = m88e6390_get_temp(phydev, temp);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return err;
+}
+
+static umode_t m88e6390_hwmon_is_visible(const void *data,
+					 enum hwmon_sensor_types type,
+					 u32 attr, int channel)
+{
+	if (type != hwmon_temp)
+		return 0;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		return 0444;
+	default:
+		return 0;
+	}
+}
+
+static u32 m88e6390_hwmon_temp_config[] = {
+	HWMON_T_INPUT,
+	0
+};
+
+static const struct hwmon_channel_info m88e6390_hwmon_temp = {
+	.type = hwmon_temp,
+	.config = m88e6390_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info *m88e6390_hwmon_info[] = {
+	&m88e1121_hwmon_chip,
+	&m88e6390_hwmon_temp,
+	NULL
+};
+
+static const struct hwmon_ops m88e6390_hwmon_hwmon_ops = {
+	.is_visible = m88e6390_hwmon_is_visible,
+	.read = m88e6390_hwmon_read,
+};
+
+static const struct hwmon_chip_info m88e6390_hwmon_chip_info = {
+	.ops = &m88e6390_hwmon_hwmon_ops,
+	.info = m88e6390_hwmon_info,
+};
+
+static int marvell_hwmon_name(struct phy_device *phydev)
+{
+	struct marvell_priv *priv = phydev->priv;
+	struct device *dev = &phydev->mdio.dev;
+	const char *devname = dev_name(dev);
+	size_t len = strlen(devname);
+	int i, j;
+
+	priv->hwmon_name = devm_kzalloc(dev, len, GFP_KERNEL);
+	if (!priv->hwmon_name)
+		return -ENOMEM;
+
+	for (i = j = 0; i < len && devname[i]; i++) {
+		if (isalnum(devname[i]))
+			priv->hwmon_name[j++] = devname[i];
+	}
+
+	return 0;
+}
+
+static int marvell_hwmon_probe(struct phy_device *phydev,
+			       const struct hwmon_chip_info *chip)
+{
+	struct marvell_priv *priv = phydev->priv;
+	struct device *dev = &phydev->mdio.dev;
+	int err;
+
+	err = marvell_hwmon_name(phydev);
+	if (err)
+		return err;
+
+	priv->hwmon_dev = devm_hwmon_device_register_with_info(
+		dev, priv->hwmon_name, phydev, chip, NULL);
+
+	return PTR_ERR_OR_ZERO(priv->hwmon_dev);
+}
+
+static int m88e1121_hwmon_probe(struct phy_device *phydev)
+{
+	return marvell_hwmon_probe(phydev, &m88e1121_hwmon_chip_info);
+}
+
+static int m88e1510_hwmon_probe(struct phy_device *phydev)
+{
+	return marvell_hwmon_probe(phydev, &m88e1510_hwmon_chip_info);
+}
+
+static int m88e6390_hwmon_probe(struct phy_device *phydev)
+{
+	return marvell_hwmon_probe(phydev, &m88e6390_hwmon_chip_info);
+}
+#else
+static int m88e1121_hwmon_probe(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int m88e1510_hwmon_probe(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int m88e6390_hwmon_probe(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif
+
+static int marvell_probe(struct phy_device *phydev)
+{
+	struct marvell_priv *priv;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static int m88e1121_probe(struct phy_device *phydev)
+{
+	int err;
+
+	err = marvell_probe(phydev);
+	if (err)
+		return err;
+
+	return m88e1121_hwmon_probe(phydev);
+}
+
+static int m88e1510_probe(struct phy_device *phydev)
+{
+	int err;
+
+	err = marvell_probe(phydev);
+	if (err)
+		return err;
+
+	return m88e1510_hwmon_probe(phydev);
+}
+
+static int m88e6390_probe(struct phy_device *phydev)
+{
+	int err;
+
+	err = marvell_probe(phydev);
+	if (err)
+		return err;
+
+	return m88e6390_hwmon_probe(phydev);
+}
+
+static struct phy_driver marvell_drivers[] = {
+	{
+		.phy_id = MARVELL_PHY_ID_88E1101,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1101",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &marvell_config_init,
+		.config_aneg = &m88e1101_config_aneg,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1112,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1112",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1111_config_init,
+		.config_aneg = &marvell_config_aneg,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1111,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1111",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1111_config_init,
+		.config_aneg = &m88e1111_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1118,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1118",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1118_config_init,
+		.config_aneg = &m88e1118_config_aneg,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1121R,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1121R",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = &m88e1121_probe,
+		.config_init = &marvell_config_init,
+		.config_aneg = &m88e1121_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1318S,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1318S",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1318_config_init,
+		.config_aneg = &m88e1318_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.get_wol = &m88e1318_get_wol,
+		.set_wol = &m88e1318_set_wol,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1145,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1145",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1145_config_init,
+		.config_aneg = &m88e1101_config_aneg,
+		.read_status = &genphy_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1149R,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1149R",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1149_config_init,
+		.config_aneg = &m88e1118_config_aneg,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1240,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1240",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1111_config_init,
+		.config_aneg = &marvell_config_aneg,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1116R,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1116R",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e1116r_config_init,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1510,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1510",
+		.features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = &m88e1510_probe,
+		.config_init = &m88e1510_config_init,
+		.config_aneg = &m88e1510_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.get_wol = &m88e1318_get_wol,
+		.set_wol = &m88e1318_set_wol,
+		.resume = &marvell_resume,
+		.suspend = &marvell_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+		.set_loopback = genphy_loopback,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1540,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1540",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = m88e1510_probe,
+		.config_init = &marvell_config_init,
+		.config_aneg = &m88e1510_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E1545,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E1545",
+		.probe = m88e1510_probe,
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.config_init = &marvell_config_init,
+		.config_aneg = &m88e1510_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E3016,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E3016",
+		.features = PHY_BASIC_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = marvell_probe,
+		.config_init = &m88e3016_config_init,
+		.aneg_done = &marvell_aneg_done,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+	{
+		.phy_id = MARVELL_PHY_ID_88E6390,
+		.phy_id_mask = MARVELL_PHY_ID_MASK,
+		.name = "Marvell 88E6390",
+		.features = PHY_GBIT_FEATURES,
+		.flags = PHY_HAS_INTERRUPT,
+		.probe = m88e6390_probe,
+		.config_init = &marvell_config_init,
+		.config_aneg = &m88e6390_config_aneg,
+		.read_status = &marvell_read_status,
+		.ack_interrupt = &marvell_ack_interrupt,
+		.config_intr = &marvell_config_intr,
+		.did_interrupt = &m88e1121_did_interrupt,
+		.resume = &genphy_resume,
+		.suspend = &genphy_suspend,
+		.read_page = marvell_read_page,
+		.write_page = marvell_write_page,
+		.get_sset_count = marvell_get_sset_count,
+		.get_strings = marvell_get_strings,
+		.get_stats = marvell_get_stats,
+	},
+};
+
+module_phy_driver(marvell_drivers);
+
+static struct mdio_device_id __maybe_unused marvell_tbl[] = {
+	{ MARVELL_PHY_ID_88E1101, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1112, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1111, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1118, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1121R, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1145, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1149R, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1240, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1318S, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1116R, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1510, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1540, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E1545, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E3016, MARVELL_PHY_ID_MASK },
+	{ MARVELL_PHY_ID_88E6390, MARVELL_PHY_ID_MASK },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, marvell_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/marvell10g.c b/src/kernel/linux/v4.19/drivers/net/phy/marvell10g.c
new file mode 100644
index 0000000..b2043f5
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/marvell10g.c
@@ -0,0 +1,603 @@
+/*
+ * Marvell 10G 88x3310 PHY driver
+ *
+ * Based upon the ID registers, this PHY appears to be a mixture of IPs
+ * from two different companies.
+ *
+ * There appears to be several different data paths through the PHY which
+ * are automatically managed by the PHY.  The following has been determined
+ * via observation and experimentation for a setup using single-lane Serdes:
+ *
+ *       SGMII PHYXS -- BASE-T PCS -- 10G PMA -- AN -- Copper (for <= 1G)
+ *  10GBASE-KR PHYXS -- BASE-T PCS -- 10G PMA -- AN -- Copper (for 10G)
+ *  10GBASE-KR PHYXS -- BASE-R PCS -- Fiber
+ *
+ * With XAUI, observation shows:
+ *
+ *        XAUI PHYXS -- <appropriate PCS as above>
+ *
+ * and no switching of the host interface mode occurs.
+ *
+ * If both the fiber and copper ports are connected, the first to gain
+ * link takes priority and the other port is completely locked out.
+ */
+#include <linux/ctype.h>
+#include <linux/hwmon.h>
+#include <linux/marvell_phy.h>
+#include <linux/phy.h>
+#include <linux/sfp.h>
+
+enum {
+	MV_PMA_BOOT		= 0xc050,
+	MV_PMA_BOOT_FATAL	= BIT(0),
+
+	MV_PCS_BASE_T		= 0x0000,
+	MV_PCS_BASE_R		= 0x1000,
+	MV_PCS_1000BASEX	= 0x2000,
+
+	MV_PCS_PAIRSWAP		= 0x8182,
+	MV_PCS_PAIRSWAP_MASK	= 0x0003,
+	MV_PCS_PAIRSWAP_AB	= 0x0002,
+	MV_PCS_PAIRSWAP_NONE	= 0x0003,
+
+	/* These registers appear at 0x800X and 0xa00X - the 0xa00X control
+	 * registers appear to set themselves to the 0x800X when AN is
+	 * restarted, but status registers appear readable from either.
+	 */
+	MV_AN_CTRL1000		= 0x8000, /* 1000base-T control register */
+	MV_AN_STAT1000		= 0x8001, /* 1000base-T status register */
+
+	/* Vendor2 MMD registers */
+	MV_V2_TEMP_CTRL		= 0xf08a,
+	MV_V2_TEMP_CTRL_MASK	= 0xc000,
+	MV_V2_TEMP_CTRL_SAMPLE	= 0x0000,
+	MV_V2_TEMP_CTRL_DISABLE	= 0xc000,
+	MV_V2_TEMP		= 0xf08c,
+	MV_V2_TEMP_UNKNOWN	= 0x9600, /* unknown function */
+};
+
+struct mv3310_priv {
+	struct device *hwmon_dev;
+	char *hwmon_name;
+};
+
+static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg,
+			 u16 mask, u16 bits)
+{
+	int old, val, ret;
+
+	old = phy_read_mmd(phydev, devad, reg);
+	if (old < 0)
+		return old;
+
+	val = (old & ~mask) | (bits & mask);
+	if (val == old)
+		return 0;
+
+	ret = phy_write_mmd(phydev, devad, reg, val);
+
+	return ret < 0 ? ret : 1;
+}
+
+#ifdef CONFIG_HWMON
+static umode_t mv3310_hwmon_is_visible(const void *data,
+				       enum hwmon_sensor_types type,
+				       u32 attr, int channel)
+{
+	if (type == hwmon_chip && attr == hwmon_chip_update_interval)
+		return 0444;
+	if (type == hwmon_temp && attr == hwmon_temp_input)
+		return 0444;
+	return 0;
+}
+
+static int mv3310_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+			     u32 attr, int channel, long *value)
+{
+	struct phy_device *phydev = dev_get_drvdata(dev);
+	int temp;
+
+	if (type == hwmon_chip && attr == hwmon_chip_update_interval) {
+		*value = MSEC_PER_SEC;
+		return 0;
+	}
+
+	if (type == hwmon_temp && attr == hwmon_temp_input) {
+		temp = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP);
+		if (temp < 0)
+			return temp;
+
+		*value = ((temp & 0xff) - 75) * 1000;
+
+		return 0;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops mv3310_hwmon_ops = {
+	.is_visible = mv3310_hwmon_is_visible,
+	.read = mv3310_hwmon_read,
+};
+
+static u32 mv3310_hwmon_chip_config[] = {
+	HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL,
+	0,
+};
+
+static const struct hwmon_channel_info mv3310_hwmon_chip = {
+	.type = hwmon_chip,
+	.config = mv3310_hwmon_chip_config,
+};
+
+static u32 mv3310_hwmon_temp_config[] = {
+	HWMON_T_INPUT,
+	0,
+};
+
+static const struct hwmon_channel_info mv3310_hwmon_temp = {
+	.type = hwmon_temp,
+	.config = mv3310_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info *mv3310_hwmon_info[] = {
+	&mv3310_hwmon_chip,
+	&mv3310_hwmon_temp,
+	NULL,
+};
+
+static const struct hwmon_chip_info mv3310_hwmon_chip_info = {
+	.ops = &mv3310_hwmon_ops,
+	.info = mv3310_hwmon_info,
+};
+
+static int mv3310_hwmon_config(struct phy_device *phydev, bool enable)
+{
+	u16 val;
+	int ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP,
+			    MV_V2_TEMP_UNKNOWN);
+	if (ret < 0)
+		return ret;
+
+	val = enable ? MV_V2_TEMP_CTRL_SAMPLE : MV_V2_TEMP_CTRL_DISABLE;
+	ret = mv3310_modify(phydev, MDIO_MMD_VEND2, MV_V2_TEMP_CTRL,
+			    MV_V2_TEMP_CTRL_MASK, val);
+
+	return ret < 0 ? ret : 0;
+}
+
+static void mv3310_hwmon_disable(void *data)
+{
+	struct phy_device *phydev = data;
+
+	mv3310_hwmon_config(phydev, false);
+}
+
+static int mv3310_hwmon_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+	int i, j, ret;
+
+	priv->hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
+	if (!priv->hwmon_name)
+		return -ENODEV;
+
+	for (i = j = 0; priv->hwmon_name[i]; i++) {
+		if (isalnum(priv->hwmon_name[i])) {
+			if (i != j)
+				priv->hwmon_name[j] = priv->hwmon_name[i];
+			j++;
+		}
+	}
+	priv->hwmon_name[j] = '\0';
+
+	ret = mv3310_hwmon_config(phydev, true);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, mv3310_hwmon_disable, phydev);
+	if (ret)
+		return ret;
+
+	priv->hwmon_dev = devm_hwmon_device_register_with_info(dev,
+				priv->hwmon_name, phydev,
+				&mv3310_hwmon_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(priv->hwmon_dev);
+}
+#else
+static inline int mv3310_hwmon_config(struct phy_device *phydev, bool enable)
+{
+	return 0;
+}
+
+static int mv3310_hwmon_probe(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif
+
+static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+	struct phy_device *phydev = upstream;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
+	phy_interface_t iface;
+
+	sfp_parse_support(phydev->sfp_bus, id, support);
+	iface = sfp_select_interface(phydev->sfp_bus, support);
+
+	if (iface != PHY_INTERFACE_MODE_10GKR) {
+		dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static const struct sfp_upstream_ops mv3310_sfp_ops = {
+	.attach = phy_sfp_attach,
+	.detach = phy_sfp_detach,
+	.module_insert = mv3310_sfp_insert,
+};
+
+static int mv3310_probe(struct phy_device *phydev)
+{
+	struct mv3310_priv *priv;
+	u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+	int ret;
+
+	if (!phydev->is_c45 ||
+	    (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
+		return -ENODEV;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_BOOT);
+	if (ret < 0)
+		return ret;
+
+	if (ret & MV_PMA_BOOT_FATAL) {
+		dev_warn(&phydev->mdio.dev,
+			 "PHY failed to boot firmware, status=%04x\n", ret);
+		return -ENODEV;
+	}
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(&phydev->mdio.dev, priv);
+
+	ret = mv3310_hwmon_probe(phydev);
+	if (ret)
+		return ret;
+
+	return phy_sfp_probe(phydev, &mv3310_sfp_ops);
+}
+
+static int mv3310_suspend(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int mv3310_resume(struct phy_device *phydev)
+{
+	return mv3310_hwmon_config(phydev, true);
+}
+
+static int mv3310_config_init(struct phy_device *phydev)
+{
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
+	u32 mask;
+	int val;
+
+	/* Check that the PHY interface type is compatible */
+	if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+	    phydev->interface != PHY_INTERFACE_MODE_XAUI &&
+	    phydev->interface != PHY_INTERFACE_MODE_RXAUI &&
+	    phydev->interface != PHY_INTERFACE_MODE_10GKR)
+		return -ENODEV;
+
+	__set_bit(ETHTOOL_LINK_MODE_Pause_BIT, supported);
+	__set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, supported);
+
+	if (phydev->c45_ids.devices_in_package & MDIO_DEVS_AN) {
+		val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+		if (val < 0)
+			return val;
+
+		if (val & MDIO_AN_STAT1_ABLE)
+			__set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, supported);
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT2);
+	if (val < 0)
+		return val;
+
+	/* Ethtool does not support the WAN mode bits */
+	if (val & (MDIO_PMA_STAT2_10GBSR | MDIO_PMA_STAT2_10GBLR |
+		   MDIO_PMA_STAT2_10GBER | MDIO_PMA_STAT2_10GBLX4 |
+		   MDIO_PMA_STAT2_10GBSW | MDIO_PMA_STAT2_10GBLW |
+		   MDIO_PMA_STAT2_10GBEW))
+		__set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported);
+	if (val & MDIO_PMA_STAT2_10GBSR)
+		__set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, supported);
+	if (val & MDIO_PMA_STAT2_10GBLR)
+		__set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, supported);
+	if (val & MDIO_PMA_STAT2_10GBER)
+		__set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, supported);
+
+	if (val & MDIO_PMA_STAT2_EXTABLE) {
+		val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_EXTABLE);
+		if (val < 0)
+			return val;
+
+		if (val & (MDIO_PMA_EXTABLE_10GBT | MDIO_PMA_EXTABLE_1000BT |
+			   MDIO_PMA_EXTABLE_100BTX | MDIO_PMA_EXTABLE_10BT))
+			__set_bit(ETHTOOL_LINK_MODE_TP_BIT, supported);
+		if (val & MDIO_PMA_EXTABLE_10GBLRM)
+			__set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported);
+		if (val & (MDIO_PMA_EXTABLE_10GBKX4 | MDIO_PMA_EXTABLE_10GBKR |
+			   MDIO_PMA_EXTABLE_1000BKX))
+			__set_bit(ETHTOOL_LINK_MODE_Backplane_BIT, supported);
+		if (val & MDIO_PMA_EXTABLE_10GBLRM)
+			__set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
+				  supported);
+		if (val & MDIO_PMA_EXTABLE_10GBT)
+			__set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+				  supported);
+		if (val & MDIO_PMA_EXTABLE_10GBKX4)
+			__set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+				  supported);
+		if (val & MDIO_PMA_EXTABLE_10GBKR)
+			__set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+				  supported);
+		if (val & MDIO_PMA_EXTABLE_1000BT)
+			__set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+				  supported);
+		if (val & MDIO_PMA_EXTABLE_1000BKX)
+			__set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+				  supported);
+		if (val & MDIO_PMA_EXTABLE_100BTX) {
+			__set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+				  supported);
+			__set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+				  supported);
+		}
+		if (val & MDIO_PMA_EXTABLE_10BT) {
+			__set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+				  supported);
+			__set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+				  supported);
+		}
+	}
+
+	if (!ethtool_convert_link_mode_to_legacy_u32(&mask, supported))
+		dev_warn(&phydev->mdio.dev,
+			 "PHY supports (%*pb) more modes than phylib supports, some modes not supported.\n",
+			 __ETHTOOL_LINK_MODE_MASK_NBITS, supported);
+
+	phydev->supported &= mask;
+	phydev->advertising &= phydev->supported;
+
+	return 0;
+}
+
+static int mv3310_config_aneg(struct phy_device *phydev)
+{
+	bool changed = false;
+	u32 advertising;
+	int ret;
+
+	/* We don't support manual MDI control */
+	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+	if (phydev->autoneg == AUTONEG_DISABLE) {
+		ret = genphy_c45_pma_setup_forced(phydev);
+		if (ret < 0)
+			return ret;
+
+		return genphy_c45_an_disable_aneg(phydev);
+	}
+
+	phydev->advertising &= phydev->supported;
+	advertising = phydev->advertising;
+
+	ret = mv3310_modify(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE,
+			    ADVERTISE_ALL | ADVERTISE_100BASE4 |
+			    ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+			    ethtool_adv_to_mii_adv_t(advertising));
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	ret = mv3310_modify(phydev, MDIO_MMD_AN, MV_AN_CTRL1000,
+			    ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+			    ethtool_adv_to_mii_ctrl1000_t(advertising));
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	/* 10G control register */
+	ret = mv3310_modify(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+			    MDIO_AN_10GBT_CTRL_ADV10G,
+			    advertising & ADVERTISED_10000baseT_Full ?
+				MDIO_AN_10GBT_CTRL_ADV10G : 0);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	if (changed)
+		ret = genphy_c45_restart_aneg(phydev);
+
+	return ret;
+}
+
+static int mv3310_aneg_done(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_STAT1_LSTATUS)
+		return 1;
+
+	return genphy_c45_aneg_done(phydev);
+}
+
+static void mv3310_update_interface(struct phy_device *phydev)
+{
+	if ((phydev->interface == PHY_INTERFACE_MODE_SGMII ||
+	     phydev->interface == PHY_INTERFACE_MODE_10GKR) && phydev->link) {
+		/* The PHY automatically switches its serdes interface (and
+		 * active PHYXS instance) between Cisco SGMII and 10GBase-KR
+		 * modes according to the speed.  Florian suggests setting
+		 * phydev->interface to communicate this to the MAC. Only do
+		 * this if we are already in either SGMII or 10GBase-KR mode.
+		 */
+		if (phydev->speed == SPEED_10000)
+			phydev->interface = PHY_INTERFACE_MODE_10GKR;
+		else if (phydev->speed >= SPEED_10 &&
+			 phydev->speed < SPEED_10000)
+			phydev->interface = PHY_INTERFACE_MODE_SGMII;
+	}
+}
+
+/* 10GBASE-ER,LR,LRM,SR do not support autonegotiation. */
+static int mv3310_read_10gbr_status(struct phy_device *phydev)
+{
+	phydev->link = 1;
+	phydev->speed = SPEED_10000;
+	phydev->duplex = DUPLEX_FULL;
+
+	mv3310_update_interface(phydev);
+
+	return 0;
+}
+
+static int mv3310_read_status(struct phy_device *phydev)
+{
+	u32 mmd_mask = phydev->c45_ids.devices_in_package;
+	int val;
+
+	/* The vendor devads do not report link status.  Avoid the PHYXS
+	 * instance as there are three, and its status depends on the MAC
+	 * being appropriately configured for the negotiated speed.
+	 */
+	mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2) |
+		      BIT(MDIO_MMD_PHYXS));
+
+	phydev->speed = SPEED_UNKNOWN;
+	phydev->duplex = DUPLEX_UNKNOWN;
+	phydev->lp_advertising = 0;
+	phydev->link = 0;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+	phydev->mdix = 0;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_STAT1_LSTATUS)
+		return mv3310_read_10gbr_status(phydev);
+
+	val = genphy_c45_read_link(phydev, mmd_mask);
+	if (val < 0)
+		return val;
+
+	phydev->link = val > 0 ? 1 : 0;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_STAT1_COMPLETE) {
+		val = genphy_c45_read_lpa(phydev);
+		if (val < 0)
+			return val;
+
+		/* Read the link partner's 1G advertisement */
+		val = phy_read_mmd(phydev, MDIO_MMD_AN, MV_AN_STAT1000);
+		if (val < 0)
+			return val;
+
+		phydev->lp_advertising |= mii_stat1000_to_ethtool_lpa_t(val);
+
+		if (phydev->autoneg == AUTONEG_ENABLE)
+			phy_resolve_aneg_linkmode(phydev);
+	}
+
+	if (phydev->autoneg != AUTONEG_ENABLE) {
+		val = genphy_c45_read_pma(phydev);
+		if (val < 0)
+			return val;
+	}
+
+	if (phydev->speed == SPEED_10000) {
+		val = genphy_c45_read_mdix(phydev);
+		if (val < 0)
+			return val;
+	} else {
+		val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_PAIRSWAP);
+		if (val < 0)
+			return val;
+
+		switch (val & MV_PCS_PAIRSWAP_MASK) {
+		case MV_PCS_PAIRSWAP_AB:
+			phydev->mdix = ETH_TP_MDI_X;
+			break;
+		case MV_PCS_PAIRSWAP_NONE:
+			phydev->mdix = ETH_TP_MDI;
+			break;
+		default:
+			phydev->mdix = ETH_TP_MDI_INVALID;
+			break;
+		}
+	}
+
+	mv3310_update_interface(phydev);
+
+	return 0;
+}
+
+static struct phy_driver mv3310_drivers[] = {
+	{
+		.phy_id		= 0x002b09aa,
+		.phy_id_mask	= MARVELL_PHY_ID_MASK,
+		.name		= "mv88x3310",
+		.features	= SUPPORTED_10baseT_Full |
+				  SUPPORTED_10baseT_Half |
+				  SUPPORTED_100baseT_Full |
+				  SUPPORTED_100baseT_Half |
+				  SUPPORTED_1000baseT_Full |
+				  SUPPORTED_Autoneg |
+				  SUPPORTED_TP |
+				  SUPPORTED_FIBRE |
+				  SUPPORTED_10000baseT_Full |
+				  SUPPORTED_Backplane,
+		.soft_reset	= gen10g_no_soft_reset,
+		.config_init	= mv3310_config_init,
+		.probe		= mv3310_probe,
+		.suspend	= mv3310_suspend,
+		.resume		= mv3310_resume,
+		.config_aneg	= mv3310_config_aneg,
+		.aneg_done	= mv3310_aneg_done,
+		.read_status	= mv3310_read_status,
+	},
+};
+
+module_phy_driver(mv3310_drivers);
+
+static struct mdio_device_id __maybe_unused mv3310_tbl[] = {
+	{ 0x002b09aa, MARVELL_PHY_ID_MASK },
+	{ },
+};
+MODULE_DEVICE_TABLE(mdio, mv3310_tbl);
+MODULE_DESCRIPTION("Marvell Alaska X 10Gigabit Ethernet PHY driver (MV88X3310)");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-bcm-iproc.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-bcm-iproc.c
new file mode 100644
index 0000000..46fe1ae
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-bcm-iproc.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+
+#define IPROC_GPHY_MDCDIV    0x1a
+
+#define MII_CTRL_OFFSET      0x000
+
+#define MII_CTRL_DIV_SHIFT   0
+#define MII_CTRL_PRE_SHIFT   7
+#define MII_CTRL_BUSY_SHIFT  8
+
+#define MII_DATA_OFFSET      0x004
+#define MII_DATA_MASK        0xffff
+#define MII_DATA_TA_SHIFT    16
+#define MII_DATA_TA_VAL      2
+#define MII_DATA_RA_SHIFT    18
+#define MII_DATA_PA_SHIFT    23
+#define MII_DATA_OP_SHIFT    28
+#define MII_DATA_OP_WRITE    1
+#define MII_DATA_OP_READ     2
+#define MII_DATA_SB_SHIFT    30
+
+struct iproc_mdio_priv {
+	struct mii_bus *mii_bus;
+	void __iomem *base;
+};
+
+static inline int iproc_mdio_wait_for_idle(void __iomem *base)
+{
+	u32 val;
+	unsigned int timeout = 1000; /* loop for 1s */
+
+	do {
+		val = readl(base + MII_CTRL_OFFSET);
+		if ((val & BIT(MII_CTRL_BUSY_SHIFT)) == 0)
+			return 0;
+
+		usleep_range(1000, 2000);
+	} while (timeout--);
+
+	return -ETIMEDOUT;
+}
+
+static inline void iproc_mdio_config_clk(void __iomem *base)
+{
+	u32 val;
+
+	val = (IPROC_GPHY_MDCDIV << MII_CTRL_DIV_SHIFT) |
+		  BIT(MII_CTRL_PRE_SHIFT);
+	writel(val, base + MII_CTRL_OFFSET);
+}
+
+static int iproc_mdio_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	struct iproc_mdio_priv *priv = bus->priv;
+	u32 cmd;
+	int rc;
+
+	rc = iproc_mdio_wait_for_idle(priv->base);
+	if (rc)
+		return rc;
+
+	/* Prepare the read operation */
+	cmd = (MII_DATA_TA_VAL << MII_DATA_TA_SHIFT) |
+		(reg << MII_DATA_RA_SHIFT) |
+		(phy_id << MII_DATA_PA_SHIFT) |
+		BIT(MII_DATA_SB_SHIFT) |
+		(MII_DATA_OP_READ << MII_DATA_OP_SHIFT);
+
+	writel(cmd, priv->base + MII_DATA_OFFSET);
+
+	rc = iproc_mdio_wait_for_idle(priv->base);
+	if (rc)
+		return rc;
+
+	cmd = readl(priv->base + MII_DATA_OFFSET) & MII_DATA_MASK;
+
+	return cmd;
+}
+
+static int iproc_mdio_write(struct mii_bus *bus, int phy_id,
+			    int reg, u16 val)
+{
+	struct iproc_mdio_priv *priv = bus->priv;
+	u32 cmd;
+	int rc;
+
+	rc = iproc_mdio_wait_for_idle(priv->base);
+	if (rc)
+		return rc;
+
+	/* Prepare the write operation */
+	cmd = (MII_DATA_TA_VAL << MII_DATA_TA_SHIFT) |
+		(reg << MII_DATA_RA_SHIFT) |
+		(phy_id << MII_DATA_PA_SHIFT) |
+		BIT(MII_DATA_SB_SHIFT) |
+		(MII_DATA_OP_WRITE << MII_DATA_OP_SHIFT) |
+		((u32)(val) & MII_DATA_MASK);
+
+	writel(cmd, priv->base + MII_DATA_OFFSET);
+
+	rc = iproc_mdio_wait_for_idle(priv->base);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static int iproc_mdio_probe(struct platform_device *pdev)
+{
+	struct iproc_mdio_priv *priv;
+	struct mii_bus *bus;
+	struct resource *res;
+	int rc;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->base)) {
+		dev_err(&pdev->dev, "failed to ioremap register\n");
+		return PTR_ERR(priv->base);
+	}
+
+	priv->mii_bus = mdiobus_alloc();
+	if (!priv->mii_bus) {
+		dev_err(&pdev->dev, "MDIO bus alloc failed\n");
+		return -ENOMEM;
+	}
+
+	bus = priv->mii_bus;
+	bus->priv = priv;
+	bus->name = "iProc MDIO bus";
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id);
+	bus->parent = &pdev->dev;
+	bus->read = iproc_mdio_read;
+	bus->write = iproc_mdio_write;
+
+	iproc_mdio_config_clk(priv->base);
+
+	rc = of_mdiobus_register(bus, pdev->dev.of_node);
+	if (rc) {
+		dev_err(&pdev->dev, "MDIO bus registration failed\n");
+		goto err_iproc_mdio;
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	dev_info(&pdev->dev, "Broadcom iProc MDIO bus at 0x%p\n", priv->base);
+
+	return 0;
+
+err_iproc_mdio:
+	mdiobus_free(bus);
+	return rc;
+}
+
+static int iproc_mdio_remove(struct platform_device *pdev)
+{
+	struct iproc_mdio_priv *priv = platform_get_drvdata(pdev);
+
+	mdiobus_unregister(priv->mii_bus);
+	mdiobus_free(priv->mii_bus);
+
+	return 0;
+}
+
+static const struct of_device_id iproc_mdio_of_match[] = {
+	{ .compatible = "brcm,iproc-mdio", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, iproc_mdio_of_match);
+
+static struct platform_driver iproc_mdio_driver = {
+	.driver = {
+		.name = "iproc-mdio",
+		.of_match_table = iproc_mdio_of_match,
+	},
+	.probe = iproc_mdio_probe,
+	.remove = iproc_mdio_remove,
+};
+
+module_platform_driver(iproc_mdio_driver);
+
+MODULE_AUTHOR("Broadcom Corporation");
+MODULE_DESCRIPTION("Broadcom iProc MDIO bus controller");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:iproc-mdio");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-bcm-unimac.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-bcm-unimac.c
new file mode 100644
index 0000000..df75efa
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-bcm-unimac.c
@@ -0,0 +1,372 @@
+/*
+ * Broadcom UniMAC MDIO bus controller driver
+ *
+ * Copyright (C) 2014-2017 Broadcom
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_mdio.h>
+
+#include <linux/platform_data/mdio-bcm-unimac.h>
+
+#define MDIO_CMD		0x00
+#define  MDIO_START_BUSY	(1 << 29)
+#define  MDIO_READ_FAIL		(1 << 28)
+#define  MDIO_RD		(2 << 26)
+#define  MDIO_WR		(1 << 26)
+#define  MDIO_PMD_SHIFT		21
+#define  MDIO_PMD_MASK		0x1F
+#define  MDIO_REG_SHIFT		16
+#define  MDIO_REG_MASK		0x1F
+
+#define MDIO_CFG		0x04
+#define  MDIO_C22		(1 << 0)
+#define  MDIO_C45		0
+#define  MDIO_CLK_DIV_SHIFT	4
+#define  MDIO_CLK_DIV_MASK	0x3F
+#define  MDIO_SUPP_PREAMBLE	(1 << 12)
+
+struct unimac_mdio_priv {
+	struct mii_bus		*mii_bus;
+	void __iomem		*base;
+	int (*wait_func)	(void *wait_func_data);
+	void			*wait_func_data;
+	struct clk		*clk;
+	u32			clk_freq;
+};
+
+static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset)
+{
+	/* MIPS chips strapped for BE will automagically configure the
+	 * peripheral registers for CPU-native byte order.
+	 */
+	if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+		return __raw_readl(priv->base + offset);
+	else
+		return readl_relaxed(priv->base + offset);
+}
+
+static inline void unimac_mdio_writel(struct unimac_mdio_priv *priv, u32 val,
+				      u32 offset)
+{
+	if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+		__raw_writel(val, priv->base + offset);
+	else
+		writel_relaxed(val, priv->base + offset);
+}
+
+static inline void unimac_mdio_start(struct unimac_mdio_priv *priv)
+{
+	u32 reg;
+
+	reg = unimac_mdio_readl(priv, MDIO_CMD);
+	reg |= MDIO_START_BUSY;
+	unimac_mdio_writel(priv, reg, MDIO_CMD);
+}
+
+static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv)
+{
+	return unimac_mdio_readl(priv, MDIO_CMD) & MDIO_START_BUSY;
+}
+
+static int unimac_mdio_poll(void *wait_func_data)
+{
+	struct unimac_mdio_priv *priv = wait_func_data;
+	unsigned int timeout = 1000;
+
+	do {
+		if (!unimac_mdio_busy(priv))
+			return 0;
+
+		usleep_range(1000, 2000);
+	} while (--timeout);
+
+	if (!timeout)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	struct unimac_mdio_priv *priv = bus->priv;
+	int ret;
+	u32 cmd;
+
+	/* Prepare the read operation */
+	cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT);
+	unimac_mdio_writel(priv, cmd, MDIO_CMD);
+
+	/* Start MDIO transaction */
+	unimac_mdio_start(priv);
+
+	ret = priv->wait_func(priv->wait_func_data);
+	if (ret)
+		return ret;
+
+	cmd = unimac_mdio_readl(priv, MDIO_CMD);
+
+	/* Some broken devices are known not to release the line during
+	 * turn-around, e.g: Broadcom BCM53125 external switches, so check for
+	 * that condition here and ignore the MDIO controller read failure
+	 * indication.
+	 */
+	if (!(bus->phy_ignore_ta_mask & 1 << phy_id) && (cmd & MDIO_READ_FAIL))
+		return -EIO;
+
+	return cmd & 0xffff;
+}
+
+static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
+			     int reg, u16 val)
+{
+	struct unimac_mdio_priv *priv = bus->priv;
+	u32 cmd;
+
+	/* Prepare the write operation */
+	cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) |
+		(reg << MDIO_REG_SHIFT) | (0xffff & val);
+	unimac_mdio_writel(priv, cmd, MDIO_CMD);
+
+	unimac_mdio_start(priv);
+
+	return priv->wait_func(priv->wait_func_data);
+}
+
+/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with
+ * their internal MDIO management controller making them fail to successfully
+ * be read from or written to for the first transaction.  We insert a dummy
+ * BMSR read here to make sure that phy_get_device() and get_phy_id() can
+ * correctly read the PHY MII_PHYSID1/2 registers and successfully register a
+ * PHY device for this peripheral.
+ *
+ * Once the PHY driver is registered, we can workaround subsequent reads from
+ * there (e.g: during system-wide power management).
+ *
+ * bus->reset is invoked before mdiobus_scan during mdiobus_register and is
+ * therefore the right location to stick that workaround. Since we do not want
+ * to read from non-existing PHYs, we either use bus->phy_mask or do a manual
+ * Device Tree scan to limit the search area.
+ */
+static int unimac_mdio_reset(struct mii_bus *bus)
+{
+	struct device_node *np = bus->dev.of_node;
+	struct device_node *child;
+	u32 read_mask = 0;
+	int addr;
+
+	if (!np) {
+		read_mask = ~bus->phy_mask;
+	} else {
+		for_each_available_child_of_node(np, child) {
+			addr = of_mdio_parse_addr(&bus->dev, child);
+			if (addr < 0)
+				continue;
+
+			read_mask |= 1 << addr;
+		}
+	}
+
+	for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+		if (read_mask & 1 << addr) {
+			dev_dbg(&bus->dev, "Workaround for PHY @ %d\n", addr);
+			mdiobus_read(bus, addr, MII_BMSR);
+		}
+	}
+
+	return 0;
+}
+
+static void unimac_mdio_clk_set(struct unimac_mdio_priv *priv)
+{
+	unsigned long rate;
+	u32 reg, div;
+
+	/* Keep the hardware default values */
+	if (!priv->clk_freq)
+		return;
+
+	if (!priv->clk)
+		rate = 250000000;
+	else
+		rate = clk_get_rate(priv->clk);
+
+	div = (rate / (2 * priv->clk_freq)) - 1;
+	if (div & ~MDIO_CLK_DIV_MASK) {
+		pr_warn("Incorrect MDIO clock frequency, ignoring\n");
+		return;
+	}
+
+	/* The MDIO clock is the reference clock (typicaly 250Mhz) divided by
+	 * 2 x (MDIO_CLK_DIV + 1)
+	 */
+	reg = unimac_mdio_readl(priv, MDIO_CFG);
+	reg &= ~(MDIO_CLK_DIV_MASK << MDIO_CLK_DIV_SHIFT);
+	reg |= div << MDIO_CLK_DIV_SHIFT;
+	unimac_mdio_writel(priv, reg, MDIO_CFG);
+}
+
+static int unimac_mdio_probe(struct platform_device *pdev)
+{
+	struct unimac_mdio_pdata *pdata = pdev->dev.platform_data;
+	struct unimac_mdio_priv *priv;
+	struct device_node *np;
+	struct mii_bus *bus;
+	struct resource *r;
+	int ret;
+
+	np = pdev->dev.of_node;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r)
+		return -EINVAL;
+
+	/* Just ioremap, as this MDIO block is usually integrated into an
+	 * Ethernet MAC controller register range
+	 */
+	priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+	if (!priv->base) {
+		dev_err(&pdev->dev, "failed to remap register\n");
+		return -ENOMEM;
+	}
+
+	priv->clk = devm_clk_get(&pdev->dev, NULL);
+	if (PTR_ERR(priv->clk) == -EPROBE_DEFER)
+		return PTR_ERR(priv->clk);
+	else
+		priv->clk = NULL;
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		return ret;
+
+	if (of_property_read_u32(np, "clock-frequency", &priv->clk_freq))
+		priv->clk_freq = 0;
+
+	unimac_mdio_clk_set(priv);
+
+	priv->mii_bus = mdiobus_alloc();
+	if (!priv->mii_bus) {
+		ret = -ENOMEM;
+		goto out_clk_disable;
+	}
+
+	bus = priv->mii_bus;
+	bus->priv = priv;
+	if (pdata) {
+		bus->name = pdata->bus_name;
+		priv->wait_func = pdata->wait_func;
+		priv->wait_func_data = pdata->wait_func_data;
+		bus->phy_mask = ~pdata->phy_mask;
+	} else {
+		bus->name = "unimac MII bus";
+		priv->wait_func_data = priv;
+		priv->wait_func = unimac_mdio_poll;
+	}
+	bus->parent = &pdev->dev;
+	bus->read = unimac_mdio_read;
+	bus->write = unimac_mdio_write;
+	bus->reset = unimac_mdio_reset;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id);
+
+	ret = of_mdiobus_register(bus, np);
+	if (ret) {
+		dev_err(&pdev->dev, "MDIO bus registration failed\n");
+		goto out_mdio_free;
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus at 0x%p\n", priv->base);
+
+	return 0;
+
+out_mdio_free:
+	mdiobus_free(bus);
+out_clk_disable:
+	clk_disable_unprepare(priv->clk);
+	return ret;
+}
+
+static int unimac_mdio_remove(struct platform_device *pdev)
+{
+	struct unimac_mdio_priv *priv = platform_get_drvdata(pdev);
+
+	mdiobus_unregister(priv->mii_bus);
+	mdiobus_free(priv->mii_bus);
+	clk_disable_unprepare(priv->clk);
+
+	return 0;
+}
+
+static int __maybe_unused unimac_mdio_suspend(struct device *d)
+{
+	struct unimac_mdio_priv *priv = dev_get_drvdata(d);
+
+	clk_disable_unprepare(priv->clk);
+
+	return 0;
+}
+
+static int __maybe_unused unimac_mdio_resume(struct device *d)
+{
+	struct unimac_mdio_priv *priv = dev_get_drvdata(d);
+	int ret;
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		return ret;
+
+	unimac_mdio_clk_set(priv);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(unimac_mdio_pm_ops,
+			 unimac_mdio_suspend, unimac_mdio_resume);
+
+static const struct of_device_id unimac_mdio_ids[] = {
+	{ .compatible = "brcm,genet-mdio-v5", },
+	{ .compatible = "brcm,genet-mdio-v4", },
+	{ .compatible = "brcm,genet-mdio-v3", },
+	{ .compatible = "brcm,genet-mdio-v2", },
+	{ .compatible = "brcm,genet-mdio-v1", },
+	{ .compatible = "brcm,unimac-mdio", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, unimac_mdio_ids);
+
+static struct platform_driver unimac_mdio_driver = {
+	.driver = {
+		.name = UNIMAC_MDIO_DRV_NAME,
+		.of_match_table = unimac_mdio_ids,
+		.pm = &unimac_mdio_pm_ops,
+	},
+	.probe	= unimac_mdio_probe,
+	.remove	= unimac_mdio_remove,
+};
+module_platform_driver(unimac_mdio_driver);
+
+MODULE_AUTHOR("Broadcom Corporation");
+MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" UNIMAC_MDIO_DRV_NAME);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-bitbang.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-bitbang.c
new file mode 100644
index 0000000..15352f9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-bitbang.c
@@ -0,0 +1,235 @@
+/*
+ * Bitbanged MDIO support.
+ *
+ * Author: Scott Wood <scottwood@freescale.com>
+ * Copyright (c) 2007 Freescale Semiconductor
+ *
+ * Based on CPM2 MDIO code which is:
+ *
+ * Copyright (c) 2003 Intracom S.A.
+ *  by Pantelis Antoniou <panto@intracom.gr>
+ *
+ * 2005 (c) MontaVista Software, Inc.
+ * Vitaly Bordug <vbordug@ru.mvista.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/mdio-bitbang.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+
+#define MDIO_READ 2
+#define MDIO_WRITE 1
+
+#define MDIO_C45 (1<<15)
+#define MDIO_C45_ADDR (MDIO_C45 | 0)
+#define MDIO_C45_READ (MDIO_C45 | 3)
+#define MDIO_C45_WRITE (MDIO_C45 | 1)
+
+#define MDIO_SETUP_TIME 10
+#define MDIO_HOLD_TIME 10
+
+/* Minimum MDC period is 400 ns, plus some margin for error.  MDIO_DELAY
+ * is done twice per period.
+ */
+#define MDIO_DELAY 250
+
+/* The PHY may take up to 300 ns to produce data, plus some margin
+ * for error.
+ */
+#define MDIO_READ_DELAY 350
+
+/* MDIO must already be configured as output. */
+static void mdiobb_send_bit(struct mdiobb_ctrl *ctrl, int val)
+{
+	const struct mdiobb_ops *ops = ctrl->ops;
+
+	ops->set_mdio_data(ctrl, val);
+	ndelay(MDIO_DELAY);
+	ops->set_mdc(ctrl, 1);
+	ndelay(MDIO_DELAY);
+	ops->set_mdc(ctrl, 0);
+}
+
+/* MDIO must already be configured as input. */
+static int mdiobb_get_bit(struct mdiobb_ctrl *ctrl)
+{
+	const struct mdiobb_ops *ops = ctrl->ops;
+
+	ndelay(MDIO_DELAY);
+	ops->set_mdc(ctrl, 1);
+	ndelay(MDIO_READ_DELAY);
+	ops->set_mdc(ctrl, 0);
+
+	return ops->get_mdio_data(ctrl);
+}
+
+/* MDIO must already be configured as output. */
+static void mdiobb_send_num(struct mdiobb_ctrl *ctrl, u16 val, int bits)
+{
+	int i;
+
+	for (i = bits - 1; i >= 0; i--)
+		mdiobb_send_bit(ctrl, (val >> i) & 1);
+}
+
+/* MDIO must already be configured as input. */
+static u16 mdiobb_get_num(struct mdiobb_ctrl *ctrl, int bits)
+{
+	int i;
+	u16 ret = 0;
+
+	for (i = bits - 1; i >= 0; i--) {
+		ret <<= 1;
+		ret |= mdiobb_get_bit(ctrl);
+	}
+
+	return ret;
+}
+
+/* Utility to send the preamble, address, and
+ * register (common to read and write).
+ */
+static void mdiobb_cmd(struct mdiobb_ctrl *ctrl, int op, u8 phy, u8 reg)
+{
+	const struct mdiobb_ops *ops = ctrl->ops;
+	int i;
+
+	ops->set_mdio_dir(ctrl, 1);
+
+	/*
+	 * Send a 32 bit preamble ('1's) with an extra '1' bit for good
+	 * measure.  The IEEE spec says this is a PHY optional
+	 * requirement.  The AMD 79C874 requires one after power up and
+	 * one after a MII communications error.  This means that we are
+	 * doing more preambles than we need, but it is safer and will be
+	 * much more robust.
+	 */
+
+	for (i = 0; i < 32; i++)
+		mdiobb_send_bit(ctrl, 1);
+
+	/* send the start bit (01) and the read opcode (10) or write (01).
+	   Clause 45 operation uses 00 for the start and 11, 10 for
+	   read/write */
+	mdiobb_send_bit(ctrl, 0);
+	if (op & MDIO_C45)
+		mdiobb_send_bit(ctrl, 0);
+	else
+		mdiobb_send_bit(ctrl, 1);
+	mdiobb_send_bit(ctrl, (op >> 1) & 1);
+	mdiobb_send_bit(ctrl, (op >> 0) & 1);
+
+	mdiobb_send_num(ctrl, phy, 5);
+	mdiobb_send_num(ctrl, reg, 5);
+}
+
+/* In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the
+   lower 16 bits of the 21 bit address. This transfer is done identically to a
+   MDIO_WRITE except for a different code. To enable clause 45 mode or
+   MII_ADDR_C45 into the address. Theoretically clause 45 and normal devices
+   can exist on the same bus. Normal devices should ignore the MDIO_ADDR
+   phase. */
+static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr)
+{
+	unsigned int dev_addr = (addr >> 16) & 0x1F;
+	unsigned int reg = addr & 0xFFFF;
+	mdiobb_cmd(ctrl, MDIO_C45_ADDR, phy, dev_addr);
+
+	/* send the turnaround (10) */
+	mdiobb_send_bit(ctrl, 1);
+	mdiobb_send_bit(ctrl, 0);
+
+	mdiobb_send_num(ctrl, reg, 16);
+
+	ctrl->ops->set_mdio_dir(ctrl, 0);
+	mdiobb_get_bit(ctrl);
+
+	return dev_addr;
+}
+
+static int mdiobb_read(struct mii_bus *bus, int phy, int reg)
+{
+	struct mdiobb_ctrl *ctrl = bus->priv;
+	int ret, i;
+
+	if (reg & MII_ADDR_C45) {
+		reg = mdiobb_cmd_addr(ctrl, phy, reg);
+		mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg);
+	} else
+		mdiobb_cmd(ctrl, MDIO_READ, phy, reg);
+
+	ctrl->ops->set_mdio_dir(ctrl, 0);
+
+	/* check the turnaround bit: the PHY should be driving it to zero, if this
+	 * PHY is listed in phy_ignore_ta_mask as having broken TA, skip that
+	 */
+	if (mdiobb_get_bit(ctrl) != 0 &&
+	    !(bus->phy_ignore_ta_mask & (1 << phy))) {
+		/* PHY didn't drive TA low -- flush any bits it
+		 * may be trying to send.
+		 */
+		for (i = 0; i < 32; i++)
+			mdiobb_get_bit(ctrl);
+
+		return 0xffff;
+	}
+
+	ret = mdiobb_get_num(ctrl, 16);
+	mdiobb_get_bit(ctrl);
+	return ret;
+}
+
+static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
+{
+	struct mdiobb_ctrl *ctrl = bus->priv;
+
+	if (reg & MII_ADDR_C45) {
+		reg = mdiobb_cmd_addr(ctrl, phy, reg);
+		mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg);
+	} else
+		mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg);
+
+	/* send the turnaround (10) */
+	mdiobb_send_bit(ctrl, 1);
+	mdiobb_send_bit(ctrl, 0);
+
+	mdiobb_send_num(ctrl, val, 16);
+
+	ctrl->ops->set_mdio_dir(ctrl, 0);
+	mdiobb_get_bit(ctrl);
+	return 0;
+}
+
+struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
+{
+	struct mii_bus *bus;
+
+	bus = mdiobus_alloc();
+	if (!bus)
+		return NULL;
+
+	__module_get(ctrl->ops->owner);
+
+	bus->read = mdiobb_read;
+	bus->write = mdiobb_write;
+	bus->priv = ctrl;
+
+	return bus;
+}
+EXPORT_SYMBOL(alloc_mdio_bitbang);
+
+void free_mdio_bitbang(struct mii_bus *bus)
+{
+	struct mdiobb_ctrl *ctrl = bus->priv;
+
+	module_put(ctrl->ops->owner);
+	mdiobus_free(bus);
+}
+EXPORT_SYMBOL(free_mdio_bitbang);
+
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-boardinfo.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-boardinfo.c
new file mode 100644
index 0000000..863496f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-boardinfo.c
@@ -0,0 +1,83 @@
+/*
+ * mdio-boardinfo - Collect pre-declarations for MDIO devices
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+
+#include "mdio-boardinfo.h"
+
+static LIST_HEAD(mdio_board_list);
+static DEFINE_MUTEX(mdio_board_lock);
+
+/**
+ * mdiobus_setup_mdiodev_from_board_info - create and setup MDIO devices
+ * from pre-collected board specific MDIO information
+ * @mdiodev: MDIO device pointer
+ * Context: can sleep
+ */
+void mdiobus_setup_mdiodev_from_board_info(struct mii_bus *bus,
+					   int (*cb)
+					   (struct mii_bus *bus,
+					    struct mdio_board_info *bi))
+{
+	struct mdio_board_entry *be;
+	struct mdio_board_entry *tmp;
+	struct mdio_board_info *bi;
+	int ret;
+
+	mutex_lock(&mdio_board_lock);
+	list_for_each_entry_safe(be, tmp, &mdio_board_list, list) {
+		bi = &be->board_info;
+
+		if (strcmp(bus->id, bi->bus_id))
+			continue;
+
+		mutex_unlock(&mdio_board_lock);
+		ret = cb(bus, bi);
+		mutex_lock(&mdio_board_lock);
+		if (ret)
+			continue;
+
+	}
+	mutex_unlock(&mdio_board_lock);
+}
+EXPORT_SYMBOL(mdiobus_setup_mdiodev_from_board_info);
+
+/**
+ * mdio_register_board_info - register MDIO devices for a given board
+ * @info: array of devices descriptors
+ * @n: number of descriptors provided
+ * Context: can sleep
+ *
+ * The board info passed can be marked with __initdata but be pointers
+ * such as platform_data etc. are copied as-is
+ */
+int mdiobus_register_board_info(const struct mdio_board_info *info,
+				unsigned int n)
+{
+	struct mdio_board_entry *be;
+	unsigned int i;
+
+	be = kcalloc(n, sizeof(*be), GFP_KERNEL);
+	if (!be)
+		return -ENOMEM;
+
+	for (i = 0; i < n; i++, be++, info++) {
+		memcpy(&be->board_info, info, sizeof(*info));
+		mutex_lock(&mdio_board_lock);
+		list_add_tail(&be->list, &mdio_board_list);
+		mutex_unlock(&mdio_board_lock);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(mdiobus_register_board_info);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-boardinfo.h b/src/kernel/linux/v4.19/drivers/net/phy/mdio-boardinfo.h
new file mode 100644
index 0000000..773bb51
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-boardinfo.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * mdio-boardinfo.h - board info interface internal to the mdio_bus
+ * component
+ */
+
+#ifndef __MDIO_BOARD_INFO_H
+#define __MDIO_BOARD_INFO_H
+
+#include <linux/phy.h>
+#include <linux/mutex.h>
+
+struct mdio_board_entry {
+	struct list_head	list;
+	struct mdio_board_info	board_info;
+};
+
+void mdiobus_setup_mdiodev_from_board_info(struct mii_bus *bus,
+					   int (*cb)
+					   (struct mii_bus *bus,
+					    struct mdio_board_info *bi));
+
+#endif /* __MDIO_BOARD_INFO_H */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-cavium.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-cavium.c
new file mode 100644
index 0000000..6df2fa7
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-cavium.c
@@ -0,0 +1,153 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2009-2016 Cavium, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+
+#include "mdio-cavium.h"
+
+static void cavium_mdiobus_set_mode(struct cavium_mdiobus *p,
+				    enum cavium_mdiobus_mode m)
+{
+	union cvmx_smix_clk smi_clk;
+
+	if (m == p->mode)
+		return;
+
+	smi_clk.u64 = oct_mdio_readq(p->register_base + SMI_CLK);
+	smi_clk.s.mode = (m == C45) ? 1 : 0;
+	smi_clk.s.preamble = 1;
+	oct_mdio_writeq(smi_clk.u64, p->register_base + SMI_CLK);
+	p->mode = m;
+}
+
+static int cavium_mdiobus_c45_addr(struct cavium_mdiobus *p,
+				   int phy_id, int regnum)
+{
+	union cvmx_smix_cmd smi_cmd;
+	union cvmx_smix_wr_dat smi_wr;
+	int timeout = 1000;
+
+	cavium_mdiobus_set_mode(p, C45);
+
+	smi_wr.u64 = 0;
+	smi_wr.s.dat = regnum & 0xffff;
+	oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT);
+
+	regnum = (regnum >> 16) & 0x1f;
+
+	smi_cmd.u64 = 0;
+	smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */
+	smi_cmd.s.phy_adr = phy_id;
+	smi_cmd.s.reg_adr = regnum;
+	oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD);
+
+	do {
+		/* Wait 1000 clocks so we don't saturate the RSL bus
+		 * doing reads.
+		 */
+		__delay(1000);
+		smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT);
+	} while (smi_wr.s.pending && --timeout);
+
+	if (timeout <= 0)
+		return -EIO;
+	return 0;
+}
+
+int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+	struct cavium_mdiobus *p = bus->priv;
+	union cvmx_smix_cmd smi_cmd;
+	union cvmx_smix_rd_dat smi_rd;
+	unsigned int op = 1; /* MDIO_CLAUSE_22_READ */
+	int timeout = 1000;
+
+	if (regnum & MII_ADDR_C45) {
+		int r = cavium_mdiobus_c45_addr(p, phy_id, regnum);
+
+		if (r < 0)
+			return r;
+
+		regnum = (regnum >> 16) & 0x1f;
+		op = 3; /* MDIO_CLAUSE_45_READ */
+	} else {
+		cavium_mdiobus_set_mode(p, C22);
+	}
+
+	smi_cmd.u64 = 0;
+	smi_cmd.s.phy_op = op;
+	smi_cmd.s.phy_adr = phy_id;
+	smi_cmd.s.reg_adr = regnum;
+	oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD);
+
+	do {
+		/* Wait 1000 clocks so we don't saturate the RSL bus
+		 * doing reads.
+		 */
+		__delay(1000);
+		smi_rd.u64 = oct_mdio_readq(p->register_base + SMI_RD_DAT);
+	} while (smi_rd.s.pending && --timeout);
+
+	if (smi_rd.s.val)
+		return smi_rd.s.dat;
+	else
+		return -EIO;
+}
+EXPORT_SYMBOL(cavium_mdiobus_read);
+
+int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
+{
+	struct cavium_mdiobus *p = bus->priv;
+	union cvmx_smix_cmd smi_cmd;
+	union cvmx_smix_wr_dat smi_wr;
+	unsigned int op = 0; /* MDIO_CLAUSE_22_WRITE */
+	int timeout = 1000;
+
+	if (regnum & MII_ADDR_C45) {
+		int r = cavium_mdiobus_c45_addr(p, phy_id, regnum);
+
+		if (r < 0)
+			return r;
+
+		regnum = (regnum >> 16) & 0x1f;
+		op = 1; /* MDIO_CLAUSE_45_WRITE */
+	} else {
+		cavium_mdiobus_set_mode(p, C22);
+	}
+
+	smi_wr.u64 = 0;
+	smi_wr.s.dat = val;
+	oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT);
+
+	smi_cmd.u64 = 0;
+	smi_cmd.s.phy_op = op;
+	smi_cmd.s.phy_adr = phy_id;
+	smi_cmd.s.reg_adr = regnum;
+	oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD);
+
+	do {
+		/* Wait 1000 clocks so we don't saturate the RSL bus
+		 * doing reads.
+		 */
+		__delay(1000);
+		smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT);
+	} while (smi_wr.s.pending && --timeout);
+
+	if (timeout <= 0)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL(cavium_mdiobus_write);
+
+MODULE_DESCRIPTION("Common code for OCTEON and Thunder MDIO bus drivers");
+MODULE_AUTHOR("David Daney");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-cavium.h b/src/kernel/linux/v4.19/drivers/net/phy/mdio-cavium.h
new file mode 100644
index 0000000..4bccd45
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-cavium.h
@@ -0,0 +1,119 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2009-2016 Cavium, Inc.
+ */
+
+enum cavium_mdiobus_mode {
+	UNINIT = 0,
+	C22,
+	C45
+};
+
+#define SMI_CMD		0x0
+#define SMI_WR_DAT	0x8
+#define SMI_RD_DAT	0x10
+#define SMI_CLK		0x18
+#define SMI_EN		0x20
+
+#ifdef __BIG_ENDIAN_BITFIELD
+#define OCT_MDIO_BITFIELD_FIELD(field, more)	\
+	field;					\
+	more
+
+#else
+#define OCT_MDIO_BITFIELD_FIELD(field, more)	\
+	more					\
+	field;
+
+#endif
+
+union cvmx_smix_clk {
+	u64 u64;
+	struct cvmx_smix_clk_s {
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_25_63:39,
+	  OCT_MDIO_BITFIELD_FIELD(u64 mode:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_21_23:3,
+	  OCT_MDIO_BITFIELD_FIELD(u64 sample_hi:5,
+	  OCT_MDIO_BITFIELD_FIELD(u64 sample_mode:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_14_14:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 clk_idle:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 preamble:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 sample:4,
+	  OCT_MDIO_BITFIELD_FIELD(u64 phase:8,
+	  ;))))))))))
+	} s;
+};
+
+union cvmx_smix_cmd {
+	u64 u64;
+	struct cvmx_smix_cmd_s {
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46,
+	  OCT_MDIO_BITFIELD_FIELD(u64 phy_op:2,
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_13_15:3,
+	  OCT_MDIO_BITFIELD_FIELD(u64 phy_adr:5,
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_5_7:3,
+	  OCT_MDIO_BITFIELD_FIELD(u64 reg_adr:5,
+	  ;))))))
+	} s;
+};
+
+union cvmx_smix_en {
+	u64 u64;
+	struct cvmx_smix_en_s {
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_1_63:63,
+	  OCT_MDIO_BITFIELD_FIELD(u64 en:1,
+	  ;))
+	} s;
+};
+
+union cvmx_smix_rd_dat {
+	u64 u64;
+	struct cvmx_smix_rd_dat_s {
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46,
+	  OCT_MDIO_BITFIELD_FIELD(u64 pending:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 val:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 dat:16,
+	  ;))))
+	} s;
+};
+
+union cvmx_smix_wr_dat {
+	u64 u64;
+	struct cvmx_smix_wr_dat_s {
+	  OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46,
+	  OCT_MDIO_BITFIELD_FIELD(u64 pending:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 val:1,
+	  OCT_MDIO_BITFIELD_FIELD(u64 dat:16,
+	  ;))))
+	} s;
+};
+
+struct cavium_mdiobus {
+	struct mii_bus *mii_bus;
+	u64 register_base;
+	enum cavium_mdiobus_mode mode;
+};
+
+#ifdef CONFIG_CAVIUM_OCTEON_SOC
+
+#include <asm/octeon/octeon.h>
+
+static inline void oct_mdio_writeq(u64 val, u64 addr)
+{
+	cvmx_write_csr(addr, val);
+}
+
+static inline u64 oct_mdio_readq(u64 addr)
+{
+	return cvmx_read_csr(addr);
+}
+#else
+#define oct_mdio_writeq(val, addr)	writeq(val, (void *)addr)
+#define oct_mdio_readq(addr)		readq((void *)addr)
+#endif
+
+int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum);
+int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-gpio.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-gpio.c
new file mode 100644
index 0000000..0fbcedc
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-gpio.c
@@ -0,0 +1,213 @@
+/*
+ * GPIO based MDIO bitbang driver.
+ * Supports OpenFirmware.
+ *
+ * Copyright (c) 2008 CSE Semaphore Belgium.
+ *  by Laurent Pinchart <laurentp@cse-semaphore.com>
+ *
+ * Copyright (C) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ *
+ * Based on earlier work by
+ *
+ * Copyright (c) 2003 Intracom S.A.
+ *  by Pantelis Antoniou <panto@intracom.gr>
+ *
+ * 2005 (c) MontaVista Software, Inc.
+ * Vitaly Bordug <vbordug@ru.mvista.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mdio-bitbang.h>
+#include <linux/mdio-gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_mdio.h>
+
+struct mdio_gpio_info {
+	struct mdiobb_ctrl ctrl;
+	struct gpio_desc *mdc, *mdio, *mdo;
+};
+
+static int mdio_gpio_get_data(struct device *dev,
+			      struct mdio_gpio_info *bitbang)
+{
+	bitbang->mdc = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDC,
+					    GPIOD_OUT_LOW);
+	if (IS_ERR(bitbang->mdc))
+		return PTR_ERR(bitbang->mdc);
+
+	bitbang->mdio = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDIO,
+					     GPIOD_IN);
+	if (IS_ERR(bitbang->mdio))
+		return PTR_ERR(bitbang->mdio);
+
+	bitbang->mdo = devm_gpiod_get_index_optional(dev, NULL, MDIO_GPIO_MDO,
+						     GPIOD_OUT_LOW);
+	return PTR_ERR_OR_ZERO(bitbang->mdo);
+}
+
+static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir)
+{
+	struct mdio_gpio_info *bitbang =
+		container_of(ctrl, struct mdio_gpio_info, ctrl);
+
+	if (bitbang->mdo) {
+		/* Separate output pin. Always set its value to high
+		 * when changing direction. If direction is input,
+		 * assume the pin serves as pull-up. If direction is
+		 * output, the default value is high.
+		 */
+		gpiod_set_value_cansleep(bitbang->mdo, 1);
+		return;
+	}
+
+	if (dir)
+		gpiod_direction_output(bitbang->mdio, 1);
+	else
+		gpiod_direction_input(bitbang->mdio);
+}
+
+static int mdio_get(struct mdiobb_ctrl *ctrl)
+{
+	struct mdio_gpio_info *bitbang =
+		container_of(ctrl, struct mdio_gpio_info, ctrl);
+
+	return gpiod_get_value_cansleep(bitbang->mdio);
+}
+
+static void mdio_set(struct mdiobb_ctrl *ctrl, int what)
+{
+	struct mdio_gpio_info *bitbang =
+		container_of(ctrl, struct mdio_gpio_info, ctrl);
+
+	if (bitbang->mdo)
+		gpiod_set_value_cansleep(bitbang->mdo, what);
+	else
+		gpiod_set_value_cansleep(bitbang->mdio, what);
+}
+
+static void mdc_set(struct mdiobb_ctrl *ctrl, int what)
+{
+	struct mdio_gpio_info *bitbang =
+		container_of(ctrl, struct mdio_gpio_info, ctrl);
+
+	gpiod_set_value_cansleep(bitbang->mdc, what);
+}
+
+static const struct mdiobb_ops mdio_gpio_ops = {
+	.owner = THIS_MODULE,
+	.set_mdc = mdc_set,
+	.set_mdio_dir = mdio_dir,
+	.set_mdio_data = mdio_set,
+	.get_mdio_data = mdio_get,
+};
+
+static struct mii_bus *mdio_gpio_bus_init(struct device *dev,
+					  struct mdio_gpio_info *bitbang,
+					  int bus_id)
+{
+	struct mii_bus *new_bus;
+
+	bitbang->ctrl.ops = &mdio_gpio_ops;
+
+	new_bus = alloc_mdio_bitbang(&bitbang->ctrl);
+	if (!new_bus)
+		return NULL;
+
+	new_bus->name = "GPIO Bitbanged MDIO";
+	new_bus->parent = dev;
+
+	if (bus_id != -1)
+		snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id);
+	else
+		strncpy(new_bus->id, "gpio", MII_BUS_ID_SIZE);
+
+	dev_set_drvdata(dev, new_bus);
+
+	return new_bus;
+}
+
+static void mdio_gpio_bus_deinit(struct device *dev)
+{
+	struct mii_bus *bus = dev_get_drvdata(dev);
+
+	free_mdio_bitbang(bus);
+}
+
+static void mdio_gpio_bus_destroy(struct device *dev)
+{
+	struct mii_bus *bus = dev_get_drvdata(dev);
+
+	mdiobus_unregister(bus);
+	mdio_gpio_bus_deinit(dev);
+}
+
+static int mdio_gpio_probe(struct platform_device *pdev)
+{
+	struct mdio_gpio_info *bitbang;
+	struct mii_bus *new_bus;
+	int ret, bus_id;
+
+	bitbang = devm_kzalloc(&pdev->dev, sizeof(*bitbang), GFP_KERNEL);
+	if (!bitbang)
+		return -ENOMEM;
+
+	ret = mdio_gpio_get_data(&pdev->dev, bitbang);
+	if (ret)
+		return ret;
+
+	if (pdev->dev.of_node) {
+		bus_id = of_alias_get_id(pdev->dev.of_node, "mdio-gpio");
+		if (bus_id < 0) {
+			dev_warn(&pdev->dev, "failed to get alias id\n");
+			bus_id = 0;
+		}
+	} else {
+		bus_id = pdev->id;
+	}
+
+	new_bus = mdio_gpio_bus_init(&pdev->dev, bitbang, bus_id);
+	if (!new_bus)
+		return -ENODEV;
+
+	ret = of_mdiobus_register(new_bus, pdev->dev.of_node);
+	if (ret)
+		mdio_gpio_bus_deinit(&pdev->dev);
+
+	return ret;
+}
+
+static int mdio_gpio_remove(struct platform_device *pdev)
+{
+	mdio_gpio_bus_destroy(&pdev->dev);
+
+	return 0;
+}
+
+static const struct of_device_id mdio_gpio_of_match[] = {
+	{ .compatible = "virtual,mdio-gpio", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mdio_gpio_of_match);
+
+static struct platform_driver mdio_gpio_driver = {
+	.probe = mdio_gpio_probe,
+	.remove = mdio_gpio_remove,
+	.driver		= {
+		.name	= "mdio-gpio",
+		.of_match_table = mdio_gpio_of_match,
+	},
+};
+
+module_platform_driver(mdio_gpio_driver);
+
+MODULE_ALIAS("platform:mdio-gpio");
+MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic driver for MDIO bus emulation using GPIO");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-hisi-femac.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-hisi-femac.c
new file mode 100644
index 0000000..b03fedd
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-hisi-femac.c
@@ -0,0 +1,166 @@
+/*
+ * Hisilicon Fast Ethernet MDIO Bus Driver
+ *
+ * Copyright (c) 2016 HiSilicon Technologies Co., Ltd.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+
+#define MDIO_RWCTRL		0x00
+#define MDIO_RO_DATA		0x04
+#define MDIO_WRITE		BIT(13)
+#define MDIO_RW_FINISH		BIT(15)
+#define BIT_PHY_ADDR_OFFSET	8
+#define BIT_WR_DATA_OFFSET	16
+
+struct hisi_femac_mdio_data {
+	struct clk *clk;
+	void __iomem *membase;
+};
+
+static int hisi_femac_mdio_wait_ready(struct hisi_femac_mdio_data *data)
+{
+	u32 val;
+
+	return readl_poll_timeout(data->membase + MDIO_RWCTRL,
+				  val, val & MDIO_RW_FINISH, 20, 10000);
+}
+
+static int hisi_femac_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+	struct hisi_femac_mdio_data *data = bus->priv;
+	int ret;
+
+	ret = hisi_femac_mdio_wait_ready(data);
+	if (ret)
+		return ret;
+
+	writel((mii_id << BIT_PHY_ADDR_OFFSET) | regnum,
+	       data->membase + MDIO_RWCTRL);
+
+	ret = hisi_femac_mdio_wait_ready(data);
+	if (ret)
+		return ret;
+
+	return readl(data->membase + MDIO_RO_DATA) & 0xFFFF;
+}
+
+static int hisi_femac_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
+				 u16 value)
+{
+	struct hisi_femac_mdio_data *data = bus->priv;
+	int ret;
+
+	ret = hisi_femac_mdio_wait_ready(data);
+	if (ret)
+		return ret;
+
+	writel(MDIO_WRITE | (value << BIT_WR_DATA_OFFSET) |
+	       (mii_id << BIT_PHY_ADDR_OFFSET) | regnum,
+	       data->membase + MDIO_RWCTRL);
+
+	return hisi_femac_mdio_wait_ready(data);
+}
+
+static int hisi_femac_mdio_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct mii_bus *bus;
+	struct hisi_femac_mdio_data *data;
+	struct resource *res;
+	int ret;
+
+	bus = mdiobus_alloc_size(sizeof(*data));
+	if (!bus)
+		return -ENOMEM;
+
+	bus->name = "hisi_femac_mii_bus";
+	bus->read = &hisi_femac_mdio_read;
+	bus->write = &hisi_femac_mdio_write;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
+	bus->parent = &pdev->dev;
+
+	data = bus->priv;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->membase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->membase)) {
+		ret = PTR_ERR(data->membase);
+		goto err_out_free_mdiobus;
+	}
+
+	data->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(data->clk)) {
+		ret = PTR_ERR(data->clk);
+		goto err_out_free_mdiobus;
+	}
+
+	ret = clk_prepare_enable(data->clk);
+	if (ret)
+		goto err_out_free_mdiobus;
+
+	ret = of_mdiobus_register(bus, np);
+	if (ret)
+		goto err_out_disable_clk;
+
+	platform_set_drvdata(pdev, bus);
+
+	return 0;
+
+err_out_disable_clk:
+	clk_disable_unprepare(data->clk);
+err_out_free_mdiobus:
+	mdiobus_free(bus);
+	return ret;
+}
+
+static int hisi_femac_mdio_remove(struct platform_device *pdev)
+{
+	struct mii_bus *bus = platform_get_drvdata(pdev);
+	struct hisi_femac_mdio_data *data = bus->priv;
+
+	mdiobus_unregister(bus);
+	clk_disable_unprepare(data->clk);
+	mdiobus_free(bus);
+
+	return 0;
+}
+
+static const struct of_device_id hisi_femac_mdio_dt_ids[] = {
+	{ .compatible = "hisilicon,hisi-femac-mdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, hisi_femac_mdio_dt_ids);
+
+static struct platform_driver hisi_femac_mdio_driver = {
+	.probe = hisi_femac_mdio_probe,
+	.remove = hisi_femac_mdio_remove,
+	.driver = {
+		.name = "hisi-femac-mdio",
+		.of_match_table = hisi_femac_mdio_dt_ids,
+	},
+};
+
+module_platform_driver(hisi_femac_mdio_driver);
+
+MODULE_DESCRIPTION("Hisilicon Fast Ethernet MAC MDIO interface driver");
+MODULE_AUTHOR("Dongpo Li <lidongpo@hisilicon.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-i2c.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-i2c.c
new file mode 100644
index 0000000..b2f09c8
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-i2c.c
@@ -0,0 +1,121 @@
+/*
+ * MDIO I2C bridge
+ *
+ * Copyright (C) 2015-2016 Russell King
+ *
+ * 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.
+ *
+ * Network PHYs can appear on I2C buses when they are part of SFP module.
+ * This driver exposes these PHYs to the networking PHY code, allowing
+ * our PHY drivers access to these PHYs, and so allowing configuration
+ * of their settings.
+ */
+#include <linux/i2c.h>
+#include <linux/phy.h>
+
+#include "mdio-i2c.h"
+
+/*
+ * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
+ * specified to be present in SFP modules.  These correspond with PHY
+ * addresses 16 and 17.  Disallow access to these "phy" addresses.
+ */
+static bool i2c_mii_valid_phy_id(int phy_id)
+{
+	return phy_id != 0x10 && phy_id != 0x11;
+}
+
+static unsigned int i2c_mii_phy_addr(int phy_id)
+{
+	return phy_id + 0x40;
+}
+
+static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	struct i2c_adapter *i2c = bus->priv;
+	struct i2c_msg msgs[2];
+	u8 addr[3], data[2], *p;
+	int bus_addr, ret;
+
+	if (!i2c_mii_valid_phy_id(phy_id))
+		return 0xffff;
+
+	p = addr;
+	if (reg & MII_ADDR_C45) {
+		*p++ = 0x20 | ((reg >> 16) & 31);
+		*p++ = reg >> 8;
+	}
+	*p++ = reg;
+
+	bus_addr = i2c_mii_phy_addr(phy_id);
+	msgs[0].addr = bus_addr;
+	msgs[0].flags = 0;
+	msgs[0].len = p - addr;
+	msgs[0].buf = addr;
+	msgs[1].addr = bus_addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = sizeof(data);
+	msgs[1].buf = data;
+
+	ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return 0xffff;
+
+	return data[0] << 8 | data[1];
+}
+
+static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
+{
+	struct i2c_adapter *i2c = bus->priv;
+	struct i2c_msg msg;
+	int ret;
+	u8 data[5], *p;
+
+	if (!i2c_mii_valid_phy_id(phy_id))
+		return 0;
+
+	p = data;
+	if (reg & MII_ADDR_C45) {
+		*p++ = (reg >> 16) & 31;
+		*p++ = reg >> 8;
+	}
+	*p++ = reg;
+	*p++ = val >> 8;
+	*p++ = val;
+
+	msg.addr = i2c_mii_phy_addr(phy_id);
+	msg.flags = 0;
+	msg.len = p - data;
+	msg.buf = data;
+
+	ret = i2c_transfer(i2c, &msg, 1);
+
+	return ret < 0 ? ret : 0;
+}
+
+struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
+{
+	struct mii_bus *mii;
+
+	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+		return ERR_PTR(-EINVAL);
+
+	mii = mdiobus_alloc();
+	if (!mii)
+		return ERR_PTR(-ENOMEM);
+
+	snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent));
+	mii->parent = parent;
+	mii->read = i2c_mii_read;
+	mii->write = i2c_mii_write;
+	mii->priv = i2c;
+
+	return mii;
+}
+EXPORT_SYMBOL_GPL(mdio_i2c_alloc);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("MDIO I2C bridge library");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-i2c.h b/src/kernel/linux/v4.19/drivers/net/phy/mdio-i2c.h
new file mode 100644
index 0000000..889ab57
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-i2c.h
@@ -0,0 +1,19 @@
+/*
+ * MDIO I2C bridge
+ *
+ * Copyright (C) 2015 Russell King
+ *
+ * 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 MDIO_I2C_H
+#define MDIO_I2C_H
+
+struct device;
+struct i2c_adapter;
+struct mii_bus;
+
+struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-moxart.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-moxart.c
new file mode 100644
index 0000000..5bb56d1
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-moxart.c
@@ -0,0 +1,193 @@
+/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver
+ *
+ * Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#define REG_PHY_CTRL            0
+#define REG_PHY_WRITE_DATA      4
+
+/* REG_PHY_CTRL */
+#define MIIWR                   BIT(27) /* init write sequence (auto cleared)*/
+#define MIIRD                   BIT(26)
+#define REGAD_MASK              0x3e00000
+#define PHYAD_MASK              0x1f0000
+#define MIIRDATA_MASK           0xffff
+
+/* REG_PHY_WRITE_DATA */
+#define MIIWDATA_MASK           0xffff
+
+struct moxart_mdio_data {
+	void __iomem		*base;
+};
+
+static int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+	struct moxart_mdio_data *data = bus->priv;
+	u32 ctrl = 0;
+	unsigned int count = 5;
+
+	dev_dbg(&bus->dev, "%s\n", __func__);
+
+	ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) |
+		((regnum << 21) & REGAD_MASK);
+
+	writel(ctrl, data->base + REG_PHY_CTRL);
+
+	do {
+		ctrl = readl(data->base + REG_PHY_CTRL);
+
+		if (!(ctrl & MIIRD))
+			return ctrl & MIIRDATA_MASK;
+
+		mdelay(10);
+		count--;
+	} while (count > 0);
+
+	dev_dbg(&bus->dev, "%s timed out\n", __func__);
+
+	return -ETIMEDOUT;
+}
+
+static int moxart_mdio_write(struct mii_bus *bus, int mii_id,
+			     int regnum, u16 value)
+{
+	struct moxart_mdio_data *data = bus->priv;
+	u32 ctrl = 0;
+	unsigned int count = 5;
+
+	dev_dbg(&bus->dev, "%s\n", __func__);
+
+	ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) |
+		((regnum << 21) & REGAD_MASK);
+
+	value &= MIIWDATA_MASK;
+
+	writel(value, data->base + REG_PHY_WRITE_DATA);
+	writel(ctrl, data->base + REG_PHY_CTRL);
+
+	do {
+		ctrl = readl(data->base + REG_PHY_CTRL);
+
+		if (!(ctrl & MIIWR))
+			return 0;
+
+		mdelay(10);
+		count--;
+	} while (count > 0);
+
+	dev_dbg(&bus->dev, "%s timed out\n", __func__);
+
+	return -ETIMEDOUT;
+}
+
+static int moxart_mdio_reset(struct mii_bus *bus)
+{
+	int data, i;
+
+	for (i = 0; i < PHY_MAX_ADDR; i++) {
+		data = moxart_mdio_read(bus, i, MII_BMCR);
+		if (data < 0)
+			continue;
+
+		data |= BMCR_RESET;
+		if (moxart_mdio_write(bus, i, MII_BMCR, data) < 0)
+			continue;
+	}
+
+	return 0;
+}
+
+static int moxart_mdio_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct mii_bus *bus;
+	struct moxart_mdio_data *data;
+	struct resource *res;
+	int ret, i;
+
+	bus = mdiobus_alloc_size(sizeof(*data));
+	if (!bus)
+		return -ENOMEM;
+
+	bus->name = "MOXA ART Ethernet MII";
+	bus->read = &moxart_mdio_read;
+	bus->write = &moxart_mdio_write;
+	bus->reset = &moxart_mdio_reset;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d-mii", pdev->name, pdev->id);
+	bus->parent = &pdev->dev;
+
+	/* Setting PHY_IGNORE_INTERRUPT here even if it has no effect,
+	 * of_mdiobus_register() sets these PHY_POLL.
+	 * Ideally, the interrupt from MAC controller could be used to
+	 * detect link state changes, not polling, i.e. if there was
+	 * a way phy_driver could set PHY_HAS_INTERRUPT but have that
+	 * interrupt handled in ethernet drivercode.
+	 */
+	for (i = 0; i < PHY_MAX_ADDR; i++)
+		bus->irq[i] = PHY_IGNORE_INTERRUPT;
+
+	data = bus->priv;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->base)) {
+		ret = PTR_ERR(data->base);
+		goto err_out_free_mdiobus;
+	}
+
+	ret = of_mdiobus_register(bus, np);
+	if (ret < 0)
+		goto err_out_free_mdiobus;
+
+	platform_set_drvdata(pdev, bus);
+
+	return 0;
+
+err_out_free_mdiobus:
+	mdiobus_free(bus);
+	return ret;
+}
+
+static int moxart_mdio_remove(struct platform_device *pdev)
+{
+	struct mii_bus *bus = platform_get_drvdata(pdev);
+
+	mdiobus_unregister(bus);
+	mdiobus_free(bus);
+
+	return 0;
+}
+
+static const struct of_device_id moxart_mdio_dt_ids[] = {
+	{ .compatible = "moxa,moxart-mdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids);
+
+static struct platform_driver moxart_mdio_driver = {
+	.probe = moxart_mdio_probe,
+	.remove = moxart_mdio_remove,
+	.driver = {
+		.name = "moxart-mdio",
+		.of_match_table = moxart_mdio_dt_ids,
+	},
+};
+
+module_platform_driver(moxart_mdio_driver);
+
+MODULE_DESCRIPTION("MOXA ART MDIO interface driver");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-mscc-miim.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mscc-miim.c
new file mode 100644
index 0000000..badbc99
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mscc-miim.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Driver for the MDIO interface of Microsemi network switches.
+ *
+ * Author: Alexandre Belloni <alexandre.belloni@bootlin.com>
+ * Copyright (c) 2017 Microsemi Corporation
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/of_mdio.h>
+
+#define MSCC_MIIM_REG_STATUS		0x0
+#define		MSCC_MIIM_STATUS_STAT_BUSY	BIT(3)
+#define MSCC_MIIM_REG_CMD		0x8
+#define		MSCC_MIIM_CMD_OPR_WRITE		BIT(1)
+#define		MSCC_MIIM_CMD_OPR_READ		BIT(2)
+#define		MSCC_MIIM_CMD_WRDATA_SHIFT	4
+#define		MSCC_MIIM_CMD_REGAD_SHIFT	20
+#define		MSCC_MIIM_CMD_PHYAD_SHIFT	25
+#define		MSCC_MIIM_CMD_VLD		BIT(31)
+#define MSCC_MIIM_REG_DATA		0xC
+#define		MSCC_MIIM_DATA_ERROR		(BIT(16) | BIT(17))
+
+#define MSCC_PHY_REG_PHY_CFG	0x0
+#define		PHY_CFG_PHY_ENA		(BIT(0) | BIT(1) | BIT(2) | BIT(3))
+#define		PHY_CFG_PHY_COMMON_RESET BIT(4)
+#define		PHY_CFG_PHY_RESET	(BIT(5) | BIT(6) | BIT(7) | BIT(8))
+#define MSCC_PHY_REG_PHY_STATUS	0x4
+
+struct mscc_miim_dev {
+	void __iomem *regs;
+	void __iomem *phy_regs;
+};
+
+static int mscc_miim_wait_ready(struct mii_bus *bus)
+{
+	struct mscc_miim_dev *miim = bus->priv;
+	u32 val;
+
+	readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val,
+			   !(val & MSCC_MIIM_STATUS_STAT_BUSY), 100, 250000);
+	if (val & MSCC_MIIM_STATUS_STAT_BUSY)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+	struct mscc_miim_dev *miim = bus->priv;
+	u32 val;
+	int ret;
+
+	ret = mscc_miim_wait_ready(bus);
+	if (ret)
+		goto out;
+
+	writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
+	       (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ,
+	       miim->regs + MSCC_MIIM_REG_CMD);
+
+	ret = mscc_miim_wait_ready(bus);
+	if (ret)
+		goto out;
+
+	val = readl(miim->regs + MSCC_MIIM_REG_DATA);
+	if (val & MSCC_MIIM_DATA_ERROR) {
+		ret = -EIO;
+		goto out;
+	}
+
+	ret = val & 0xFFFF;
+out:
+	return ret;
+}
+
+static int mscc_miim_write(struct mii_bus *bus, int mii_id,
+			   int regnum, u16 value)
+{
+	struct mscc_miim_dev *miim = bus->priv;
+	int ret;
+
+	ret = mscc_miim_wait_ready(bus);
+	if (ret < 0)
+		goto out;
+
+	writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) |
+	       (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) |
+	       (value << MSCC_MIIM_CMD_WRDATA_SHIFT) |
+	       MSCC_MIIM_CMD_OPR_WRITE,
+	       miim->regs + MSCC_MIIM_REG_CMD);
+
+out:
+	return ret;
+}
+
+static int mscc_miim_reset(struct mii_bus *bus)
+{
+	struct mscc_miim_dev *miim = bus->priv;
+
+	if (miim->phy_regs) {
+		writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
+		writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG);
+		mdelay(500);
+	}
+
+	return 0;
+}
+
+static int mscc_miim_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct mii_bus *bus;
+	struct mscc_miim_dev *dev;
+	int ret;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev));
+	if (!bus)
+		return -ENOMEM;
+
+	bus->name = "mscc_miim";
+	bus->read = mscc_miim_read;
+	bus->write = mscc_miim_write;
+	bus->reset = mscc_miim_reset;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
+	bus->parent = &pdev->dev;
+
+	dev = bus->priv;
+	dev->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(dev->regs)) {
+		dev_err(&pdev->dev, "Unable to map MIIM registers\n");
+		return PTR_ERR(dev->regs);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (res) {
+		dev->phy_regs = devm_ioremap_resource(&pdev->dev, res);
+		if (IS_ERR(dev->phy_regs)) {
+			dev_err(&pdev->dev, "Unable to map internal phy registers\n");
+			return PTR_ERR(dev->phy_regs);
+		}
+	}
+
+	ret = of_mdiobus_register(bus, pdev->dev.of_node);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, bus);
+
+	return 0;
+}
+
+static int mscc_miim_remove(struct platform_device *pdev)
+{
+	struct mii_bus *bus = platform_get_drvdata(pdev);
+
+	mdiobus_unregister(bus);
+
+	return 0;
+}
+
+static const struct of_device_id mscc_miim_match[] = {
+	{ .compatible = "mscc,ocelot-miim" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mscc_miim_match);
+
+static struct platform_driver mscc_miim_driver = {
+	.probe = mscc_miim_probe,
+	.remove = mscc_miim_remove,
+	.driver = {
+		.name = "mscc-miim",
+		.of_match_table = mscc_miim_match,
+	},
+};
+
+module_platform_driver(mscc_miim_driver);
+
+MODULE_DESCRIPTION("Microsemi MIIM driver");
+MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-bcm-iproc.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-bcm-iproc.c
new file mode 100644
index 0000000..c017486
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-bcm-iproc.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2016 Broadcom
+ *
+ * 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 (the "GPL").
+ *
+ * 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 version 2 (GPLv2) for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 (GPLv2) along with this source code.
+ */
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/mdio-mux.h>
+#include <linux/delay.h>
+
+#define MDIO_RATE_ADJ_EXT_OFFSET	0x000
+#define MDIO_RATE_ADJ_INT_OFFSET	0x004
+#define MDIO_RATE_ADJ_DIVIDENT_SHIFT	16
+
+#define MDIO_SCAN_CTRL_OFFSET		0x008
+#define MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR	28
+
+#define MDIO_PARAM_OFFSET		0x23c
+#define MDIO_PARAM_MIIM_CYCLE		29
+#define MDIO_PARAM_INTERNAL_SEL		25
+#define MDIO_PARAM_BUS_ID		22
+#define MDIO_PARAM_C45_SEL		21
+#define MDIO_PARAM_PHY_ID		16
+#define MDIO_PARAM_PHY_DATA		0
+
+#define MDIO_READ_OFFSET		0x240
+#define MDIO_READ_DATA_MASK		0xffff
+#define MDIO_ADDR_OFFSET		0x244
+
+#define MDIO_CTRL_OFFSET		0x248
+#define MDIO_CTRL_WRITE_OP		0x1
+#define MDIO_CTRL_READ_OP		0x2
+
+#define MDIO_STAT_OFFSET		0x24c
+#define MDIO_STAT_DONE			1
+
+#define BUS_MAX_ADDR			32
+#define EXT_BUS_START_ADDR		16
+
+#define MDIO_REG_ADDR_SPACE_SIZE	0x250
+
+#define MDIO_OPERATING_FREQUENCY	11000000
+#define MDIO_RATE_ADJ_DIVIDENT		1
+
+struct iproc_mdiomux_desc {
+	void *mux_handle;
+	void __iomem *base;
+	struct device *dev;
+	struct mii_bus *mii_bus;
+	struct clk *core_clk;
+};
+
+static void mdio_mux_iproc_config(struct iproc_mdiomux_desc *md)
+{
+	u32 divisor;
+	u32 val;
+
+	/* Disable external mdio master access */
+	val = readl(md->base + MDIO_SCAN_CTRL_OFFSET);
+	val |= BIT(MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR);
+	writel(val, md->base + MDIO_SCAN_CTRL_OFFSET);
+
+	if (md->core_clk) {
+		/* use rate adjust regs to derrive the mdio's operating
+		 * frequency from the specified core clock
+		 */
+		divisor = clk_get_rate(md->core_clk) / MDIO_OPERATING_FREQUENCY;
+		divisor = divisor / (MDIO_RATE_ADJ_DIVIDENT + 1);
+		val = divisor;
+		val |= MDIO_RATE_ADJ_DIVIDENT << MDIO_RATE_ADJ_DIVIDENT_SHIFT;
+		writel(val, md->base + MDIO_RATE_ADJ_EXT_OFFSET);
+		writel(val, md->base + MDIO_RATE_ADJ_INT_OFFSET);
+	}
+}
+
+static int iproc_mdio_wait_for_idle(void __iomem *base, bool result)
+{
+	unsigned int timeout = 1000; /* loop for 1s */
+	u32 val;
+
+	do {
+		val = readl(base + MDIO_STAT_OFFSET);
+		if ((val & MDIO_STAT_DONE) == result)
+			return 0;
+
+		usleep_range(1000, 2000);
+	} while (timeout--);
+
+	return -ETIMEDOUT;
+}
+
+/* start_miim_ops- Program and start MDIO transaction over mdio bus.
+ * @base: Base address
+ * @phyid: phyid of the selected bus.
+ * @reg: register offset to be read/written.
+ * @val :0 if read op else value to be written in @reg;
+ * @op: Operation that need to be carried out.
+ *      MDIO_CTRL_READ_OP: Read transaction.
+ *      MDIO_CTRL_WRITE_OP: Write transaction.
+ *
+ * Return value: Successful Read operation returns read reg values and write
+ *      operation returns 0. Failure operation returns negative error code.
+ */
+static int start_miim_ops(void __iomem *base,
+			  u16 phyid, u32 reg, u16 val, u32 op)
+{
+	u32 param;
+	int ret;
+
+	writel(0, base + MDIO_CTRL_OFFSET);
+	ret = iproc_mdio_wait_for_idle(base, 0);
+	if (ret)
+		goto err;
+
+	param = readl(base + MDIO_PARAM_OFFSET);
+	param |= phyid << MDIO_PARAM_PHY_ID;
+	param |= val << MDIO_PARAM_PHY_DATA;
+	if (reg & MII_ADDR_C45)
+		param |= BIT(MDIO_PARAM_C45_SEL);
+
+	writel(param, base + MDIO_PARAM_OFFSET);
+
+	writel(reg, base + MDIO_ADDR_OFFSET);
+
+	writel(op, base + MDIO_CTRL_OFFSET);
+
+	ret = iproc_mdio_wait_for_idle(base, 1);
+	if (ret)
+		goto err;
+
+	if (op == MDIO_CTRL_READ_OP)
+		ret = readl(base + MDIO_READ_OFFSET) & MDIO_READ_DATA_MASK;
+err:
+	return ret;
+}
+
+static int iproc_mdiomux_read(struct mii_bus *bus, int phyid, int reg)
+{
+	struct iproc_mdiomux_desc *md = bus->priv;
+	int ret;
+
+	ret = start_miim_ops(md->base, phyid, reg, 0, MDIO_CTRL_READ_OP);
+	if (ret < 0)
+		dev_err(&bus->dev, "mdiomux read operation failed!!!");
+
+	return ret;
+}
+
+static int iproc_mdiomux_write(struct mii_bus *bus,
+			       int phyid, int reg, u16 val)
+{
+	struct iproc_mdiomux_desc *md = bus->priv;
+	int ret;
+
+	/* Write val at reg offset */
+	ret = start_miim_ops(md->base, phyid, reg, val, MDIO_CTRL_WRITE_OP);
+	if (ret < 0)
+		dev_err(&bus->dev, "mdiomux write operation failed!!!");
+
+	return ret;
+}
+
+static int mdio_mux_iproc_switch_fn(int current_child, int desired_child,
+				    void *data)
+{
+	struct iproc_mdiomux_desc *md = data;
+	u32 param, bus_id;
+	bool bus_dir;
+
+	/* select bus and its properties */
+	bus_dir = (desired_child < EXT_BUS_START_ADDR);
+	bus_id = bus_dir ? desired_child : (desired_child - EXT_BUS_START_ADDR);
+
+	param = (bus_dir ? 1 : 0) << MDIO_PARAM_INTERNAL_SEL;
+	param |= (bus_id << MDIO_PARAM_BUS_ID);
+
+	writel(param, md->base + MDIO_PARAM_OFFSET);
+	return 0;
+}
+
+static int mdio_mux_iproc_probe(struct platform_device *pdev)
+{
+	struct iproc_mdiomux_desc *md;
+	struct mii_bus *bus;
+	struct resource *res;
+	int rc;
+
+	md = devm_kzalloc(&pdev->dev, sizeof(*md), GFP_KERNEL);
+	if (!md)
+		return -ENOMEM;
+	md->dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res->start & 0xfff) {
+		/* For backward compatibility in case the
+		 * base address is specified with an offset.
+		 */
+		dev_info(&pdev->dev, "fix base address in dt-blob\n");
+		res->start &= ~0xfff;
+		res->end = res->start + MDIO_REG_ADDR_SPACE_SIZE - 1;
+	}
+	md->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(md->base)) {
+		dev_err(&pdev->dev, "failed to ioremap register\n");
+		return PTR_ERR(md->base);
+	}
+
+	md->mii_bus = devm_mdiobus_alloc(&pdev->dev);
+	if (!md->mii_bus) {
+		dev_err(&pdev->dev, "mdiomux bus alloc failed\n");
+		return -ENOMEM;
+	}
+
+	md->core_clk = devm_clk_get(&pdev->dev, NULL);
+	if (md->core_clk == ERR_PTR(-ENOENT) ||
+	    md->core_clk == ERR_PTR(-EINVAL))
+		md->core_clk = NULL;
+	else if (IS_ERR(md->core_clk))
+		return PTR_ERR(md->core_clk);
+
+	rc = clk_prepare_enable(md->core_clk);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to enable core clk\n");
+		return rc;
+	}
+
+	bus = md->mii_bus;
+	bus->priv = md;
+	bus->name = "iProc MDIO mux bus";
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id);
+	bus->parent = &pdev->dev;
+	bus->read = iproc_mdiomux_read;
+	bus->write = iproc_mdiomux_write;
+
+	bus->phy_mask = ~0;
+	bus->dev.of_node = pdev->dev.of_node;
+	rc = mdiobus_register(bus);
+	if (rc) {
+		dev_err(&pdev->dev, "mdiomux registration failed\n");
+		goto out_clk;
+	}
+
+	platform_set_drvdata(pdev, md);
+
+	rc = mdio_mux_init(md->dev, md->dev->of_node, mdio_mux_iproc_switch_fn,
+			   &md->mux_handle, md, md->mii_bus);
+	if (rc) {
+		dev_info(md->dev, "mdiomux initialization failed\n");
+		goto out_register;
+	}
+
+	mdio_mux_iproc_config(md);
+
+	dev_info(md->dev, "iProc mdiomux registered\n");
+	return 0;
+
+out_register:
+	mdiobus_unregister(bus);
+out_clk:
+	clk_disable_unprepare(md->core_clk);
+	return rc;
+}
+
+static int mdio_mux_iproc_remove(struct platform_device *pdev)
+{
+	struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev);
+
+	mdio_mux_uninit(md->mux_handle);
+	mdiobus_unregister(md->mii_bus);
+	clk_disable_unprepare(md->core_clk);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mdio_mux_iproc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(md->core_clk);
+
+	return 0;
+}
+
+static int mdio_mux_iproc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev);
+
+	clk_prepare_enable(md->core_clk);
+	mdio_mux_iproc_config(md);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(mdio_mux_iproc_pm_ops,
+			 mdio_mux_iproc_suspend, mdio_mux_iproc_resume);
+
+static const struct of_device_id mdio_mux_iproc_match[] = {
+	{
+		.compatible = "brcm,mdio-mux-iproc",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, mdio_mux_iproc_match);
+
+static struct platform_driver mdiomux_iproc_driver = {
+	.driver = {
+		.name		= "mdio-mux-iproc",
+		.of_match_table = mdio_mux_iproc_match,
+		.pm		= &mdio_mux_iproc_pm_ops,
+	},
+	.probe		= mdio_mux_iproc_probe,
+	.remove		= mdio_mux_iproc_remove,
+};
+
+module_platform_driver(mdiomux_iproc_driver);
+
+MODULE_DESCRIPTION("iProc MDIO Mux Bus Driver");
+MODULE_AUTHOR("Pramod Kumar <pramod.kumar@broadcom.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-gpio.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-gpio.c
new file mode 100644
index 0000000..bc90764
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-gpio.c
@@ -0,0 +1,109 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2011, 2012 Cavium, Inc.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/mdio-mux.h>
+#include <linux/gpio/consumer.h>
+
+#define DRV_VERSION "1.1"
+#define DRV_DESCRIPTION "GPIO controlled MDIO bus multiplexer driver"
+
+struct mdio_mux_gpio_state {
+	struct gpio_descs *gpios;
+	void *mux_handle;
+	int values[];
+};
+
+static int mdio_mux_gpio_switch_fn(int current_child, int desired_child,
+				   void *data)
+{
+	struct mdio_mux_gpio_state *s = data;
+	unsigned int n;
+
+	if (current_child == desired_child)
+		return 0;
+
+	for (n = 0; n < s->gpios->ndescs; n++)
+		s->values[n] = (desired_child >> n) & 1;
+
+	gpiod_set_array_value_cansleep(s->gpios->ndescs, s->gpios->desc,
+				       s->values);
+
+	return 0;
+}
+
+static int mdio_mux_gpio_probe(struct platform_device *pdev)
+{
+	struct mdio_mux_gpio_state *s;
+	struct gpio_descs *gpios;
+	int r;
+
+	gpios = gpiod_get_array(&pdev->dev, NULL, GPIOD_OUT_LOW);
+	if (IS_ERR(gpios))
+		return PTR_ERR(gpios);
+
+	s = devm_kzalloc(&pdev->dev, struct_size(s, values, gpios->ndescs),
+			 GFP_KERNEL);
+	if (!s) {
+		gpiod_put_array(gpios);
+		return -ENOMEM;
+	}
+
+	s->gpios = gpios;
+
+	r = mdio_mux_init(&pdev->dev, pdev->dev.of_node,
+			  mdio_mux_gpio_switch_fn, &s->mux_handle, s, NULL);
+
+	if (r != 0) {
+		gpiod_put_array(s->gpios);
+		return r;
+	}
+
+	pdev->dev.platform_data = s;
+	return 0;
+}
+
+static int mdio_mux_gpio_remove(struct platform_device *pdev)
+{
+	struct mdio_mux_gpio_state *s = dev_get_platdata(&pdev->dev);
+	mdio_mux_uninit(s->mux_handle);
+	gpiod_put_array(s->gpios);
+	return 0;
+}
+
+static const struct of_device_id mdio_mux_gpio_match[] = {
+	{
+		.compatible = "mdio-mux-gpio",
+	},
+	{
+		/* Legacy compatible property. */
+		.compatible = "cavium,mdio-mux-sn74cbtlv3253",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, mdio_mux_gpio_match);
+
+static struct platform_driver mdio_mux_gpio_driver = {
+	.driver = {
+		.name		= "mdio-mux-gpio",
+		.of_match_table = mdio_mux_gpio_match,
+	},
+	.probe		= mdio_mux_gpio_probe,
+	.remove		= mdio_mux_gpio_remove,
+};
+
+module_platform_driver(mdio_mux_gpio_driver);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_VERSION(DRV_VERSION);
+MODULE_AUTHOR("David Daney");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-mmioreg.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-mmioreg.c
new file mode 100644
index 0000000..70f6115
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux-mmioreg.c
@@ -0,0 +1,207 @@
+/*
+ * Simple memory-mapped device MDIO MUX driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/mdio-mux.h>
+
+struct mdio_mux_mmioreg_state {
+	void *mux_handle;
+	phys_addr_t phys;
+	unsigned int iosize;
+	unsigned int mask;
+};
+
+/*
+ * MDIO multiplexing switch function
+ *
+ * This function is called by the mdio-mux layer when it thinks the mdio bus
+ * multiplexer needs to switch.
+ *
+ * 'current_child' is the current value of the mux register (masked via
+ * s->mask).
+ *
+ * 'desired_child' is the value of the 'reg' property of the target child MDIO
+ * node.
+ *
+ * The first time this function is called, current_child == -1.
+ *
+ * If current_child == desired_child, then the mux is already set to the
+ * correct bus.
+ */
+static int mdio_mux_mmioreg_switch_fn(int current_child, int desired_child,
+				      void *data)
+{
+	struct mdio_mux_mmioreg_state *s = data;
+
+	if (current_child ^ desired_child) {
+		void __iomem *p = ioremap(s->phys, s->iosize);
+		if (!p)
+			return -ENOMEM;
+
+		switch (s->iosize) {
+		case sizeof(uint8_t): {
+			uint8_t x, y;
+
+			x = ioread8(p);
+			y = (x & ~s->mask) | desired_child;
+			if (x != y) {
+				iowrite8((x & ~s->mask) | desired_child, p);
+				pr_debug("%s: %02x -> %02x\n", __func__, x, y);
+			}
+
+			break;
+		}
+		case sizeof(uint16_t): {
+			uint16_t x, y;
+
+			x = ioread16(p);
+			y = (x & ~s->mask) | desired_child;
+			if (x != y) {
+				iowrite16((x & ~s->mask) | desired_child, p);
+				pr_debug("%s: %04x -> %04x\n", __func__, x, y);
+			}
+
+			break;
+		}
+		case sizeof(uint32_t): {
+			uint32_t x, y;
+
+			x = ioread32(p);
+			y = (x & ~s->mask) | desired_child;
+			if (x != y) {
+				iowrite32((x & ~s->mask) | desired_child, p);
+				pr_debug("%s: %08x -> %08x\n", __func__, x, y);
+			}
+
+			break;
+		}
+		}
+
+		iounmap(p);
+	}
+
+	return 0;
+}
+
+static int mdio_mux_mmioreg_probe(struct platform_device *pdev)
+{
+	struct device_node *np2, *np = pdev->dev.of_node;
+	struct mdio_mux_mmioreg_state *s;
+	struct resource res;
+	const __be32 *iprop;
+	int len, ret;
+
+	dev_dbg(&pdev->dev, "probing node %pOF\n", np);
+
+	s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
+	if (!s)
+		return -ENOMEM;
+
+	ret = of_address_to_resource(np, 0, &res);
+	if (ret) {
+		dev_err(&pdev->dev, "could not obtain memory map for node %pOF\n",
+			np);
+		return ret;
+	}
+	s->phys = res.start;
+
+	s->iosize = resource_size(&res);
+	if (s->iosize != sizeof(uint8_t) &&
+	    s->iosize != sizeof(uint16_t) &&
+	    s->iosize != sizeof(uint32_t)) {
+		dev_err(&pdev->dev, "only 8/16/32-bit registers are supported\n");
+		return -EINVAL;
+	}
+
+	iprop = of_get_property(np, "mux-mask", &len);
+	if (!iprop || len != sizeof(uint32_t)) {
+		dev_err(&pdev->dev, "missing or invalid mux-mask property\n");
+		return -ENODEV;
+	}
+	if (be32_to_cpup(iprop) >= BIT(s->iosize * 8)) {
+		dev_err(&pdev->dev, "only 8/16/32-bit registers are supported\n");
+		return -EINVAL;
+	}
+	s->mask = be32_to_cpup(iprop);
+
+	/*
+	 * Verify that the 'reg' property of each child MDIO bus does not
+	 * set any bits outside of the 'mask'.
+	 */
+	for_each_available_child_of_node(np, np2) {
+		iprop = of_get_property(np2, "reg", &len);
+		if (!iprop || len != sizeof(uint32_t)) {
+			dev_err(&pdev->dev, "mdio-mux child node %pOF is "
+				"missing a 'reg' property\n", np2);
+			of_node_put(np2);
+			return -ENODEV;
+		}
+		if (be32_to_cpup(iprop) & ~s->mask) {
+			dev_err(&pdev->dev, "mdio-mux child node %pOF has "
+				"a 'reg' value with unmasked bits\n",
+				np2);
+			of_node_put(np2);
+			return -ENODEV;
+		}
+	}
+
+	ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node,
+			    mdio_mux_mmioreg_switch_fn,
+			    &s->mux_handle, s, NULL);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"failed to register mdio-mux bus %pOF\n", np);
+		return ret;
+	}
+
+	pdev->dev.platform_data = s;
+
+	return 0;
+}
+
+static int mdio_mux_mmioreg_remove(struct platform_device *pdev)
+{
+	struct mdio_mux_mmioreg_state *s = dev_get_platdata(&pdev->dev);
+
+	mdio_mux_uninit(s->mux_handle);
+
+	return 0;
+}
+
+static const struct of_device_id mdio_mux_mmioreg_match[] = {
+	{
+		.compatible = "mdio-mux-mmioreg",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, mdio_mux_mmioreg_match);
+
+static struct platform_driver mdio_mux_mmioreg_driver = {
+	.driver = {
+		.name		= "mdio-mux-mmioreg",
+		.of_match_table = mdio_mux_mmioreg_match,
+	},
+	.probe		= mdio_mux_mmioreg_probe,
+	.remove		= mdio_mux_mmioreg_remove,
+};
+
+module_platform_driver(mdio_mux_mmioreg_driver);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Memory-mapped device MDIO MUX driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux.c
new file mode 100644
index 0000000..0a86f1e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-mux.c
@@ -0,0 +1,213 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2011, 2012 Cavium, Inc.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/mdio-mux.h>
+#include <linux/of_mdio.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define DRV_DESCRIPTION "MDIO bus multiplexer driver"
+
+struct mdio_mux_child_bus;
+
+struct mdio_mux_parent_bus {
+	struct mii_bus *mii_bus;
+	int current_child;
+	int parent_id;
+	void *switch_data;
+	int (*switch_fn)(int current_child, int desired_child, void *data);
+
+	/* List of our children linked through their next fields. */
+	struct mdio_mux_child_bus *children;
+};
+
+struct mdio_mux_child_bus {
+	struct mii_bus *mii_bus;
+	struct mdio_mux_parent_bus *parent;
+	struct mdio_mux_child_bus *next;
+	int bus_number;
+};
+
+/*
+ * The parent bus' lock is used to order access to the switch_fn.
+ */
+static int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+	struct mdio_mux_child_bus *cb = bus->priv;
+	struct mdio_mux_parent_bus *pb = cb->parent;
+	int r;
+
+	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
+	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
+	if (r)
+		goto out;
+
+	pb->current_child = cb->bus_number;
+
+	r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum);
+out:
+	mutex_unlock(&pb->mii_bus->mdio_lock);
+
+	return r;
+}
+
+/*
+ * The parent bus' lock is used to order access to the switch_fn.
+ */
+static int mdio_mux_write(struct mii_bus *bus, int phy_id,
+			  int regnum, u16 val)
+{
+	struct mdio_mux_child_bus *cb = bus->priv;
+	struct mdio_mux_parent_bus *pb = cb->parent;
+
+	int r;
+
+	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
+	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
+	if (r)
+		goto out;
+
+	pb->current_child = cb->bus_number;
+
+	r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val);
+out:
+	mutex_unlock(&pb->mii_bus->mdio_lock);
+
+	return r;
+}
+
+static int parent_count;
+
+int mdio_mux_init(struct device *dev,
+		  struct device_node *mux_node,
+		  int (*switch_fn)(int cur, int desired, void *data),
+		  void **mux_handle,
+		  void *data,
+		  struct mii_bus *mux_bus)
+{
+	struct device_node *parent_bus_node;
+	struct device_node *child_bus_node;
+	int r, ret_val;
+	struct mii_bus *parent_bus;
+	struct mdio_mux_parent_bus *pb;
+	struct mdio_mux_child_bus *cb;
+
+	if (!mux_node)
+		return -ENODEV;
+
+	if (!mux_bus) {
+		parent_bus_node = of_parse_phandle(mux_node,
+						   "mdio-parent-bus", 0);
+
+		if (!parent_bus_node)
+			return -ENODEV;
+
+		parent_bus = of_mdio_find_bus(parent_bus_node);
+		if (!parent_bus) {
+			ret_val = -EPROBE_DEFER;
+			goto err_parent_bus;
+		}
+	} else {
+		parent_bus_node = NULL;
+		parent_bus = mux_bus;
+		get_device(&parent_bus->dev);
+	}
+
+	pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
+	if (!pb) {
+		ret_val = -ENOMEM;
+		goto err_pb_kz;
+	}
+
+	pb->switch_data = data;
+	pb->switch_fn = switch_fn;
+	pb->current_child = -1;
+	pb->parent_id = parent_count++;
+	pb->mii_bus = parent_bus;
+
+	ret_val = -ENODEV;
+	for_each_available_child_of_node(mux_node, child_bus_node) {
+		int v;
+
+		r = of_property_read_u32(child_bus_node, "reg", &v);
+		if (r) {
+			dev_err(dev,
+				"Error: Failed to find reg for child %pOF\n",
+				child_bus_node);
+			continue;
+		}
+
+		cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
+		if (!cb) {
+			ret_val = -ENOMEM;
+			continue;
+		}
+		cb->bus_number = v;
+		cb->parent = pb;
+
+		cb->mii_bus = mdiobus_alloc();
+		if (!cb->mii_bus) {
+			ret_val = -ENOMEM;
+			devm_kfree(dev, cb);
+			continue;
+		}
+		cb->mii_bus->priv = cb;
+
+		cb->mii_bus->name = "mdio_mux";
+		snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%x.%x",
+			 pb->parent_id, v);
+		cb->mii_bus->parent = dev;
+		cb->mii_bus->read = mdio_mux_read;
+		cb->mii_bus->write = mdio_mux_write;
+		r = of_mdiobus_register(cb->mii_bus, child_bus_node);
+		if (r) {
+			dev_err(dev,
+				"Error: Failed to register MDIO bus for child %pOF\n",
+				child_bus_node);
+			mdiobus_free(cb->mii_bus);
+			devm_kfree(dev, cb);
+		} else {
+			cb->next = pb->children;
+			pb->children = cb;
+		}
+	}
+	if (pb->children) {
+		*mux_handle = pb;
+		return 0;
+	}
+
+	dev_err(dev, "Error: No acceptable child buses found\n");
+	devm_kfree(dev, pb);
+err_pb_kz:
+	put_device(&parent_bus->dev);
+err_parent_bus:
+	of_node_put(parent_bus_node);
+	return ret_val;
+}
+EXPORT_SYMBOL_GPL(mdio_mux_init);
+
+void mdio_mux_uninit(void *mux_handle)
+{
+	struct mdio_mux_parent_bus *pb = mux_handle;
+	struct mdio_mux_child_bus *cb = pb->children;
+
+	while (cb) {
+		mdiobus_unregister(cb->mii_bus);
+		mdiobus_free(cb->mii_bus);
+		cb = cb->next;
+	}
+
+	put_device(&pb->mii_bus->dev);
+}
+EXPORT_SYMBOL_GPL(mdio_mux_uninit);
+
+MODULE_DESCRIPTION(DRV_DESCRIPTION);
+MODULE_AUTHOR("David Daney");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-octeon.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-octeon.c
new file mode 100644
index 0000000..ab6914f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-octeon.c
@@ -0,0 +1,125 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2009-2015 Cavium, Inc.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+
+#include "mdio-cavium.h"
+
+static int octeon_mdiobus_probe(struct platform_device *pdev)
+{
+	struct cavium_mdiobus *bus;
+	struct mii_bus *mii_bus;
+	struct resource *res_mem;
+	resource_size_t mdio_phys;
+	resource_size_t regsize;
+	union cvmx_smix_en smi_en;
+	int err = -ENOENT;
+
+	mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus));
+	if (!mii_bus)
+		return -ENOMEM;
+
+	res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res_mem == NULL) {
+		dev_err(&pdev->dev, "found no memory resource\n");
+		return -ENXIO;
+	}
+
+	bus = mii_bus->priv;
+	bus->mii_bus = mii_bus;
+	mdio_phys = res_mem->start;
+	regsize = resource_size(res_mem);
+
+	if (!devm_request_mem_region(&pdev->dev, mdio_phys, regsize,
+				     res_mem->name)) {
+		dev_err(&pdev->dev, "request_mem_region failed\n");
+		return -ENXIO;
+	}
+
+	bus->register_base =
+		(u64)devm_ioremap(&pdev->dev, mdio_phys, regsize);
+	if (!bus->register_base) {
+		dev_err(&pdev->dev, "dev_ioremap failed\n");
+		return -ENOMEM;
+	}
+
+	smi_en.u64 = 0;
+	smi_en.s.en = 1;
+	oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN);
+
+	bus->mii_bus->name = KBUILD_MODNAME;
+	snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", bus->register_base);
+	bus->mii_bus->parent = &pdev->dev;
+
+	bus->mii_bus->read = cavium_mdiobus_read;
+	bus->mii_bus->write = cavium_mdiobus_write;
+
+	platform_set_drvdata(pdev, bus);
+
+	err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node);
+	if (err)
+		goto fail_register;
+
+	dev_info(&pdev->dev, "Probed\n");
+
+	return 0;
+fail_register:
+	mdiobus_free(bus->mii_bus);
+	smi_en.u64 = 0;
+	oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN);
+	return err;
+}
+
+static int octeon_mdiobus_remove(struct platform_device *pdev)
+{
+	struct cavium_mdiobus *bus;
+	union cvmx_smix_en smi_en;
+
+	bus = platform_get_drvdata(pdev);
+
+	mdiobus_unregister(bus->mii_bus);
+	mdiobus_free(bus->mii_bus);
+	smi_en.u64 = 0;
+	oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN);
+	return 0;
+}
+
+static const struct of_device_id octeon_mdiobus_match[] = {
+	{
+		.compatible = "cavium,octeon-3860-mdio",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, octeon_mdiobus_match);
+
+static struct platform_driver octeon_mdiobus_driver = {
+	.driver = {
+		.name		= KBUILD_MODNAME,
+		.of_match_table = octeon_mdiobus_match,
+	},
+	.probe		= octeon_mdiobus_probe,
+	.remove		= octeon_mdiobus_remove,
+};
+
+void octeon_mdiobus_force_mod_depencency(void)
+{
+	/* Let ethernet drivers force us to be loaded.  */
+}
+EXPORT_SYMBOL(octeon_mdiobus_force_mod_depencency);
+
+module_platform_driver(octeon_mdiobus_driver);
+
+MODULE_DESCRIPTION("Cavium OCTEON MDIO bus driver");
+MODULE_AUTHOR("David Daney");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-sun4i.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-sun4i.c
new file mode 100644
index 0000000..6425ce0
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-sun4i.c
@@ -0,0 +1,182 @@
+/*
+ * Allwinner EMAC MDIO interface driver
+ *
+ * Copyright 2012-2013 Stefan Roese <sr@denx.de>
+ * Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * Based on the Linux driver provided by Allwinner:
+ * Copyright (C) 1997  Sten Wang
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#define EMAC_MAC_MCMD_REG	(0x00)
+#define EMAC_MAC_MADR_REG	(0x04)
+#define EMAC_MAC_MWTD_REG	(0x08)
+#define EMAC_MAC_MRDD_REG	(0x0c)
+#define EMAC_MAC_MIND_REG	(0x10)
+#define EMAC_MAC_SSRR_REG	(0x14)
+
+#define MDIO_TIMEOUT		(msecs_to_jiffies(100))
+
+struct sun4i_mdio_data {
+	void __iomem		*membase;
+	struct regulator	*regulator;
+};
+
+static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+	struct sun4i_mdio_data *data = bus->priv;
+	unsigned long timeout_jiffies;
+	int value;
+
+	/* issue the phy address and reg */
+	writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG);
+	/* pull up the phy io line */
+	writel(0x1, data->membase + EMAC_MAC_MCMD_REG);
+
+	/* Wait read complete */
+	timeout_jiffies = jiffies + MDIO_TIMEOUT;
+	while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) {
+		if (time_is_before_jiffies(timeout_jiffies))
+			return -ETIMEDOUT;
+		msleep(1);
+	}
+
+	/* push down the phy io line */
+	writel(0x0, data->membase + EMAC_MAC_MCMD_REG);
+	/* and read data */
+	value = readl(data->membase + EMAC_MAC_MRDD_REG);
+
+	return value;
+}
+
+static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
+			    u16 value)
+{
+	struct sun4i_mdio_data *data = bus->priv;
+	unsigned long timeout_jiffies;
+
+	/* issue the phy address and reg */
+	writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG);
+	/* pull up the phy io line */
+	writel(0x1, data->membase + EMAC_MAC_MCMD_REG);
+
+	/* Wait read complete */
+	timeout_jiffies = jiffies + MDIO_TIMEOUT;
+	while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) {
+		if (time_is_before_jiffies(timeout_jiffies))
+			return -ETIMEDOUT;
+		msleep(1);
+	}
+
+	/* push down the phy io line */
+	writel(0x0, data->membase + EMAC_MAC_MCMD_REG);
+	/* and write data */
+	writel(value, data->membase + EMAC_MAC_MWTD_REG);
+
+	return 0;
+}
+
+static int sun4i_mdio_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct mii_bus *bus;
+	struct sun4i_mdio_data *data;
+	struct resource *res;
+	int ret;
+
+	bus = mdiobus_alloc_size(sizeof(*data));
+	if (!bus)
+		return -ENOMEM;
+
+	bus->name = "sun4i_mii_bus";
+	bus->read = &sun4i_mdio_read;
+	bus->write = &sun4i_mdio_write;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
+	bus->parent = &pdev->dev;
+
+	data = bus->priv;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->membase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->membase)) {
+		ret = PTR_ERR(data->membase);
+		goto err_out_free_mdiobus;
+	}
+
+	data->regulator = devm_regulator_get(&pdev->dev, "phy");
+	if (IS_ERR(data->regulator)) {
+		if (PTR_ERR(data->regulator) == -EPROBE_DEFER) {
+			ret = -EPROBE_DEFER;
+			goto err_out_free_mdiobus;
+		}
+
+		dev_info(&pdev->dev, "no regulator found\n");
+		data->regulator = NULL;
+	} else {
+		ret = regulator_enable(data->regulator);
+		if (ret)
+			goto err_out_free_mdiobus;
+	}
+
+	ret = of_mdiobus_register(bus, np);
+	if (ret < 0)
+		goto err_out_disable_regulator;
+
+	platform_set_drvdata(pdev, bus);
+
+	return 0;
+
+err_out_disable_regulator:
+	if (data->regulator)
+		regulator_disable(data->regulator);
+err_out_free_mdiobus:
+	mdiobus_free(bus);
+	return ret;
+}
+
+static int sun4i_mdio_remove(struct platform_device *pdev)
+{
+	struct mii_bus *bus = platform_get_drvdata(pdev);
+
+	mdiobus_unregister(bus);
+	mdiobus_free(bus);
+
+	return 0;
+}
+
+static const struct of_device_id sun4i_mdio_dt_ids[] = {
+	{ .compatible = "allwinner,sun4i-a10-mdio" },
+
+	/* Deprecated */
+	{ .compatible = "allwinner,sun4i-mdio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids);
+
+static struct platform_driver sun4i_mdio_driver = {
+	.probe = sun4i_mdio_probe,
+	.remove = sun4i_mdio_remove,
+	.driver = {
+		.name = "sun4i-mdio",
+		.of_match_table = sun4i_mdio_dt_ids,
+	},
+};
+
+module_platform_driver(sun4i_mdio_driver);
+
+MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver");
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-thunder.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-thunder.c
new file mode 100644
index 0000000..5646169
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-thunder.c
@@ -0,0 +1,154 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2009-2016 Cavium, Inc.
+ */
+
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+#include <linux/pci.h>
+
+#include "mdio-cavium.h"
+
+struct thunder_mdiobus_nexus {
+	void __iomem *bar0;
+	struct cavium_mdiobus *buses[4];
+};
+
+static int thunder_mdiobus_pci_probe(struct pci_dev *pdev,
+				     const struct pci_device_id *ent)
+{
+	struct device_node *node;
+	struct fwnode_handle *fwn;
+	struct thunder_mdiobus_nexus *nexus;
+	int err;
+	int i;
+
+	nexus = devm_kzalloc(&pdev->dev, sizeof(*nexus), GFP_KERNEL);
+	if (!nexus)
+		return -ENOMEM;
+
+	pci_set_drvdata(pdev, nexus);
+
+	err = pcim_enable_device(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to enable PCI device\n");
+		pci_set_drvdata(pdev, NULL);
+		return err;
+	}
+
+	err = pci_request_regions(pdev, KBUILD_MODNAME);
+	if (err) {
+		dev_err(&pdev->dev, "pci_request_regions failed\n");
+		goto err_disable_device;
+	}
+
+	nexus->bar0 = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0));
+	if (!nexus->bar0) {
+		err = -ENOMEM;
+		goto err_release_regions;
+	}
+
+	i = 0;
+	device_for_each_child_node(&pdev->dev, fwn) {
+		struct resource r;
+		struct mii_bus *mii_bus;
+		struct cavium_mdiobus *bus;
+		union cvmx_smix_en smi_en;
+
+		/* If it is not an OF node we cannot handle it yet, so
+		 * exit the loop.
+		 */
+		node = to_of_node(fwn);
+		if (!node)
+			break;
+
+		err = of_address_to_resource(node, 0, &r);
+		if (err) {
+			dev_err(&pdev->dev,
+				"Couldn't translate address for \"%s\"\n",
+				node->name);
+			break;
+		}
+
+		mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus));
+		if (!mii_bus)
+			break;
+		bus = mii_bus->priv;
+		bus->mii_bus = mii_bus;
+
+		nexus->buses[i] = bus;
+		i++;
+
+		bus->register_base = (u64)nexus->bar0 +
+			r.start - pci_resource_start(pdev, 0);
+
+		smi_en.u64 = 0;
+		smi_en.s.en = 1;
+		oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN);
+		bus->mii_bus->name = KBUILD_MODNAME;
+		snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", r.start);
+		bus->mii_bus->parent = &pdev->dev;
+		bus->mii_bus->read = cavium_mdiobus_read;
+		bus->mii_bus->write = cavium_mdiobus_write;
+
+		err = of_mdiobus_register(bus->mii_bus, node);
+		if (err)
+			dev_err(&pdev->dev, "of_mdiobus_register failed\n");
+
+		dev_info(&pdev->dev, "Added bus at %llx\n", r.start);
+		if (i >= ARRAY_SIZE(nexus->buses))
+			break;
+	}
+	return 0;
+
+err_release_regions:
+	pci_release_regions(pdev);
+
+err_disable_device:
+	pci_set_drvdata(pdev, NULL);
+	return err;
+}
+
+static void thunder_mdiobus_pci_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct thunder_mdiobus_nexus *nexus = pci_get_drvdata(pdev);
+
+	for (i = 0; i < ARRAY_SIZE(nexus->buses); i++) {
+		struct cavium_mdiobus *bus = nexus->buses[i];
+
+		if (!bus)
+			continue;
+
+		mdiobus_unregister(bus->mii_bus);
+		mdiobus_free(bus->mii_bus);
+		oct_mdio_writeq(0, bus->register_base + SMI_EN);
+	}
+	pci_set_drvdata(pdev, NULL);
+}
+
+static const struct pci_device_id thunder_mdiobus_id_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa02b) },
+	{ 0, } /* End of table. */
+};
+MODULE_DEVICE_TABLE(pci, thunder_mdiobus_id_table);
+
+static struct pci_driver thunder_mdiobus_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = thunder_mdiobus_id_table,
+	.probe = thunder_mdiobus_pci_probe,
+	.remove = thunder_mdiobus_pci_remove,
+};
+
+module_pci_driver(thunder_mdiobus_driver);
+
+MODULE_DESCRIPTION("Cavium ThunderX MDIO bus driver");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-xgene.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio-xgene.c
new file mode 100644
index 0000000..07c6048
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-xgene.c
@@ -0,0 +1,480 @@
+/* Applied Micro X-Gene SoC MDIO Driver
+ *
+ * Copyright (c) 2016, Applied Micro Circuits Corporation
+ * Author: Iyappan Subramanian <isubramanian@apm.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/efi.h>
+#include <linux/if_vlan.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+#include <linux/prefetch.h>
+#include <linux/phy.h>
+#include <net/ip.h>
+#include "mdio-xgene.h"
+
+static bool xgene_mdio_status;
+
+u32 xgene_mdio_rd_mac(struct xgene_mdio_pdata *pdata, u32 rd_addr)
+{
+	void __iomem *addr, *rd, *cmd, *cmd_done;
+	u32 done, rd_data = BUSY_MASK;
+	u8 wait = 10;
+
+	addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET;
+	rd = pdata->mac_csr_addr + MAC_READ_REG_OFFSET;
+	cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET;
+	cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET;
+
+	spin_lock(&pdata->mac_lock);
+	iowrite32(rd_addr, addr);
+	iowrite32(XGENE_ENET_RD_CMD, cmd);
+
+	while (!(done = ioread32(cmd_done)) && wait--)
+		udelay(1);
+
+	if (done)
+		rd_data = ioread32(rd);
+
+	iowrite32(0, cmd);
+	spin_unlock(&pdata->mac_lock);
+
+	return rd_data;
+}
+EXPORT_SYMBOL(xgene_mdio_rd_mac);
+
+void xgene_mdio_wr_mac(struct xgene_mdio_pdata *pdata, u32 wr_addr, u32 data)
+{
+	void __iomem *addr, *wr, *cmd, *cmd_done;
+	u8 wait = 10;
+	u32 done;
+
+	addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET;
+	wr = pdata->mac_csr_addr + MAC_WRITE_REG_OFFSET;
+	cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET;
+	cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET;
+
+	spin_lock(&pdata->mac_lock);
+	iowrite32(wr_addr, addr);
+	iowrite32(data, wr);
+	iowrite32(XGENE_ENET_WR_CMD, cmd);
+
+	while (!(done = ioread32(cmd_done)) && wait--)
+		udelay(1);
+
+	if (!done)
+		pr_err("MCX mac write failed, addr: 0x%04x\n", wr_addr);
+
+	iowrite32(0, cmd);
+	spin_unlock(&pdata->mac_lock);
+}
+EXPORT_SYMBOL(xgene_mdio_wr_mac);
+
+int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv;
+	u32 data, done;
+	u8 wait = 10;
+
+	data = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg);
+	xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, data);
+	xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, READ_CYCLE_MASK);
+	do {
+		usleep_range(5, 10);
+		done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR);
+	} while ((done & BUSY_MASK) && wait--);
+
+	if (done & BUSY_MASK) {
+		dev_err(&bus->dev, "MII_MGMT read failed\n");
+		return -EBUSY;
+	}
+
+	data = xgene_mdio_rd_mac(pdata, MII_MGMT_STATUS_ADDR);
+	xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, 0);
+
+	return data;
+}
+EXPORT_SYMBOL(xgene_mdio_rgmii_read);
+
+int xgene_mdio_rgmii_write(struct mii_bus *bus, int phy_id, int reg, u16 data)
+{
+	struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv;
+	u32 val, done;
+	u8 wait = 10;
+
+	val = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg);
+	xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, val);
+
+	xgene_mdio_wr_mac(pdata, MII_MGMT_CONTROL_ADDR, data);
+	do {
+		usleep_range(5, 10);
+		done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR);
+	} while ((done & BUSY_MASK) && wait--);
+
+	if (done & BUSY_MASK) {
+		dev_err(&bus->dev, "MII_MGMT write failed\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(xgene_mdio_rgmii_write);
+
+static u32 xgene_menet_rd_diag_csr(struct xgene_mdio_pdata *pdata, u32 offset)
+{
+	return ioread32(pdata->diag_csr_addr + offset);
+}
+
+static void xgene_menet_wr_diag_csr(struct xgene_mdio_pdata *pdata,
+				    u32 offset, u32 val)
+{
+	iowrite32(val, pdata->diag_csr_addr + offset);
+}
+
+static int xgene_enet_ecc_init(struct xgene_mdio_pdata *pdata)
+{
+	u32 data;
+	u8 wait = 10;
+
+	xgene_menet_wr_diag_csr(pdata, MENET_CFG_MEM_RAM_SHUTDOWN_ADDR, 0x0);
+	do {
+		usleep_range(100, 110);
+		data = xgene_menet_rd_diag_csr(pdata, MENET_BLOCK_MEM_RDY_ADDR);
+	} while ((data != 0xffffffff) && wait--);
+
+	if (data != 0xffffffff) {
+		dev_err(pdata->dev, "Failed to release memory from shutdown\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void xgene_gmac_reset(struct xgene_mdio_pdata *pdata)
+{
+	xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, SOFT_RESET);
+	xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, 0);
+}
+
+static int xgene_mdio_reset(struct xgene_mdio_pdata *pdata)
+{
+	int ret;
+
+	if (pdata->dev->of_node) {
+		clk_prepare_enable(pdata->clk);
+		udelay(5);
+		clk_disable_unprepare(pdata->clk);
+		udelay(5);
+		clk_prepare_enable(pdata->clk);
+		udelay(5);
+	} else {
+#ifdef CONFIG_ACPI
+		acpi_evaluate_object(ACPI_HANDLE(pdata->dev),
+				     "_RST", NULL, NULL);
+#endif
+	}
+
+	ret = xgene_enet_ecc_init(pdata);
+	if (ret) {
+		if (pdata->dev->of_node)
+			clk_disable_unprepare(pdata->clk);
+		return ret;
+	}
+	xgene_gmac_reset(pdata);
+
+	return 0;
+}
+
+static void xgene_enet_rd_mdio_csr(void __iomem *base_addr,
+				   u32 offset, u32 *val)
+{
+	void __iomem *addr = base_addr  + offset;
+
+	*val = ioread32(addr);
+}
+
+static void xgene_enet_wr_mdio_csr(void __iomem *base_addr,
+				   u32 offset, u32 val)
+{
+	void __iomem *addr = base_addr  + offset;
+
+	iowrite32(val, addr);
+}
+
+static int xgene_xfi_mdio_write(struct mii_bus *bus, int phy_id,
+				int reg, u16 data)
+{
+	void __iomem *addr = (void __iomem *)bus->priv;
+	int timeout = 100;
+	u32 status, val;
+
+	val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg) |
+	      SET_VAL(HSTMIIMWRDAT, data);
+	xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val);
+
+	val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_WRITE);
+	xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val);
+
+	do {
+		usleep_range(5, 10);
+		xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status);
+	} while ((status & BUSY_MASK) && timeout--);
+
+	xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0);
+
+	return 0;
+}
+
+static int xgene_xfi_mdio_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	void __iomem *addr = (void __iomem *)bus->priv;
+	u32 data, status, val;
+	int timeout = 100;
+
+	val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg);
+	xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val);
+
+	val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_READ);
+	xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val);
+
+	do {
+		usleep_range(5, 10);
+		xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status);
+	} while ((status & BUSY_MASK) && timeout--);
+
+	if (status & BUSY_MASK) {
+		pr_err("XGENET_MII_MGMT write failed\n");
+		return -EBUSY;
+	}
+
+	xgene_enet_rd_mdio_csr(addr, MIIMRD_FIELD_ADDR, &data);
+	xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0);
+
+	return data;
+}
+
+struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr)
+{
+	struct phy_device *phy_dev;
+
+	phy_dev = get_phy_device(bus, phy_addr, false);
+	if (!phy_dev || IS_ERR(phy_dev))
+		return NULL;
+
+	if (phy_device_register(phy_dev))
+		phy_device_free(phy_dev);
+
+	return phy_dev;
+}
+EXPORT_SYMBOL(xgene_enet_phy_register);
+
+#ifdef CONFIG_ACPI
+static acpi_status acpi_register_phy(acpi_handle handle, u32 lvl,
+				     void *context, void **ret)
+{
+	struct mii_bus *mdio = context;
+	struct acpi_device *adev;
+	struct phy_device *phy_dev;
+	const union acpi_object *obj;
+	u32 phy_addr;
+
+	if (acpi_bus_get_device(handle, &adev))
+		return AE_OK;
+
+	if (acpi_dev_get_property(adev, "phy-channel", ACPI_TYPE_INTEGER, &obj))
+		return AE_OK;
+	phy_addr = obj->integer.value;
+
+	phy_dev = xgene_enet_phy_register(mdio, phy_addr);
+	adev->driver_data = phy_dev;
+
+	return AE_OK;
+}
+#endif
+
+static const struct of_device_id xgene_mdio_of_match[] = {
+	{
+		.compatible = "apm,xgene-mdio-rgmii",
+		.data = (void *)XGENE_MDIO_RGMII
+	},
+	{
+		.compatible = "apm,xgene-mdio-xfi",
+		.data = (void *)XGENE_MDIO_XFI
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, xgene_mdio_of_match);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id xgene_mdio_acpi_match[] = {
+	{ "APMC0D65", XGENE_MDIO_RGMII },
+	{ "APMC0D66", XGENE_MDIO_XFI },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(acpi, xgene_mdio_acpi_match);
+#endif
+
+
+static int xgene_mdio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mii_bus *mdio_bus;
+	const struct of_device_id *of_id;
+	struct resource *res;
+	struct xgene_mdio_pdata *pdata;
+	void __iomem *csr_base;
+	int mdio_id = 0, ret = 0;
+
+	of_id = of_match_device(xgene_mdio_of_match, &pdev->dev);
+	if (of_id) {
+		mdio_id = (enum xgene_mdio_id)of_id->data;
+	} else {
+#ifdef CONFIG_ACPI
+		const struct acpi_device_id *acpi_id;
+
+		acpi_id = acpi_match_device(xgene_mdio_acpi_match, &pdev->dev);
+		if (acpi_id)
+			mdio_id = (enum xgene_mdio_id)acpi_id->driver_data;
+#endif
+	}
+
+	if (!mdio_id)
+		return -ENODEV;
+
+	pdata = devm_kzalloc(dev, sizeof(struct xgene_mdio_pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+	pdata->mdio_id = mdio_id;
+	pdata->dev = dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	csr_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(csr_base))
+		return PTR_ERR(csr_base);
+	pdata->mac_csr_addr = csr_base;
+	pdata->mdio_csr_addr = csr_base + BLOCK_XG_MDIO_CSR_OFFSET;
+	pdata->diag_csr_addr = csr_base + BLOCK_DIAG_CSR_OFFSET;
+
+	if (mdio_id == XGENE_MDIO_RGMII)
+		spin_lock_init(&pdata->mac_lock);
+
+	if (dev->of_node) {
+		pdata->clk = devm_clk_get(dev, NULL);
+		if (IS_ERR(pdata->clk)) {
+			dev_err(dev, "Unable to retrieve clk\n");
+			return PTR_ERR(pdata->clk);
+		}
+	}
+
+	ret = xgene_mdio_reset(pdata);
+	if (ret)
+		return ret;
+
+	mdio_bus = mdiobus_alloc();
+	if (!mdio_bus) {
+		ret = -ENOMEM;
+		goto out_clk;
+	}
+
+	mdio_bus->name = "APM X-Gene MDIO bus";
+
+	if (mdio_id == XGENE_MDIO_RGMII) {
+		mdio_bus->read = xgene_mdio_rgmii_read;
+		mdio_bus->write = xgene_mdio_rgmii_write;
+		mdio_bus->priv = (void __force *)pdata;
+		snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s",
+			 "xgene-mii-rgmii");
+	} else {
+		mdio_bus->read = xgene_xfi_mdio_read;
+		mdio_bus->write = xgene_xfi_mdio_write;
+		mdio_bus->priv = (void __force *)pdata->mdio_csr_addr;
+		snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s",
+			 "xgene-mii-xfi");
+	}
+
+	mdio_bus->parent = dev;
+	platform_set_drvdata(pdev, pdata);
+
+	if (dev->of_node) {
+		ret = of_mdiobus_register(mdio_bus, dev->of_node);
+	} else {
+#ifdef CONFIG_ACPI
+		/* Mask out all PHYs from auto probing. */
+		mdio_bus->phy_mask = ~0;
+		ret = mdiobus_register(mdio_bus);
+		if (ret)
+			goto out_mdiobus;
+
+		acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(dev), 1,
+				    acpi_register_phy, NULL, mdio_bus, NULL);
+#endif
+	}
+
+	if (ret)
+		goto out_mdiobus;
+
+	pdata->mdio_bus = mdio_bus;
+	xgene_mdio_status = true;
+
+	return 0;
+
+out_mdiobus:
+	mdiobus_free(mdio_bus);
+
+out_clk:
+	if (dev->of_node)
+		clk_disable_unprepare(pdata->clk);
+
+	return ret;
+}
+
+static int xgene_mdio_remove(struct platform_device *pdev)
+{
+	struct xgene_mdio_pdata *pdata = platform_get_drvdata(pdev);
+	struct mii_bus *mdio_bus = pdata->mdio_bus;
+	struct device *dev = &pdev->dev;
+
+	mdiobus_unregister(mdio_bus);
+	mdiobus_free(mdio_bus);
+
+	if (dev->of_node)
+		clk_disable_unprepare(pdata->clk);
+
+	return 0;
+}
+
+static struct platform_driver xgene_mdio_driver = {
+	.driver = {
+		.name = "xgene-mdio",
+		.of_match_table = of_match_ptr(xgene_mdio_of_match),
+		.acpi_match_table = ACPI_PTR(xgene_mdio_acpi_match),
+	},
+	.probe = xgene_mdio_probe,
+	.remove = xgene_mdio_remove,
+};
+
+module_platform_driver(xgene_mdio_driver);
+
+MODULE_DESCRIPTION("APM X-Gene SoC MDIO driver");
+MODULE_AUTHOR("Iyappan Subramanian <isubramanian@apm.com>");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio-xgene.h b/src/kernel/linux/v4.19/drivers/net/phy/mdio-xgene.h
new file mode 100644
index 0000000..3c85f3e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio-xgene.h
@@ -0,0 +1,142 @@
+/* Applied Micro X-Gene SoC MDIO Driver
+ *
+ * Copyright (c) 2016, Applied Micro Circuits Corporation
+ * Author: Iyappan Subramanian <isubramanian@apm.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MDIO_XGENE_H__
+#define __MDIO_XGENE_H__
+
+#define BLOCK_XG_MDIO_CSR_OFFSET	0x5000
+#define BLOCK_DIAG_CSR_OFFSET		0xd000
+#define XGENET_CONFIG_REG_ADDR		0x20
+
+#define MAC_ADDR_REG_OFFSET		0x00
+#define MAC_COMMAND_REG_OFFSET		0x04
+#define MAC_WRITE_REG_OFFSET		0x08
+#define MAC_READ_REG_OFFSET		0x0c
+#define MAC_COMMAND_DONE_REG_OFFSET	0x10
+
+#define CLKEN_OFFSET			0x08
+#define SRST_OFFSET			0x00
+
+#define MENET_CFG_MEM_RAM_SHUTDOWN_ADDR	0x70
+#define MENET_BLOCK_MEM_RDY_ADDR	0x74
+
+#define MAC_CONFIG_1_ADDR		0x00
+#define MII_MGMT_COMMAND_ADDR		0x24
+#define MII_MGMT_ADDRESS_ADDR		0x28
+#define MII_MGMT_CONTROL_ADDR		0x2c
+#define MII_MGMT_STATUS_ADDR		0x30
+#define MII_MGMT_INDICATORS_ADDR	0x34
+#define SOFT_RESET			BIT(31)
+
+#define MII_MGMT_CONFIG_ADDR            0x20
+#define MII_MGMT_COMMAND_ADDR           0x24
+#define MII_MGMT_ADDRESS_ADDR           0x28
+#define MII_MGMT_CONTROL_ADDR           0x2c
+#define MII_MGMT_STATUS_ADDR            0x30
+#define MII_MGMT_INDICATORS_ADDR        0x34
+
+#define MIIM_COMMAND_ADDR               0x20
+#define MIIM_FIELD_ADDR                 0x24
+#define MIIM_CONFIGURATION_ADDR         0x28
+#define MIIM_LINKFAILVECTOR_ADDR        0x2c
+#define MIIM_INDICATOR_ADDR             0x30
+#define MIIMRD_FIELD_ADDR               0x34
+
+#define MDIO_CSR_OFFSET			0x5000
+
+#define REG_ADDR_POS			0
+#define REG_ADDR_LEN			5
+#define PHY_ADDR_POS			8
+#define PHY_ADDR_LEN			5
+
+#define HSTMIIMWRDAT_POS		0
+#define HSTMIIMWRDAT_LEN		16
+#define HSTPHYADX_POS			23
+#define HSTPHYADX_LEN			5
+#define HSTREGADX_POS			18
+#define HSTREGADX_LEN			5
+#define HSTLDCMD			BIT(3)
+#define HSTMIIMCMD_POS			0
+#define HSTMIIMCMD_LEN			3
+
+#define BUSY_MASK			BIT(0)
+#define READ_CYCLE_MASK			BIT(0)
+
+enum xgene_enet_cmd {
+	XGENE_ENET_WR_CMD = BIT(31),
+	XGENE_ENET_RD_CMD = BIT(30)
+};
+
+enum {
+	MIIM_CMD_IDLE,
+	MIIM_CMD_LEGACY_WRITE,
+	MIIM_CMD_LEGACY_READ,
+};
+
+enum xgene_mdio_id {
+	XGENE_MDIO_RGMII = 1,
+	XGENE_MDIO_XFI
+};
+
+struct xgene_mdio_pdata {
+	struct clk *clk;
+	struct device *dev;
+	void __iomem *mac_csr_addr;
+	void __iomem *diag_csr_addr;
+	void __iomem *mdio_csr_addr;
+	struct mii_bus *mdio_bus;
+	int mdio_id;
+	spinlock_t mac_lock; /* mac lock */
+};
+
+/* Set the specified value into a bit-field defined by its starting position
+ * and length within a single u64.
+ */
+static inline u64 xgene_enet_set_field_value(int pos, int len, u64 val)
+{
+	return (val & ((1ULL << len) - 1)) << pos;
+}
+
+#define SET_VAL(field, val) \
+		xgene_enet_set_field_value(field ## _POS, field ## _LEN, val)
+
+#define SET_BIT(field) \
+		xgene_enet_set_field_value(field ## _POS, 1, 1)
+
+/* Get the value from a bit-field defined by its starting position
+ * and length within the specified u64.
+ */
+static inline u64 xgene_enet_get_field_value(int pos, int len, u64 src)
+{
+	return (src >> pos) & ((1ULL << len) - 1);
+}
+
+#define GET_VAL(field, src) \
+		xgene_enet_get_field_value(field ## _POS, field ## _LEN, src)
+
+#define GET_BIT(field, src) \
+		xgene_enet_get_field_value(field ## _POS, 1, src)
+
+u32 xgene_mdio_rd_mac(struct xgene_mdio_pdata *pdata, u32 rd_addr);
+void xgene_mdio_wr_mac(struct xgene_mdio_pdata *pdata, u32 wr_addr, u32 data);
+int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg);
+int xgene_mdio_rgmii_write(struct mii_bus *bus, int phy_id, int reg, u16 data);
+struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr);
+
+#endif  /* __MDIO_XGENE_H__ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio_bus.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio_bus.c
new file mode 100644
index 0000000..c5588d4
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio_bus.c
@@ -0,0 +1,754 @@
+/* MDIO Bus interface
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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/device.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/of_mdio.h>
+#include <linux/of_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/ethtool.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/gpio/consumer.h>
+
+#include <asm/irq.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/mdio.h>
+
+#include "mdio-boardinfo.h"
+
+static int mdiobus_register_gpiod(struct mdio_device *mdiodev)
+{
+	struct gpio_desc *gpiod = NULL;
+
+	/* Deassert the optional reset signal */
+	if (mdiodev->dev.of_node)
+		gpiod = fwnode_get_named_gpiod(&mdiodev->dev.of_node->fwnode,
+					       "reset-gpios", 0, GPIOD_OUT_LOW,
+					       "PHY reset");
+	if (PTR_ERR(gpiod) == -ENOENT ||
+	    PTR_ERR(gpiod) == -ENOSYS)
+		gpiod = NULL;
+	else if (IS_ERR(gpiod))
+		return PTR_ERR(gpiod);
+
+	mdiodev->reset = gpiod;
+
+	/* Assert the reset signal again */
+	mdio_device_reset(mdiodev, 1);
+
+	return 0;
+}
+
+int mdiobus_register_device(struct mdio_device *mdiodev)
+{
+	int err;
+
+	if (mdiodev->bus->mdio_map[mdiodev->addr])
+		return -EBUSY;
+
+	if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) {
+		err = mdiobus_register_gpiod(mdiodev);
+		if (err)
+			return err;
+	}
+
+	mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;
+
+	return 0;
+}
+EXPORT_SYMBOL(mdiobus_register_device);
+
+int mdiobus_unregister_device(struct mdio_device *mdiodev)
+{
+	if (mdiodev->bus->mdio_map[mdiodev->addr] != mdiodev)
+		return -EINVAL;
+
+	mdiodev->bus->mdio_map[mdiodev->addr] = NULL;
+
+	return 0;
+}
+EXPORT_SYMBOL(mdiobus_unregister_device);
+
+struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
+{
+	struct mdio_device *mdiodev = bus->mdio_map[addr];
+
+	if (!mdiodev)
+		return NULL;
+
+	if (!(mdiodev->flags & MDIO_DEVICE_FLAG_PHY))
+		return NULL;
+
+	return container_of(mdiodev, struct phy_device, mdio);
+}
+EXPORT_SYMBOL(mdiobus_get_phy);
+
+bool mdiobus_is_registered_device(struct mii_bus *bus, int addr)
+{
+	return bus->mdio_map[addr];
+}
+EXPORT_SYMBOL(mdiobus_is_registered_device);
+
+/**
+ * mdiobus_alloc_size - allocate a mii_bus structure
+ * @size: extra amount of memory to allocate for private storage.
+ * If non-zero, then bus->priv is points to that memory.
+ *
+ * Description: called by a bus driver to allocate an mii_bus
+ * structure to fill in.
+ */
+struct mii_bus *mdiobus_alloc_size(size_t size)
+{
+	struct mii_bus *bus;
+	size_t aligned_size = ALIGN(sizeof(*bus), NETDEV_ALIGN);
+	size_t alloc_size;
+	int i;
+
+	/* If we alloc extra space, it should be aligned */
+	if (size)
+		alloc_size = aligned_size + size;
+	else
+		alloc_size = sizeof(*bus);
+
+	bus = kzalloc(alloc_size, GFP_KERNEL);
+	if (!bus)
+		return NULL;
+
+	bus->state = MDIOBUS_ALLOCATED;
+	if (size)
+		bus->priv = (void *)bus + aligned_size;
+
+	/* Initialise the interrupts to polling */
+	for (i = 0; i < PHY_MAX_ADDR; i++)
+		bus->irq[i] = PHY_POLL;
+
+	return bus;
+}
+EXPORT_SYMBOL(mdiobus_alloc_size);
+
+static void _devm_mdiobus_free(struct device *dev, void *res)
+{
+	mdiobus_free(*(struct mii_bus **)res);
+}
+
+static int devm_mdiobus_match(struct device *dev, void *res, void *data)
+{
+	struct mii_bus **r = res;
+
+	if (WARN_ON(!r || !*r))
+		return 0;
+
+	return *r == data;
+}
+
+/**
+ * devm_mdiobus_alloc_size - Resource-managed mdiobus_alloc_size()
+ * @dev:		Device to allocate mii_bus for
+ * @sizeof_priv:	Space to allocate for private structure.
+ *
+ * Managed mdiobus_alloc_size. mii_bus allocated with this function is
+ * automatically freed on driver detach.
+ *
+ * If an mii_bus allocated with this function needs to be freed separately,
+ * devm_mdiobus_free() must be used.
+ *
+ * RETURNS:
+ * Pointer to allocated mii_bus on success, NULL on failure.
+ */
+struct mii_bus *devm_mdiobus_alloc_size(struct device *dev, int sizeof_priv)
+{
+	struct mii_bus **ptr, *bus;
+
+	ptr = devres_alloc(_devm_mdiobus_free, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return NULL;
+
+	/* use raw alloc_dr for kmalloc caller tracing */
+	bus = mdiobus_alloc_size(sizeof_priv);
+	if (bus) {
+		*ptr = bus;
+		devres_add(dev, ptr);
+	} else {
+		devres_free(ptr);
+	}
+
+	return bus;
+}
+EXPORT_SYMBOL_GPL(devm_mdiobus_alloc_size);
+
+/**
+ * devm_mdiobus_free - Resource-managed mdiobus_free()
+ * @dev:		Device this mii_bus belongs to
+ * @bus:		the mii_bus associated with the device
+ *
+ * Free mii_bus allocated with devm_mdiobus_alloc_size().
+ */
+void devm_mdiobus_free(struct device *dev, struct mii_bus *bus)
+{
+	int rc;
+
+	rc = devres_release(dev, _devm_mdiobus_free,
+			    devm_mdiobus_match, bus);
+	WARN_ON(rc);
+}
+EXPORT_SYMBOL_GPL(devm_mdiobus_free);
+
+/**
+ * mdiobus_release - mii_bus device release callback
+ * @d: the target struct device that contains the mii_bus
+ *
+ * Description: called when the last reference to an mii_bus is
+ * dropped, to free the underlying memory.
+ */
+static void mdiobus_release(struct device *d)
+{
+	struct mii_bus *bus = to_mii_bus(d);
+	BUG_ON(bus->state != MDIOBUS_RELEASED &&
+	       /* for compatibility with error handling in drivers */
+	       bus->state != MDIOBUS_ALLOCATED);
+	kfree(bus);
+}
+
+static struct class mdio_bus_class = {
+	.name		= "mdio_bus",
+	.dev_release	= mdiobus_release,
+};
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+/* Helper function for of_mdio_find_bus */
+static int of_mdio_bus_match(struct device *dev, const void *mdio_bus_np)
+{
+	return dev->of_node == mdio_bus_np;
+}
+/**
+ * of_mdio_find_bus - Given an mii_bus node, find the mii_bus.
+ * @mdio_bus_np: Pointer to the mii_bus.
+ *
+ * Returns a reference to the mii_bus, or NULL if none found.  The
+ * embedded struct device will have its reference count incremented,
+ * and this must be put once the bus is finished with.
+ *
+ * Because the association of a device_node and mii_bus is made via
+ * of_mdiobus_register(), the mii_bus cannot be found before it is
+ * registered with of_mdiobus_register().
+ *
+ */
+struct mii_bus *of_mdio_find_bus(struct device_node *mdio_bus_np)
+{
+	struct device *d;
+
+	if (!mdio_bus_np)
+		return NULL;
+
+	d = class_find_device(&mdio_bus_class, NULL,  mdio_bus_np,
+			      of_mdio_bus_match);
+
+	return d ? to_mii_bus(d) : NULL;
+}
+EXPORT_SYMBOL(of_mdio_find_bus);
+
+/* Walk the list of subnodes of a mdio bus and look for a node that
+ * matches the mdio device's address with its 'reg' property. If
+ * found, set the of_node pointer for the mdio device. This allows
+ * auto-probed phy devices to be supplied with information passed in
+ * via DT.
+ */
+static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
+				    struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct device_node *child;
+
+	if (dev->of_node || !bus->dev.of_node)
+		return;
+
+	for_each_available_child_of_node(bus->dev.of_node, child) {
+		int addr;
+
+		addr = of_mdio_parse_addr(dev, child);
+		if (addr < 0)
+			continue;
+
+		if (addr == mdiodev->addr) {
+			dev->of_node = child;
+			dev->fwnode = of_fwnode_handle(child);
+			return;
+		}
+	}
+}
+#else /* !IS_ENABLED(CONFIG_OF_MDIO) */
+static inline void of_mdiobus_link_mdiodev(struct mii_bus *mdio,
+					   struct mdio_device *mdiodev)
+{
+}
+#endif
+
+/**
+ * mdiobus_create_device_from_board_info - create a full MDIO device given
+ * a mdio_board_info structure
+ * @bus: MDIO bus to create the devices on
+ * @bi: mdio_board_info structure describing the devices
+ *
+ * Returns 0 on success or < 0 on error.
+ */
+static int mdiobus_create_device(struct mii_bus *bus,
+				 struct mdio_board_info *bi)
+{
+	struct mdio_device *mdiodev;
+	int ret = 0;
+
+	mdiodev = mdio_device_create(bus, bi->mdio_addr);
+	if (IS_ERR(mdiodev))
+		return -ENODEV;
+
+	strncpy(mdiodev->modalias, bi->modalias,
+		sizeof(mdiodev->modalias));
+	mdiodev->bus_match = mdio_device_bus_match;
+	mdiodev->dev.platform_data = (void *)bi->platform_data;
+
+	ret = mdio_device_register(mdiodev);
+	if (ret)
+		mdio_device_free(mdiodev);
+
+	return ret;
+}
+
+/**
+ * __mdiobus_register - bring up all the PHYs on a given bus and attach them to bus
+ * @bus: target mii_bus
+ * @owner: module containing bus accessor functions
+ *
+ * Description: Called by a bus driver to bring up all the PHYs
+ *   on a given bus, and attach them to the bus. Drivers should use
+ *   mdiobus_register() rather than __mdiobus_register() unless they
+ *   need to pass a specific owner module. MDIO devices which are not
+ *   PHYs will not be brought up by this function. They are expected to
+ *   to be explicitly listed in DT and instantiated by of_mdiobus_register().
+ *
+ * Returns 0 on success or < 0 on error.
+ */
+int __mdiobus_register(struct mii_bus *bus, struct module *owner)
+{
+	struct mdio_device *mdiodev;
+	int i, err;
+	struct gpio_desc *gpiod;
+
+	if (NULL == bus || NULL == bus->name ||
+	    NULL == bus->read || NULL == bus->write)
+		return -EINVAL;
+
+	BUG_ON(bus->state != MDIOBUS_ALLOCATED &&
+	       bus->state != MDIOBUS_UNREGISTERED);
+
+	bus->owner = owner;
+	bus->dev.parent = bus->parent;
+	bus->dev.class = &mdio_bus_class;
+	bus->dev.groups = NULL;
+	dev_set_name(&bus->dev, "%s", bus->id);
+
+	err = device_register(&bus->dev);
+	if (err) {
+		pr_err("mii_bus %s failed to register\n", bus->id);
+		return -EINVAL;
+	}
+
+	mutex_init(&bus->mdio_lock);
+
+	/* de-assert bus level PHY GPIO reset */
+	gpiod = devm_gpiod_get_optional(&bus->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(gpiod)) {
+		dev_err(&bus->dev, "mii_bus %s couldn't get reset GPIO\n",
+			bus->id);
+		device_del(&bus->dev);
+		return PTR_ERR(gpiod);
+	} else	if (gpiod) {
+		bus->reset_gpiod = gpiod;
+
+		gpiod_set_value_cansleep(gpiod, 1);
+		udelay(bus->reset_delay_us);
+		gpiod_set_value_cansleep(gpiod, 0);
+	}
+
+	if (bus->reset)
+		bus->reset(bus);
+
+	for (i = 0; i < PHY_MAX_ADDR; i++) {
+		if ((bus->phy_mask & (1 << i)) == 0) {
+			struct phy_device *phydev;
+
+			phydev = mdiobus_scan(bus, i);
+			if (IS_ERR(phydev) && (PTR_ERR(phydev) != -ENODEV)) {
+				err = PTR_ERR(phydev);
+				goto error;
+			}
+		}
+	}
+
+	mdiobus_setup_mdiodev_from_board_info(bus, mdiobus_create_device);
+
+	bus->state = MDIOBUS_REGISTERED;
+	pr_info("%s: probed\n", bus->name);
+	return 0;
+
+error:
+	while (--i >= 0) {
+		mdiodev = bus->mdio_map[i];
+		if (!mdiodev)
+			continue;
+
+		mdiodev->device_remove(mdiodev);
+		mdiodev->device_free(mdiodev);
+	}
+
+	/* Put PHYs in RESET to save power */
+	if (bus->reset_gpiod)
+		gpiod_set_value_cansleep(bus->reset_gpiod, 1);
+
+	device_del(&bus->dev);
+	return err;
+}
+EXPORT_SYMBOL(__mdiobus_register);
+
+void mdiobus_unregister(struct mii_bus *bus)
+{
+	struct mdio_device *mdiodev;
+	int i;
+
+	BUG_ON(bus->state != MDIOBUS_REGISTERED);
+	bus->state = MDIOBUS_UNREGISTERED;
+
+	for (i = 0; i < PHY_MAX_ADDR; i++) {
+		mdiodev = bus->mdio_map[i];
+		if (!mdiodev)
+			continue;
+
+		if (mdiodev->reset)
+			gpiod_put(mdiodev->reset);
+
+		mdiodev->device_remove(mdiodev);
+		mdiodev->device_free(mdiodev);
+	}
+
+	/* Put PHYs in RESET to save power */
+	if (bus->reset_gpiod)
+		gpiod_set_value_cansleep(bus->reset_gpiod, 1);
+
+	device_del(&bus->dev);
+}
+EXPORT_SYMBOL(mdiobus_unregister);
+
+/**
+ * mdiobus_free - free a struct mii_bus
+ * @bus: mii_bus to free
+ *
+ * This function releases the reference to the underlying device
+ * object in the mii_bus.  If this is the last reference, the mii_bus
+ * will be freed.
+ */
+void mdiobus_free(struct mii_bus *bus)
+{
+	/* For compatibility with error handling in drivers. */
+	if (bus->state == MDIOBUS_ALLOCATED) {
+		kfree(bus);
+		return;
+	}
+
+	BUG_ON(bus->state != MDIOBUS_UNREGISTERED);
+	bus->state = MDIOBUS_RELEASED;
+
+	put_device(&bus->dev);
+}
+EXPORT_SYMBOL(mdiobus_free);
+
+/**
+ * mdiobus_scan - scan a bus for MDIO devices.
+ * @bus: mii_bus to scan
+ * @addr: address on bus to scan
+ *
+ * This function scans the MDIO bus, looking for devices which can be
+ * identified using a vendor/product ID in registers 2 and 3. Not all
+ * MDIO devices have such registers, but PHY devices typically
+ * do. Hence this function assumes anything found is a PHY, or can be
+ * treated as a PHY. Other MDIO devices, such as switches, will
+ * probably not be found during the scan.
+ */
+struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
+{
+	struct phy_device *phydev;
+	int err;
+
+	phydev = get_phy_device(bus, addr, false);
+	if (IS_ERR(phydev))
+		return phydev;
+
+	/*
+	 * For DT, see if the auto-probed phy has a correspoding child
+	 * in the bus node, and set the of_node pointer in this case.
+	 */
+	of_mdiobus_link_mdiodev(bus, &phydev->mdio);
+
+	err = phy_device_register(phydev);
+	if (err) {
+		phy_device_free(phydev);
+		return ERR_PTR(-ENODEV);
+	}
+
+	return phydev;
+}
+EXPORT_SYMBOL(mdiobus_scan);
+
+/**
+ * __mdiobus_read - Unlocked version of the mdiobus_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to read
+ *
+ * Read a MDIO bus register. Caller must hold the mdio bus lock.
+ *
+ * NOTE: MUST NOT be called from interrupt context.
+ */
+int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
+{
+	int retval;
+
+	WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
+
+	retval = bus->read(bus, addr, regnum);
+
+	trace_mdio_access(bus, 1, addr, regnum, retval, retval);
+
+	return retval;
+}
+EXPORT_SYMBOL(__mdiobus_read);
+
+/**
+ * __mdiobus_write - Unlocked version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * Write a MDIO bus register. Caller must hold the mdio bus lock.
+ *
+ * NOTE: MUST NOT be called from interrupt context.
+ */
+int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
+{
+	int err;
+
+	WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
+
+	err = bus->write(bus, addr, regnum, val);
+
+	trace_mdio_access(bus, 0, addr, regnum, val, err);
+
+	return err;
+}
+EXPORT_SYMBOL(__mdiobus_write);
+
+/**
+ * mdiobus_read_nested - Nested version of the mdiobus_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to read
+ *
+ * In case of nested MDIO bus access avoid lockdep false positives by
+ * using mutex_lock_nested().
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum)
+{
+	int retval;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+	retval = __mdiobus_read(bus, addr, regnum);
+	mutex_unlock(&bus->mdio_lock);
+
+	return retval;
+}
+EXPORT_SYMBOL(mdiobus_read_nested);
+
+/**
+ * mdiobus_read - Convenience function for reading a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to read
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
+{
+	int retval;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&bus->mdio_lock);
+	retval = __mdiobus_read(bus, addr, regnum);
+	mutex_unlock(&bus->mdio_lock);
+
+	return retval;
+}
+EXPORT_SYMBOL(mdiobus_read);
+
+/**
+ * mdiobus_write_nested - Nested version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * In case of nested MDIO bus access avoid lockdep false positives by
+ * using mutex_lock_nested().
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int mdiobus_write_nested(struct mii_bus *bus, int addr, u32 regnum, u16 val)
+{
+	int err;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+	err = __mdiobus_write(bus, addr, regnum, val);
+	mutex_unlock(&bus->mdio_lock);
+
+	return err;
+}
+EXPORT_SYMBOL(mdiobus_write_nested);
+
+/**
+ * mdiobus_write - Convenience function for writing a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
+{
+	int err;
+
+	BUG_ON(in_interrupt());
+
+	mutex_lock(&bus->mdio_lock);
+	err = __mdiobus_write(bus, addr, regnum, val);
+	mutex_unlock(&bus->mdio_lock);
+
+	return err;
+}
+EXPORT_SYMBOL(mdiobus_write);
+
+/**
+ * mdio_bus_match - determine if given MDIO driver supports the given
+ *		    MDIO device
+ * @dev: target MDIO device
+ * @drv: given MDIO driver
+ *
+ * Description: Given a MDIO device, and a MDIO driver, return 1 if
+ *   the driver supports the device.  Otherwise, return 0. This may
+ *   require calling the devices own match function, since different classes
+ *   of MDIO devices have different match criteria.
+ */
+static int mdio_bus_match(struct device *dev, struct device_driver *drv)
+{
+	struct mdio_device *mdio = to_mdio_device(dev);
+
+	if (of_driver_match_device(dev, drv))
+		return 1;
+
+	if (mdio->bus_match)
+		return mdio->bus_match(dev, drv);
+
+	return 0;
+}
+
+static int mdio_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	int rc;
+
+	/* Some devices have extra OF data and an OF-style MODALIAS */
+	rc = of_device_uevent_modalias(dev, env);
+	if (rc != -ENODEV)
+		return rc;
+
+	return 0;
+}
+
+struct bus_type mdio_bus_type = {
+	.name		= "mdio_bus",
+	.match		= mdio_bus_match,
+	.uevent		= mdio_uevent,
+};
+EXPORT_SYMBOL(mdio_bus_type);
+
+int __init mdio_bus_init(void)
+{
+	int ret;
+
+	ret = class_register(&mdio_bus_class);
+	if (!ret) {
+		ret = bus_register(&mdio_bus_type);
+		if (ret)
+			class_unregister(&mdio_bus_class);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mdio_bus_init);
+
+#if IS_ENABLED(CONFIG_PHYLIB)
+void mdio_bus_exit(void)
+{
+	class_unregister(&mdio_bus_class);
+	bus_unregister(&mdio_bus_type);
+}
+EXPORT_SYMBOL_GPL(mdio_bus_exit);
+#else
+module_init(mdio_bus_init);
+/* no module_exit, intentional */
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MDIO bus/device layer");
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mdio_device.c b/src/kernel/linux/v4.19/drivers/net/phy/mdio_device.c
new file mode 100644
index 0000000..c924700
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mdio_device.c
@@ -0,0 +1,212 @@
+/* Framework for MDIO devices, other than PHYs.
+ *
+ * Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/unistd.h>
+#include <linux/delay.h>
+
+void mdio_device_free(struct mdio_device *mdiodev)
+{
+	put_device(&mdiodev->dev);
+}
+EXPORT_SYMBOL(mdio_device_free);
+
+static void mdio_device_release(struct device *dev)
+{
+	kfree(to_mdio_device(dev));
+}
+
+int mdio_device_bus_match(struct device *dev, struct device_driver *drv)
+{
+	struct mdio_device *mdiodev = to_mdio_device(dev);
+	struct mdio_driver *mdiodrv = to_mdio_driver(drv);
+
+	if (mdiodrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY)
+		return 0;
+
+	return strcmp(mdiodev->modalias, drv->name) == 0;
+}
+
+struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr)
+{
+	struct mdio_device *mdiodev;
+
+	/* We allocate the device, and initialize the default values */
+	mdiodev = kzalloc(sizeof(*mdiodev), GFP_KERNEL);
+	if (!mdiodev)
+		return ERR_PTR(-ENOMEM);
+
+	mdiodev->dev.release = mdio_device_release;
+	mdiodev->dev.parent = &bus->dev;
+	mdiodev->dev.bus = &mdio_bus_type;
+	mdiodev->device_free = mdio_device_free;
+	mdiodev->device_remove = mdio_device_remove;
+	mdiodev->bus = bus;
+	mdiodev->addr = addr;
+
+	dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
+
+	device_initialize(&mdiodev->dev);
+
+	return mdiodev;
+}
+EXPORT_SYMBOL(mdio_device_create);
+
+/**
+ * mdio_device_register - Register the mdio device on the MDIO bus
+ * @mdiodev: mdio_device structure to be added to the MDIO bus
+ */
+int mdio_device_register(struct mdio_device *mdiodev)
+{
+	int err;
+
+	dev_dbg(&mdiodev->dev, "mdio_device_register\n");
+
+	err = mdiobus_register_device(mdiodev);
+	if (err)
+		return err;
+
+	err = device_add(&mdiodev->dev);
+	if (err) {
+		pr_err("MDIO %d failed to add\n", mdiodev->addr);
+		goto out;
+	}
+
+	return 0;
+
+ out:
+	mdiobus_unregister_device(mdiodev);
+	return err;
+}
+EXPORT_SYMBOL(mdio_device_register);
+
+/**
+ * mdio_device_remove - Remove a previously registered mdio device from the
+ *			MDIO bus
+ * @mdiodev: mdio_device structure to remove
+ *
+ * This doesn't free the mdio_device itself, it merely reverses the effects
+ * of mdio_device_register(). Use mdio_device_free() to free the device
+ * after calling this function.
+ */
+void mdio_device_remove(struct mdio_device *mdiodev)
+{
+	device_del(&mdiodev->dev);
+	mdiobus_unregister_device(mdiodev);
+}
+EXPORT_SYMBOL(mdio_device_remove);
+
+void mdio_device_reset(struct mdio_device *mdiodev, int value)
+{
+	unsigned int d;
+
+	if (!mdiodev->reset)
+		return;
+
+	gpiod_set_value(mdiodev->reset, value);
+
+	d = value ? mdiodev->reset_assert_delay : mdiodev->reset_deassert_delay;
+	if (d)
+		usleep_range(d, d + max_t(unsigned int, d / 10, 100));
+}
+EXPORT_SYMBOL(mdio_device_reset);
+
+/**
+ * mdio_probe - probe an MDIO device
+ * @dev: device to probe
+ *
+ * Description: Take care of setting up the mdio_device structure
+ * and calling the driver to probe the device.
+ */
+static int mdio_probe(struct device *dev)
+{
+	struct mdio_device *mdiodev = to_mdio_device(dev);
+	struct device_driver *drv = mdiodev->dev.driver;
+	struct mdio_driver *mdiodrv = to_mdio_driver(drv);
+	int err = 0;
+
+	if (mdiodrv->probe) {
+		/* Deassert the reset signal */
+		mdio_device_reset(mdiodev, 0);
+
+		err = mdiodrv->probe(mdiodev);
+		if (err) {
+			/* Assert the reset signal */
+			mdio_device_reset(mdiodev, 1);
+		}
+	}
+
+	return err;
+}
+
+static int mdio_remove(struct device *dev)
+{
+	struct mdio_device *mdiodev = to_mdio_device(dev);
+	struct device_driver *drv = mdiodev->dev.driver;
+	struct mdio_driver *mdiodrv = to_mdio_driver(drv);
+
+	if (mdiodrv->remove) {
+		mdiodrv->remove(mdiodev);
+
+		/* Assert the reset signal */
+		mdio_device_reset(mdiodev, 1);
+	}
+
+	return 0;
+}
+
+/**
+ * mdio_driver_register - register an mdio_driver with the MDIO layer
+ * @new_driver: new mdio_driver to register
+ */
+int mdio_driver_register(struct mdio_driver *drv)
+{
+	struct mdio_driver_common *mdiodrv = &drv->mdiodrv;
+	int retval;
+
+	pr_debug("mdio_driver_register: %s\n", mdiodrv->driver.name);
+
+	mdiodrv->driver.bus = &mdio_bus_type;
+	mdiodrv->driver.probe = mdio_probe;
+	mdiodrv->driver.remove = mdio_remove;
+
+	retval = driver_register(&mdiodrv->driver);
+	if (retval) {
+		pr_err("%s: Error %d in registering driver\n",
+		       mdiodrv->driver.name, retval);
+
+		return retval;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(mdio_driver_register);
+
+void mdio_driver_unregister(struct mdio_driver *drv)
+{
+	struct mdio_driver_common *mdiodrv = &drv->mdiodrv;
+
+	driver_unregister(&mdiodrv->driver);
+}
+EXPORT_SYMBOL(mdio_driver_unregister);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/meson-gxl.c b/src/kernel/linux/v4.19/drivers/net/phy/meson-gxl.c
new file mode 100644
index 0000000..7ceebbc
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/meson-gxl.c
@@ -0,0 +1,265 @@
+/*
+ * Amlogic Meson GXL Internal PHY Driver
+ *
+ * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
+ * Copyright (C) 2016 BayLibre, SAS. All rights reserved.
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ *
+ * 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/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/bitfield.h>
+
+#define TSTCNTL		20
+#define  TSTCNTL_READ		BIT(15)
+#define  TSTCNTL_WRITE		BIT(14)
+#define  TSTCNTL_REG_BANK_SEL	GENMASK(12, 11)
+#define  TSTCNTL_TEST_MODE	BIT(10)
+#define  TSTCNTL_READ_ADDRESS	GENMASK(9, 5)
+#define  TSTCNTL_WRITE_ADDRESS	GENMASK(4, 0)
+#define TSTREAD1	21
+#define TSTWRITE	23
+#define INTSRC_FLAG	29
+#define  INTSRC_ANEG_PR		BIT(1)
+#define  INTSRC_PARALLEL_FAULT	BIT(2)
+#define  INTSRC_ANEG_LP_ACK	BIT(3)
+#define  INTSRC_LINK_DOWN	BIT(4)
+#define  INTSRC_REMOTE_FAULT	BIT(5)
+#define  INTSRC_ANEG_COMPLETE	BIT(6)
+#define INTSRC_MASK	30
+
+#define BANK_ANALOG_DSP		0
+#define BANK_WOL		1
+#define BANK_BIST		3
+
+/* WOL Registers */
+#define LPI_STATUS	0xc
+#define  LPI_STATUS_RSV12	BIT(12)
+
+/* BIST Registers */
+#define FR_PLL_CONTROL	0x1b
+#define FR_PLL_DIV0	0x1c
+#define FR_PLL_DIV1	0x1d
+
+static int meson_gxl_open_banks(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Enable Analog and DSP register Bank access by
+	 * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
+	 */
+	ret = phy_write(phydev, TSTCNTL, 0);
+	if (ret)
+		return ret;
+	ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
+	if (ret)
+		return ret;
+	ret = phy_write(phydev, TSTCNTL, 0);
+	if (ret)
+		return ret;
+	return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
+}
+
+static void meson_gxl_close_banks(struct phy_device *phydev)
+{
+	phy_write(phydev, TSTCNTL, 0);
+}
+
+static int meson_gxl_read_reg(struct phy_device *phydev,
+			      unsigned int bank, unsigned int reg)
+{
+	int ret;
+
+	ret = meson_gxl_open_banks(phydev);
+	if (ret)
+		goto out;
+
+	ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
+			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
+			TSTCNTL_TEST_MODE |
+			FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
+	if (ret)
+		goto out;
+
+	ret = phy_read(phydev, TSTREAD1);
+out:
+	/* Close the bank access on our way out */
+	meson_gxl_close_banks(phydev);
+	return ret;
+}
+
+static int meson_gxl_write_reg(struct phy_device *phydev,
+			       unsigned int bank, unsigned int reg,
+			       uint16_t value)
+{
+	int ret;
+
+	ret = meson_gxl_open_banks(phydev);
+	if (ret)
+		goto out;
+
+	ret = phy_write(phydev, TSTWRITE, value);
+	if (ret)
+		goto out;
+
+	ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
+			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
+			TSTCNTL_TEST_MODE |
+			FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
+
+out:
+	/* Close the bank access on our way out */
+	meson_gxl_close_banks(phydev);
+	return ret;
+}
+
+static int meson_gxl_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Enable fractional PLL */
+	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
+	if (ret)
+		return ret;
+
+	/* Program fraction FR_PLL_DIV1 */
+	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
+	if (ret)
+		return ret;
+
+	/* Program fraction FR_PLL_DIV1 */
+	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
+	if (ret)
+		return ret;
+
+	return genphy_config_init(phydev);
+}
+
+/* This function is provided to cope with the possible failures of this phy
+ * during aneg process. When aneg fails, the PHY reports that aneg is done
+ * but the value found in MII_LPA is wrong:
+ *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
+ *    the link partner (LP) supports aneg but the LP never acked our base
+ *    code word, it is likely that we never sent it to begin with.
+ *  - Late failures: MII_LPA is filled with a value which seems to make sense
+ *    but it actually is not what the LP is advertising. It seems that we
+ *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
+ *    If this particular bit is not set when aneg is reported being done,
+ *    it means MII_LPA is likely to be wrong.
+ *
+ * In both case, forcing a restart of the aneg process solve the problem.
+ * When this failure happens, the first retry is usually successful but,
+ * in some cases, it may take up to 6 retries to get a decent result
+ */
+static int meson_gxl_read_status(struct phy_device *phydev)
+{
+	int ret, wol, lpa, exp;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		ret = genphy_aneg_done(phydev);
+		if (ret < 0)
+			return ret;
+		else if (!ret)
+			goto read_status_continue;
+
+		/* Aneg is done, let's check everything is fine */
+		wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
+		if (wol < 0)
+			return wol;
+
+		lpa = phy_read(phydev, MII_LPA);
+		if (lpa < 0)
+			return lpa;
+
+		exp = phy_read(phydev, MII_EXPANSION);
+		if (exp < 0)
+			return exp;
+
+		if (!(wol & LPI_STATUS_RSV12) ||
+		    ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
+			/* Looks like aneg failed after all */
+			phydev_dbg(phydev, "LPA corruption - aneg restart\n");
+			return genphy_restart_aneg(phydev);
+		}
+	}
+
+read_status_continue:
+	return genphy_read_status(phydev);
+}
+
+static int meson_gxl_ack_interrupt(struct phy_device *phydev)
+{
+	int ret = phy_read(phydev, INTSRC_FLAG);
+
+	return ret < 0 ? ret : 0;
+}
+
+static int meson_gxl_config_intr(struct phy_device *phydev)
+{
+	u16 val;
+	int ret;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		val = INTSRC_ANEG_PR
+			| INTSRC_PARALLEL_FAULT
+			| INTSRC_ANEG_LP_ACK
+			| INTSRC_LINK_DOWN
+			| INTSRC_REMOTE_FAULT
+			| INTSRC_ANEG_COMPLETE;
+	} else {
+		val = 0;
+	}
+
+	/* Ack any pending IRQ */
+	ret = meson_gxl_ack_interrupt(phydev);
+	if (ret)
+		return ret;
+
+	return phy_write(phydev, INTSRC_MASK, val);
+}
+
+static struct phy_driver meson_gxl_phy[] = {
+	{
+		.phy_id		= 0x01814400,
+		.phy_id_mask	= 0xfffffff0,
+		.name		= "Meson GXL Internal PHY",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_IS_INTERNAL | PHY_HAS_INTERRUPT,
+		.config_init	= meson_gxl_config_init,
+		.aneg_done      = genphy_aneg_done,
+		.read_status	= meson_gxl_read_status,
+		.ack_interrupt	= meson_gxl_ack_interrupt,
+		.config_intr	= meson_gxl_config_intr,
+		.suspend        = genphy_suspend,
+		.resume         = genphy_resume,
+	},
+};
+
+static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
+	{ 0x01814400, 0xfffffff0 },
+	{ }
+};
+
+module_phy_driver(meson_gxl_phy);
+
+MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
+
+MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
+MODULE_AUTHOR("Baoqi wang");
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/micrel.c b/src/kernel/linux/v4.19/drivers/net/phy/micrel.c
new file mode 100644
index 0000000..3957ada
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/micrel.c
@@ -0,0 +1,1418 @@
+/*
+ * drivers/net/phy/micrel.c
+ *
+ * Driver for Micrel PHYs
+ *
+ * Author: David J. Choi
+ *
+ * Copyright (c) 2010-2013 Micrel, Inc.
+ * Copyright (c) 2014 Johan Hovold <johan@kernel.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.
+ *
+ * Support : Micrel Phys:
+ *		Giga phys: ksz9021, ksz9031
+ *		100/10 Phys : ksz8001, ksz8721, ksz8737, ksz8041
+ *			   ksz8021, ksz8031, ksz8051,
+ *			   ksz8081, ksz8091,
+ *			   ksz8061,
+ *		Switch : ksz8873, ksz886x
+ *			 ksz9477
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/micrel_phy.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/of_net.h>
+
+
+/* Operation Mode Strap Override */
+#define MII_KSZPHY_OMSO				0x16
+#define KSZPHY_OMSO_FACTORY_TEST		BIT(15)
+#define KSZPHY_OMSO_B_CAST_OFF			BIT(9)
+#define KSZPHY_OMSO_NAND_TREE_ON		BIT(5)
+#define KSZPHY_OMSO_RMII_OVERRIDE		BIT(1)
+#define KSZPHY_OMSO_MII_OVERRIDE		BIT(0)
+
+/* general Interrupt control/status reg in vendor specific block. */
+#define MII_KSZPHY_INTCS			0x1B
+#define	KSZPHY_INTCS_JABBER			BIT(15)
+#define	KSZPHY_INTCS_RECEIVE_ERR		BIT(14)
+#define	KSZPHY_INTCS_PAGE_RECEIVE		BIT(13)
+#define	KSZPHY_INTCS_PARELLEL			BIT(12)
+#define	KSZPHY_INTCS_LINK_PARTNER_ACK		BIT(11)
+#define	KSZPHY_INTCS_LINK_DOWN			BIT(10)
+#define	KSZPHY_INTCS_REMOTE_FAULT		BIT(9)
+#define	KSZPHY_INTCS_LINK_UP			BIT(8)
+#define	KSZPHY_INTCS_ALL			(KSZPHY_INTCS_LINK_UP |\
+						KSZPHY_INTCS_LINK_DOWN)
+
+/* PHY Control 1 */
+#define	MII_KSZPHY_CTRL_1			0x1e
+
+/* PHY Control 2 / PHY Control (if no PHY Control 1) */
+#define	MII_KSZPHY_CTRL_2			0x1f
+#define	MII_KSZPHY_CTRL				MII_KSZPHY_CTRL_2
+/* bitmap of PHY register to set interrupt mode */
+#define KSZPHY_CTRL_INT_ACTIVE_HIGH		BIT(9)
+#define KSZPHY_RMII_REF_CLK_SEL			BIT(7)
+
+/* Write/read to/from extended registers */
+#define MII_KSZPHY_EXTREG                       0x0b
+#define KSZPHY_EXTREG_WRITE                     0x8000
+
+#define MII_KSZPHY_EXTREG_WRITE                 0x0c
+#define MII_KSZPHY_EXTREG_READ                  0x0d
+
+/* Extended registers */
+#define MII_KSZPHY_CLK_CONTROL_PAD_SKEW         0x104
+#define MII_KSZPHY_RX_DATA_PAD_SKEW             0x105
+#define MII_KSZPHY_TX_DATA_PAD_SKEW             0x106
+
+#define PS_TO_REG				200
+
+struct kszphy_hw_stat {
+	const char *string;
+	u8 reg;
+	u8 bits;
+};
+
+static struct kszphy_hw_stat kszphy_hw_stats[] = {
+	{ "phy_receive_errors", 21, 16},
+	{ "phy_idle_errors", 10, 8 },
+};
+
+struct kszphy_type {
+	u32 led_mode_reg;
+	u16 interrupt_level_mask;
+	bool has_broadcast_disable;
+	bool has_nand_tree_disable;
+	bool has_rmii_ref_clk_sel;
+};
+
+struct kszphy_priv {
+	const struct kszphy_type *type;
+	int led_mode;
+	bool rmii_ref_clk_sel;
+	bool rmii_ref_clk_sel_val;
+	u64 stats[ARRAY_SIZE(kszphy_hw_stats)];
+};
+
+static const struct kszphy_type ksz8021_type = {
+	.led_mode_reg		= MII_KSZPHY_CTRL_2,
+	.has_broadcast_disable	= true,
+	.has_nand_tree_disable	= true,
+	.has_rmii_ref_clk_sel	= true,
+};
+
+static const struct kszphy_type ksz8041_type = {
+	.led_mode_reg		= MII_KSZPHY_CTRL_1,
+};
+
+static const struct kszphy_type ksz8051_type = {
+	.led_mode_reg		= MII_KSZPHY_CTRL_2,
+	.has_nand_tree_disable	= true,
+};
+
+static const struct kszphy_type ksz8081_type = {
+	.led_mode_reg		= MII_KSZPHY_CTRL_2,
+	.has_broadcast_disable	= true,
+	.has_nand_tree_disable	= true,
+	.has_rmii_ref_clk_sel	= true,
+};
+
+static const struct kszphy_type ks8737_type = {
+	.interrupt_level_mask	= BIT(14),
+};
+
+static const struct kszphy_type ksz9021_type = {
+	.interrupt_level_mask	= BIT(14),
+};
+
+static int kszphy_extended_write(struct phy_device *phydev,
+				u32 regnum, u16 val)
+{
+	phy_write(phydev, MII_KSZPHY_EXTREG, KSZPHY_EXTREG_WRITE | regnum);
+	return phy_write(phydev, MII_KSZPHY_EXTREG_WRITE, val);
+}
+
+static int kszphy_extended_read(struct phy_device *phydev,
+				u32 regnum)
+{
+	phy_write(phydev, MII_KSZPHY_EXTREG, regnum);
+	return phy_read(phydev, MII_KSZPHY_EXTREG_READ);
+}
+
+static int kszphy_ack_interrupt(struct phy_device *phydev)
+{
+	/* bit[7..0] int status, which is a read and clear register. */
+	int rc;
+
+	rc = phy_read(phydev, MII_KSZPHY_INTCS);
+
+	return (rc < 0) ? rc : 0;
+}
+
+static int kszphy_config_intr(struct phy_device *phydev)
+{
+	const struct kszphy_type *type = phydev->drv->driver_data;
+	int temp;
+	u16 mask;
+
+	if (type && type->interrupt_level_mask)
+		mask = type->interrupt_level_mask;
+	else
+		mask = KSZPHY_CTRL_INT_ACTIVE_HIGH;
+
+	/* set the interrupt pin active low */
+	temp = phy_read(phydev, MII_KSZPHY_CTRL);
+	if (temp < 0)
+		return temp;
+	temp &= ~mask;
+	phy_write(phydev, MII_KSZPHY_CTRL, temp);
+
+	/* enable / disable interrupts */
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		temp = KSZPHY_INTCS_ALL;
+	else
+		temp = 0;
+
+	return phy_write(phydev, MII_KSZPHY_INTCS, temp);
+}
+
+static int kszphy_rmii_clk_sel(struct phy_device *phydev, bool val)
+{
+	int ctrl;
+
+	ctrl = phy_read(phydev, MII_KSZPHY_CTRL);
+	if (ctrl < 0)
+		return ctrl;
+
+	if (val)
+		ctrl |= KSZPHY_RMII_REF_CLK_SEL;
+	else
+		ctrl &= ~KSZPHY_RMII_REF_CLK_SEL;
+
+	return phy_write(phydev, MII_KSZPHY_CTRL, ctrl);
+}
+
+static int kszphy_setup_led(struct phy_device *phydev, u32 reg, int val)
+{
+	int rc, temp, shift;
+
+	switch (reg) {
+	case MII_KSZPHY_CTRL_1:
+		shift = 14;
+		break;
+	case MII_KSZPHY_CTRL_2:
+		shift = 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	temp = phy_read(phydev, reg);
+	if (temp < 0) {
+		rc = temp;
+		goto out;
+	}
+
+	temp &= ~(3 << shift);
+	temp |= val << shift;
+	rc = phy_write(phydev, reg, temp);
+out:
+	if (rc < 0)
+		phydev_err(phydev, "failed to set led mode\n");
+
+	return rc;
+}
+
+/* Disable PHY address 0 as the broadcast address, so that it can be used as a
+ * unique (non-broadcast) address on a shared bus.
+ */
+static int kszphy_broadcast_disable(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_read(phydev, MII_KSZPHY_OMSO);
+	if (ret < 0)
+		goto out;
+
+	ret = phy_write(phydev, MII_KSZPHY_OMSO, ret | KSZPHY_OMSO_B_CAST_OFF);
+out:
+	if (ret)
+		phydev_err(phydev, "failed to disable broadcast address\n");
+
+	return ret;
+}
+
+static int kszphy_nand_tree_disable(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_read(phydev, MII_KSZPHY_OMSO);
+	if (ret < 0)
+		goto out;
+
+	if (!(ret & KSZPHY_OMSO_NAND_TREE_ON))
+		return 0;
+
+	ret = phy_write(phydev, MII_KSZPHY_OMSO,
+			ret & ~KSZPHY_OMSO_NAND_TREE_ON);
+out:
+	if (ret)
+		phydev_err(phydev, "failed to disable NAND tree mode\n");
+
+	return ret;
+}
+
+/* Some config bits need to be set again on resume, handle them here. */
+static int kszphy_config_reset(struct phy_device *phydev)
+{
+	struct kszphy_priv *priv = phydev->priv;
+	int ret;
+
+	if (priv->rmii_ref_clk_sel) {
+		ret = kszphy_rmii_clk_sel(phydev, priv->rmii_ref_clk_sel_val);
+		if (ret) {
+			phydev_err(phydev,
+				   "failed to set rmii reference clock\n");
+			return ret;
+		}
+	}
+
+	if (priv->led_mode >= 0)
+		kszphy_setup_led(phydev, priv->type->led_mode_reg, priv->led_mode);
+
+	return 0;
+}
+
+static int kszphy_config_init(struct phy_device *phydev)
+{
+	struct kszphy_priv *priv = phydev->priv;
+	const struct kszphy_type *type;
+
+	if (!priv)
+		return 0;
+
+	type = priv->type;
+
+	if (type->has_broadcast_disable)
+		kszphy_broadcast_disable(phydev);
+
+	if (type->has_nand_tree_disable)
+		kszphy_nand_tree_disable(phydev);
+
+	return kszphy_config_reset(phydev);
+}
+
+static int ksz8041_config_init(struct phy_device *phydev)
+{
+	struct device_node *of_node = phydev->mdio.dev.of_node;
+
+	/* Limit supported and advertised modes in fiber mode */
+	if (of_property_read_bool(of_node, "micrel,fiber-mode")) {
+		phydev->dev_flags |= MICREL_PHY_FXEN;
+		phydev->supported &= SUPPORTED_100baseT_Full |
+				     SUPPORTED_100baseT_Half;
+		phydev->supported |= SUPPORTED_FIBRE;
+		phydev->advertising &= ADVERTISED_100baseT_Full |
+				       ADVERTISED_100baseT_Half;
+		phydev->advertising |= ADVERTISED_FIBRE;
+		phydev->autoneg = AUTONEG_DISABLE;
+	}
+
+	return kszphy_config_init(phydev);
+}
+
+static int ksz8041_config_aneg(struct phy_device *phydev)
+{
+	/* Skip auto-negotiation in fiber mode */
+	if (phydev->dev_flags & MICREL_PHY_FXEN) {
+		phydev->speed = SPEED_100;
+		return 0;
+	}
+
+	return genphy_config_aneg(phydev);
+}
+
+static int ksz8061_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_DEVID1, 0xB61A);
+	if (ret)
+		return ret;
+
+	return kszphy_config_init(phydev);
+}
+
+static int ksz9021_load_values_from_of(struct phy_device *phydev,
+				       const struct device_node *of_node,
+				       u16 reg,
+				       const char *field1, const char *field2,
+				       const char *field3, const char *field4)
+{
+	int val1 = -1;
+	int val2 = -2;
+	int val3 = -3;
+	int val4 = -4;
+	int newval;
+	int matches = 0;
+
+	if (!of_property_read_u32(of_node, field1, &val1))
+		matches++;
+
+	if (!of_property_read_u32(of_node, field2, &val2))
+		matches++;
+
+	if (!of_property_read_u32(of_node, field3, &val3))
+		matches++;
+
+	if (!of_property_read_u32(of_node, field4, &val4))
+		matches++;
+
+	if (!matches)
+		return 0;
+
+	if (matches < 4)
+		newval = kszphy_extended_read(phydev, reg);
+	else
+		newval = 0;
+
+	if (val1 != -1)
+		newval = ((newval & 0xfff0) | ((val1 / PS_TO_REG) & 0xf) << 0);
+
+	if (val2 != -2)
+		newval = ((newval & 0xff0f) | ((val2 / PS_TO_REG) & 0xf) << 4);
+
+	if (val3 != -3)
+		newval = ((newval & 0xf0ff) | ((val3 / PS_TO_REG) & 0xf) << 8);
+
+	if (val4 != -4)
+		newval = ((newval & 0x0fff) | ((val4 / PS_TO_REG) & 0xf) << 12);
+
+	return kszphy_extended_write(phydev, reg, newval);
+}
+
+static int ksz9021_config_init(struct phy_device *phydev)
+{
+	const struct device *dev = &phydev->mdio.dev;
+	const struct device_node *of_node = dev->of_node;
+	const struct device *dev_walker;
+
+	/* The Micrel driver has a deprecated option to place phy OF
+	 * properties in the MAC node. Walk up the tree of devices to
+	 * find a device with an OF node.
+	 */
+	dev_walker = &phydev->mdio.dev;
+	do {
+		of_node = dev_walker->of_node;
+		dev_walker = dev_walker->parent;
+
+	} while (!of_node && dev_walker);
+
+	if (of_node) {
+		ksz9021_load_values_from_of(phydev, of_node,
+				    MII_KSZPHY_CLK_CONTROL_PAD_SKEW,
+				    "txen-skew-ps", "txc-skew-ps",
+				    "rxdv-skew-ps", "rxc-skew-ps");
+		ksz9021_load_values_from_of(phydev, of_node,
+				    MII_KSZPHY_RX_DATA_PAD_SKEW,
+				    "rxd0-skew-ps", "rxd1-skew-ps",
+				    "rxd2-skew-ps", "rxd3-skew-ps");
+		ksz9021_load_values_from_of(phydev, of_node,
+				    MII_KSZPHY_TX_DATA_PAD_SKEW,
+				    "txd0-skew-ps", "txd1-skew-ps",
+				    "txd2-skew-ps", "txd3-skew-ps");
+	}
+	return 0;
+}
+
+#define MII_KSZ9031RN_MMD_CTRL_REG	0x0d
+#define MII_KSZ9031RN_MMD_REGDATA_REG	0x0e
+#define OP_DATA				1
+#define KSZ9031_PS_TO_REG		60
+
+/* Extended registers */
+/* MMD Address 0x0 */
+#define MII_KSZ9031RN_FLP_BURST_TX_LO	3
+#define MII_KSZ9031RN_FLP_BURST_TX_HI	4
+
+/* MMD Address 0x2 */
+#define MII_KSZ9031RN_CONTROL_PAD_SKEW	4
+#define MII_KSZ9031RN_RX_DATA_PAD_SKEW	5
+#define MII_KSZ9031RN_TX_DATA_PAD_SKEW	6
+#define MII_KSZ9031RN_CLK_PAD_SKEW	8
+
+/* MMD Address 0x1C */
+#define MII_KSZ9031RN_EDPD		0x23
+#define MII_KSZ9031RN_EDPD_ENABLE	BIT(0)
+
+static int ksz9031_extended_write(struct phy_device *phydev,
+				  u8 mode, u32 dev_addr, u32 regnum, u16 val)
+{
+	phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, dev_addr);
+	phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, regnum);
+	phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, (mode << 14) | dev_addr);
+	return phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, val);
+}
+
+static int ksz9031_extended_read(struct phy_device *phydev,
+				 u8 mode, u32 dev_addr, u32 regnum)
+{
+	phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, dev_addr);
+	phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, regnum);
+	phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, (mode << 14) | dev_addr);
+	return phy_read(phydev, MII_KSZ9031RN_MMD_REGDATA_REG);
+}
+
+static int ksz9031_of_load_skew_values(struct phy_device *phydev,
+				       const struct device_node *of_node,
+				       u16 reg, size_t field_sz,
+				       const char *field[], u8 numfields)
+{
+	int val[4] = {-1, -2, -3, -4};
+	int matches = 0;
+	u16 mask;
+	u16 maxval;
+	u16 newval;
+	int i;
+
+	for (i = 0; i < numfields; i++)
+		if (!of_property_read_u32(of_node, field[i], val + i))
+			matches++;
+
+	if (!matches)
+		return 0;
+
+	if (matches < numfields)
+		newval = ksz9031_extended_read(phydev, OP_DATA, 2, reg);
+	else
+		newval = 0;
+
+	maxval = (field_sz == 4) ? 0xf : 0x1f;
+	for (i = 0; i < numfields; i++)
+		if (val[i] != -(i + 1)) {
+			mask = 0xffff;
+			mask ^= maxval << (field_sz * i);
+			newval = (newval & mask) |
+				(((val[i] / KSZ9031_PS_TO_REG) & maxval)
+					<< (field_sz * i));
+		}
+
+	return ksz9031_extended_write(phydev, OP_DATA, 2, reg, newval);
+}
+
+/* Center KSZ9031RNX FLP timing at 16ms. */
+static int ksz9031_center_flp_timing(struct phy_device *phydev)
+{
+	int result;
+
+	result = ksz9031_extended_write(phydev, OP_DATA, 0,
+					MII_KSZ9031RN_FLP_BURST_TX_HI, 0x0006);
+	if (result)
+		return result;
+
+	result = ksz9031_extended_write(phydev, OP_DATA, 0,
+					MII_KSZ9031RN_FLP_BURST_TX_LO, 0x1A80);
+	if (result)
+		return result;
+
+	return genphy_restart_aneg(phydev);
+}
+
+/* Enable energy-detect power-down mode */
+static int ksz9031_enable_edpd(struct phy_device *phydev)
+{
+	int reg;
+
+	reg = ksz9031_extended_read(phydev, OP_DATA, 0x1C, MII_KSZ9031RN_EDPD);
+	if (reg < 0)
+		return reg;
+	return ksz9031_extended_write(phydev, OP_DATA, 0x1C, MII_KSZ9031RN_EDPD,
+				      reg | MII_KSZ9031RN_EDPD_ENABLE);
+}
+
+static int ksz9031_config_init(struct phy_device *phydev)
+{
+	const struct device *dev = &phydev->mdio.dev;
+	const struct device_node *of_node = dev->of_node;
+	static const char *clk_skews[2] = {"rxc-skew-ps", "txc-skew-ps"};
+	static const char *rx_data_skews[4] = {
+		"rxd0-skew-ps", "rxd1-skew-ps",
+		"rxd2-skew-ps", "rxd3-skew-ps"
+	};
+	static const char *tx_data_skews[4] = {
+		"txd0-skew-ps", "txd1-skew-ps",
+		"txd2-skew-ps", "txd3-skew-ps"
+	};
+	static const char *control_skews[2] = {"txen-skew-ps", "rxdv-skew-ps"};
+	const struct device *dev_walker;
+	int result;
+
+	result = ksz9031_enable_edpd(phydev);
+	if (result < 0)
+		return result;
+
+	/* The Micrel driver has a deprecated option to place phy OF
+	 * properties in the MAC node. Walk up the tree of devices to
+	 * find a device with an OF node.
+	 */
+	dev_walker = &phydev->mdio.dev;
+	do {
+		of_node = dev_walker->of_node;
+		dev_walker = dev_walker->parent;
+	} while (!of_node && dev_walker);
+
+	if (of_node) {
+		ksz9031_of_load_skew_values(phydev, of_node,
+				MII_KSZ9031RN_CLK_PAD_SKEW, 5,
+				clk_skews, 2);
+
+		ksz9031_of_load_skew_values(phydev, of_node,
+				MII_KSZ9031RN_CONTROL_PAD_SKEW, 4,
+				control_skews, 2);
+
+		ksz9031_of_load_skew_values(phydev, of_node,
+				MII_KSZ9031RN_RX_DATA_PAD_SKEW, 4,
+				rx_data_skews, 4);
+
+		ksz9031_of_load_skew_values(phydev, of_node,
+				MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4,
+				tx_data_skews, 4);
+
+		/* Silicon Errata Sheet (DS80000691D or DS80000692D):
+		 * When the device links in the 1000BASE-T slave mode only,
+		 * the optional 125MHz reference output clock (CLK125_NDO)
+		 * has wide duty cycle variation.
+		 *
+		 * The optional CLK125_NDO clock does not meet the RGMII
+		 * 45/55 percent (min/max) duty cycle requirement and therefore
+		 * cannot be used directly by the MAC side for clocking
+		 * applications that have setup/hold time requirements on
+		 * rising and falling clock edges.
+		 *
+		 * Workaround:
+		 * Force the phy to be the master to receive a stable clock
+		 * which meets the duty cycle requirement.
+		 */
+		if (of_property_read_bool(of_node, "micrel,force-master")) {
+			result = phy_read(phydev, MII_CTRL1000);
+			if (result < 0)
+				goto err_force_master;
+
+			/* enable master mode, config & prefer master */
+			result |= CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER;
+			result = phy_write(phydev, MII_CTRL1000, result);
+			if (result < 0)
+				goto err_force_master;
+		}
+	}
+
+	return ksz9031_center_flp_timing(phydev);
+
+err_force_master:
+	phydev_err(phydev, "failed to force the phy to master mode\n");
+	return result;
+}
+
+#define KSZ9131_SKEW_5BIT_MAX	2400
+#define KSZ9131_SKEW_4BIT_MAX	800
+#define KSZ9131_OFFSET		700
+#define KSZ9131_STEP		100
+
+static int ksz9131_of_load_skew_values(struct phy_device *phydev,
+				       struct device_node *of_node,
+				       u16 reg, size_t field_sz,
+				       char *field[], u8 numfields)
+{
+	int val[4] = {-(1 + KSZ9131_OFFSET), -(2 + KSZ9131_OFFSET),
+		      -(3 + KSZ9131_OFFSET), -(4 + KSZ9131_OFFSET)};
+	int skewval, skewmax = 0;
+	int matches = 0;
+	u16 maxval;
+	u16 newval;
+	u16 mask;
+	int i;
+
+	/* psec properties in dts should mean x pico seconds */
+	if (field_sz == 5)
+		skewmax = KSZ9131_SKEW_5BIT_MAX;
+	else
+		skewmax = KSZ9131_SKEW_4BIT_MAX;
+
+	for (i = 0; i < numfields; i++)
+		if (!of_property_read_s32(of_node, field[i], &skewval)) {
+			if (skewval < -KSZ9131_OFFSET)
+				skewval = -KSZ9131_OFFSET;
+			else if (skewval > skewmax)
+				skewval = skewmax;
+
+			val[i] = skewval + KSZ9131_OFFSET;
+			matches++;
+		}
+
+	if (!matches)
+		return 0;
+
+	if (matches < numfields)
+		newval = phy_read_mmd(phydev, 2, reg);
+	else
+		newval = 0;
+
+	maxval = (field_sz == 4) ? 0xf : 0x1f;
+	for (i = 0; i < numfields; i++)
+		if (val[i] != -(i + 1 + KSZ9131_OFFSET)) {
+			mask = 0xffff;
+			mask ^= maxval << (field_sz * i);
+			newval = (newval & mask) |
+				(((val[i] / KSZ9131_STEP) & maxval)
+					<< (field_sz * i));
+		}
+
+	return phy_write_mmd(phydev, 2, reg, newval);
+}
+
+#define KSZ9131RN_MMD_COMMON_CTRL_REG	2
+#define KSZ9131RN_OPERATION_MODE_STRAP_OVERRIDE	2
+#define KSZ9131RN_RGMII_PME_ON_INT_MODE	BIT(10)
+#define KSZ9131RN_WAKE_ON_LAN_CONTROL	16
+#define KSZ9131RN_WOL_PME_OUTPUT_SELECT	BIT(15)
+#define KSZ9131RN_WOL_RESET	BIT(7)
+#define KSZ9131RN_WOL_ENABLE_MAGIC_PKT	BIT(6)
+#define KSZ9131RN_WAKE_ON_LAN_MAC_LO	17
+#define KSZ9131RN_WAKE_ON_LAN_MAC_MI	18
+#define KSZ9131RN_WAKE_ON_LAN_MAC_HI	19
+#define KSZ9131RN_WAKE_ON_LAN_MAC_ADDR GENMASK(15, 0)
+#define KSZ9131RN_RXC_DLL_CTRL		76
+#define KSZ9131RN_TXC_DLL_CTRL		77
+#define KSZ9131RN_DLL_CTRL_BYPASS	BIT_MASK(12)
+#define KSZ9131RN_DLL_ENABLE_DELAY	0
+#define KSZ9131RN_DLL_DISABLE_DELAY	BIT(12)
+
+static int ksz9131_ack_interrupt(struct phy_device *phydev)
+{
+	int ret;
+
+	/* bit[7..0] int status, which is a read and clear register. */
+	ret  = phy_read(phydev, MII_KSZPHY_INTCS);
+	if (ret < 0)
+		return ret;
+	else if (ret & 0xff)
+		return 0;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_CONTROL,
+			     KSZ9131RN_WOL_ENABLE_MAGIC_PKT, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_CONTROL,
+			     KSZ9131RN_WOL_RESET, KSZ9131RN_WOL_RESET);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_CONTROL,
+			     KSZ9131RN_WOL_RESET, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_CONTROL,
+			     KSZ9131RN_WOL_ENABLE_MAGIC_PKT,
+			     KSZ9131RN_WOL_ENABLE_MAGIC_PKT);
+
+	return (ret < 0) ? ret : 0;
+}
+
+static int ksz9131_config_rgmii_delay(struct phy_device *phydev)
+{
+	u16 rxcdll_val, txcdll_val;
+	int ret;
+
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_RGMII:
+		rxcdll_val = KSZ9131RN_DLL_DISABLE_DELAY;
+		txcdll_val = KSZ9131RN_DLL_DISABLE_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_ID:
+		rxcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+		txcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+		rxcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+		txcdll_val = KSZ9131RN_DLL_DISABLE_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		rxcdll_val = KSZ9131RN_DLL_DISABLE_DELAY;
+		txcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+		break;
+	default:
+		return 0;
+	}
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_RXC_DLL_CTRL, KSZ9131RN_DLL_CTRL_BYPASS,
+			     rxcdll_val);
+	if (ret < 0)
+		return ret;
+
+	return phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			      KSZ9131RN_TXC_DLL_CTRL, KSZ9131RN_DLL_CTRL_BYPASS,
+			      txcdll_val);
+}
+
+static int ksz9131_set_wol(struct phy_device *phydev,
+			   struct ethtool_wolinfo *wol)
+{
+	struct net_device *ndev;
+	struct device_node *np;
+	u16 mac_addr_hi, mac_addr_mi, mac_addr_lo, pme_on_int, wol_enable;
+	const char *mac_addr;
+	int ret;
+
+	ndev = phydev->attached_dev;
+	if (!ndev)
+		return -ENODEV;
+
+	if (wol->wolopts & WAKE_MAGIC) {
+		np = ndev->dev.parent->of_node;
+		mac_addr = of_get_mac_address(np);
+		mac_addr_lo = ((u16)mac_addr[4] << 8) | (u16)mac_addr[5];
+		mac_addr_mi = ((u16)mac_addr[2] << 8) | (u16)mac_addr[3];
+		mac_addr_hi = ((u16)mac_addr[0] << 8) | (u16)mac_addr[1];
+		pme_on_int = KSZ9131RN_RGMII_PME_ON_INT_MODE;
+		wol_enable = KSZ9131RN_WOL_ENABLE_MAGIC_PKT |
+			     KSZ9131RN_WOL_PME_OUTPUT_SELECT;
+	} else {
+		mac_addr_lo = 0;
+		mac_addr_mi = 0;
+		mac_addr_hi = 0;
+		pme_on_int = 0;
+		wol_enable = 0;
+	}
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_OPERATION_MODE_STRAP_OVERRIDE,
+			     KSZ9131RN_RGMII_PME_ON_INT_MODE, pme_on_int);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_CONTROL,
+			     KSZ9131RN_WOL_ENABLE_MAGIC_PKT, wol_enable);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_MAC_LO,
+			     KSZ9131RN_WAKE_ON_LAN_MAC_ADDR, mac_addr_lo);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_MAC_MI,
+			     KSZ9131RN_WAKE_ON_LAN_MAC_ADDR, mac_addr_mi);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_MAC_HI,
+			     KSZ9131RN_WAKE_ON_LAN_MAC_ADDR, mac_addr_hi);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void ksz9131_get_wol(struct phy_device *phydev,
+			    struct ethtool_wolinfo *wol)
+{
+	int value;
+
+	wol->supported = WAKE_MAGIC;
+	wol->wolopts = 0;
+
+	value = phy_read_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+			     KSZ9131RN_WAKE_ON_LAN_CONTROL);
+	if (value != 0xffff)
+		if (value & KSZ9131RN_WOL_ENABLE_MAGIC_PKT)
+			wol->wolopts = WAKE_MAGIC;
+}
+
+static int ksz9131_config_init(struct phy_device *phydev)
+{
+	const struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	char *clk_skews[2] = {"rxc-skew-psec", "txc-skew-psec"};
+	char *rx_data_skews[4] = {
+		"rxd0-skew-psec", "rxd1-skew-psec",
+		"rxd2-skew-psec", "rxd3-skew-psec"
+	};
+	char *tx_data_skews[4] = {
+		"txd0-skew-psec", "txd1-skew-psec",
+		"txd2-skew-psec", "txd3-skew-psec"
+	};
+	char *control_skews[2] = {"txen-skew-psec", "rxdv-skew-psec"};
+	const struct device *dev_walker;
+	int ret;
+
+	dev_walker = &phydev->mdio.dev;
+	do {
+		of_node = dev_walker->of_node;
+		dev_walker = dev_walker->parent;
+	} while (!of_node && dev_walker);
+
+	if (!of_node)
+		return 0;
+
+	if (phy_interface_is_rgmii(phydev)) {
+		ret = ksz9131_config_rgmii_delay(phydev);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = ksz9131_of_load_skew_values(phydev, of_node,
+					  MII_KSZ9031RN_CLK_PAD_SKEW, 5,
+					  clk_skews, 2);
+	if (ret < 0)
+		return ret;
+
+	ret = ksz9131_of_load_skew_values(phydev, of_node,
+					  MII_KSZ9031RN_CONTROL_PAD_SKEW, 4,
+					  control_skews, 2);
+	if (ret < 0)
+		return ret;
+
+	ret = ksz9131_of_load_skew_values(phydev, of_node,
+					  MII_KSZ9031RN_RX_DATA_PAD_SKEW, 4,
+					  rx_data_skews, 4);
+	if (ret < 0)
+		return ret;
+
+	ret = ksz9131_of_load_skew_values(phydev, of_node,
+					  MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4,
+					  tx_data_skews, 4);
+	if (ret < 0)
+		return ret;
+
+	genphy_soft_reset(phydev);
+
+	return 0;
+}
+
+
+#define KSZ8873MLL_GLOBAL_CONTROL_4	0x06
+#define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX	BIT(6)
+#define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED	BIT(4)
+static int ksz8873mll_read_status(struct phy_device *phydev)
+{
+	int regval;
+
+	/* dummy read */
+	regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4);
+
+	regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4);
+
+	if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX)
+		phydev->duplex = DUPLEX_HALF;
+	else
+		phydev->duplex = DUPLEX_FULL;
+
+	if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_SPEED)
+		phydev->speed = SPEED_10;
+	else
+		phydev->speed = SPEED_100;
+
+	phydev->link = 1;
+	phydev->pause = phydev->asym_pause = 0;
+
+	return 0;
+}
+
+static int ksz9031_read_status(struct phy_device *phydev)
+{
+	int err;
+	int regval;
+
+	err = genphy_read_status(phydev);
+	if (err)
+		return err;
+
+	/* Make sure the PHY is not broken. Read idle error count,
+	 * and reset the PHY if it is maxed out.
+	 */
+	regval = phy_read(phydev, MII_STAT1000);
+	if ((regval & 0xFF) == 0xFF) {
+		phy_init_hw(phydev);
+		phydev->link = 0;
+		if (phydev->drv->config_intr && phy_interrupt_is_valid(phydev))
+			phydev->drv->config_intr(phydev);
+		return genphy_config_aneg(phydev);
+	}
+
+	return 0;
+}
+
+static int ksz8873mll_config_aneg(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int kszphy_get_sset_count(struct phy_device *phydev)
+{
+	return ARRAY_SIZE(kszphy_hw_stats);
+}
+
+static void kszphy_get_strings(struct phy_device *phydev, u8 *data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(kszphy_hw_stats); i++) {
+		strlcpy(data + i * ETH_GSTRING_LEN,
+			kszphy_hw_stats[i].string, ETH_GSTRING_LEN);
+	}
+}
+
+static u64 kszphy_get_stat(struct phy_device *phydev, int i)
+{
+	struct kszphy_hw_stat stat = kszphy_hw_stats[i];
+	struct kszphy_priv *priv = phydev->priv;
+	int val;
+	u64 ret;
+
+	val = phy_read(phydev, stat.reg);
+	if (val < 0) {
+		ret = U64_MAX;
+	} else {
+		val = val & ((1 << stat.bits) - 1);
+		priv->stats[i] += val;
+		ret = priv->stats[i];
+	}
+
+	return ret;
+}
+
+static void kszphy_get_stats(struct phy_device *phydev,
+			     struct ethtool_stats *stats, u64 *data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(kszphy_hw_stats); i++)
+		data[i] = kszphy_get_stat(phydev, i);
+}
+
+static int kszphy_suspend(struct phy_device *phydev)
+{
+	/* Disable PHY Interrupts */
+	if (phy_interrupt_is_valid(phydev)) {
+		phydev->interrupts = PHY_INTERRUPT_DISABLED;
+		if (phydev->drv->config_intr)
+			phydev->drv->config_intr(phydev);
+	}
+
+	return genphy_suspend(phydev);
+}
+
+static int kszphy_resume(struct phy_device *phydev)
+{
+	int ret;
+
+	genphy_resume(phydev);
+
+	/* After switching from power-down to normal mode, an internal global
+	 * reset is automatically generated. Wait a minimum of 1 ms before
+	 * read/write access to the PHY registers.
+	 */
+	usleep_range(1000, 2000);
+	ret = kszphy_config_reset(phydev);
+	if (ret)
+		return ret;
+
+	/* Enable PHY Interrupts */
+	if (phy_interrupt_is_valid(phydev)) {
+		phydev->interrupts = PHY_INTERRUPT_ENABLED;
+		if (phydev->drv->config_intr)
+			phydev->drv->config_intr(phydev);
+	}
+
+	return 0;
+}
+
+static int kszphy_probe(struct phy_device *phydev)
+{
+	const struct kszphy_type *type = phydev->drv->driver_data;
+	const struct device_node *np = phydev->mdio.dev.of_node;
+	struct kszphy_priv *priv;
+	struct clk *clk;
+	int ret;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	priv->type = type;
+
+	if (type->led_mode_reg) {
+		ret = of_property_read_u32(np, "micrel,led-mode",
+				&priv->led_mode);
+		if (ret)
+			priv->led_mode = -1;
+
+		if (priv->led_mode > 3) {
+			phydev_err(phydev, "invalid led mode: 0x%02x\n",
+				   priv->led_mode);
+			priv->led_mode = -1;
+		}
+	} else {
+		priv->led_mode = -1;
+	}
+
+	clk = devm_clk_get(&phydev->mdio.dev, "rmii-ref");
+	/* NOTE: clk may be NULL if building without CONFIG_HAVE_CLK */
+	if (!IS_ERR_OR_NULL(clk)) {
+		unsigned long rate = clk_get_rate(clk);
+		bool rmii_ref_clk_sel_25_mhz;
+
+		priv->rmii_ref_clk_sel = type->has_rmii_ref_clk_sel;
+		rmii_ref_clk_sel_25_mhz = of_property_read_bool(np,
+				"micrel,rmii-reference-clock-select-25-mhz");
+
+		if (rate > 24500000 && rate < 25500000) {
+			priv->rmii_ref_clk_sel_val = rmii_ref_clk_sel_25_mhz;
+		} else if (rate > 49500000 && rate < 50500000) {
+			priv->rmii_ref_clk_sel_val = !rmii_ref_clk_sel_25_mhz;
+		} else {
+			phydev_err(phydev, "Clock rate out of range: %ld\n",
+				   rate);
+			return -EINVAL;
+		}
+	}
+
+	/* Support legacy board-file configuration */
+	if (phydev->dev_flags & MICREL_PHY_50MHZ_CLK) {
+		priv->rmii_ref_clk_sel = true;
+		priv->rmii_ref_clk_sel_val = true;
+	}
+
+	return 0;
+}
+
+static int ksz9131_loopback(struct phy_device *phydev, bool enable)
+{
+	int ret;
+
+	if(enable) {
+		ret = phy_read(phydev, MII_BMCR);
+		ret |= (BMCR_LOOPBACK | BMCR_SPEED1000 | BMCR_FULLDPLX);
+		ret &= ~BMCR_ANENABLE;
+		ret &= ~BMCR_SPEED100;
+
+		phy_write(phydev, MII_BMCR, ret);
+
+		ret = phy_read(phydev, MII_CTRL1000);
+		ret |= ADVERTISE_RESV;
+		ret &= ~ADVERTISE_PAUSE_ASYM;
+
+		phy_write(phydev, MII_CTRL1000, ret);
+
+		phydev->autoneg = AUTONEG_DISABLE;
+
+		if (phydev->autoneg == AUTONEG_ENABLE)
+			phydev->advertising |= ADVERTISED_Autoneg;
+		else
+			phydev->advertising &= ~ADVERTISED_Autoneg;
+
+	} else {
+		ret = phy_read(phydev, MII_BMCR);
+		ret |= BMCR_ANENABLE;
+		ret &= ~BMCR_LOOPBACK;
+
+		phy_write(phydev, MII_BMCR, ret);
+
+		ret = phy_read(phydev, MII_CTRL1000);
+		ret &= ~ADVERTISE_RESV;
+
+		phy_write(phydev, MII_CTRL1000, ret);
+
+		phydev->autoneg = AUTONEG_ENABLE;
+
+		if (phydev->autoneg == AUTONEG_ENABLE)
+			phydev->advertising |= ADVERTISED_Autoneg;
+		else
+			phydev->advertising &= ~ADVERTISED_Autoneg;
+
+	}
+
+	return 0;
+}
+
+static struct phy_driver ksphy_driver[] = {
+{
+	.phy_id		= PHY_ID_KS8737,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KS8737",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ks8737_type,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8021,
+	.phy_id_mask	= 0x00ffffff,
+	.name		= "Micrel KSZ8021 or KSZ8031",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8021_type,
+	.probe		= kszphy_probe,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8031,
+	.phy_id_mask	= 0x00ffffff,
+	.name		= "Micrel KSZ8031",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8021_type,
+	.probe		= kszphy_probe,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8041,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ8041",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8041_type,
+	.probe		= kszphy_probe,
+	.config_init	= ksz8041_config_init,
+	.config_aneg	= ksz8041_config_aneg,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8041RNLI,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ8041RNLI",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8041_type,
+	.probe		= kszphy_probe,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8051,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ8051",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8051_type,
+	.probe		= kszphy_probe,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8001,
+	.name		= "Micrel KSZ8001 or KS8721",
+	.phy_id_mask	= 0x00fffffc,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8041_type,
+	.probe		= kszphy_probe,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8081,
+	.name		= "Micrel KSZ8081 or KSZ8091",
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz8081_type,
+	.probe		= kszphy_probe,
+	.config_init	= kszphy_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= kszphy_suspend,
+	.resume		= kszphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8061,
+	.name		= "Micrel KSZ8061",
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= ksz8061_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ9021,
+	.phy_id_mask	= 0x000ffffe,
+	.name		= "Micrel KSZ9021 Gigabit PHY",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz9021_type,
+	.probe		= kszphy_probe,
+	.config_init	= ksz9021_config_init,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+	.read_mmd	= genphy_read_mmd_unsupported,
+	.write_mmd	= genphy_write_mmd_unsupported,
+}, {
+	.phy_id		= PHY_ID_KSZ9031,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ9031 Gigabit PHY",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.driver_data	= &ksz9021_type,
+	.probe		= kszphy_probe,
+	.config_init	= ksz9031_config_init,
+	.read_status	= ksz9031_read_status,
+	.ack_interrupt	= kszphy_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.suspend	= genphy_suspend,
+	.resume		= kszphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ9131,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Microchip KSZ9131 Gigabit PHY",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	/* PHY_GBIT_FEATURES */
+	.driver_data	= &ksz9021_type,
+	.probe		= kszphy_probe,
+	.config_init	= ksz9131_config_init,
+	.read_status	= genphy_read_status,
+	.ack_interrupt	= ksz9131_ack_interrupt,
+	.config_intr	= kszphy_config_intr,
+	.set_wol	= ksz9131_set_wol,
+	.get_wol	= ksz9131_get_wol,
+	.get_sset_count = kszphy_get_sset_count,
+	.get_strings	= kszphy_get_strings,
+	.get_stats	= kszphy_get_stats,
+	.set_loopback   = ksz9131_loopback,
+	.suspend	= genphy_suspend,
+	.resume		= kszphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8873MLL,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ8873MLL Switch",
+	.config_init	= kszphy_config_init,
+	.config_aneg	= ksz8873mll_config_aneg,
+	.read_status	= ksz8873mll_read_status,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ886X,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ886X Switch",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= kszphy_config_init,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ8795,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Micrel KSZ8795",
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= kszphy_config_init,
+	.config_aneg	= ksz8873mll_config_aneg,
+	.read_status	= ksz8873mll_read_status,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= PHY_ID_KSZ9477,
+	.phy_id_mask	= MICREL_PHY_ID_MASK,
+	.name		= "Microchip KSZ9477",
+	.features	= PHY_GBIT_FEATURES,
+	.config_init	= kszphy_config_init,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+} };
+
+module_phy_driver(ksphy_driver);
+
+MODULE_DESCRIPTION("Micrel PHY driver");
+MODULE_AUTHOR("David J. Choi");
+MODULE_LICENSE("GPL");
+
+static struct mdio_device_id __maybe_unused micrel_tbl[] = {
+	{ PHY_ID_KSZ9021, 0x000ffffe },
+	{ PHY_ID_KSZ9031, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ9131, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ8001, 0x00fffffc },
+	{ PHY_ID_KS8737, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ8021, 0x00ffffff },
+	{ PHY_ID_KSZ8031, 0x00ffffff },
+	{ PHY_ID_KSZ8041, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ8051, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ8061, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ8081, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ8873MLL, MICREL_PHY_ID_MASK },
+	{ PHY_ID_KSZ886X, MICREL_PHY_ID_MASK },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, micrel_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/microchip.c b/src/kernel/linux/v4.19/drivers/net/phy/microchip.c
new file mode 100644
index 0000000..2d67937
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/microchip.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2015 Microchip Technology
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/microchipphy.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <dt-bindings/net/microchip-lan78xx.h>
+
+#define DRIVER_AUTHOR	"WOOJUNG HUH <woojung.huh@microchip.com>"
+#define DRIVER_DESC	"Microchip LAN88XX PHY driver"
+
+struct lan88xx_priv {
+	int	chip_id;
+	int	chip_rev;
+	__u32	wolopts;
+};
+
+static int lan88xx_read_page(struct phy_device *phydev)
+{
+	return __phy_read(phydev, LAN88XX_EXT_PAGE_ACCESS);
+}
+
+static int lan88xx_write_page(struct phy_device *phydev, int page)
+{
+	return __phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, page);
+}
+
+static int lan88xx_phy_config_intr(struct phy_device *phydev)
+{
+	int rc;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* unmask all source and clear them before enable */
+		rc = phy_write(phydev, LAN88XX_INT_MASK, 0x7FFF);
+		rc = phy_read(phydev, LAN88XX_INT_STS);
+		rc = phy_write(phydev, LAN88XX_INT_MASK,
+			       LAN88XX_INT_MASK_MDINTPIN_EN_ |
+			       LAN88XX_INT_MASK_LINK_CHANGE_);
+	} else {
+		rc = phy_write(phydev, LAN88XX_INT_MASK, 0);
+	}
+
+	return rc < 0 ? rc : 0;
+}
+
+static int lan88xx_phy_ack_interrupt(struct phy_device *phydev)
+{
+	int rc = phy_read(phydev, LAN88XX_INT_STS);
+
+	return rc < 0 ? rc : 0;
+}
+
+static int lan88xx_suspend(struct phy_device *phydev)
+{
+	struct lan88xx_priv *priv = phydev->priv;
+
+	/* do not power down PHY when WOL is enabled */
+	if (!priv->wolopts)
+		genphy_suspend(phydev);
+
+	return 0;
+}
+
+static int lan88xx_TR_reg_set(struct phy_device *phydev, u16 regaddr,
+			      u32 data)
+{
+	int val, save_page, ret = 0;
+	u16 buf;
+
+	/* Save current page */
+	save_page = phy_save_page(phydev);
+	if (save_page < 0) {
+		pr_warn("Failed to get current page\n");
+		goto err;
+	}
+
+	/* Switch to TR page */
+	lan88xx_write_page(phydev, LAN88XX_EXT_PAGE_ACCESS_TR);
+
+	ret = __phy_write(phydev, LAN88XX_EXT_PAGE_TR_LOW_DATA,
+			  (data & 0xFFFF));
+	if (ret < 0) {
+		pr_warn("Failed to write TR low data\n");
+		goto err;
+	}
+
+	ret = __phy_write(phydev, LAN88XX_EXT_PAGE_TR_HIGH_DATA,
+			  (data & 0x00FF0000) >> 16);
+	if (ret < 0) {
+		pr_warn("Failed to write TR high data\n");
+		goto err;
+	}
+
+	/* Config control bits [15:13] of register */
+	buf = (regaddr & ~(0x3 << 13));/* Clr [14:13] to write data in reg */
+	buf |= 0x8000; /* Set [15] to Packet transmit */
+
+	ret = __phy_write(phydev, LAN88XX_EXT_PAGE_TR_CR, buf);
+	if (ret < 0) {
+		pr_warn("Failed to write data in reg\n");
+		goto err;
+	}
+
+	usleep_range(1000, 2000);/* Wait for Data to be written */
+	val = __phy_read(phydev, LAN88XX_EXT_PAGE_TR_CR);
+	if (!(val & 0x8000))
+		pr_warn("TR Register[0x%X] configuration failed\n", regaddr);
+err:
+	return phy_restore_page(phydev, save_page, ret);
+}
+
+static void lan88xx_config_TR_regs(struct phy_device *phydev)
+{
+	int err;
+
+	/* Get access to Channel 0x1, Node 0xF , Register 0x01.
+	 * Write 24-bit value 0x12B00A to register. Setting MrvlTrFix1000Kf,
+	 * MrvlTrFix1000Kp, MasterEnableTR bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x0F82, 0x12B00A);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x0F82]\n");
+
+	/* Get access to Channel b'10, Node b'1101, Register 0x06.
+	 * Write 24-bit value 0xD2C46F to register. Setting SSTrKf1000Slv,
+	 * SSTrKp1000Mas bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x168C, 0xD2C46F);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x168C]\n");
+
+	/* Get access to Channel b'10, Node b'1111, Register 0x11.
+	 * Write 24-bit value 0x620 to register. Setting rem_upd_done_thresh
+	 * bits
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x17A2, 0x620);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x17A2]\n");
+
+	/* Get access to Channel b'10, Node b'1101, Register 0x10.
+	 * Write 24-bit value 0xEEFFDD to register. Setting
+	 * eee_TrKp1Long_1000, eee_TrKp2Long_1000, eee_TrKp3Long_1000,
+	 * eee_TrKp1Short_1000,eee_TrKp2Short_1000, eee_TrKp3Short_1000 bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x16A0, 0xEEFFDD);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x16A0]\n");
+
+	/* Get access to Channel b'10, Node b'1101, Register 0x13.
+	 * Write 24-bit value 0x071448 to register. Setting
+	 * slv_lpi_tr_tmr_val1, slv_lpi_tr_tmr_val2 bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x16A6, 0x071448);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x16A6]\n");
+
+	/* Get access to Channel b'10, Node b'1101, Register 0x12.
+	 * Write 24-bit value 0x13132F to register. Setting
+	 * slv_sigdet_timer_val1, slv_sigdet_timer_val2 bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x16A4, 0x13132F);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x16A4]\n");
+
+	/* Get access to Channel b'10, Node b'1101, Register 0x14.
+	 * Write 24-bit value 0x0 to register. Setting eee_3level_delay,
+	 * eee_TrKf_freeze_delay bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x16A8, 0x0);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x16A8]\n");
+
+	/* Get access to Channel b'01, Node b'1111, Register 0x34.
+	 * Write 24-bit value 0x91B06C to register. Setting
+	 * FastMseSearchThreshLong1000, FastMseSearchThreshShort1000,
+	 * FastMseSearchUpdGain1000 bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x0FE8, 0x91B06C);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x0FE8]\n");
+
+	/* Get access to Channel b'01, Node b'1111, Register 0x3E.
+	 * Write 24-bit value 0xC0A028 to register. Setting
+	 * FastMseKp2ThreshLong1000, FastMseKp2ThreshShort1000,
+	 * FastMseKp2UpdGain1000, FastMseKp2ExitEn1000 bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x0FFC, 0xC0A028);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x0FFC]\n");
+
+	/* Get access to Channel b'01, Node b'1111, Register 0x35.
+	 * Write 24-bit value 0x041600 to register. Setting
+	 * FastMseSearchPhShNum1000, FastMseSearchClksPerPh1000,
+	 * FastMsePhChangeDelay1000 bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x0FEA, 0x041600);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x0FEA]\n");
+
+	/* Get access to Channel b'10, Node b'1101, Register 0x03.
+	 * Write 24-bit value 0x000004 to register. Setting TrFreeze bits.
+	 */
+	err = lan88xx_TR_reg_set(phydev, 0x1686, 0x000004);
+	if (err < 0)
+		pr_warn("Failed to Set Register[0x1686]\n");
+}
+
+static int lan88xx_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct lan88xx_priv *priv;
+	u32 led_modes[4];
+	int len;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wolopts = 0;
+
+	len = of_property_read_variable_u32_array(dev->of_node,
+						  "microchip,led-modes",
+						  led_modes,
+						  0,
+						  ARRAY_SIZE(led_modes));
+	if (len >= 0) {
+		u32 reg = 0;
+		int i;
+
+		for (i = 0; i < len; i++) {
+			if (led_modes[i] > 15)
+				return -EINVAL;
+			reg |= led_modes[i] << (i * 4);
+		}
+		for (; i < ARRAY_SIZE(led_modes); i++)
+			reg |= LAN78XX_FORCE_LED_OFF << (i * 4);
+		(void)phy_write(phydev, LAN78XX_PHY_LED_MODE_SELECT, reg);
+	} else if (len == -EOVERFLOW) {
+		return -EINVAL;
+	}
+
+	/* these values can be used to identify internal PHY */
+	priv->chip_id = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_ID);
+	priv->chip_rev = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_REV);
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static void lan88xx_remove(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct lan88xx_priv *priv = phydev->priv;
+
+	if (priv)
+		devm_kfree(dev, priv);
+}
+
+static int lan88xx_set_wol(struct phy_device *phydev,
+			   struct ethtool_wolinfo *wol)
+{
+	struct lan88xx_priv *priv = phydev->priv;
+
+	priv->wolopts = wol->wolopts;
+
+	return 0;
+}
+
+static void lan88xx_set_mdix(struct phy_device *phydev)
+{
+	int buf;
+	int val;
+
+	switch (phydev->mdix_ctrl) {
+	case ETH_TP_MDI:
+		val = LAN88XX_EXT_MODE_CTRL_MDI_;
+		break;
+	case ETH_TP_MDI_X:
+		val = LAN88XX_EXT_MODE_CTRL_MDI_X_;
+		break;
+	case ETH_TP_MDI_AUTO:
+		val = LAN88XX_EXT_MODE_CTRL_AUTO_MDIX_;
+		break;
+	default:
+		return;
+	}
+
+	phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, LAN88XX_EXT_PAGE_SPACE_1);
+	buf = phy_read(phydev, LAN88XX_EXT_MODE_CTRL);
+	buf &= ~LAN88XX_EXT_MODE_CTRL_MDIX_MASK_;
+	buf |= val;
+	phy_write(phydev, LAN88XX_EXT_MODE_CTRL, buf);
+	phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, LAN88XX_EXT_PAGE_SPACE_0);
+}
+
+static int lan88xx_config_init(struct phy_device *phydev)
+{
+	int val;
+
+	genphy_config_init(phydev);
+	/*Zerodetect delay enable */
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS,
+			   PHY_ARDENNES_MMD_DEV_3_PHY_CFG);
+	val |= PHY_ARDENNES_MMD_DEV_3_PHY_CFG_ZD_DLY_EN_;
+
+	phy_write_mmd(phydev, MDIO_MMD_PCS, PHY_ARDENNES_MMD_DEV_3_PHY_CFG,
+		      val);
+
+	/* Config DSP registers */
+	lan88xx_config_TR_regs(phydev);
+
+	return 0;
+}
+
+static int lan88xx_config_aneg(struct phy_device *phydev)
+{
+	lan88xx_set_mdix(phydev);
+
+	return genphy_config_aneg(phydev);
+}
+
+static struct phy_driver microchip_phy_driver[] = {
+{
+	.phy_id		= 0x0007c130,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "Microchip LAN88xx",
+
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+
+	.probe		= lan88xx_probe,
+	.remove		= lan88xx_remove,
+
+	.config_init	= lan88xx_config_init,
+	.config_aneg	= lan88xx_config_aneg,
+
+	.ack_interrupt	= lan88xx_phy_ack_interrupt,
+	.config_intr	= lan88xx_phy_config_intr,
+
+	.suspend	= lan88xx_suspend,
+	.resume		= genphy_resume,
+	.set_wol	= lan88xx_set_wol,
+	.read_page	= lan88xx_read_page,
+	.write_page	= lan88xx_write_page,
+} };
+
+module_phy_driver(microchip_phy_driver);
+
+static struct mdio_device_id __maybe_unused microchip_tbl[] = {
+	{ 0x0007c130, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, microchip_tbl);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/microchip_t1.c b/src/kernel/linux/v4.19/drivers/net/phy/microchip_t1.c
new file mode 100644
index 0000000..b1917dd
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/microchip_t1.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 Microchip Technology
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+/* Interrupt Source Register */
+#define LAN87XX_INTERRUPT_SOURCE                (0x18)
+
+/* Interrupt Mask Register */
+#define LAN87XX_INTERRUPT_MASK                  (0x19)
+#define LAN87XX_MASK_LINK_UP                    (0x0004)
+#define LAN87XX_MASK_LINK_DOWN                  (0x0002)
+
+#define DRIVER_AUTHOR	"Nisar Sayed <nisar.sayed@microchip.com>"
+#define DRIVER_DESC	"Microchip LAN87XX T1 PHY driver"
+
+static int lan87xx_phy_config_intr(struct phy_device *phydev)
+{
+	int rc, val = 0;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* unmask all source and clear them before enable */
+		rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, 0x7FFF);
+		rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
+		val = LAN87XX_MASK_LINK_UP | LAN87XX_MASK_LINK_DOWN;
+	}
+
+	rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
+
+	return rc < 0 ? rc : 0;
+}
+
+static int lan87xx_phy_ack_interrupt(struct phy_device *phydev)
+{
+	int rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
+
+	return rc < 0 ? rc : 0;
+}
+
+static struct phy_driver microchip_t1_phy_driver[] = {
+	{
+		.phy_id         = 0x0007c150,
+		.phy_id_mask    = 0xfffffff0,
+		.name           = "Microchip LAN87xx T1",
+
+		.features       = SUPPORTED_100baseT_Full,
+		.flags          = PHY_HAS_INTERRUPT,
+
+		.config_init    = genphy_config_init,
+		.config_aneg    = genphy_config_aneg,
+
+		.ack_interrupt  = lan87xx_phy_ack_interrupt,
+		.config_intr    = lan87xx_phy_config_intr,
+
+		.suspend        = genphy_suspend,
+		.resume         = genphy_resume,
+	}
+};
+
+module_phy_driver(microchip_t1_phy_driver);
+
+static struct mdio_device_id __maybe_unused microchip_t1_tbl[] = {
+	{ 0x0007c150, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, microchip_t1_tbl);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mscc.c b/src/kernel/linux/v4.19/drivers/net/phy/mscc.c
new file mode 100644
index 0000000..36647b7
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mscc.c
@@ -0,0 +1,761 @@
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Author: Nagaraju Lakkaraju
+ * License: Dual MIT/GPL
+ * Copyright (c) 2016 Microsemi Corporation
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/netdevice.h>
+#include <dt-bindings/net/mscc-phy-vsc8531.h>
+
+enum rgmii_rx_clock_delay {
+	RGMII_RX_CLK_DELAY_0_2_NS = 0,
+	RGMII_RX_CLK_DELAY_0_8_NS = 1,
+	RGMII_RX_CLK_DELAY_1_1_NS = 2,
+	RGMII_RX_CLK_DELAY_1_7_NS = 3,
+	RGMII_RX_CLK_DELAY_2_0_NS = 4,
+	RGMII_RX_CLK_DELAY_2_3_NS = 5,
+	RGMII_RX_CLK_DELAY_2_6_NS = 6,
+	RGMII_RX_CLK_DELAY_3_4_NS = 7
+};
+
+/* Microsemi VSC85xx PHY registers */
+/* IEEE 802. Std Registers */
+#define MSCC_PHY_BYPASS_CONTROL		  18
+#define DISABLE_HP_AUTO_MDIX_MASK	  0x0080
+#define DISABLE_PAIR_SWAP_CORR_MASK	  0x0020
+#define DISABLE_POLARITY_CORR_MASK	  0x0010
+
+#define MSCC_PHY_EXT_PHY_CNTL_1           23
+#define MAC_IF_SELECTION_MASK             0x1800
+#define MAC_IF_SELECTION_GMII             0
+#define MAC_IF_SELECTION_RMII             1
+#define MAC_IF_SELECTION_RGMII            2
+#define MAC_IF_SELECTION_POS              11
+#define FAR_END_LOOPBACK_MODE_MASK        0x0008
+
+#define MII_VSC85XX_INT_MASK		  25
+#define MII_VSC85XX_INT_MASK_MASK	  0xa000
+#define MII_VSC85XX_INT_MASK_WOL	  0x0040
+#define MII_VSC85XX_INT_STATUS		  26
+
+#define MSCC_PHY_WOL_MAC_CONTROL          27
+#define EDGE_RATE_CNTL_POS                5
+#define EDGE_RATE_CNTL_MASK               0x00E0
+
+#define MSCC_PHY_DEV_AUX_CNTL		  28
+#define HP_AUTO_MDIX_X_OVER_IND_MASK	  0x2000
+
+#define MSCC_PHY_LED_MODE_SEL		  29
+#define LED_1_MODE_SEL_MASK		  0x00F0
+#define LED_0_MODE_SEL_MASK		  0x000F
+#define LED_1_MODE_SEL_POS		  4
+
+#define MSCC_EXT_PAGE_ACCESS		  31
+#define MSCC_PHY_PAGE_STANDARD		  0x0000 /* Standard registers */
+#define MSCC_PHY_PAGE_EXTENDED		  0x0001 /* Extended registers */
+#define MSCC_PHY_PAGE_EXTENDED_2	  0x0002 /* Extended reg - page 2 */
+
+/* Extended Page 1 Registers */
+#define MSCC_PHY_EXT_MODE_CNTL		  19
+#define FORCE_MDI_CROSSOVER_MASK	  0x000C
+#define FORCE_MDI_CROSSOVER_MDIX	  0x000C
+#define FORCE_MDI_CROSSOVER_MDI		  0x0008
+
+#define MSCC_PHY_ACTIPHY_CNTL		  20
+#define DOWNSHIFT_CNTL_MASK		  0x001C
+#define DOWNSHIFT_EN			  0x0010
+#define DOWNSHIFT_CNTL_POS		  2
+
+/* Extended Page 2 Registers */
+#define MSCC_PHY_RGMII_CNTL		  20
+#define RGMII_RX_CLK_DELAY_MASK		  0x0070
+#define RGMII_RX_CLK_DELAY_POS		  4
+
+#define MSCC_PHY_WOL_LOWER_MAC_ADDR	  21
+#define MSCC_PHY_WOL_MID_MAC_ADDR	  22
+#define MSCC_PHY_WOL_UPPER_MAC_ADDR	  23
+#define MSCC_PHY_WOL_LOWER_PASSWD	  24
+#define MSCC_PHY_WOL_MID_PASSWD		  25
+#define MSCC_PHY_WOL_UPPER_PASSWD	  26
+
+#define MSCC_PHY_WOL_MAC_CONTROL	  27
+#define SECURE_ON_ENABLE		  0x8000
+#define SECURE_ON_PASSWD_LEN_4		  0x4000
+
+/* Microsemi PHY ID's */
+#define PHY_ID_VSC8530			  0x00070560
+#define PHY_ID_VSC8531			  0x00070570
+#define PHY_ID_VSC8540			  0x00070760
+#define PHY_ID_VSC8541			  0x00070770
+
+#define MSCC_VDDMAC_1500		  1500
+#define MSCC_VDDMAC_1800		  1800
+#define MSCC_VDDMAC_2500		  2500
+#define MSCC_VDDMAC_3300		  3300
+
+#define DOWNSHIFT_COUNT_MAX		  5
+
+struct vsc8531_private {
+	int rate_magic;
+	u8 led_0_mode;
+	u8 led_1_mode;
+};
+
+#ifdef CONFIG_OF_MDIO
+struct vsc8531_edge_rate_table {
+	u32 vddmac;
+	u32 slowdown[8];
+};
+
+static const struct vsc8531_edge_rate_table edge_table[] = {
+	{MSCC_VDDMAC_3300, { 0, 2,  4,  7, 10, 17, 29, 53} },
+	{MSCC_VDDMAC_2500, { 0, 3,  6, 10, 14, 23, 37, 63} },
+	{MSCC_VDDMAC_1800, { 0, 5,  9, 16, 23, 35, 52, 76} },
+	{MSCC_VDDMAC_1500, { 0, 6, 14, 21, 29, 42, 58, 77} },
+};
+#endif /* CONFIG_OF_MDIO */
+
+static int vsc85xx_phy_page_set(struct phy_device *phydev, u16 page)
+{
+	int rc;
+
+	rc = phy_write(phydev, MSCC_EXT_PAGE_ACCESS, page);
+	return rc;
+}
+
+static int vsc85xx_led_cntl_set(struct phy_device *phydev,
+				u8 led_num,
+				u8 mode)
+{
+	int rc;
+	u16 reg_val;
+
+	mutex_lock(&phydev->lock);
+	reg_val = phy_read(phydev, MSCC_PHY_LED_MODE_SEL);
+	if (led_num) {
+		reg_val &= ~LED_1_MODE_SEL_MASK;
+		reg_val |= (((u16)mode << LED_1_MODE_SEL_POS) &
+			    LED_1_MODE_SEL_MASK);
+	} else {
+		reg_val &= ~LED_0_MODE_SEL_MASK;
+		reg_val |= ((u16)mode & LED_0_MODE_SEL_MASK);
+	}
+	rc = phy_write(phydev, MSCC_PHY_LED_MODE_SEL, reg_val);
+	mutex_unlock(&phydev->lock);
+
+	return rc;
+}
+
+static int vsc85xx_mdix_get(struct phy_device *phydev, u8 *mdix)
+{
+	u16 reg_val;
+
+	reg_val = phy_read(phydev, MSCC_PHY_DEV_AUX_CNTL);
+	if (reg_val & HP_AUTO_MDIX_X_OVER_IND_MASK)
+		*mdix = ETH_TP_MDI_X;
+	else
+		*mdix = ETH_TP_MDI;
+
+	return 0;
+}
+
+static int vsc85xx_mdix_set(struct phy_device *phydev, u8 mdix)
+{
+	int rc;
+	u16 reg_val;
+
+	reg_val = phy_read(phydev, MSCC_PHY_BYPASS_CONTROL);
+	if ((mdix == ETH_TP_MDI) || (mdix == ETH_TP_MDI_X)) {
+		reg_val |= (DISABLE_PAIR_SWAP_CORR_MASK |
+			    DISABLE_POLARITY_CORR_MASK  |
+			    DISABLE_HP_AUTO_MDIX_MASK);
+	} else {
+		reg_val &= ~(DISABLE_PAIR_SWAP_CORR_MASK |
+			     DISABLE_POLARITY_CORR_MASK  |
+			     DISABLE_HP_AUTO_MDIX_MASK);
+	}
+	rc = phy_write(phydev, MSCC_PHY_BYPASS_CONTROL, reg_val);
+	if (rc != 0)
+		return rc;
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED);
+	if (rc != 0)
+		return rc;
+
+	reg_val = phy_read(phydev, MSCC_PHY_EXT_MODE_CNTL);
+	reg_val &= ~(FORCE_MDI_CROSSOVER_MASK);
+	if (mdix == ETH_TP_MDI)
+		reg_val |= FORCE_MDI_CROSSOVER_MDI;
+	else if (mdix == ETH_TP_MDI_X)
+		reg_val |= FORCE_MDI_CROSSOVER_MDIX;
+	rc = phy_write(phydev, MSCC_PHY_EXT_MODE_CNTL, reg_val);
+	if (rc != 0)
+		return rc;
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+	if (rc != 0)
+		return rc;
+
+	return genphy_restart_aneg(phydev);
+}
+
+static int vsc85xx_downshift_get(struct phy_device *phydev, u8 *count)
+{
+	int rc;
+	u16 reg_val;
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED);
+	if (rc != 0)
+		goto out;
+
+	reg_val = phy_read(phydev, MSCC_PHY_ACTIPHY_CNTL);
+	reg_val &= DOWNSHIFT_CNTL_MASK;
+	if (!(reg_val & DOWNSHIFT_EN))
+		*count = DOWNSHIFT_DEV_DISABLE;
+	else
+		*count = ((reg_val & ~DOWNSHIFT_EN) >> DOWNSHIFT_CNTL_POS) + 2;
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+
+out:
+	return rc;
+}
+
+static int vsc85xx_downshift_set(struct phy_device *phydev, u8 count)
+{
+	int rc;
+	u16 reg_val;
+
+	if (count == DOWNSHIFT_DEV_DEFAULT_COUNT) {
+		/* Default downshift count 3 (i.e. Bit3:2 = 0b01) */
+		count = ((1 << DOWNSHIFT_CNTL_POS) | DOWNSHIFT_EN);
+	} else if (count > DOWNSHIFT_COUNT_MAX || count == 1) {
+		phydev_err(phydev, "Downshift count should be 2,3,4 or 5\n");
+		return -ERANGE;
+	} else if (count) {
+		/* Downshift count is either 2,3,4 or 5 */
+		count = (((count - 2) << DOWNSHIFT_CNTL_POS) | DOWNSHIFT_EN);
+	}
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED);
+	if (rc != 0)
+		goto out;
+
+	reg_val = phy_read(phydev, MSCC_PHY_ACTIPHY_CNTL);
+	reg_val &= ~(DOWNSHIFT_CNTL_MASK);
+	reg_val |= count;
+	rc = phy_write(phydev, MSCC_PHY_ACTIPHY_CNTL, reg_val);
+	if (rc != 0)
+		goto out;
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+
+out:
+	return rc;
+}
+
+static int vsc85xx_wol_set(struct phy_device *phydev,
+			   struct ethtool_wolinfo *wol)
+{
+	int rc;
+	u16 reg_val;
+	u8  i;
+	u16 pwd[3] = {0, 0, 0};
+	struct ethtool_wolinfo *wol_conf = wol;
+	u8 *mac_addr = phydev->attached_dev->dev_addr;
+
+	mutex_lock(&phydev->lock);
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2);
+	if (rc != 0)
+		goto out_unlock;
+
+	if (wol->wolopts & WAKE_MAGIC) {
+		/* Store the device address for the magic packet */
+		for (i = 0; i < ARRAY_SIZE(pwd); i++)
+			pwd[i] = mac_addr[5 - (i * 2 + 1)] << 8 |
+				 mac_addr[5 - i * 2];
+		phy_write(phydev, MSCC_PHY_WOL_LOWER_MAC_ADDR, pwd[0]);
+		phy_write(phydev, MSCC_PHY_WOL_MID_MAC_ADDR, pwd[1]);
+		phy_write(phydev, MSCC_PHY_WOL_UPPER_MAC_ADDR, pwd[2]);
+	} else {
+		phy_write(phydev, MSCC_PHY_WOL_LOWER_MAC_ADDR, 0);
+		phy_write(phydev, MSCC_PHY_WOL_MID_MAC_ADDR, 0);
+		phy_write(phydev, MSCC_PHY_WOL_UPPER_MAC_ADDR, 0);
+	}
+
+	if (wol_conf->wolopts & WAKE_MAGICSECURE) {
+		for (i = 0; i < ARRAY_SIZE(pwd); i++)
+			pwd[i] = wol_conf->sopass[5 - (i * 2 + 1)] << 8 |
+				 wol_conf->sopass[5 - i * 2];
+		phy_write(phydev, MSCC_PHY_WOL_LOWER_PASSWD, pwd[0]);
+		phy_write(phydev, MSCC_PHY_WOL_MID_PASSWD, pwd[1]);
+		phy_write(phydev, MSCC_PHY_WOL_UPPER_PASSWD, pwd[2]);
+	} else {
+		phy_write(phydev, MSCC_PHY_WOL_LOWER_PASSWD, 0);
+		phy_write(phydev, MSCC_PHY_WOL_MID_PASSWD, 0);
+		phy_write(phydev, MSCC_PHY_WOL_UPPER_PASSWD, 0);
+	}
+
+	reg_val = phy_read(phydev, MSCC_PHY_WOL_MAC_CONTROL);
+	if (wol_conf->wolopts & WAKE_MAGICSECURE)
+		reg_val |= SECURE_ON_ENABLE;
+	else
+		reg_val &= ~SECURE_ON_ENABLE;
+	phy_write(phydev, MSCC_PHY_WOL_MAC_CONTROL, reg_val);
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+	if (rc != 0)
+		goto out_unlock;
+
+	if (wol->wolopts & WAKE_MAGIC) {
+		/* Enable the WOL interrupt */
+		reg_val = phy_read(phydev, MII_VSC85XX_INT_MASK);
+		reg_val |= MII_VSC85XX_INT_MASK_WOL;
+		rc = phy_write(phydev, MII_VSC85XX_INT_MASK, reg_val);
+		if (rc != 0)
+			goto out_unlock;
+	} else {
+		/* Disable the WOL interrupt */
+		reg_val = phy_read(phydev, MII_VSC85XX_INT_MASK);
+		reg_val &= (~MII_VSC85XX_INT_MASK_WOL);
+		rc = phy_write(phydev, MII_VSC85XX_INT_MASK, reg_val);
+		if (rc != 0)
+			goto out_unlock;
+	}
+	/* Clear WOL iterrupt status */
+	reg_val = phy_read(phydev, MII_VSC85XX_INT_STATUS);
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+
+	return rc;
+}
+
+static void vsc85xx_wol_get(struct phy_device *phydev,
+			    struct ethtool_wolinfo *wol)
+{
+	int rc;
+	u16 reg_val;
+	u8  i;
+	u16 pwd[3] = {0, 0, 0};
+	struct ethtool_wolinfo *wol_conf = wol;
+
+	mutex_lock(&phydev->lock);
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2);
+	if (rc != 0)
+		goto out_unlock;
+
+	reg_val = phy_read(phydev, MSCC_PHY_WOL_MAC_CONTROL);
+	if (reg_val & SECURE_ON_ENABLE)
+		wol_conf->wolopts |= WAKE_MAGICSECURE;
+	if (wol_conf->wolopts & WAKE_MAGICSECURE) {
+		pwd[0] = phy_read(phydev, MSCC_PHY_WOL_LOWER_PASSWD);
+		pwd[1] = phy_read(phydev, MSCC_PHY_WOL_MID_PASSWD);
+		pwd[2] = phy_read(phydev, MSCC_PHY_WOL_UPPER_PASSWD);
+		for (i = 0; i < ARRAY_SIZE(pwd); i++) {
+			wol_conf->sopass[5 - i * 2] = pwd[i] & 0x00ff;
+			wol_conf->sopass[5 - (i * 2 + 1)] = (pwd[i] & 0xff00)
+							    >> 8;
+		}
+	}
+
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+}
+
+#ifdef CONFIG_OF_MDIO
+static int vsc85xx_edge_rate_magic_get(struct phy_device *phydev)
+{
+	u32 vdd, sd;
+	int rc, i, j;
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	u8 sd_array_size = ARRAY_SIZE(edge_table[0].slowdown);
+
+	if (!of_node)
+		return -ENODEV;
+
+	rc = of_property_read_u32(of_node, "vsc8531,vddmac", &vdd);
+	if (rc != 0)
+		vdd = MSCC_VDDMAC_3300;
+
+	rc = of_property_read_u32(of_node, "vsc8531,edge-slowdown", &sd);
+	if (rc != 0)
+		sd = 0;
+
+	for (i = 0; i < ARRAY_SIZE(edge_table); i++)
+		if (edge_table[i].vddmac == vdd)
+			for (j = 0; j < sd_array_size; j++)
+				if (edge_table[i].slowdown[j] == sd)
+					return (sd_array_size - j - 1);
+
+	return -EINVAL;
+}
+
+static int vsc85xx_dt_led_mode_get(struct phy_device *phydev,
+				   char *led,
+				   u8 default_mode)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	u8 led_mode;
+	int err;
+
+	if (!of_node)
+		return -ENODEV;
+
+	led_mode = default_mode;
+	err = of_property_read_u8(of_node, led, &led_mode);
+	if (!err && (led_mode > 15 || led_mode == 7 || led_mode == 11)) {
+		phydev_err(phydev, "DT %s invalid\n", led);
+		return -EINVAL;
+	}
+
+	return led_mode;
+}
+
+#else
+static int vsc85xx_edge_rate_magic_get(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int vsc85xx_dt_led_mode_get(struct phy_device *phydev,
+				   char *led,
+				   u8 default_mode)
+{
+	return default_mode;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int vsc85xx_edge_rate_cntl_set(struct phy_device *phydev, u8 edge_rate)
+{
+	int rc;
+	u16 reg_val;
+
+	mutex_lock(&phydev->lock);
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2);
+	if (rc != 0)
+		goto out_unlock;
+	reg_val = phy_read(phydev, MSCC_PHY_WOL_MAC_CONTROL);
+	reg_val &= ~(EDGE_RATE_CNTL_MASK);
+	reg_val |= (edge_rate << EDGE_RATE_CNTL_POS);
+	rc = phy_write(phydev, MSCC_PHY_WOL_MAC_CONTROL, reg_val);
+	if (rc != 0)
+		goto out_unlock;
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+
+	return rc;
+}
+
+static int vsc85xx_mac_if_set(struct phy_device *phydev,
+			      phy_interface_t interface)
+{
+	int rc;
+	u16 reg_val;
+
+	mutex_lock(&phydev->lock);
+	reg_val = phy_read(phydev, MSCC_PHY_EXT_PHY_CNTL_1);
+	reg_val &= ~(MAC_IF_SELECTION_MASK);
+	switch (interface) {
+	case PHY_INTERFACE_MODE_RGMII:
+		reg_val |= (MAC_IF_SELECTION_RGMII << MAC_IF_SELECTION_POS);
+		break;
+	case PHY_INTERFACE_MODE_RMII:
+		reg_val |= (MAC_IF_SELECTION_RMII << MAC_IF_SELECTION_POS);
+		break;
+	case PHY_INTERFACE_MODE_MII:
+	case PHY_INTERFACE_MODE_GMII:
+		reg_val |= (MAC_IF_SELECTION_GMII << MAC_IF_SELECTION_POS);
+		break;
+	default:
+		rc = -EINVAL;
+		goto out_unlock;
+	}
+	rc = phy_write(phydev, MSCC_PHY_EXT_PHY_CNTL_1, reg_val);
+	if (rc != 0)
+		goto out_unlock;
+
+	rc = genphy_soft_reset(phydev);
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+
+	return rc;
+}
+
+static int vsc85xx_default_config(struct phy_device *phydev)
+{
+	int rc;
+	u16 reg_val;
+
+	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+	mutex_lock(&phydev->lock);
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2);
+	if (rc != 0)
+		goto out_unlock;
+
+	reg_val = phy_read(phydev, MSCC_PHY_RGMII_CNTL);
+	reg_val &= ~(RGMII_RX_CLK_DELAY_MASK);
+	reg_val |= (RGMII_RX_CLK_DELAY_1_1_NS << RGMII_RX_CLK_DELAY_POS);
+	phy_write(phydev, MSCC_PHY_RGMII_CNTL, reg_val);
+	rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+
+	return rc;
+}
+
+static int vsc85xx_get_tunable(struct phy_device *phydev,
+			       struct ethtool_tunable *tuna, void *data)
+{
+	switch (tuna->id) {
+	case ETHTOOL_PHY_DOWNSHIFT:
+		return vsc85xx_downshift_get(phydev, (u8 *)data);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int vsc85xx_set_tunable(struct phy_device *phydev,
+			       struct ethtool_tunable *tuna,
+			       const void *data)
+{
+	switch (tuna->id) {
+	case ETHTOOL_PHY_DOWNSHIFT:
+		return vsc85xx_downshift_set(phydev, *(u8 *)data);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int vsc85xx_config_init(struct phy_device *phydev)
+{
+	int rc;
+	struct vsc8531_private *vsc8531 = phydev->priv;
+
+	rc = vsc85xx_default_config(phydev);
+	if (rc)
+		return rc;
+
+	rc = vsc85xx_mac_if_set(phydev, phydev->interface);
+	if (rc)
+		return rc;
+
+	rc = vsc85xx_edge_rate_cntl_set(phydev, vsc8531->rate_magic);
+	if (rc)
+		return rc;
+
+	rc = vsc85xx_led_cntl_set(phydev, 1, vsc8531->led_1_mode);
+	if (rc)
+		return rc;
+
+	rc = vsc85xx_led_cntl_set(phydev, 0, vsc8531->led_0_mode);
+	if (rc)
+		return rc;
+
+	rc = genphy_config_init(phydev);
+
+	return rc;
+}
+
+static int vsc85xx_ack_interrupt(struct phy_device *phydev)
+{
+	int rc = 0;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		rc = phy_read(phydev, MII_VSC85XX_INT_STATUS);
+
+	return (rc < 0) ? rc : 0;
+}
+
+static int vsc85xx_config_intr(struct phy_device *phydev)
+{
+	int rc;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		rc = phy_write(phydev, MII_VSC85XX_INT_MASK,
+			       MII_VSC85XX_INT_MASK_MASK);
+	} else {
+		rc = phy_write(phydev, MII_VSC85XX_INT_MASK, 0);
+		if (rc < 0)
+			return rc;
+		rc = phy_read(phydev, MII_VSC85XX_INT_STATUS);
+	}
+
+	return rc;
+}
+
+static int vsc85xx_config_aneg(struct phy_device *phydev)
+{
+	int rc;
+
+	rc = vsc85xx_mdix_set(phydev, phydev->mdix_ctrl);
+	if (rc < 0)
+		return rc;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int vsc85xx_read_status(struct phy_device *phydev)
+{
+	int rc;
+
+	rc = vsc85xx_mdix_get(phydev, &phydev->mdix);
+	if (rc < 0)
+		return rc;
+
+	return genphy_read_status(phydev);
+}
+
+static int vsc85xx_probe(struct phy_device *phydev)
+{
+	struct vsc8531_private *vsc8531;
+	int rate_magic;
+	int led_mode;
+
+	rate_magic = vsc85xx_edge_rate_magic_get(phydev);
+	if (rate_magic < 0)
+		return rate_magic;
+
+	vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL);
+	if (!vsc8531)
+		return -ENOMEM;
+
+	phydev->priv = vsc8531;
+
+	vsc8531->rate_magic = rate_magic;
+
+	/* LED[0] and LED[1] mode */
+	led_mode = vsc85xx_dt_led_mode_get(phydev, "vsc8531,led-0-mode",
+					   VSC8531_LINK_1000_ACTIVITY);
+	if (led_mode < 0)
+		return led_mode;
+	vsc8531->led_0_mode = led_mode;
+
+	led_mode = vsc85xx_dt_led_mode_get(phydev, "vsc8531,led-1-mode",
+					   VSC8531_LINK_100_ACTIVITY);
+	if (led_mode < 0)
+		return led_mode;
+	vsc8531->led_1_mode = led_mode;
+
+	return 0;
+}
+
+/* Microsemi VSC85xx PHYs */
+static struct phy_driver vsc85xx_driver[] = {
+{
+	.phy_id		= PHY_ID_VSC8530,
+	.name		= "Microsemi FE VSC8530",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.soft_reset	= &genphy_soft_reset,
+	.config_init	= &vsc85xx_config_init,
+	.config_aneg    = &vsc85xx_config_aneg,
+	.aneg_done	= &genphy_aneg_done,
+	.read_status	= &vsc85xx_read_status,
+	.ack_interrupt	= &vsc85xx_ack_interrupt,
+	.config_intr	= &vsc85xx_config_intr,
+	.suspend	= &genphy_suspend,
+	.resume		= &genphy_resume,
+	.probe		= &vsc85xx_probe,
+	.set_wol	= &vsc85xx_wol_set,
+	.get_wol	= &vsc85xx_wol_get,
+	.get_tunable	= &vsc85xx_get_tunable,
+	.set_tunable	= &vsc85xx_set_tunable,
+},
+{
+	.phy_id		= PHY_ID_VSC8531,
+	.name		= "Microsemi VSC8531",
+	.phy_id_mask    = 0xfffffff0,
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.soft_reset	= &genphy_soft_reset,
+	.config_init    = &vsc85xx_config_init,
+	.config_aneg    = &vsc85xx_config_aneg,
+	.aneg_done	= &genphy_aneg_done,
+	.read_status	= &vsc85xx_read_status,
+	.ack_interrupt  = &vsc85xx_ack_interrupt,
+	.config_intr    = &vsc85xx_config_intr,
+	.suspend	= &genphy_suspend,
+	.resume		= &genphy_resume,
+	.probe		= &vsc85xx_probe,
+	.set_wol	= &vsc85xx_wol_set,
+	.get_wol	= &vsc85xx_wol_get,
+	.get_tunable	= &vsc85xx_get_tunable,
+	.set_tunable	= &vsc85xx_set_tunable,
+},
+{
+	.phy_id		= PHY_ID_VSC8540,
+	.name		= "Microsemi FE VSC8540 SyncE",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.soft_reset	= &genphy_soft_reset,
+	.config_init	= &vsc85xx_config_init,
+	.config_aneg	= &vsc85xx_config_aneg,
+	.aneg_done	= &genphy_aneg_done,
+	.read_status	= &vsc85xx_read_status,
+	.ack_interrupt	= &vsc85xx_ack_interrupt,
+	.config_intr	= &vsc85xx_config_intr,
+	.suspend	= &genphy_suspend,
+	.resume		= &genphy_resume,
+	.probe		= &vsc85xx_probe,
+	.set_wol	= &vsc85xx_wol_set,
+	.get_wol	= &vsc85xx_wol_get,
+	.get_tunable	= &vsc85xx_get_tunable,
+	.set_tunable	= &vsc85xx_set_tunable,
+},
+{
+	.phy_id		= PHY_ID_VSC8541,
+	.name		= "Microsemi VSC8541 SyncE",
+	.phy_id_mask    = 0xfffffff0,
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.soft_reset	= &genphy_soft_reset,
+	.config_init    = &vsc85xx_config_init,
+	.config_aneg    = &vsc85xx_config_aneg,
+	.aneg_done	= &genphy_aneg_done,
+	.read_status	= &vsc85xx_read_status,
+	.ack_interrupt  = &vsc85xx_ack_interrupt,
+	.config_intr    = &vsc85xx_config_intr,
+	.suspend	= &genphy_suspend,
+	.resume		= &genphy_resume,
+	.probe		= &vsc85xx_probe,
+	.set_wol	= &vsc85xx_wol_set,
+	.get_wol	= &vsc85xx_wol_get,
+	.get_tunable	= &vsc85xx_get_tunable,
+	.set_tunable	= &vsc85xx_set_tunable,
+}
+
+};
+
+module_phy_driver(vsc85xx_driver);
+
+static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = {
+	{ PHY_ID_VSC8530, 0xfffffff0, },
+	{ PHY_ID_VSC8531, 0xfffffff0, },
+	{ PHY_ID_VSC8540, 0xfffffff0, },
+	{ PHY_ID_VSC8541, 0xfffffff0, },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, vsc85xx_tbl);
+
+MODULE_DESCRIPTION("Microsemi VSC85xx PHY driver");
+MODULE_AUTHOR("Nagaraju Lakkaraju");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/Kconfig b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/Kconfig
new file mode 100644
index 0000000..d9e0230
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/Kconfig
@@ -0,0 +1,3 @@
+
+config MT753X_GSW
+	tristate "Driver for the MediaTek MT753x switch"
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/Makefile b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/Makefile
new file mode 100755
index 0000000..e304fcb
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for MediaTek MT753x gigabit switch
+#
+
+obj-$(CONFIG_MT753X_GSW)	+= mt753x.o
+
+mt753x-$(CONFIG_SWCONFIG)	+= mt753x_swconfig.o
+
+mt753x-y			+= mt753x_mdio.o mt7530.o mt7531.o \
+					mt753x_common.o mt753x_vlan.o mt753x_nl.o
+
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7530.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7530.c
new file mode 100644
index 0000000..c696543
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7530.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "mt753x.h"
+#include "mt753x_regs.h"
+
+/* MT7530 registers */
+
+/* Unique fields of PMCR for MT7530 */
+#define FORCE_MODE			BIT(15)
+
+/* Unique fields of GMACCR for MT7530 */
+#define VLAN_SUPT_NO_S			14
+#define VLAN_SUPT_NO_M			0x1c000
+#define LATE_COL_DROP			BIT(13)
+
+/* Unique fields of (M)HWSTRAP for MT7530 */
+#define BOND_OPTION			BIT(24)
+#define P5_PHY0_SEL			BIT(20)
+#define CHG_TRAP			BIT(16)
+#define LOOPDET_DIS			BIT(14)
+#define P5_INTF_SEL_GMAC5		BIT(13)
+#define SMI_ADDR_S			11
+#define SMI_ADDR_M			0x1800
+#define XTAL_FSEL_S			9
+#define XTAL_FSEL_M			0x600
+#define P6_INTF_DIS			BIT(8)
+#define P5_INTF_MODE_RGMII		BIT(7)
+#define P5_INTF_DIS_S			BIT(6)
+#define C_MDIO_BPS_S			BIT(5)
+#define EEPROM_EN_S			BIT(4)
+
+/* PHY EEE Register bitmap of define */
+#define PHY_DEV07			0x07
+#define PHY_DEV07_REG_03C		0x3c
+
+/* PHY Extend Register 0x14 bitmap of define */
+#define PHY_EXT_REG_14			0x14
+
+/* Fields of PHY_EXT_REG_14 */
+#define PHY_EN_DOWN_SHFIT		BIT(4)
+
+/* PHY Token Ring Register 0x10 bitmap of define */
+#define PHY_TR_REG_10			0x10
+
+/* PHY Token Ring Register 0x12 bitmap of define */
+#define PHY_TR_REG_12			0x12
+
+/* PHY LPI PCS/DSP Control Register bitmap of define */
+#define PHY_LPI_REG_11			0x11
+
+/* PHY DEV 0x1e Register bitmap of define */
+#define PHY_DEV1E			0x1e
+#define PHY_DEV1E_REG_123		0x123
+#define PHY_DEV1E_REG_A6		0xa6
+
+/* Values of XTAL_FSEL */
+#define XTAL_20MHZ			1
+#define XTAL_40MHZ			2
+#define XTAL_25MHZ			3
+
+/* Top single control CR define */
+#define TOP_SIG_CTRL			0x7808
+
+/* TOP_SIG_CTRL Register bitmap of define */
+#define OUTPUT_INTR_S			16
+#define OUTPUT_INTR_M			0x30000
+
+#define P6ECR				0x7830
+#define P6_INTF_MODE_TRGMII		BIT(0)
+
+#define TRGMII_TXCTRL			0x7a40
+#define TRAIN_TXEN			BIT(31)
+#define TXC_INV				BIT(30)
+#define TX_DOEO				BIT(29)
+#define TX_RST				BIT(28)
+
+#define TRGMII_TD0_CTRL			0x7a50
+#define TRGMII_TD1_CTRL			0x7a58
+#define TRGMII_TD2_CTRL			0x7a60
+#define TRGMII_TD3_CTRL			0x7a68
+#define TRGMII_TXCTL_CTRL		0x7a70
+#define TRGMII_TCK_CTRL			0x7a78
+#define TRGMII_TD_CTRL(n)		(0x7a50 + (n) * 8)
+#define NUM_TRGMII_CTRL			6
+#define TX_DMPEDRV			BIT(31)
+#define TX_DM_SR			BIT(15)
+#define TX_DMERODT			BIT(14)
+#define TX_DMOECTL			BIT(13)
+#define TX_TAP_S			8
+#define TX_TAP_M			0xf00
+#define TX_TRAIN_WD_S			0
+#define TX_TRAIN_WD_M			0xff
+
+#define TRGMII_TD0_ODT			0x7a54
+#define TRGMII_TD1_ODT			0x7a5c
+#define TRGMII_TD2_ODT			0x7a64
+#define TRGMII_TD3_ODT			0x7a6c
+#define TRGMII_TXCTL_ODT		0x7574
+#define TRGMII_TCK_ODT			0x757c
+#define TRGMII_TD_ODT(n)		(0x7a54 + (n) * 8)
+#define NUM_TRGMII_ODT			6
+#define TX_DM_DRVN_PRE_S		30
+#define TX_DM_DRVN_PRE_M		0xc0000000
+#define TX_DM_DRVP_PRE_S		28
+#define TX_DM_DRVP_PRE_M		0x30000000
+#define TX_DM_TDSEL_S			24
+#define TX_DM_TDSEL_M			0xf000000
+#define TX_ODTEN			BIT(23)
+#define TX_DME_PRE			BIT(20)
+#define TX_DM_DRVNT0			BIT(19)
+#define TX_DM_DRVPT0			BIT(18)
+#define TX_DM_DRVNTE			BIT(17)
+#define TX_DM_DRVPTE			BIT(16)
+#define TX_DM_ODTN_S			12
+#define TX_DM_ODTN_M			0x7000
+#define TX_DM_ODTP_S			8
+#define TX_DM_ODTP_M			0x700
+#define TX_DM_DRVN_S			4
+#define TX_DM_DRVN_M			0xf0
+#define TX_DM_DRVP_S			0
+#define TX_DM_DRVP_M			0x0f
+
+#define P5RGMIIRXCR			0x7b00
+#define CSR_RGMII_RCTL_CFG_S		24
+#define CSR_RGMII_RCTL_CFG_M		0x7000000
+#define CSR_RGMII_RXD_CFG_S		16
+#define CSR_RGMII_RXD_CFG_M		0x70000
+#define CSR_RGMII_EDGE_ALIGN		BIT(8)
+#define CSR_RGMII_RXC_90DEG_CFG_S	4
+#define CSR_RGMII_RXC_90DEG_CFG_M	0xf0
+#define CSR_RGMII_RXC_0DEG_CFG_S	0
+#define CSR_RGMII_RXC_0DEG_CFG_M	0x0f
+
+#define P5RGMIITXCR			0x7b04
+#define CSR_RGMII_TXEN_CFG_S		16
+#define CSR_RGMII_TXEN_CFG_M		0x70000
+#define CSR_RGMII_TXD_CFG_S		8
+#define CSR_RGMII_TXD_CFG_M		0x700
+#define CSR_RGMII_TXC_CFG_S		0
+#define CSR_RGMII_TXC_CFG_M		0x1f
+
+#define CHIP_REV			0x7ffc
+#define CHIP_NAME_S			16
+#define CHIP_NAME_M			0xffff0000
+#define CHIP_REV_S			0
+#define CHIP_REV_M			0x0f
+
+/* MMD registers */
+#define CORE_PLL_GROUP2			0x401
+#define RG_SYSPLL_EN_NORMAL		BIT(15)
+#define RG_SYSPLL_VODEN			BIT(14)
+#define RG_SYSPLL_POSDIV_S		5
+#define RG_SYSPLL_POSDIV_M		0x60
+
+#define CORE_PLL_GROUP4			0x403
+#define RG_SYSPLL_DDSFBK_EN		BIT(12)
+#define RG_SYSPLL_BIAS_EN		BIT(11)
+#define RG_SYSPLL_BIAS_LPF_EN		BIT(10)
+
+#define CORE_PLL_GROUP5			0x404
+#define RG_LCDDS_PCW_NCPO1_S		0
+#define RG_LCDDS_PCW_NCPO1_M		0xffff
+
+#define CORE_PLL_GROUP6			0x405
+#define RG_LCDDS_PCW_NCPO0_S		0
+#define RG_LCDDS_PCW_NCPO0_M		0xffff
+
+#define CORE_PLL_GROUP7			0x406
+#define RG_LCDDS_PWDB			BIT(15)
+#define RG_LCDDS_ISO_EN			BIT(13)
+#define RG_LCCDS_C_S			4
+#define RG_LCCDS_C_M			0x70
+#define RG_LCDDS_PCW_NCPO_CHG		BIT(3)
+
+#define CORE_PLL_GROUP10		0x409
+#define RG_LCDDS_SSC_DELTA_S		0
+#define RG_LCDDS_SSC_DELTA_M		0xfff
+
+#define CORE_PLL_GROUP11		0x40a
+#define RG_LCDDS_SSC_DELTA1_S		0
+#define RG_LCDDS_SSC_DELTA1_M		0xfff
+
+#define CORE_GSWPLL_GCR_1		0x040d
+#define GSWPLL_PREDIV_S			14
+#define GSWPLL_PREDIV_M			0xc000
+#define GSWPLL_POSTDIV_200M_S		12
+#define GSWPLL_POSTDIV_200M_M		0x3000
+#define GSWPLL_EN_PRE			BIT(11)
+#define GSWPLL_FBKSEL			BIT(10)
+#define GSWPLL_BP			BIT(9)
+#define GSWPLL_BR			BIT(8)
+#define GSWPLL_FBKDIV_200M_S		0
+#define GSWPLL_FBKDIV_200M_M		0xff
+
+#define CORE_GSWPLL_GCR_2		0x040e
+#define GSWPLL_POSTDIV_500M_S		8
+#define GSWPLL_POSTDIV_500M_M		0x300
+#define GSWPLL_FBKDIV_500M_S		0
+#define GSWPLL_FBKDIV_500M_M		0xff
+
+#define TRGMII_GSW_CLK_CG		0x0410
+#define TRGMIICK_EN			BIT(1)
+#define GSWCK_EN			BIT(0)
+
+static int mt7530_mii_read(struct gsw_mt753x *gsw, int phy, int reg)
+{
+	if (phy < MT753X_NUM_PHYS)
+		phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;
+
+	return mdiobus_read(gsw->host_bus, phy, reg);
+}
+
+static void mt7530_mii_write(struct gsw_mt753x *gsw, int phy, int reg, u16 val)
+{
+	if (phy < MT753X_NUM_PHYS)
+		phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;
+
+	mdiobus_write(gsw->host_bus, phy, reg, val);
+}
+
+static int mt7530_mmd_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg)
+{
+	u16 val;
+
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ACC_CTL_REG,
+			     (MMD_ADDR << MMD_CMD_S) |
+			     ((devad << MMD_DEVAD_S) & MMD_DEVAD_M));
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ADDR_DATA_REG, reg);
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ACC_CTL_REG,
+			     (MMD_DATA << MMD_CMD_S) |
+			     ((devad << MMD_DEVAD_S) & MMD_DEVAD_M));
+
+	val = gsw->host_bus->read(gsw->host_bus, addr, MII_MMD_ADDR_DATA_REG);
+
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+
+	return val;
+}
+
+static void mt7530_mmd_write(struct gsw_mt753x *gsw, int addr, int devad,
+			     u16 reg, u16 val)
+{
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ACC_CTL_REG,
+		      (MMD_ADDR << MMD_CMD_S) |
+		      ((devad << MMD_DEVAD_S) & MMD_DEVAD_M));
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ADDR_DATA_REG, reg);
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ACC_CTL_REG,
+		      (MMD_DATA << MMD_CMD_S) |
+		      ((devad << MMD_DEVAD_S) & MMD_DEVAD_M));
+
+	gsw->host_bus->write(gsw->host_bus, addr, MII_MMD_ADDR_DATA_REG, val);
+
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+}
+
+static void mt7530_core_reg_write(struct gsw_mt753x *gsw, u32 reg, u32 val)
+{
+	gsw->mmd_write(gsw, 0, 0x1f, reg, val);
+}
+
+static void mt7530_trgmii_setting(struct gsw_mt753x *gsw)
+{
+	u16 i;
+
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP5, 0x0780);
+	mdelay(1);
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP6, 0);
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP10, 0x87);
+	mdelay(1);
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP11, 0x87);
+
+	/* PLL BIAS enable */
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP4,
+			      RG_SYSPLL_DDSFBK_EN | RG_SYSPLL_BIAS_EN);
+	mdelay(1);
+
+	/* PLL LPF enable */
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP4,
+			      RG_SYSPLL_DDSFBK_EN |
+			      RG_SYSPLL_BIAS_EN | RG_SYSPLL_BIAS_LPF_EN);
+
+	/* sys PLL enable */
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP2,
+			      RG_SYSPLL_EN_NORMAL | RG_SYSPLL_VODEN |
+			      (1 << RG_SYSPLL_POSDIV_S));
+
+	/* LCDDDS PWDS */
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP7,
+			      (3 << RG_LCCDS_C_S) |
+			      RG_LCDDS_PWDB | RG_LCDDS_ISO_EN);
+	mdelay(1);
+
+	/* Enable MT7530 TRGMII clock */
+	mt7530_core_reg_write(gsw, TRGMII_GSW_CLK_CG, GSWCK_EN | TRGMIICK_EN);
+
+	/* lower Tx Driving */
+	for (i = 0 ; i < NUM_TRGMII_ODT; i++)
+		mt753x_reg_write(gsw, TRGMII_TD_ODT(i),
+				 (4 << TX_DM_DRVP_S) | (4 << TX_DM_DRVN_S));
+}
+
+static void mt7530_rgmii_setting(struct gsw_mt753x *gsw)
+{
+	u32 val;
+
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP5, 0x0c80);
+	mdelay(1);
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP6, 0);
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP10, 0x87);
+	mdelay(1);
+	mt7530_core_reg_write(gsw, CORE_PLL_GROUP11, 0x87);
+
+	val = mt753x_reg_read(gsw, TRGMII_TXCTRL);
+	val &= ~TXC_INV;
+	mt753x_reg_write(gsw, TRGMII_TXCTRL, val);
+
+	mt753x_reg_write(gsw, TRGMII_TCK_CTRL,
+			 (8 << TX_TAP_S) | (0x55 << TX_TRAIN_WD_S));
+}
+
+static int mt7530_mac_port_setup(struct gsw_mt753x *gsw)
+{
+	u32 hwstrap, p6ecr = 0, p5mcr, p6mcr, phyad = 0;
+
+	hwstrap = mt753x_reg_read(gsw, MHWSTRAP);
+	hwstrap &= ~(P6_INTF_DIS | P5_INTF_MODE_RGMII | P5_INTF_DIS_S);
+	hwstrap |= P5_INTF_SEL_GMAC5;
+	if (!gsw->port5_cfg.enabled) {
+		p5mcr = FORCE_MODE;
+		hwstrap |= P5_INTF_DIS_S;
+	} else {
+		p5mcr = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) |
+			MAC_MODE | MAC_TX_EN | MAC_RX_EN |
+			BKOFF_EN | BACKPR_EN;
+
+		if (gsw->port5_cfg.force_link) {
+			p5mcr |= FORCE_MODE | FORCE_LINK | FORCE_RX_FC |
+				 FORCE_TX_FC;
+			p5mcr |= gsw->port5_cfg.speed << FORCE_SPD_S;
+
+			if (gsw->port5_cfg.duplex)
+				p5mcr |= FORCE_DPX;
+		}
+
+		switch (gsw->port5_cfg.phy_mode) {
+		case PHY_INTERFACE_MODE_MII:
+		case PHY_INTERFACE_MODE_GMII:
+			break;
+		case PHY_INTERFACE_MODE_RGMII:
+			hwstrap |= P5_INTF_MODE_RGMII;
+			break;
+		default:
+			dev_info(gsw->dev, "%s is not supported by port5\n",
+				 phy_modes(gsw->port5_cfg.phy_mode));
+			p5mcr = FORCE_MODE;
+			hwstrap |= P5_INTF_DIS_S;
+		}
+
+		/* Port5 to PHY direct mode */
+		if (of_property_read_u32(gsw->port5_cfg.np, "phy-address",
+					 &phyad))
+			goto parse_p6;
+
+		if (phyad != 0 && phyad != 4) {
+			dev_info(gsw->dev,
+				 "Only PHY 0/4 can be connected to Port 5\n");
+			goto parse_p6;
+		}
+
+		hwstrap &= ~P5_INTF_SEL_GMAC5;
+		if (phyad == 0)
+			hwstrap |= P5_PHY0_SEL;
+		else
+			hwstrap &= ~P5_PHY0_SEL;
+	}
+
+parse_p6:
+	if (!gsw->port6_cfg.enabled) {
+		p6mcr = FORCE_MODE;
+		hwstrap |= P6_INTF_DIS;
+	} else {
+		p6mcr = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) |
+			MAC_MODE | MAC_TX_EN | MAC_RX_EN |
+			BKOFF_EN | BACKPR_EN;
+
+		if (gsw->port6_cfg.force_link) {
+			p6mcr |= FORCE_MODE | FORCE_LINK | FORCE_RX_FC |
+				 FORCE_TX_FC;
+			p6mcr |= gsw->port6_cfg.speed << FORCE_SPD_S;
+
+			if (gsw->port6_cfg.duplex)
+				p6mcr |= FORCE_DPX;
+		}
+
+		switch (gsw->port6_cfg.phy_mode) {
+		case PHY_INTERFACE_MODE_RGMII:
+			p6ecr = BIT(1);
+			break;
+		case PHY_INTERFACE_MODE_TRGMII:
+			/* set MT7530 central align */
+			p6ecr = BIT(0);
+			break;
+		default:
+			dev_info(gsw->dev, "%s is not supported by port6\n",
+				 phy_modes(gsw->port6_cfg.phy_mode));
+			p6mcr = FORCE_MODE;
+			hwstrap |= P6_INTF_DIS;
+		}
+	}
+
+	mt753x_reg_write(gsw, MHWSTRAP, hwstrap);
+	mt753x_reg_write(gsw, P6ECR, p6ecr);
+
+	mt753x_reg_write(gsw, PMCR(5), p5mcr);
+	mt753x_reg_write(gsw, PMCR(6), p6mcr);
+
+	return 0;
+}
+
+static void mt7530_core_pll_setup(struct gsw_mt753x *gsw)
+{
+	u32 hwstrap;
+
+	hwstrap = mt753x_reg_read(gsw, HWSTRAP);
+
+	switch ((hwstrap & XTAL_FSEL_M) >> XTAL_FSEL_S) {
+	case XTAL_40MHZ:
+		/* Disable MT7530 core clock */
+		mt7530_core_reg_write(gsw, TRGMII_GSW_CLK_CG, 0);
+
+		/* disable MT7530 PLL */
+		mt7530_core_reg_write(gsw, CORE_GSWPLL_GCR_1,
+				      (2 << GSWPLL_POSTDIV_200M_S) |
+				      (32 << GSWPLL_FBKDIV_200M_S));
+
+		/* For MT7530 core clock = 500Mhz */
+		mt7530_core_reg_write(gsw, CORE_GSWPLL_GCR_2,
+				      (1 << GSWPLL_POSTDIV_500M_S) |
+				      (25 << GSWPLL_FBKDIV_500M_S));
+
+		/* Enable MT7530 PLL */
+		mt7530_core_reg_write(gsw, CORE_GSWPLL_GCR_1,
+				      (2 << GSWPLL_POSTDIV_200M_S) |
+				      (32 << GSWPLL_FBKDIV_200M_S) |
+				      GSWPLL_EN_PRE);
+
+		usleep_range(20, 40);
+
+		/* Enable MT7530 core clock */
+		mt7530_core_reg_write(gsw, TRGMII_GSW_CLK_CG, GSWCK_EN);
+		break;
+	default:
+		/* TODO: PLL settings for 20/25MHz */
+		break;
+	}
+
+	hwstrap = mt753x_reg_read(gsw, HWSTRAP);
+	hwstrap |= CHG_TRAP;
+	if (gsw->direct_phy_access)
+		hwstrap &= ~C_MDIO_BPS_S;
+	else
+		hwstrap |= C_MDIO_BPS_S;
+
+	mt753x_reg_write(gsw, MHWSTRAP, hwstrap);
+
+	if (gsw->port6_cfg.enabled &&
+	    gsw->port6_cfg.phy_mode == PHY_INTERFACE_MODE_TRGMII) {
+		mt7530_trgmii_setting(gsw);
+	} else {
+		/* RGMII */
+		mt7530_rgmii_setting(gsw);
+	}
+
+	/* delay setting for 10/1000M */
+	mt753x_reg_write(gsw, P5RGMIIRXCR,
+			 CSR_RGMII_EDGE_ALIGN |
+			 (2 << CSR_RGMII_RXC_0DEG_CFG_S));
+	mt753x_reg_write(gsw, P5RGMIITXCR, 0x14 << CSR_RGMII_TXC_CFG_S);
+}
+
+static int mt7530_sw_detect(struct gsw_mt753x *gsw, struct chip_rev *crev)
+{
+	u32 rev;
+
+	rev = mt753x_reg_read(gsw, CHIP_REV);
+
+	if (((rev & CHIP_NAME_M) >> CHIP_NAME_S) == MT7530) {
+		if (crev) {
+			crev->rev = rev & CHIP_REV_M;
+			crev->name = "MT7530";
+		}
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void mt7530_phy_setting(struct gsw_mt753x *gsw)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		/* Disable EEE */
+		gsw->mmd_write(gsw, i, PHY_DEV07, PHY_DEV07_REG_03C, 0);
+
+		/* Enable HW auto downshift */
+		gsw->mii_write(gsw, i, 0x1f, 0x1);
+		val = gsw->mii_read(gsw, i, PHY_EXT_REG_14);
+		val |= PHY_EN_DOWN_SHFIT;
+		gsw->mii_write(gsw, i, PHY_EXT_REG_14, val);
+
+		/* Increase SlvDPSready time */
+		gsw->mii_write(gsw, i, 0x1f, 0x52b5);
+		gsw->mii_write(gsw, i, PHY_TR_REG_10, 0xafae);
+		gsw->mii_write(gsw, i, PHY_TR_REG_12, 0x2f);
+		gsw->mii_write(gsw, i, PHY_TR_REG_10, 0x8fae);
+
+		/* Increase post_update_timer */
+		gsw->mii_write(gsw, i, 0x1f, 0x3);
+		gsw->mii_write(gsw, i, PHY_LPI_REG_11, 0x4b);
+		gsw->mii_write(gsw, i, 0x1f, 0);
+
+		/* Adjust 100_mse_threshold */
+		gsw->mmd_write(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_123, 0xffff);
+
+		/* Disable mcc */
+		gsw->mmd_write(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_A6, 0x300);
+	}
+}
+
+static inline bool get_phy_access_mode(const struct device_node *np)
+{
+	return of_property_read_bool(np, "mt7530,direct-phy-access");
+}
+
+static int mt7530_sw_init(struct gsw_mt753x *gsw)
+{
+	int i;
+	u32 val;
+
+	gsw->direct_phy_access = get_phy_access_mode(gsw->dev->of_node);
+
+	/* Force MT7530 to use (in)direct PHY access */
+	val = mt753x_reg_read(gsw, HWSTRAP);
+	val |= CHG_TRAP;
+	if (gsw->direct_phy_access)
+		val &= ~C_MDIO_BPS_S;
+	else
+		val |= C_MDIO_BPS_S;
+	mt753x_reg_write(gsw, MHWSTRAP, val);
+
+	/* Read PHY address base from HWSTRAP */
+	gsw->phy_base  = (((val & SMI_ADDR_M) >> SMI_ADDR_S) << 3) + 8;
+	gsw->phy_base &= MT753X_SMI_ADDR_MASK;
+
+	if (gsw->direct_phy_access) {
+		gsw->mii_read = mt7530_mii_read;
+		gsw->mii_write = mt7530_mii_write;
+		gsw->mmd_read = mt7530_mmd_read;
+		gsw->mmd_write = mt7530_mmd_write;
+	} else {
+		gsw->mii_read = mt753x_mii_read;
+		gsw->mii_write = mt753x_mii_write;
+		gsw->mmd_read = mt753x_mmd_ind_read;
+		gsw->mmd_write = mt753x_mmd_ind_write;
+	}
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMCR);
+		val |= BMCR_PDOWN;
+		gsw->mii_write(gsw, i, MII_BMCR, val);
+	}
+
+	/* Force MAC link down before reset */
+	mt753x_reg_write(gsw, PMCR(5), FORCE_MODE);
+	mt753x_reg_write(gsw, PMCR(6), FORCE_MODE);
+
+	/* Switch soft reset */
+	/* BUG: sw reset causes gsw int flooding */
+	mt753x_reg_write(gsw, SYS_CTRL, SW_PHY_RST | SW_SYS_RST | SW_REG_RST);
+	usleep_range(10, 20);
+
+	/* global mac control settings configuration */
+	mt753x_reg_write(gsw, GMACCR,
+			 LATE_COL_DROP | (15 << MTCC_LMT_S) |
+			 (2 << MAX_RX_JUMBO_S) | RX_PKT_LEN_MAX_JUMBO);
+
+	/* Output INTR selected */
+	val = mt753x_reg_read(gsw, TOP_SIG_CTRL);
+	val &= ~OUTPUT_INTR_M;
+	val |= (3 << OUTPUT_INTR_S);
+	mt753x_reg_write(gsw, TOP_SIG_CTRL, val);
+
+	mt7530_core_pll_setup(gsw);
+	mt7530_mac_port_setup(gsw);
+
+	return 0;
+}
+
+static int mt7530_sw_post_init(struct gsw_mt753x *gsw)
+{
+	int i;
+	u32 val;
+
+	mt7530_phy_setting(gsw);
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMCR);
+		val &= ~BMCR_PDOWN;
+		gsw->mii_write(gsw, i, MII_BMCR, val);
+	}
+
+	return 0;
+}
+
+struct mt753x_sw_id mt7530_id = {
+	.model = MT7530,
+	.detect = mt7530_sw_detect,
+	.init = mt7530_sw_init,
+	.post_init = mt7530_sw_post_init
+};
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7530.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7530.h
new file mode 100644
index 0000000..ef2d684
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7530.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ */
+
+#ifndef _MT7530_H_
+#define _MT7530_H_
+
+#include "mt753x.h"
+
+extern struct mt753x_sw_id mt7530_id;
+
+#endif /* _MT7530_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7531.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7531.c
new file mode 100755
index 0000000..7253042
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7531.c
@@ -0,0 +1,1058 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Zhanguo Ju <zhanguo.ju@mediatek.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+
+#include "mt753x.h"
+#include "mt753x_regs.h"
+
+/* MT7531 registers */
+#define SGMII_REG_BASE			0x5000
+#define SGMII_REG_PORT_BASE		0x1000
+#define SGMII_REG(p, r)			(SGMII_REG_BASE + \
+					(p) * SGMII_REG_PORT_BASE + (r))
+#define PCS_CONTROL_1(p)		SGMII_REG(p, 0x00)
+#define SGMII_MODE(p)			SGMII_REG(p, 0x20)
+#define QPHY_PWR_STATE_CTRL(p)		SGMII_REG(p, 0xe8)
+#define ANA_CKBG(p)			SGMII_REG(p, 0x100)
+#define ANA_DA_FORCE_MODE1(p)		SGMII_REG(p, 0x110)
+#define PHYA_CTRL_SIGNAL3(p)		SGMII_REG(p, 0x128)
+#define PHYA_ANA_SYSPLL(p)		SGMII_REG(p, 0x158)
+
+/* Fields of PCS_CONTROL_1 */
+#define SGMII_LINK_STATUS		BIT(18)
+#define SGMII_AN_ENABLE			BIT(12)
+#define SGMII_AN_RESTART		BIT(9)
+
+/* Fields of SGMII_MODE */
+#define SGMII_REMOTE_FAULT_DIS		BIT(8)
+#define SGMII_IF_MODE_FORCE_DUPLEX	BIT(4)
+#define SGMII_IF_MODE_FORCE_SPEED_S	0x2
+#define SGMII_IF_MODE_FORCE_SPEED_M	0x0c
+#define SGMII_IF_MODE_ADVERT_AN		BIT(1)
+
+/* Values of SGMII_IF_MODE_FORCE_SPEED */
+#define SGMII_IF_MODE_FORCE_SPEED_10	0
+#define SGMII_IF_MODE_FORCE_SPEED_100	1
+#define SGMII_IF_MODE_FORCE_SPEED_1000	2
+
+/* Fields of QPHY_PWR_STATE_CTRL */
+#define PHYA_PWD			BIT(4)
+
+/* Fields of ANA_CKBG */
+#define SSUSB_PLL_SSC_EN		BIT(21)
+
+/* Fields of ANA_DA_FORCE_MODE1 */
+#define FORCE_PLL_SSC_EN		BIT(30)
+
+/* Fields of PHYA_CTRL_SIGNAL3 */
+#define RG_TPHY_SPEED_S			2
+#define RG_TPHY_SPEED_M			0x0c
+
+/* Values of RG_TPHY_SPEED */
+#define RG_TPHY_SPEED_1000		0
+#define RG_TPHY_SPEED_2500		1
+
+/* Fields of PHYA_ANA_SYSPLL */
+#define RG_VUSB10_ON			BIT(29)
+
+/* Unique fields of (M)HWSTRAP for MT7531 */
+#define XTAL_FSEL_S			7
+#define XTAL_FSEL_M			BIT(7)
+#define PHY_EN				BIT(6)
+#define CHG_STRAP			BIT(8)
+
+/* Efuse Register Define */
+#define GBE_EFUSE			0x7bc8
+#define GBE_SEL_EFUSE_EN		BIT(0)
+
+/* PHY ENABLE Register bitmap define */
+#define PHY_DEV1F			0x1f
+#define PHY_DEV1F_REG_44		0x44
+#define PHY_DEV1F_REG_104		0x104
+#define PHY_DEV1F_REG_10A		0x10a
+#define PHY_DEV1F_REG_10B		0x10b
+#define PHY_DEV1F_REG_10C		0x10c
+#define PHY_DEV1F_REG_10D		0x10d
+#define PHY_DEV1F_REG_268		0x268
+#define PHY_DEV1F_REG_269		0x269
+#define PHY_DEV1F_REG_26A		0x26A
+#define PHY_DEV1F_REG_403		0x403
+
+/* Fields of PHY_DEV1F_REG_403 */
+#define GBE_EFUSE_SETTING		BIT(3)
+#define PHY_EN_BYPASS_MODE		BIT(4)
+#define POWER_ON_OFF			BIT(5)
+#define PHY_PLL_M			GENMASK(9, 8)
+#define PHY_PLL_SEL(x)			(((x) << 8) & GENMASK(9, 8))
+
+/* PHY EEE Register bitmap of define */
+#define PHY_DEV07			0x07
+#define PHY_DEV07_REG_03C		0x3c
+
+/* PHY Extend Register 0x14 bitmap of define */
+#define PHY_EXT_REG_14			0x14
+
+/* Fields of PHY_EXT_REG_14 */
+#define PHY_EN_DOWN_SHFIT		BIT(4)
+
+/* PHY Extend Register 0x17 bitmap of define */
+#define PHY_EXT_REG_17			0x17
+
+/* Fields of PHY_EXT_REG_17 */
+#define PHY_LINKDOWN_POWER_SAVING_EN	BIT(4)
+
+/* PHY PMA Register 0x17 bitmap of define */
+#define SLV_DSP_READY_TIME_S		15
+#define SLV_DSP_READY_TIME_M		(0xff << SLV_DSP_READY_TIME_S)
+
+/* PHY PMA Register 0x18 bitmap of define */
+#define ENABLE_RANDOM_UPDATE_TRIGGER	BIT(8)
+
+/* PHY DEV 0x1e Register bitmap of define */
+#define PHY_DEV1E			0x1e
+#define PHY_TX_MLT3_BASE		0x0
+#define PHY_DEV1E_REG_13		0x13
+#define PHY_DEV1E_REG_14		0x14
+#define PHY_DEV1E_REG_41		0x41
+#define PHY_DEV1E_REG_A6		0xa6
+#define PHY_DEV1E_REG_0C6		0x0c6
+#define PHY_DEV1E_REG_0FE		0x0fe
+#define PHY_DEV1E_REG_123		0x123
+#define PHY_DEV1E_REG_141		0x141
+#define PHY_DEV1E_REG_189		0x189
+#define PHY_DEV1E_REG_234		0x234
+
+/* Fields of PHY_DEV1E_REG_0C6 */
+#define PHY_POWER_SAVING_S		8
+#define PHY_POWER_SAVING_M		0x300
+#define PHY_POWER_SAVING_TX		0x0
+
+/* Fields of PHY_DEV1E_REG_189 */
+#define DESCRAMBLER_CLEAR_EN		0x1
+
+/* Fields of PHY_DEV1E_REG_234 */
+#define TR_OPEN_LOOP_EN			BIT(0)
+
+/* Port debug count register */
+#define DBG_CNT_BASE			0x3018
+#define DBG_CNT_PORT_BASE		0x100
+#define DBG_CNT(p)			(DBG_CNT_BASE + \
+					(p) * DBG_CNT_PORT_BASE)
+#define DIS_CLR				BIT(31)
+
+/* Values of XTAL_FSEL_S */
+#define XTAL_40MHZ			0
+#define XTAL_25MHZ			1
+
+#define PLLGP_EN			0x7820
+#define EN_COREPLL			BIT(2)
+#define SW_CLKSW			BIT(1)
+#define SW_PLLGP			BIT(0)
+
+#define PLLGP_CR0			0x78a8
+#define RG_COREPLL_EN			BIT(22)
+#define RG_COREPLL_POSDIV_S		23
+#define RG_COREPLL_POSDIV_M		0x3800000
+#define RG_COREPLL_SDM_PCW_S		1
+#define RG_COREPLL_SDM_PCW_M		0x3ffffe
+#define RG_COREPLL_SDM_PCW_CHG		BIT(0)
+
+/* TOP Signals Status Register */
+#define TOP_SIG_SR			0x780c
+#define PAD_MCM_SMI_EN			BIT(0)
+#define PAD_DUAL_SGMII_EN		BIT(1)
+
+/* RGMII and SGMII PLL clock */
+#define ANA_PLLGP_CR2			0x78b0
+#define ANA_PLLGP_CR5			0x78bc
+
+/* GPIO mode define */
+#define GPIO_MODE_REGS(x)		(0x7c0c + (((x) / 8) * 4))
+#define GPIO_MODE_S			4
+
+/* GPIO GROUP IOLB SMT0 Control */
+#define SMT0_IOLB			0x7f04
+#define SMT_IOLB_5_SMI_MDC_EN		BIT(5)
+
+/* Unique fields of PMCR for MT7531 */
+#define FORCE_MODE_EEE1G		BIT(25)
+#define FORCE_MODE_EEE100		BIT(26)
+#define FORCE_MODE_TX_FC		BIT(27)
+#define FORCE_MODE_RX_FC		BIT(28)
+#define FORCE_MODE_DPX			BIT(29)
+#define FORCE_MODE_SPD			BIT(30)
+#define FORCE_MODE_LNK			BIT(31)
+#define FORCE_MODE			BIT(15)
+
+#define CHIP_REV			0x781C
+#define CHIP_NAME_S			16
+#define CHIP_NAME_M			0xffff0000
+#define CHIP_REV_S			0
+#define CHIP_REV_M			0x0f
+#define CHIP_REV_E1			0x0
+
+#define CLKGEN_CTRL			0x7500
+#define CLK_SKEW_OUT_S			8
+#define CLK_SKEW_OUT_M			0x300
+#define CLK_SKEW_IN_S			6
+#define CLK_SKEW_IN_M			0xc0
+#define RXCLK_NO_DELAY			BIT(5)
+#define TXCLK_NO_REVERSE		BIT(4)
+#define GP_MODE_S			1
+#define GP_MODE_M			0x06
+#define GP_CLK_EN			BIT(0)
+
+#define CPGC_CTRL			0xB0
+#define COL_EN				BIT(0)
+#define COL_CLK_EN			BIT(1)
+#define COL_RST_N			BIT(2)
+#define COL_BUSY			BIT(3)
+
+/* Values of GP_MODE */
+#define GP_MODE_RGMII			0
+#define GP_MODE_MII			1
+#define GP_MODE_REV_MII			2
+
+/* Values of CLK_SKEW_IN */
+#define CLK_SKEW_IN_NO_CHANGE		0
+#define CLK_SKEW_IN_DELAY_100PPS	1
+#define CLK_SKEW_IN_DELAY_200PPS	2
+#define CLK_SKEW_IN_REVERSE		3
+
+/* Values of CLK_SKEW_OUT */
+#define CLK_SKEW_OUT_NO_CHANGE		0
+#define CLK_SKEW_OUT_DELAY_100PPS	1
+#define CLK_SKEW_OUT_DELAY_200PPS	2
+#define CLK_SKEW_OUT_REVERSE		3
+
+/* Proprietory Control Register of Internal Phy device 0x1e */
+#define RXADC_CONTROL_3			0xc2
+#define RXADC_LDO_CONTROL_2		0xd3
+
+/* Proprietory Control Register of Internal Phy device 0x1f */
+#define TXVLD_DA_271			0x271
+#define TXVLD_DA_272			0x272
+#define TXVLD_DA_273			0x273
+
+/* gpio pinmux pins and functions define */
+static int gpio_int_pins[] = {0};
+static int gpio_int_funcs[] = {1};
+static int gpio_mdc_pins[] = {11, 20};
+static int gpio_mdc_funcs[] = {2, 2};
+static int gpio_mdio_pins[] = {12, 21};
+static int gpio_mdio_funcs[] = {2, 2};
+
+static int mt7531_set_port_sgmii_force_mode(struct gsw_mt753x *gsw, u32 port,
+					    struct mt753x_port_cfg *port_cfg)
+{
+	u32 speed, port_base, val;
+	ktime_t timeout;
+	u32 timeout_us;
+
+	if (port < 5 || port >= MT753X_NUM_PORTS) {
+		dev_info(gsw->dev, "port %d is not a SGMII port\n", port);
+		return -EINVAL;
+	}
+
+	port_base = port - 5;
+
+	switch (port_cfg->speed) {
+	case MAC_SPD_1000:
+		speed = RG_TPHY_SPEED_1000;
+		break;
+	case MAC_SPD_2500:
+		speed = RG_TPHY_SPEED_2500;
+		break;
+	default:
+		dev_info(gsw->dev, "invalid SGMII speed idx %d for port %d\n",
+			 port_cfg->speed, port);
+
+		speed = RG_TPHY_SPEED_1000;
+	}
+
+	/* Step 1: Speed select register setting */
+	val = mt753x_reg_read(gsw, PHYA_CTRL_SIGNAL3(port_base));
+	val &= ~RG_TPHY_SPEED_M;
+	val |= speed << RG_TPHY_SPEED_S;
+	mt753x_reg_write(gsw, PHYA_CTRL_SIGNAL3(port_base), val);
+
+	/* Step 2 : Disable AN */
+	val = mt753x_reg_read(gsw, PCS_CONTROL_1(port_base));
+	val &= ~SGMII_AN_ENABLE;
+	mt753x_reg_write(gsw, PCS_CONTROL_1(port_base), val);
+
+	/* Step 3: SGMII force mode setting */
+	val = mt753x_reg_read(gsw, SGMII_MODE(port_base));
+	val &= ~SGMII_IF_MODE_ADVERT_AN;
+	val &= ~SGMII_IF_MODE_FORCE_SPEED_M;
+	val |= SGMII_IF_MODE_FORCE_SPEED_1000 << SGMII_IF_MODE_FORCE_SPEED_S;
+	val |= SGMII_IF_MODE_FORCE_DUPLEX;
+	/* For sgmii force mode, 0 is full duplex and 1 is half duplex */
+	if (port_cfg->duplex)
+		val &= ~SGMII_IF_MODE_FORCE_DUPLEX;
+
+	mt753x_reg_write(gsw, SGMII_MODE(port_base), val);
+
+	/* Step 4: XXX: Disable Link partner's AN and set force mode */
+
+	/* Step 5: XXX: Special setting for PHYA ==> reserved for flexible */
+
+	/* Step 6 : Release PHYA power down state */
+	val = mt753x_reg_read(gsw, QPHY_PWR_STATE_CTRL(port_base));
+	val &= ~PHYA_PWD;
+	mt753x_reg_write(gsw, QPHY_PWR_STATE_CTRL(port_base), val);
+
+	/* Step 7 : Polling SGMII_LINK_STATUS */
+	timeout_us = 2000000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = mt753x_reg_read(gsw, PCS_CONTROL_1(port_base));
+		val &= SGMII_LINK_STATUS;
+
+		if (val)
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0)
+			return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int mt7531_set_port_sgmii_an_mode(struct gsw_mt753x *gsw, u32 port,
+					 struct mt753x_port_cfg *port_cfg)
+{
+	u32 speed, port_base, val;
+	ktime_t timeout;
+	u32 timeout_us;
+
+	if (port < 5 || port >= MT753X_NUM_PORTS) {
+		dev_info(gsw->dev, "port %d is not a SGMII port\n", port);
+		return -EINVAL;
+	}
+
+	port_base = port - 5;
+
+	switch (port_cfg->speed) {
+	case MAC_SPD_1000:
+		speed = RG_TPHY_SPEED_1000;
+		break;
+	case MAC_SPD_2500:
+		speed = RG_TPHY_SPEED_2500;
+		break;
+	default:
+		dev_info(gsw->dev, "invalid SGMII speed idx %d for port %d\n",
+			 port_cfg->speed, port);
+
+		speed = RG_TPHY_SPEED_1000;
+	}
+
+	/* Step 1: Speed select register setting */
+	val = mt753x_reg_read(gsw, PHYA_CTRL_SIGNAL3(port_base));
+	val &= ~RG_TPHY_SPEED_M;
+	val |= speed << RG_TPHY_SPEED_S;
+	mt753x_reg_write(gsw, PHYA_CTRL_SIGNAL3(port_base), val);
+
+	/* Step 2: Remote fault disable */
+	val = mt753x_reg_read(gsw, SGMII_MODE(port));
+	val |= SGMII_REMOTE_FAULT_DIS;
+	mt753x_reg_write(gsw, SGMII_MODE(port), val);
+
+	/* Step 3: Setting Link partner's AN enable = 1 */
+
+	/* Step 4: Setting Link partner's device ability for speed/duplex */
+
+	/* Step 5: AN re-start */
+	val = mt753x_reg_read(gsw, PCS_CONTROL_1(port));
+	val |= SGMII_AN_RESTART;
+	mt753x_reg_write(gsw, PCS_CONTROL_1(port), val);
+
+	/* Step 6: Special setting for PHYA ==> reserved for flexible */
+
+	/* Step 7 : Polling SGMII_LINK_STATUS */
+	timeout_us = 2000000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = mt753x_reg_read(gsw, PCS_CONTROL_1(port_base));
+		val &= SGMII_LINK_STATUS;
+
+		if (val)
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0)
+			return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static void mt7531_sgmii_ssc(struct gsw_mt753x *gsw, u32 port, int enable)
+{
+	u32 val;
+	u32 port_base = port - 5;
+
+	if (enable) {
+		val = mt753x_reg_read(gsw, ANA_CKBG(port_base));
+		val |= SSUSB_PLL_SSC_EN;
+		mt753x_reg_write(gsw, ANA_CKBG(port_base), val);
+
+		val = mt753x_reg_read(gsw, ANA_DA_FORCE_MODE1(port_base));
+		val |= FORCE_PLL_SSC_EN;
+		mt753x_reg_write(gsw, ANA_DA_FORCE_MODE1(port_base), val);
+	} else {
+		val = mt753x_reg_read(gsw, ANA_CKBG(port_base));
+		val &= ~SSUSB_PLL_SSC_EN;
+		mt753x_reg_write(gsw, ANA_CKBG(port_base), val);
+
+		val = mt753x_reg_read(gsw, ANA_DA_FORCE_MODE1(port_base));
+		val &= ~FORCE_PLL_SSC_EN;
+		mt753x_reg_write(gsw, ANA_DA_FORCE_MODE1(port_base), val);
+	}
+}
+
+static int mt7531_set_port_rgmii(struct gsw_mt753x *gsw, u32 port)
+{
+	u32 val;
+
+	if (port != 5) {
+		dev_info(gsw->dev, "RGMII mode is not available for port %d\n",
+			 port);
+		return -EINVAL;
+	}
+
+	val = mt753x_reg_read(gsw, CLKGEN_CTRL);
+	val |= GP_CLK_EN;
+	val &= ~GP_MODE_M;
+	val |= GP_MODE_RGMII << GP_MODE_S;
+	val |= TXCLK_NO_REVERSE;
+	val |= RXCLK_NO_DELAY;
+	val &= ~CLK_SKEW_IN_M;
+	val |= CLK_SKEW_IN_NO_CHANGE << CLK_SKEW_IN_S;
+	val &= ~CLK_SKEW_OUT_M;
+	val |= CLK_SKEW_OUT_NO_CHANGE << CLK_SKEW_OUT_S;
+	mt753x_reg_write(gsw, CLKGEN_CTRL, val);
+
+	return 0;
+}
+
+static int mt7531_mac_port_setup(struct gsw_mt753x *gsw, u32 port,
+				 struct mt753x_port_cfg *port_cfg)
+{
+	u32 pmcr;
+	u32 speed;
+
+	if (port < 5 || port >= MT753X_NUM_PORTS) {
+		dev_info(gsw->dev, "port %d is not a MAC port\n", port);
+		return -EINVAL;
+	}
+
+	if (port_cfg->enabled) {
+		pmcr = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) |
+		       MAC_MODE | MAC_TX_EN | MAC_RX_EN |
+		       BKOFF_EN | BACKPR_EN;
+
+		if (port_cfg->force_link) {
+			/* PMCR's speed field 0x11 is reserved,
+			 * sw should set 0x10
+			 */
+			speed = port_cfg->speed;
+			if (port_cfg->speed == MAC_SPD_2500)
+				speed = MAC_SPD_1000;
+
+			pmcr |= FORCE_MODE_LNK | FORCE_LINK |
+				FORCE_MODE_SPD | FORCE_MODE_DPX |
+				FORCE_MODE_RX_FC | FORCE_MODE_TX_FC |
+				FORCE_RX_FC | FORCE_TX_FC |
+				(speed << FORCE_SPD_S);
+
+			if (port_cfg->duplex)
+				pmcr |= FORCE_DPX;
+		}
+	} else {
+		pmcr = FORCE_MODE_LNK;
+	}
+
+	switch (port_cfg->phy_mode) {
+	case PHY_INTERFACE_MODE_RGMII:
+		mt7531_set_port_rgmii(gsw, port);
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+		if (port_cfg->force_link)
+			mt7531_set_port_sgmii_force_mode(gsw, port, port_cfg);
+		else
+			mt7531_set_port_sgmii_an_mode(gsw, port, port_cfg);
+
+		mt7531_sgmii_ssc(gsw, port, port_cfg->ssc_on);
+		break;
+	default:
+		if (port_cfg->enabled)
+			dev_info(gsw->dev, "%s is not supported by port %d\n",
+				 phy_modes(port_cfg->phy_mode), port);
+
+		pmcr = FORCE_MODE_LNK;
+	}
+
+	mt753x_reg_write(gsw, PMCR(port), pmcr);
+
+	return 0;
+}
+
+static void mt7531_core_pll_setup(struct gsw_mt753x *gsw)
+{
+	u32 val;
+	u32 top_sig;
+	u32 hwstrap;
+	u32 xtal;
+
+	val = mt753x_reg_read(gsw, CHIP_REV);
+	top_sig = mt753x_reg_read(gsw, TOP_SIG_SR);
+	hwstrap = mt753x_reg_read(gsw, HWSTRAP);
+	if ((val & CHIP_REV_M) > 0)
+		xtal = (top_sig & PAD_MCM_SMI_EN) ? XTAL_40MHZ : XTAL_25MHZ;
+	else
+		xtal = (hwstrap & XTAL_FSEL_M) >> XTAL_FSEL_S;
+
+	/* dump HW strap and XTAL */
+	dev_info(gsw->dev, "HWSTRAP=0x%x XTAL=%dMHz\n", hwstrap,
+		 (xtal == XTAL_25MHZ) ? 25 : 40);
+
+	/* Only BE needs additional setting */
+	if (top_sig & PAD_DUAL_SGMII_EN)
+		return;
+
+	/* Disable Port5 SGMII clearly */
+	val = mt753x_reg_read(gsw, PHYA_ANA_SYSPLL(0));
+	val &= ~RG_VUSB10_ON;
+	mt753x_reg_write(gsw, PHYA_ANA_SYSPLL(0), val);
+
+	switch (xtal) {
+	case XTAL_25MHZ:
+		/* Step 1 : Disable MT7531 COREPLL */
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val &= ~EN_COREPLL;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+
+		/* Step 2: switch to XTAL output */
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val |= SW_CLKSW;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_EN;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		/* Step 3: disable PLLGP and enable program PLLGP */
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val |= SW_PLLGP;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+
+		/* Step 4: program COREPLL output frequency to 500MHz */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_POSDIV_M;
+		val |= 2 << RG_COREPLL_POSDIV_S;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+		usleep_range(25, 35);
+
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_SDM_PCW_M;
+		val |= 0x140000 << RG_COREPLL_SDM_PCW_S;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		/* Set feedback divide ratio update signal to high */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val |= RG_COREPLL_SDM_PCW_CHG;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+		/* Wait for at least 16 XTAL clocks */
+		usleep_range(10, 20);
+
+		/* Step 5: set feedback divide ratio update signal to low */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_SDM_PCW_CHG;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		/* Enable 325M clock for SGMII */
+		mt753x_reg_write(gsw, ANA_PLLGP_CR5, 0xad0000);
+
+		/* Enable 250SSC clock for RGMII */
+		mt753x_reg_write(gsw, ANA_PLLGP_CR2, 0x4f40000);
+
+		/* Step 6: Enable MT7531 PLL */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val |= RG_COREPLL_EN;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val |= EN_COREPLL;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+		usleep_range(25, 35);
+
+		break;
+	case XTAL_40MHZ:
+		/* Step 1 : Disable MT7531 COREPLL */
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val &= ~EN_COREPLL;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+
+		/* Step 2: switch to XTAL output */
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val |= SW_CLKSW;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_EN;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		/* Step 3: disable PLLGP and enable program PLLGP */
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val |= SW_PLLGP;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+
+		/* Step 4: program COREPLL output frequency to 500MHz */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_POSDIV_M;
+		val |= 2 << RG_COREPLL_POSDIV_S;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+		usleep_range(25, 35);
+
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_SDM_PCW_M;
+		val |= 0x190000 << RG_COREPLL_SDM_PCW_S;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		/* Set feedback divide ratio update signal to high */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val |= RG_COREPLL_SDM_PCW_CHG;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+		/* Wait for at least 16 XTAL clocks */
+		usleep_range(10, 20);
+
+		/* Step 5: set feedback divide ratio update signal to low */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val &= ~RG_COREPLL_SDM_PCW_CHG;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		/* Enable 325M clock for SGMII */
+		mt753x_reg_write(gsw, ANA_PLLGP_CR5, 0xad0000);
+
+		/* Enable 250SSC clock for RGMII */
+		mt753x_reg_write(gsw, ANA_PLLGP_CR2, 0x4f40000);
+
+		/* Step 6: Enable MT7531 PLL */
+		val = mt753x_reg_read(gsw, PLLGP_CR0);
+		val |= RG_COREPLL_EN;
+		mt753x_reg_write(gsw, PLLGP_CR0, val);
+
+		val = mt753x_reg_read(gsw, PLLGP_EN);
+		val |= EN_COREPLL;
+		mt753x_reg_write(gsw, PLLGP_EN, val);
+		usleep_range(25, 35);
+		break;
+	}
+}
+
+static int mt7531_internal_phy_calibration(struct gsw_mt753x *gsw)
+{
+	return 0;
+}
+
+static int mt7531_sw_detect(struct gsw_mt753x *gsw, struct chip_rev *crev)
+{
+	u32 rev, topsig;
+
+	rev = mt753x_reg_read(gsw, CHIP_REV);
+
+	if (((rev & CHIP_NAME_M) >> CHIP_NAME_S) == MT7531) {
+		if (crev) {
+			topsig = mt753x_reg_read(gsw, TOP_SIG_SR);
+
+			crev->rev = rev & CHIP_REV_M;
+			crev->name = topsig & PAD_DUAL_SGMII_EN ?
+				     "MT7531AE" : "MT7531BE";
+		}
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void pinmux_set_mux_7531(struct gsw_mt753x *gsw, u32 pin, u32 mode)
+{
+	u32 val;
+
+	val = mt753x_reg_read(gsw, GPIO_MODE_REGS(pin));
+	val &= ~(0xf << (pin & 7) * GPIO_MODE_S);
+	val |= mode << (pin & 7) * GPIO_MODE_S;
+	mt753x_reg_write(gsw, GPIO_MODE_REGS(pin), val);
+}
+
+static int mt7531_set_gpio_pinmux(struct gsw_mt753x *gsw)
+{
+	u32 group = 0;
+	struct device_node *np = gsw->dev->of_node;
+
+	/* Set GPIO 0 interrupt mode */
+	pinmux_set_mux_7531(gsw, gpio_int_pins[0], gpio_int_funcs[0]);
+
+	of_property_read_u32(np, "mediatek,mdio_master_pinmux", &group);
+
+	/* group = 0: do nothing, 1: 1st group (AE), 2: 2nd group (BE) */
+	if (group > 0 && group <= 2) {
+		group--;
+		pinmux_set_mux_7531(gsw, gpio_mdc_pins[group],
+				    gpio_mdc_funcs[group]);
+		pinmux_set_mux_7531(gsw, gpio_mdio_pins[group],
+				    gpio_mdio_funcs[group]);
+	}
+
+	return 0;
+}
+
+static void mt7531_phy_pll_setup(struct gsw_mt753x *gsw)
+{
+	u32 hwstrap;
+	u32 val;
+
+	val = mt753x_reg_read(gsw, CHIP_REV);
+	if ((val & CHIP_REV_M) > 0)
+		return;
+
+	hwstrap = mt753x_reg_read(gsw, HWSTRAP);
+
+	switch ((hwstrap & XTAL_FSEL_M) >> XTAL_FSEL_S) {
+	case XTAL_25MHZ:
+		/* disable pll auto calibration */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_104, 0x608);
+
+		/* change pll sel */
+		val = gsw->mmd_read(gsw, 0, PHY_DEV1F,
+				     PHY_DEV1F_REG_403);
+		val &= ~(PHY_PLL_M);
+		val |= PHY_PLL_SEL(3);
+		gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_403, val);
+
+		/* set divider ratio */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F,
+			       PHY_DEV1F_REG_10A, 0x1009);
+
+		/* set divider ratio */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_10B, 0x7c6);
+
+		/* capacitance and resistance adjustment */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F,
+			       PHY_DEV1F_REG_10C, 0xa8be);
+
+		break;
+	case XTAL_40MHZ:
+		/* disable pll auto calibration */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_104, 0x608);
+
+		/* change pll sel */
+		val = gsw->mmd_read(gsw, 0, PHY_DEV1F,
+				     PHY_DEV1F_REG_403);
+		val &= ~(PHY_PLL_M);
+		val |= PHY_PLL_SEL(3);
+		gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_403, val);
+
+		/* set divider ratio */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F,
+			       PHY_DEV1F_REG_10A, 0x1018);
+
+		/* set divider ratio */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_10B, 0xc676);
+
+		/* capacitance and resistance adjustment */
+		gsw->mmd_write(gsw, 0, PHY_DEV1F,
+			       PHY_DEV1F_REG_10C, 0xd8be);
+		break;
+	}
+
+	/* power down pll. additional delay is not required via mdio access */
+	gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_10D, 0x10);
+
+	/* power up pll */
+	gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_10D, 0x14);
+}
+
+/* 12 registers for TX_MLT3 waveform tuning.
+ *    012 345 678 9ab
+ *  1    __
+ *     _/  \_
+ *  0_/      \
+ *            \_    _/
+ * -1           \__/
+ */
+static void mt7531_phy_100m_eye_diag_setting(struct gsw_mt753x *gsw, u32 port)
+{
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x0, 0x187);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x1, 0x1c9);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x2, 0x1c6);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x3, 0x182);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x4, 0x208);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x5, 0x205);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x6, 0x384);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x7, 0x3cb);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x8, 0x3c4);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0x9, 0x30a);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0xa, 0x00b);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_TX_MLT3_BASE + 0xb, 0x002);
+}
+
+static void mt7531_phy_setting(struct gsw_mt753x *gsw)
+{
+	int i;
+	u32 val;
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		mt7531_phy_100m_eye_diag_setting(gsw, i);
+
+		/* Enable HW auto downshift */
+		gsw->mii_write(gsw, i, 0x1f, 0x1);
+		val = gsw->mii_read(gsw, i, PHY_EXT_REG_14);
+		val |= PHY_EN_DOWN_SHFIT;
+		gsw->mii_write(gsw, i, PHY_EXT_REG_14, val);
+
+		/* Decrease SlvDPSready time */
+		val = mt753x_tr_read(gsw, i, PMA_CH, PMA_NOD, PMA_17);
+		val &= ~SLV_DSP_READY_TIME_M;
+		val |= 0xc << SLV_DSP_READY_TIME_S;
+		mt753x_tr_write(gsw, i, PMA_CH, PMA_NOD, PMA_17, val);
+
+		/* Enable Random Update Mechanism */
+		val = mt753x_tr_read(gsw, i, PMA_CH, PMA_NOD, PMA_18);
+		val |= ENABLE_RANDOM_UPDATE_TRIGGER;
+		mt753x_tr_write(gsw, i, PMA_CH, PMA_NOD, PMA_18, val);
+
+		/* PHY link down power saving enable */
+		val = gsw->mii_read(gsw, i, PHY_EXT_REG_17);
+		val |= PHY_LINKDOWN_POWER_SAVING_EN;
+		gsw->mii_write(gsw, i, PHY_EXT_REG_17, val);
+
+		val = gsw->mmd_read(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_0C6);
+		val &= ~PHY_POWER_SAVING_M;
+		val |= PHY_POWER_SAVING_TX << PHY_POWER_SAVING_S;
+		gsw->mmd_write(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_0C6, val);
+
+		/* Timing Recovery for GbE slave mode */
+		mt753x_tr_write(gsw, i, PMA_CH, PMA_NOD, PMA_01, 0x6fb90a);
+		mt753x_tr_write(gsw, i, DSP_CH, DSP_NOD, DSP_06, 0x2ebaef);
+		val = gsw->mmd_read(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_234);
+		val |= TR_OPEN_LOOP_EN;
+		gsw->mmd_write(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_234, val);
+
+		/* Enable Asymmetric Pause Capability */
+		val = gsw->mii_read(gsw, i, MII_ADVERTISE);
+		val |= ADVERTISE_PAUSE_ASYM;
+		gsw->mii_write(gsw, i, MII_ADVERTISE, val);
+	}
+}
+
+static void mt7531_adjust_line_driving(struct gsw_mt753x *gsw, u32 port)
+{
+	/* For ADC timing margin window for LDO calibration */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, RXADC_LDO_CONTROL_2, 0x2222);
+
+	/* Adjust AD sample timing */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, RXADC_CONTROL_3, 0x4444);
+
+	/* Adjust Line driver current for different mode */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, TXVLD_DA_271, 0x2ca5);
+
+	/* Adjust Line driver current for different mode */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, TXVLD_DA_272, 0xc6b);
+
+	/* Adjust Line driver gain for 10BT from 1000BT calibration result */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, TXVLD_DA_273, 0x3000);
+
+	/* Adjust RX Echo path filter */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_0FE, 0x2);
+
+	/* Adjust RX HVGA bias current */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_41, 0x3333);
+
+	/* Adjust TX class AB driver 1 */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, PHY_DEV1F_REG_268, 0x384);
+
+	/* Adjust TX class AB driver 2 */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, PHY_DEV1F_REG_269, 0x1114);
+
+	/* Adjust DAC delay for TX Pairs */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_13, 0x404);
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_14, 0x404);
+
+	/* Adjust DAC digital delay for TX Delay */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, PHY_DEV1F_REG_44, 0xc0);
+
+	/* Adjust Line driver compensation cap for stability concern due to
+	 * increase current.
+	 */
+	gsw->mmd_write(gsw, port, PHY_DEV1F, PHY_DEV1F_REG_26A, 0x3333);
+}
+
+static void mt7531_eee_setting(struct gsw_mt753x *gsw, u32 port)
+{
+	u32 val;
+
+	/* Disable EEE */
+	gsw->mmd_write(gsw, port, PHY_DEV07, PHY_DEV07_REG_03C, 0);
+
+	/* Disable generate signal to clear the scramble_lock when lpi mode */
+	val = gsw->mmd_read(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_189);
+	val &= ~DESCRAMBLER_CLEAR_EN;
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_189, val);
+
+	/* Roll back EEE Slave Mode */
+	gsw->mmd_write(gsw, port, 0x1e, 0x2d1, 0);
+	mt753x_tr_write(gsw, port, DSP_CH, DSP_NOD, DSP_08, 0x1b);
+	mt753x_tr_write(gsw, port, DSP_CH, DSP_NOD, DSP_0f, 0);
+	mt753x_tr_write(gsw, port, DSP_CH, DSP_NOD, DSP_10, 0x5000);
+
+	/* Adjust 100_mse_threshold */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_123, 0xffff);
+
+	/* Disable mcc */
+	gsw->mmd_write(gsw, port, PHY_DEV1E, PHY_DEV1E_REG_A6, 0x300);
+}
+
+static void mt7531_afifo_reset(struct gsw_mt753x *gsw, int enable)
+{
+	int p;
+	u32 val;
+
+	if (enable) {
+		for (p = 0; p < MT753X_NUM_PORTS; p++) {
+			val = mt753x_reg_read(gsw, DBG_CNT(p));
+			val &= ~DIS_CLR;
+			mt753x_reg_write(gsw, DBG_CNT(p), val);
+		}
+	} else {
+		for (p = 0; p < MT753X_NUM_PORTS; p++) {
+			val = mt753x_reg_read(gsw, DBG_CNT(p));
+			val |= DIS_CLR;
+			mt753x_reg_write(gsw, DBG_CNT(p), val);
+		}
+	}
+}
+
+static int mt7531_sw_init(struct gsw_mt753x *gsw)
+{
+	int i;
+	u32 val;
+
+	gsw->phy_base = (gsw->smi_addr + 1) & MT753X_SMI_ADDR_MASK;
+
+	gsw->mii_read = mt753x_mii_read;
+	gsw->mii_write = mt753x_mii_write;
+	gsw->mmd_read = mt753x_mmd_read;
+	gsw->mmd_write = mt753x_mmd_write;
+
+	gsw->hw_phy_cal = of_property_read_bool(gsw->dev->of_node, "mediatek,hw_phy_cal");
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMCR);
+		val |= BMCR_ISOLATE;
+		gsw->mii_write(gsw, i, MII_BMCR, val);
+	}
+
+	/* Force MAC link down before reset */
+	mt753x_reg_write(gsw, PMCR(5), FORCE_MODE_LNK);
+	mt753x_reg_write(gsw, PMCR(6), FORCE_MODE_LNK);
+
+	/* Switch soft reset */
+	mt753x_reg_write(gsw, SYS_CTRL, SW_SYS_RST | SW_REG_RST);
+	usleep_range(10, 20);
+
+	/* Enable MDC input Schmitt Trigger */
+	val = mt753x_reg_read(gsw, SMT0_IOLB);
+	mt753x_reg_write(gsw, SMT0_IOLB, val | SMT_IOLB_5_SMI_MDC_EN);
+
+	/* Set 7531 gpio pinmux */
+	mt7531_set_gpio_pinmux(gsw);
+
+	mt7531_core_pll_setup(gsw);
+	mt7531_mac_port_setup(gsw, 5, &gsw->port5_cfg);
+	mt7531_mac_port_setup(gsw, 6, &gsw->port6_cfg);
+
+	/* Global mac control settings */
+	mt753x_reg_write(gsw, GMACCR,
+			 (15 << MTCC_LMT_S) | (15 << MAX_RX_JUMBO_S) |
+			 RX_PKT_LEN_MAX_JUMBO);
+
+	/* Enable Collision Poll */
+	val = mt753x_reg_read(gsw, CPGC_CTRL);
+	val |= COL_CLK_EN;
+	mt753x_reg_write(gsw, CPGC_CTRL, val);
+	val |= COL_RST_N;
+	mt753x_reg_write(gsw, CPGC_CTRL, val);
+	val |= COL_EN;
+	mt753x_reg_write(gsw, CPGC_CTRL, val);
+
+	/* Disable AFIFO reset for extra short IPG */
+	mt7531_afifo_reset(gsw, 0);
+
+	return 0;
+}
+
+static int mt7531_sw_post_init(struct gsw_mt753x *gsw)
+{
+	int i;
+	u32 val;
+
+	/* Let internal PHYs only Tx constant data in configure stage. */
+	for (i = 0; i < MT753X_NUM_PHYS; i++)
+		gsw->mmd_write(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_141, 0x200);
+
+	/* Internal PHYs might be enabled by HW Bootstrapping, or bootloader.
+	 * Turn off PHYs before setup PHY PLL.
+	 */
+	val = gsw->mmd_read(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_403);
+	val |= PHY_EN_BYPASS_MODE;
+	val |= POWER_ON_OFF;
+	gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_403, val);
+
+	mt7531_phy_pll_setup(gsw);
+
+	/* Enable Internal PHYs before phy setting */
+	val = gsw->mmd_read(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_403);
+	val |= PHY_EN_BYPASS_MODE;
+	val &= ~POWER_ON_OFF;
+	gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_DEV1F_REG_403, val);
+
+	mt7531_phy_setting(gsw);
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMCR);
+		val &= ~BMCR_ISOLATE;
+		gsw->mii_write(gsw, i, MII_BMCR, val);
+	}
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		mt7531_adjust_line_driving(gsw, i);
+		mt7531_eee_setting(gsw, i);
+	}
+
+	/* Restore internal PHYs normal Tx function after configure stage. */
+	for (i = 0; i < MT753X_NUM_PHYS; i++)
+		gsw->mmd_write(gsw, i, PHY_DEV1E, PHY_DEV1E_REG_141, 0x0);
+
+	mt7531_internal_phy_calibration(gsw);
+
+	return 0;
+}
+
+struct mt753x_sw_id mt7531_id = {
+	.model = MT7531,
+	.detect = mt7531_sw_detect,
+	.init = mt7531_sw_init,
+	.post_init = mt7531_sw_post_init
+};
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Zhanguo Ju <zhanguo.ju@mediatek.com>");
+MODULE_DESCRIPTION("Driver for MediaTek MT753x Gigabit Switch");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7531.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7531.h
new file mode 100644
index 0000000..e4ef81e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt7531.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ */
+
+#ifndef _MT7531_H_
+#define _MT7531_H_
+
+#include "mt753x.h"
+
+extern struct mt753x_sw_id mt7531_id;
+
+#endif /* _MT7531_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x.h
new file mode 100755
index 0000000..5a3144d
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x.h
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT753X_H_
+#define _MT753X_H_
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of_mdio.h>
+#include <linux/workqueue.h>
+#include <linux/gpio/consumer.h>
+
+#ifdef CONFIG_SWCONFIG
+#include <linux/switch.h>
+#endif
+
+#include "mt753x_vlan.h"
+
+#define MT753X_DFL_CPU_PORT	6
+#define MT753X_NUM_PHYS		5
+
+#define MT753X_DFL_SMI_ADDR	0x1f
+#define MT753X_SMI_ADDR_MASK	0x1f
+
+#define PROCREG_DIR		"mt753x"
+#define PROCREG_MT753X_WOL	"mt753x_wol"
+
+struct gsw_mt753x;
+
+enum mt753x_model {
+	MT7530 = 0x7530,
+	MT7531 = 0x7531
+};
+
+struct mt753x_port_cfg {
+	struct device_node *np;
+	int phy_mode;
+	u32 enabled: 1;
+	u32 force_link: 1;
+	u32 speed: 2;
+	u32 duplex: 1;
+	bool ssc_on;
+	bool stag_on;
+};
+
+struct mt753x_phy {
+	struct gsw_mt753x *gsw;
+	struct net_device netdev;
+	struct phy_device *phydev;
+};
+
+struct gsw_mt753x {
+	u32 id;
+
+	struct device *dev;
+	struct mii_bus *host_bus;
+	struct mii_bus *gphy_bus;
+	struct mutex mii_lock;	/* MII access lock */
+	u32 smi_addr;
+	u32 phy_base;
+	int direct_phy_access;
+
+	enum mt753x_model model;
+	const char *name;
+
+	struct mt753x_port_cfg port5_cfg;
+	struct mt753x_port_cfg port6_cfg;
+
+	bool hw_phy_cal;
+	bool phy_status_poll;
+	struct mt753x_phy phys[MT753X_NUM_PHYS];
+//	int phy_irqs[PHY_MAX_ADDR]; //FIXME 
+
+	int phy_link_sts;
+
+	int irq;
+	int reset_pin;
+	struct work_struct irq_worker;
+
+#ifdef CONFIG_SWCONFIG
+	struct switch_dev swdev;
+	u32 cpu_port;
+#endif
+
+	int global_vlan_enable;
+	struct mt753x_vlan_entry vlan_entries[MT753X_NUM_VLANS];
+	struct mt753x_port_entry port_entries[MT753X_NUM_PORTS];
+
+	int (*mii_read)(struct gsw_mt753x *gsw, int phy, int reg);
+	void (*mii_write)(struct gsw_mt753x *gsw, int phy, int reg, u16 val);
+
+	int (*mmd_read)(struct gsw_mt753x *gsw, int addr, int devad, u16 reg);
+	void (*mmd_write)(struct gsw_mt753x *gsw, int addr, int devad, u16 reg,
+			  u16 val);
+
+	struct list_head list;
+};
+
+struct chip_rev {
+	const char *name;
+	u32 rev;
+};
+
+struct mt753x_sw_id {
+	enum mt753x_model model;
+	int (*detect)(struct gsw_mt753x *gsw, struct chip_rev *crev);
+	int (*init)(struct gsw_mt753x *gsw);
+	int (*post_init)(struct gsw_mt753x *gsw);
+};
+
+extern struct list_head mt753x_devs;
+
+struct gsw_mt753x *mt753x_get_gsw(u32 id);
+struct gsw_mt753x *mt753x_get_first_gsw(void);
+void mt753x_put_gsw(void);
+void mt753x_lock_gsw(void);
+
+u32 mt753x_reg_read(struct gsw_mt753x *gsw, u32 reg);
+void mt753x_reg_write(struct gsw_mt753x *gsw, u32 reg, u32 val);
+
+int mt753x_mii_read(struct gsw_mt753x *gsw, int phy, int reg);
+void mt753x_mii_write(struct gsw_mt753x *gsw, int phy, int reg, u16 val);
+
+int mt753x_mmd_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg);
+void mt753x_mmd_write(struct gsw_mt753x *gsw, int addr, int devad, u16 reg,
+		      u16 val);
+
+int mt753x_mmd_ind_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg);
+void mt753x_mmd_ind_write(struct gsw_mt753x *gsw, int addr, int devad, u16 reg,
+			  u16 val);
+
+int mt753x_tr_read(struct gsw_mt753x *gsw, int addr, u8 ch, u8 node, u8 daddr);
+void mt753x_tr_write(struct gsw_mt753x *gsw, int addr, u8 ch, u8 node, u8 daddr,
+		     u32 data);
+
+void mt753x_irq_worker(struct work_struct *work);
+void mt753x_irq_enable(struct gsw_mt753x *gsw);
+
+int mt753x_phy_calibration(struct gsw_mt753x *gsw, u8 phyaddr);
+int extphy_init(struct gsw_mt753x *gsw, int addr);
+
+/* MDIO Indirect Access Registers */
+#define MII_MMD_ACC_CTL_REG		0x0d
+#define MMD_CMD_S			14
+#define MMD_CMD_M			0xc000
+#define MMD_DEVAD_S			0
+#define MMD_DEVAD_M			0x1f
+
+/* MMD_CMD: MMD commands */
+#define MMD_ADDR			0
+#define MMD_DATA			1
+
+#define MII_MMD_ADDR_DATA_REG		0x0e
+
+/* Procedure of MT753x Internal Register Access
+ *
+ * 1. Internal Register Address
+ *
+ *    The MT753x has a 16-bit register address and each register is 32-bit.
+ *    This means the lowest two bits are not used as the register address is
+ *    4-byte aligned.
+ *
+ *    Rest of the valid bits are divided into two parts:
+ *      Bit 15..6 is the Page address
+ *      Bit 5..2 is the low address
+ *
+ *    -------------------------------------------------------------------
+ *    | 15  14  13  12  11  10   9   8   7   6 | 5   4   3   2 | 1   0  |
+ *    |----------------------------------------|---------------|--------|
+ *    |              Page Address              |    Address    | Unused |
+ *    -------------------------------------------------------------------
+ *
+ * 2. MDIO access timing
+ *
+ *    The MT753x uses the following MDIO timing for a single register read
+ *
+ *      Phase 1: Write Page Address
+ *    -------------------------------------------------------------------
+ *    | ST | OP | PHY_ADDR | TYPE | RSVD | TA |  RSVD |    PAGE_ADDR    |
+ *    -------------------------------------------------------------------
+ *    | 01 | 01 |   11111  |   1  | 1111 | xx | 00000 | REG_ADDR[15..6] |
+ *    -------------------------------------------------------------------
+ *
+ *      Phase 2: Write low Address & Read low word
+ *    -------------------------------------------------------------------
+ *    | ST | OP | PHY_ADDR | TYPE |    LOW_ADDR    | TA |      DATA     |
+ *    -------------------------------------------------------------------
+ *    | 01 | 10 |   11111  |   0  | REG_ADDR[5..2] | xx |  DATA[15..0]  |
+ *    -------------------------------------------------------------------
+ *
+ *      Phase 3: Read high word
+ *    -------------------------------------------------------------------
+ *    | ST | OP | PHY_ADDR | TYPE | RSVD | TA |           DATA          |
+ *    -------------------------------------------------------------------
+ *    | 01 | 10 |   11111  |   1  | 0000 | xx |       DATA[31..16]      |
+ *    -------------------------------------------------------------------
+ *
+ *    The MT753x uses the following MDIO timing for a single register write
+ *
+ *      Phase 1: Write Page Address (The same as read)
+ *
+ *      Phase 2: Write low Address and low word
+ *    -------------------------------------------------------------------
+ *    | ST | OP | PHY_ADDR | TYPE |    LOW_ADDR    | TA |      DATA     |
+ *    -------------------------------------------------------------------
+ *    | 01 | 01 |   11111  |   0  | REG_ADDR[5..2] | xx |  DATA[15..0]  |
+ *    -------------------------------------------------------------------
+ *
+ *      Phase 3: write high word
+ *    -------------------------------------------------------------------
+ *    | ST | OP | PHY_ADDR | TYPE | RSVD | TA |           DATA          |
+ *    -------------------------------------------------------------------
+ *    | 01 | 01 |   11111  |   1  | 0000 | xx |       DATA[31..16]      |
+ *    -------------------------------------------------------------------
+ *
+ */
+
+/* Internal Register Address fields */
+#define MT753X_REG_PAGE_ADDR_S		6
+#define MT753X_REG_PAGE_ADDR_M		0xffc0
+#define MT753X_REG_ADDR_S		2
+#define MT753X_REG_ADDR_M		0x3c
+#endif /* _MT753X_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_common.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_common.c
new file mode 100755
index 0000000..c0ac2e5
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_common.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "mt753x.h"
+#include "mt753x_regs.h"
+
+void mt753x_irq_enable(struct gsw_mt753x *gsw)
+{
+	u32 val;
+	int i;
+
+	/* Record initial PHY link status */
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		val = gsw->mii_read(gsw, i, MII_BMSR);
+		if (val & BMSR_LSTATUS)
+			gsw->phy_link_sts |= BIT(i);
+	}
+
+	val = BIT(MT753X_NUM_PHYS) - 1;
+
+	mt753x_reg_write(gsw, SYS_INT_EN, val);
+}
+
+static void display_port_link_status(struct gsw_mt753x *gsw, u32 port)
+{
+	u32 pmsr, speed_bits;
+	const char *speed;
+	
+	speed = "0Mbps";
+
+	pmsr = mt753x_reg_read(gsw, PMSR(port));
+
+	speed_bits = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S;
+
+	switch (speed_bits) {
+	case MAC_SPD_10:
+		speed = "10Mbps";
+		break;
+	case MAC_SPD_100:
+		speed = "100Mbps";
+		break;
+	case MAC_SPD_1000:
+		speed = "1Gbps";
+		break;
+	case MAC_SPD_2500:
+		speed = "2.5Gbps";
+		break;
+	}
+
+	if (pmsr & MAC_LNK_STS) {
+		dev_info(gsw->dev, "Port %d Link is Up - %s/%s\n",
+			 port, speed, (pmsr & MAC_DPX_STS) ? "Full" : "Half");
+	} else {
+		dev_info(gsw->dev, "Port %d Link is Down\n", port);
+	}
+}
+
+void mt753x_irq_worker(struct work_struct *work)
+{
+	struct gsw_mt753x *gsw;
+	u32 sts, physts, laststs;
+	int i;
+
+	gsw = container_of(work, struct gsw_mt753x, irq_worker);
+
+	sts = mt753x_reg_read(gsw, SYS_INT_STS);
+
+	/* Check for changed PHY link status */
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		if (!(sts & PHY_LC_INT(i)))
+			continue;
+
+		laststs = gsw->phy_link_sts & BIT(i);
+		physts = !!(gsw->mii_read(gsw, i, MII_BMSR) & BMSR_LSTATUS);
+		physts <<= i;
+
+		if (physts ^ laststs) {
+			gsw->phy_link_sts ^= BIT(i);
+			display_port_link_status(gsw, i);
+		}
+	}
+
+	mt753x_reg_write(gsw, SYS_INT_STS, sts);
+
+	enable_irq(gsw->irq);
+}
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_extphy.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_extphy.c
new file mode 100755
index 0000000..f58e8a6
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_extphy.c
@@ -0,0 +1,69 @@
+/*
+ * Driver for MediaTek MT7531 gigabit switch
+ *
+ * Copyright (C) 2018 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Landen Chao <landen.chao@mediatek.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <linux/kernel.h>
+#include <linux/mii.h>
+
+#include "mt753x.h"
+#include "mt753x_regs.h"
+#include "mt753x_extphy.h"
+
+int gpy211_init(struct gsw_mt753x *gsw, int addr)
+{
+	/* Enable rate adaption */
+	gsw->mmd_write(gsw, addr, 0x1e, 0x8, 0x24e2);
+
+	return 0;
+}
+
+static struct mt753x_extphy_id extphy_tbl[] = {
+        {0x67c9de00, 0x0fffffff0, gpy211_init},
+};
+
+static u32 get_cl22_phy_id(struct gsw_mt753x *gsw, int addr)
+{
+	int phy_reg;
+	u32 phy_id = 0;
+
+	phy_reg = gsw->mii_read(gsw, addr, MII_PHYSID1);
+	if (phy_reg < 0)
+		return 0;
+	phy_id = (phy_reg & 0xffff) << 16;
+
+	/* Grab the bits from PHYIR2, and put them in the lower half */
+	phy_reg = gsw->mii_read(gsw, addr, MII_PHYSID2);
+	if (phy_reg < 0)
+		return 0;
+
+	phy_id |= (phy_reg & 0xffff);
+
+	return phy_id;
+}
+
+static inline bool phy_id_is_match(u32 id, struct mt753x_extphy_id *phy)
+{
+	return ((id & phy->phy_id_mask) == (phy->phy_id & phy->phy_id_mask));
+}
+
+int extphy_init(struct gsw_mt753x *gsw, int addr)
+{
+	int i;
+	u32 phy_id;
+	struct mt753x_extphy_id *extphy;
+
+	phy_id = get_cl22_phy_id(gsw, addr);
+	for (i = 0; i < ARRAY_SIZE(extphy_tbl); i++) {
+		extphy = &extphy_tbl[i];
+		if(phy_id_is_match(phy_id, extphy))
+			extphy->init(gsw, addr);
+	}
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_extphy.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_extphy.h
new file mode 100755
index 0000000..2b72c8a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_extphy.h
@@ -0,0 +1,18 @@
+/*
+ * Driver for MediaTek MT753x gigabit switch
+ *
+ * Copyright (C) 2018 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Landen Chao <landen.chao@mediatek.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _MT753X_EXTPHY_H_
+#define _MT753X_EXTPHY_H_
+struct mt753x_extphy_id {
+        u32 phy_id;
+        u32 phy_id_mask;
+	int (*init)(struct gsw_mt753x *gsw, int addr);
+};
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_mdio.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_mdio.c
new file mode 100755
index 0000000..9deb1a9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_mdio.c
@@ -0,0 +1,1016 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/reset.h>
+#include <linux/hrtimer.h>
+#include <linux/mii.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/of_net.h>
+#include <linux/of_irq.h>
+#include <linux/phy.h>
+#include <linux/proc_fs.h>
+
+#include "mt753x.h"
+#include "mt753x_swconfig.h"
+#include "mt753x_regs.h"
+#include "mt753x_nl.h"
+#include "mt7530.h"
+#include "mt7531.h"
+
+static u32 mt753x_id;
+struct list_head mt753x_devs;
+static DEFINE_MUTEX(mt753x_devs_lock);
+static struct gsw_mt753x *global_gsw;
+
+static struct mt753x_sw_id *mt753x_sw_ids[] = {
+	&mt7530_id,
+	&mt7531_id,
+};
+
+struct proc_dir_entry *proc_mt753x_dir;
+static struct proc_dir_entry *proc_mt753x_wol;
+
+
+u32 mt753x_reg_read(struct gsw_mt753x *gsw, u32 reg)
+{
+	u32 high, low;
+
+	mutex_lock(&gsw->host_bus->mdio_lock);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f,
+		(reg & MT753X_REG_PAGE_ADDR_M) >> MT753X_REG_PAGE_ADDR_S);
+
+	low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr,
+		(reg & MT753X_REG_ADDR_M) >> MT753X_REG_ADDR_S);
+
+	high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x10);
+
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+
+	return (high << 16) | (low & 0xffff);
+}
+
+void mt753x_reg_write(struct gsw_mt753x *gsw, u32 reg, u32 val)
+{
+	mutex_lock(&gsw->host_bus->mdio_lock);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f,
+		(reg & MT753X_REG_PAGE_ADDR_M) >> MT753X_REG_PAGE_ADDR_S);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr,
+		(reg & MT753X_REG_ADDR_M) >> MT753X_REG_ADDR_S, val & 0xffff);
+
+	gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, val >> 16);
+
+	mutex_unlock(&gsw->host_bus->mdio_lock);
+}
+
+/* Indirect MDIO clause 22/45 access */
+static int mt753x_mii_rw(struct gsw_mt753x *gsw, int phy, int reg, u16 data,
+			 u32 cmd, u32 st)
+{
+	ktime_t timeout;
+	u32 val, timeout_us;
+	int ret = 0;
+
+	timeout_us = 100000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = mt753x_reg_read(gsw, PHY_IAC);
+
+		if ((val & PHY_ACS_ST) == 0)
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0)
+			return -ETIMEDOUT;
+	}
+
+	val = (st << MDIO_ST_S) |
+	      ((cmd << MDIO_CMD_S) & MDIO_CMD_M) |
+	      ((phy << MDIO_PHY_ADDR_S) & MDIO_PHY_ADDR_M) |
+	      ((reg << MDIO_REG_ADDR_S) & MDIO_REG_ADDR_M);
+
+	if (cmd == MDIO_CMD_WRITE || cmd == MDIO_CMD_ADDR)
+		val |= data & MDIO_RW_DATA_M;
+
+	mt753x_reg_write(gsw, PHY_IAC, val | PHY_ACS_ST);
+
+	timeout_us = 100000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = mt753x_reg_read(gsw, PHY_IAC);
+
+		if ((val & PHY_ACS_ST) == 0)
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0)
+			return -ETIMEDOUT;
+	}
+
+	if (cmd == MDIO_CMD_READ || cmd == MDIO_CMD_READ_C45) {
+		val = mt753x_reg_read(gsw, PHY_IAC);
+		ret = val & MDIO_RW_DATA_M;
+	}
+
+	return ret;
+}
+
+
+/* MT753x supsend/resume
+ */
+static int mt753x_phy_suspend(struct gsw_mt753x *gsw)
+{
+	u32 wol;
+	int i, value;
+	int wol_enabled;
+
+	dev_info(gsw->dev, "%s: PHY suspend\n", __func__);
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+
+		wol = mt753x_reg_read(gsw, WOL(i));
+		wol_enabled = wol & WOL_EN;
+
+		value = gsw->mii_read(gsw, i, MII_BMCR);
+
+		if (wol_enabled)
+			value |= BMCR_ISOLATE;
+		else
+			value |= BMCR_PDOWN;
+
+		gsw->mii_write(gsw, i, MII_BMCR, value);
+	}
+
+	return 0;
+}
+
+static int mt753x_phy_resume(struct gsw_mt753x *gsw)
+{
+	int i, value;
+
+	dev_info(gsw->dev, "%s: PHY resume\n", __func__);
+
+	for (i = 0; i < MT753X_NUM_PHYS; i++) {
+		value = gsw->mii_read(gsw, i, MII_BMCR);
+		value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
+		gsw->mii_write(gsw, i, MII_BMCR, value);
+	}
+
+	return 0;
+}
+
+
+int mt753x_wol_read(struct seq_file *seq, void *v)
+{
+	pr_info("mt753x_wol_read in!!\n");
+
+        // TODO for WOL
+
+	seq_puts(seq, "\n");
+	return 0;
+}
+
+ssize_t mt753x_wol_write(struct file *file, const char __user *buffer,
+			 size_t count, loff_t *data)
+{
+	pr_info("%s: mt753x_wol_write in!!\n", __func__);
+
+	if (count > 0) {
+		char c;
+		u32 wol;
+		int i, orgVal, newVal, changed;
+
+		if (get_user(c, buffer))
+			return -EFAULT;
+
+		newVal = (c != '0');
+
+		for (i = 0; i < MT753X_NUM_PHYS; i++) {
+
+			wol = mt753x_reg_read(global_gsw, WOL(i));
+			orgVal = wol & WOL_EN;
+			changed = newVal != orgVal;
+
+			if (changed) {
+				mt753x_reg_write(global_gsw, WOL(i), wol | WOL_EN);
+				pr_info("%s: Port %d updated: c=%c, orgVal = %d, newVal = %d\n", __func__,i ,c, orgVal, newVal);
+			} else {
+				pr_info("%s: Port %d not change: c=%c, orgVal = %d, newVal = %d\n", __func__,i ,c, orgVal, newVal);
+			}
+		}
+
+	}
+
+	return 0;
+}
+
+static int mt753x_wol_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mt753x_wol_read, NULL);
+}
+
+
+static const struct file_operations mt753x_wol_fops = {
+	.owner = THIS_MODULE,
+	.open = mt753x_wol_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = mt753x_wol_write,
+	.release = single_release
+};
+
+
+int mt753x_mii_read(struct gsw_mt753x *gsw, int phy, int reg)
+{
+	int val;
+
+	if (phy < MT753X_NUM_PHYS)
+		phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->mii_lock);
+	val = mt753x_mii_rw(gsw, phy, reg, 0, MDIO_CMD_READ, MDIO_ST_C22);
+	mutex_unlock(&gsw->mii_lock);
+
+	return val;
+}
+
+void mt753x_mii_write(struct gsw_mt753x *gsw, int phy, int reg, u16 val)
+{
+	if (phy < MT753X_NUM_PHYS)
+		phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->mii_lock);
+	mt753x_mii_rw(gsw, phy, reg, val, MDIO_CMD_WRITE, MDIO_ST_C22);
+	mutex_unlock(&gsw->mii_lock);
+}
+
+int mt753x_mmd_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg)
+{
+	int val;
+
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->mii_lock);
+	mt753x_mii_rw(gsw, addr, devad, reg, MDIO_CMD_ADDR, MDIO_ST_C45);
+	val = mt753x_mii_rw(gsw, addr, devad, 0, MDIO_CMD_READ_C45,
+			    MDIO_ST_C45);
+	mutex_unlock(&gsw->mii_lock);
+
+	return val;
+}
+
+void mt753x_mmd_write(struct gsw_mt753x *gsw, int addr, int devad, u16 reg,
+		      u16 val)
+{
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->mii_lock);
+	mt753x_mii_rw(gsw, addr, devad, reg, MDIO_CMD_ADDR, MDIO_ST_C45);
+	mt753x_mii_rw(gsw, addr, devad, val, MDIO_CMD_WRITE, MDIO_ST_C45);
+	mutex_unlock(&gsw->mii_lock);
+}
+
+int mt753x_mmd_ind_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg)
+{
+	u16 val;
+
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->mii_lock);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG,
+		      (MMD_ADDR << MMD_CMD_S) |
+		      ((devad << MMD_DEVAD_S) & MMD_DEVAD_M),
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, reg,
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG,
+		      (MMD_DATA << MMD_CMD_S) |
+		      ((devad << MMD_DEVAD_S) & MMD_DEVAD_M),
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	val = mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, 0,
+			    MDIO_CMD_READ, MDIO_ST_C22);
+
+	mutex_unlock(&gsw->mii_lock);
+
+	return val;
+}
+
+void mt753x_mmd_ind_write(struct gsw_mt753x *gsw, int addr, int devad, u16 reg,
+			  u16 val)
+{
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	mutex_lock(&gsw->mii_lock);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG,
+		      (MMD_ADDR << MMD_CMD_S) |
+		      ((devad << MMD_DEVAD_S) & MMD_DEVAD_M),
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, reg,
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG,
+		      (MMD_DATA << MMD_CMD_S) |
+		      ((devad << MMD_DEVAD_S) & MMD_DEVAD_M),
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, val,
+		      MDIO_CMD_WRITE, MDIO_ST_C22);
+
+	mutex_unlock(&gsw->mii_lock);
+}
+
+static inline int mt753x_get_duplex(const struct device_node *np)
+{
+	return of_property_read_bool(np, "full-duplex");
+}
+
+static void mt753x_load_port_cfg(struct gsw_mt753x *gsw)
+{
+	struct device_node *port_np;
+	struct device_node *fixed_link_node;
+	struct mt753x_port_cfg *port_cfg;
+	u32 port;
+
+	for_each_child_of_node(gsw->dev->of_node, port_np) {
+		if (!of_device_is_compatible(port_np, "mediatek,mt753x-port"))
+			continue;
+
+		if (!of_device_is_available(port_np))
+			continue;
+
+		if (of_property_read_u32(port_np, "reg", &port))
+			continue;
+
+		switch (port) {
+		case 5:
+			port_cfg = &gsw->port5_cfg;
+			break;
+		case 6:
+			port_cfg = &gsw->port6_cfg;
+			break;
+		default:
+			continue;
+		}
+
+		if (port_cfg->enabled) {
+			dev_info(gsw->dev, "duplicated node for port%d\n",
+				 port_cfg->phy_mode);
+			continue;
+		}
+
+		port_cfg->np = port_np;
+
+		port_cfg->phy_mode = of_get_phy_mode(port_np);
+		if (port_cfg->phy_mode < 0) {
+			dev_info(gsw->dev, "incorrect phy-mode %d\n", port);
+			continue;
+		}
+
+		fixed_link_node = of_get_child_by_name(port_np, "fixed-link");
+		if (fixed_link_node) {
+			u32 speed;
+
+			port_cfg->force_link = 1;
+			port_cfg->duplex = mt753x_get_duplex(fixed_link_node);
+
+			if (of_property_read_u32(fixed_link_node, "speed",
+						 &speed)) {
+				speed = 0;
+				continue;
+			}
+
+			of_node_put(fixed_link_node);
+
+			switch (speed) {
+			case 10:
+				port_cfg->speed = MAC_SPD_10;
+				break;
+			case 100:
+				port_cfg->speed = MAC_SPD_100;
+				break;
+			case 1000:
+				port_cfg->speed = MAC_SPD_1000;
+				break;
+			case 2500:
+				port_cfg->speed = MAC_SPD_2500;
+				break;
+			default:
+				dev_info(gsw->dev, "incorrect speed %d\n",
+					 speed);
+				continue;
+			}
+		}
+
+		port_cfg->ssc_on = of_property_read_bool(port_cfg->np,
+							 "mediatek,ssc-on");
+		port_cfg->stag_on = of_property_read_bool(port_cfg->np,
+							  "mediatek,stag-on");
+		port_cfg->enabled = 1;
+	}
+}
+
+void mt753x_tr_write(struct gsw_mt753x *gsw, int addr, u8 ch, u8 node, u8 daddr,
+		     u32 data)
+{
+	ktime_t timeout;
+	u32 timeout_us;
+	u32 val;
+
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, PHY_TR_PAGE);
+
+	val = gsw->mii_read(gsw, addr, PHY_TR_CTRL);
+
+	timeout_us = 100000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = gsw->mii_read(gsw, addr, PHY_TR_CTRL);
+
+		if (!!(val & PHY_TR_PKT_XMT_STA))
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0)
+			goto out;
+	}
+
+	gsw->mii_write(gsw, addr, PHY_TR_LOW_DATA, PHY_TR_LOW_VAL(data));
+	gsw->mii_write(gsw, addr, PHY_TR_HIGH_DATA, PHY_TR_HIGH_VAL(data));
+	val = PHY_TR_PKT_XMT_STA | (PHY_TR_WRITE << PHY_TR_WR_S) |
+	      (ch << PHY_TR_CH_ADDR_S) | (node << PHY_TR_NODE_ADDR_S) |
+	      (daddr << PHY_TR_DATA_ADDR_S);
+	gsw->mii_write(gsw, addr, PHY_TR_CTRL, val);
+
+	timeout_us = 100000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = gsw->mii_read(gsw, addr, PHY_TR_CTRL);
+
+		if (!!(val & PHY_TR_PKT_XMT_STA))
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0)
+			goto out;
+	}
+out:
+	gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0);
+}
+
+int mt753x_tr_read(struct gsw_mt753x *gsw, int addr, u8 ch, u8 node, u8 daddr)
+{
+	ktime_t timeout;
+	u32 timeout_us;
+	u32 val;
+	u8 val_h;
+
+	if (addr < MT753X_NUM_PHYS)
+		addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;
+
+	gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, PHY_TR_PAGE);
+
+	val = gsw->mii_read(gsw, addr, PHY_TR_CTRL);
+
+	timeout_us = 100000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = gsw->mii_read(gsw, addr, PHY_TR_CTRL);
+
+		if (!!(val & PHY_TR_PKT_XMT_STA))
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0) {
+			gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0);
+			return -ETIMEDOUT;
+		}
+	}
+
+	val = PHY_TR_PKT_XMT_STA | (PHY_TR_READ << PHY_TR_WR_S) |
+	      (ch << PHY_TR_CH_ADDR_S) | (node << PHY_TR_NODE_ADDR_S) |
+	      (daddr << PHY_TR_DATA_ADDR_S);
+	gsw->mii_write(gsw, addr, PHY_TR_CTRL, val);
+
+	timeout_us = 100000;
+	timeout = ktime_add_us(ktime_get(), timeout_us);
+	while (1) {
+		val = gsw->mii_read(gsw, addr, PHY_TR_CTRL);
+
+		if (!!(val & PHY_TR_PKT_XMT_STA))
+			break;
+
+		if (ktime_compare(ktime_get(), timeout) > 0) {
+			gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0);
+			return -ETIMEDOUT;
+		}
+	}
+
+	val = gsw->mii_read(gsw, addr, PHY_TR_LOW_DATA);
+	val_h = gsw->mii_read(gsw, addr, PHY_TR_HIGH_DATA);
+	val |= (val_h << 16);
+
+	gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0);
+
+	return val;
+}
+
+static void mt753x_add_gsw(struct gsw_mt753x *gsw)
+{
+	mutex_lock(&mt753x_devs_lock);
+	gsw->id = mt753x_id++;
+	INIT_LIST_HEAD(&gsw->list);
+	list_add_tail(&gsw->list, &mt753x_devs);
+	mutex_unlock(&mt753x_devs_lock);
+}
+
+static void mt753x_remove_gsw(struct gsw_mt753x *gsw)
+{
+	mutex_lock(&mt753x_devs_lock);
+	list_del(&gsw->list);
+	mutex_unlock(&mt753x_devs_lock);
+}
+
+
+struct gsw_mt753x *mt753x_get_gsw(u32 id)
+{
+	struct gsw_mt753x *dev;
+
+	mutex_lock(&mt753x_devs_lock);
+
+	list_for_each_entry(dev, &mt753x_devs, list) {
+		if (dev->id == id)
+			return dev;
+	}
+
+	mutex_unlock(&mt753x_devs_lock);
+
+	return NULL;
+}
+
+struct gsw_mt753x *mt753x_get_first_gsw(void)
+{
+	struct gsw_mt753x *dev;
+
+	mutex_lock(&mt753x_devs_lock);
+
+	list_for_each_entry(dev, &mt753x_devs, list)
+		return dev;
+
+	mutex_unlock(&mt753x_devs_lock);
+
+	return NULL;
+}
+
+void mt753x_put_gsw(void)
+{
+	mutex_unlock(&mt753x_devs_lock);
+}
+
+void mt753x_lock_gsw(void)
+{
+	mutex_lock(&mt753x_devs_lock);
+}
+
+static int mt753x_hw_reset(struct gsw_mt753x *gsw)
+{
+	struct device_node *np = gsw->dev->of_node;
+	struct reset_control *rstc;
+	int mcm;
+	int ret = -EINVAL;
+
+	mcm = of_property_read_bool(np, "mediatek,mcm");
+	if (mcm) {
+		rstc = devm_reset_control_get(gsw->dev, "mcm");
+		ret = IS_ERR(rstc);
+		if (IS_ERR(rstc)) {
+			dev_err(gsw->dev, "Missing reset ctrl of switch\n");
+			return ret;
+		}
+
+		reset_control_assert(rstc);
+		msleep(30);
+		reset_control_deassert(rstc);
+
+		gsw->reset_pin = -1;
+		return 0;
+	}
+
+	gsw->reset_pin = of_get_named_gpio(np, "reset-gpios", 0);
+	if (gsw->reset_pin < 0) {
+		dev_err(gsw->dev, "Missing reset pin of switch\n");
+		return ret;
+	}
+
+	ret = devm_gpio_request(gsw->dev, gsw->reset_pin, "mt753x-reset");
+	if (ret) {
+		dev_info(gsw->dev, "Failed to request gpio %d\n",
+			 gsw->reset_pin);
+		return ret;
+	}
+
+	gpio_direction_output(gsw->reset_pin, 0);
+	msleep(30);
+	gpio_set_value(gsw->reset_pin, 1);
+	msleep(500);
+
+	return 0;
+}
+#if 1 //XDXDXDXD
+static int mt753x_mdio_read(struct mii_bus *bus, int addr, int reg)
+{
+	struct gsw_mt753x *gsw = bus->priv;
+
+	return gsw->mii_read(gsw, addr, reg);
+}
+
+static int mt753x_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct gsw_mt753x *gsw = bus->priv;
+
+	gsw->mii_write(gsw, addr, reg, val);
+
+	return 0;
+}
+
+static const struct net_device_ops mt753x_dummy_netdev_ops = {
+};
+
+static void mt753x_phy_link_handler(struct net_device *dev)
+{
+	struct mt753x_phy *phy = container_of(dev, struct mt753x_phy, netdev);
+	struct phy_device *phydev = phy->phydev;
+	struct gsw_mt753x *gsw = phy->gsw;
+	u32 port = phy - gsw->phys;
+
+	if (phydev->link) {
+		dev_info(gsw->dev,
+			 "Port %d Link is Up - %s/%s - flow control %s\n",
+			 port, phy_speed_to_str(phydev->speed),
+			 (phydev->duplex == DUPLEX_FULL) ? "Full" : "Half",
+			 phydev->pause ? "rx/tx" : "off");
+	} else {
+		dev_info(gsw->dev, "Port %d Link is Down\n", port);
+	}
+}
+
+static void mt753x_connect_internal_phys(struct gsw_mt753x *gsw,
+					 struct device_node *mii_np)
+{
+	struct device_node *phy_np;
+	struct mt753x_phy *phy;
+	int phy_mode;
+	u32 phyad;
+
+	if (!mii_np)
+		return;
+
+	for_each_child_of_node(mii_np, phy_np) {
+		if (of_property_read_u32(phy_np, "reg", &phyad))
+			continue;
+
+		if (phyad >= MT753X_NUM_PHYS)
+			continue;
+
+		phy_mode = of_get_phy_mode(phy_np);
+		if (phy_mode < 0) {
+			dev_info(gsw->dev, "incorrect phy-mode %d for PHY %d\n",
+				 phy_mode, phyad);
+			continue;
+		}
+
+		phy = &gsw->phys[phyad];
+		phy->gsw = gsw;
+
+		init_dummy_netdev(&phy->netdev);
+		phy->netdev.netdev_ops = &mt753x_dummy_netdev_ops;
+
+		phy->phydev = of_phy_connect(&phy->netdev, phy_np,
+					mt753x_phy_link_handler, 0, phy_mode);
+		if (!phy->phydev) {
+			dev_info(gsw->dev, "could not connect to PHY %d\n",
+				 phyad);
+			continue;
+		}
+
+		phy_start(phy->phydev);
+	}
+}
+
+static void mt753x_disconnect_internal_phys(struct gsw_mt753x *gsw)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gsw->phys); i++) {
+		if (gsw->phys[i].phydev) {
+			phy_stop(gsw->phys[i].phydev);
+			phy_disconnect(gsw->phys[i].phydev);
+			gsw->phys[i].phydev = NULL;
+		}
+	}
+}
+
+static int mt753x_mdio_register(struct gsw_mt753x *gsw)
+{
+	struct device_node *mii_np;
+	int i, ret;
+
+	mii_np = of_get_child_by_name(gsw->dev->of_node, "mdio-bus");
+	if (mii_np && !of_device_is_available(mii_np)) {
+		ret = -ENODEV;
+		goto err_put_node;
+	}
+
+	gsw->gphy_bus = devm_mdiobus_alloc(gsw->dev);
+	if (!gsw->gphy_bus) {
+		ret = -ENOMEM;
+		goto err_put_node;
+	}
+
+	gsw->gphy_bus->name = "mt753x_mdio";
+	gsw->gphy_bus->read = mt753x_mdio_read;
+	gsw->gphy_bus->write = mt753x_mdio_write;
+	gsw->gphy_bus->priv = gsw;
+	gsw->gphy_bus->parent = gsw->dev;
+	gsw->gphy_bus->phy_mask = BIT(MT753X_NUM_PHYS) - 1;
+//	gsw->gphy_bus->irq = gsw->phy_irqs;
+
+	for (i = 0; i < PHY_MAX_ADDR; i++)
+		gsw->gphy_bus->irq[i] = PHY_POLL;
+
+	if (mii_np)
+		snprintf(gsw->gphy_bus->id, MII_BUS_ID_SIZE, "%s@%s",
+			 mii_np->name, gsw->dev->of_node->name);
+	else
+		snprintf(gsw->gphy_bus->id, MII_BUS_ID_SIZE, "mdio@%s",
+			 gsw->dev->of_node->name);
+
+	ret = of_mdiobus_register(gsw->gphy_bus, mii_np);
+
+	if (ret) {
+		devm_mdiobus_free(gsw->dev, gsw->gphy_bus);
+		gsw->gphy_bus = NULL;
+	} else {
+		if (gsw->phy_status_poll)
+			mt753x_connect_internal_phys(gsw, mii_np);
+	}
+
+err_put_node:
+	if (mii_np)
+		of_node_put(mii_np);
+
+	return ret;
+}
+#endif
+
+static irqreturn_t mt753x_irq_handler(int irq, void *dev)
+{
+	struct gsw_mt753x *gsw = dev;
+
+	disable_irq_nosync(gsw->irq);
+
+	schedule_work(&gsw->irq_worker);
+
+	return IRQ_HANDLED;
+}
+
+static int mt753x_probe(struct platform_device *pdev)
+{
+	struct gsw_mt753x *gsw;
+	struct mt753x_sw_id *sw;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *mdio;
+	struct mii_bus *mdio_bus;
+	int ret = -EINVAL;
+	struct chip_rev rev;
+	struct mt753x_mapping *map;
+	int i;
+
+	mdio = of_parse_phandle(np, "mediatek,mdio", 0);
+	if (!mdio)
+		return -EINVAL;
+
+	mdio_bus = of_mdio_find_bus(mdio);
+	if (!mdio_bus)
+		return -EPROBE_DEFER;
+
+	gsw = devm_kzalloc(&pdev->dev, sizeof(struct gsw_mt753x), GFP_KERNEL);
+	if (!gsw)
+		return -ENOMEM;
+
+	global_gsw = gsw;
+
+	gsw->host_bus = mdio_bus;
+	gsw->dev = &pdev->dev;
+	mutex_init(&gsw->mii_lock);
+
+	/* Switch hard reset */
+	if (mt753x_hw_reset(gsw))
+		goto fail;
+
+	/* Fetch the SMI address dirst */
+	if (of_property_read_u32(np, "mediatek,smi-addr", &gsw->smi_addr))
+		gsw->smi_addr = MT753X_DFL_SMI_ADDR;
+
+	/* Get LAN/WAN port mapping */
+	map = mt753x_find_mapping(np);
+	if (map) {
+		mt753x_apply_mapping(gsw, map);
+		gsw->global_vlan_enable = 1;
+		dev_info(gsw->dev, "LAN/WAN VLAN setting=%s\n", map->name);
+	}
+
+	/* Load MAC port configurations */
+	mt753x_load_port_cfg(gsw);
+
+	/* Check for valid switch and then initialize */
+	for (i = 0; i < ARRAY_SIZE(mt753x_sw_ids); i++) {
+		if (!mt753x_sw_ids[i]->detect(gsw, &rev)) {
+			sw = mt753x_sw_ids[i];
+
+			gsw->name = rev.name;
+			gsw->model = sw->model;
+
+			dev_info(gsw->dev, "Switch is MediaTek %s rev %d",
+				 gsw->name, rev.rev);
+
+			/* Initialize the switch */
+			ret = sw->init(gsw);
+			if (ret)
+				goto fail;
+
+			break;
+		}
+	}
+
+	if (i >= ARRAY_SIZE(mt753x_sw_ids)) {
+		dev_err(gsw->dev, "No mt753x switch found\n");
+		goto fail;
+	}
+
+	gsw->irq = platform_get_irq(pdev, 0);
+	if (gsw->irq >= 0) {
+		ret = devm_request_irq(gsw->dev, gsw->irq, mt753x_irq_handler,
+				       0, dev_name(gsw->dev), gsw);
+		if (ret) {
+			dev_err(gsw->dev, "Failed to request irq %d\n",
+				gsw->irq);
+			goto fail;
+		}
+
+		INIT_WORK(&gsw->irq_worker, mt753x_irq_worker);
+	}
+
+	platform_set_drvdata(pdev, gsw);
+
+	gsw->phy_status_poll = of_property_read_bool(gsw->dev->of_node,
+						     "mediatek,phy-poll");
+
+	mt753x_add_gsw(gsw);
+#if 1 //XDXD
+	mt753x_mdio_register(gsw);
+#endif
+
+	mt753x_swconfig_init(gsw);
+
+	if (sw->post_init)
+		sw->post_init(gsw);
+
+	if (gsw->irq >= 0)
+		mt753x_irq_enable(gsw);
+
+	return 0;
+
+fail:
+	devm_kfree(&pdev->dev, gsw);
+
+	return ret;
+}
+
+static int mt753x_remove(struct platform_device *pdev)
+{
+	struct gsw_mt753x *gsw = platform_get_drvdata(pdev);
+
+	if (gsw->irq >= 0)
+		cancel_work_sync(&gsw->irq_worker);
+
+	if (gsw->reset_pin >= 0)
+		devm_gpio_free(&pdev->dev, gsw->reset_pin);
+
+#ifdef CONFIG_SWCONFIG
+	mt753x_swconfig_destroy(gsw);
+#endif
+
+#if 1 //XDXD
+	mt753x_disconnect_internal_phys(gsw);
+
+	mdiobus_unregister(gsw->gphy_bus);
+#endif
+
+	mt753x_remove_gsw(gsw);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static __maybe_unused int mt753x_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct gsw_mt753x *gsw = platform_get_drvdata(pdev);
+	int ret = 0;
+
+	pr_info("%s: Eth PHY link down with kernel suspend!\n", __func__);
+	mt753x_phy_suspend(gsw);
+
+	return ret;
+}
+
+static __maybe_unused int mt753x_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct gsw_mt753x *gsw = platform_get_drvdata(pdev);
+	int ret = 0;
+
+	pr_info("%s: Eth PHY link up with kernel resume!\n", __func__);
+	mt753x_phy_resume(gsw);
+
+	return ret;
+}
+
+static const struct of_device_id mt753x_ids[] = {
+	{ .compatible = "mediatek,mt753x" },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, mt753x_ids);
+
+static const struct dev_pm_ops mt753x_pm_ops = {
+	.suspend = mt753x_suspend,
+	.resume = mt753x_resume,
+};
+
+static struct platform_driver mt753x_driver = {
+	.probe = mt753x_probe,
+	.remove = mt753x_remove,
+	.driver = {
+		.name = "mt753x",
+		.pm = &mt753x_pm_ops,
+		.of_match_table = mt753x_ids,
+	},
+};
+
+static int __init mt753x_init(void)
+{
+	int ret;
+
+	INIT_LIST_HEAD(&mt753x_devs);
+	ret = platform_driver_register(&mt753x_driver);
+
+	mt753x_nl_init();
+
+
+	if (!proc_mt753x_dir)
+		proc_mt753x_dir = proc_mkdir(PROCREG_DIR, NULL);
+
+	proc_mt753x_wol =
+		proc_create(PROCREG_MT753X_WOL, 0, proc_mt753x_dir, &mt753x_wol_fops);
+	if (!proc_mt753x_wol)
+		pr_debug("%s: FAIL to create %s PROC !!\n", __func__, PROCREG_MT753X_WOL);
+
+	return ret;
+}
+module_init(mt753x_init);
+
+static void __exit mt753x_exit(void)
+{
+	mt753x_nl_exit();
+
+	if (proc_mt753x_wol)
+		remove_proc_entry(PROCREG_MT753X_WOL, proc_mt753x_dir);
+
+	platform_driver_unregister(&mt753x_driver);
+}
+module_exit(mt753x_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Weijie Gao <weijie.gao@mediatek.com>");
+MODULE_DESCRIPTION("Driver for MediaTek MT753x Gigabit Switch");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_nl.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_nl.c
new file mode 100755
index 0000000..48ae5cc
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_nl.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Sirui Zhao <Sirui.Zhao@mediatek.com>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <net/genetlink.h>
+
+#include "mt753x.h"
+#include "mt753x_nl.h"
+
+struct mt753x_nl_cmd_item {
+	enum mt753x_cmd cmd;
+	bool require_dev;
+	int (*process)(struct genl_info *info, struct gsw_mt753x *gsw);
+	u32 nr_required_attrs;
+	const enum mt753x_attr *required_attrs;
+};
+
+static int mt753x_nl_response(struct sk_buff *skb, struct genl_info *info);
+
+static const struct nla_policy mt753x_nl_cmd_policy[] = {
+	[MT753X_ATTR_TYPE_MESG] = { .type = NLA_STRING },
+	[MT753X_ATTR_TYPE_PHY] = { .type = NLA_S32 },
+	[MT753X_ATTR_TYPE_REG] = { .type = NLA_S32 },
+	[MT753X_ATTR_TYPE_VAL] = { .type = NLA_S32 },
+	[MT753X_ATTR_TYPE_DEV_NAME] = { .type = NLA_S32 },
+	[MT753X_ATTR_TYPE_DEV_ID] = { .type = NLA_S32 },
+	[MT753X_ATTR_TYPE_DEVAD] = { .type = NLA_S32 },
+};
+
+static const struct genl_ops mt753x_nl_ops[] = {
+	{
+		.cmd = MT753X_CMD_REQUEST,
+		.doit = mt753x_nl_response,
+//		.policy = mt753x_nl_cmd_policy,
+		.flags = GENL_ADMIN_PERM,
+	}, {
+		.cmd = MT753X_CMD_READ,
+		.doit = mt753x_nl_response,
+//		.policy = mt753x_nl_cmd_policy,
+		.flags = GENL_ADMIN_PERM,
+	}, {
+		.cmd = MT753X_CMD_WRITE,
+		.doit = mt753x_nl_response,
+//		.policy = mt753x_nl_cmd_policy,
+		.flags = GENL_ADMIN_PERM,
+	},
+};
+
+static struct genl_family mt753x_nl_family = {
+	.name =		MT753X_GENL_NAME,
+	.version =	MT753X_GENL_VERSION,
+	.maxattr =	MT753X_NR_ATTR_TYPE,
+	.ops =		mt753x_nl_ops,
+	.n_ops =	ARRAY_SIZE(mt753x_nl_ops),
+	//.policy =	mt753x_nl_cmd_policy,
+};
+
+static int mt753x_nl_list_devs(char *buff, int size)
+{
+	struct gsw_mt753x *gsw;
+	int len, total = 0;
+	char buf[80];
+
+	memset(buff, 0, size);
+
+	mt753x_lock_gsw();
+
+	list_for_each_entry(gsw, &mt753x_devs, list) {
+		len = snprintf(buf, sizeof(buf),
+			       "id: %d, model: %s, node: %s\n",
+			       gsw->id, gsw->name, gsw->dev->of_node->name);
+		strncat(buff, buf, size - total);
+		total += len;
+	}
+
+	mt753x_put_gsw();
+
+	return total;
+}
+
+static int mt753x_nl_prepare_reply(struct genl_info *info, u8 cmd,
+				   struct sk_buff **skbp)
+{
+	struct sk_buff *msg;
+	void *reply;
+
+	if (!info)
+		return -EINVAL;
+
+	msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	/* Construct send-back message header */
+	reply = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+			    &mt753x_nl_family, 0, cmd);
+	if (!reply) {
+		nlmsg_free(msg);
+		return -EINVAL;
+	}
+
+	*skbp = msg;
+	return 0;
+}
+
+static int mt753x_nl_send_reply(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
+	void *reply = genlmsg_data(genlhdr);
+
+	/* Finalize a generic netlink message (update message header) */
+	genlmsg_end(skb, reply);
+
+	/* reply to a request */
+	return genlmsg_reply(skb, info);
+}
+
+static s32 mt753x_nl_get_s32(struct genl_info *info, enum mt753x_attr attr,
+			     s32 defval)
+{
+	struct nlattr *na;
+
+	na = info->attrs[attr];
+	if (na)
+		return nla_get_s32(na);
+
+	return defval;
+}
+
+static int mt753x_nl_get_u32(struct genl_info *info, enum mt753x_attr attr,
+			     u32 *val)
+{
+	struct nlattr *na;
+
+	na = info->attrs[attr];
+	if (na) {
+		*val = nla_get_u32(na);
+		return 0;
+	}
+
+	return -1;
+}
+
+static struct gsw_mt753x *mt753x_nl_parse_find_gsw(struct genl_info *info)
+{
+	struct gsw_mt753x *gsw;
+	struct nlattr *na;
+	int gsw_id;
+
+	na = info->attrs[MT753X_ATTR_TYPE_DEV_ID];
+	if (na) {
+		gsw_id = nla_get_s32(na);
+		if (gsw_id >= 0)
+			gsw = mt753x_get_gsw(gsw_id);
+		else
+			gsw = mt753x_get_first_gsw();
+	} else {
+		gsw = mt753x_get_first_gsw();
+	}
+
+	return gsw;
+}
+
+static int mt753x_nl_get_swdevs(struct genl_info *info, struct gsw_mt753x *gsw)
+{
+	struct sk_buff *rep_skb = NULL;
+	char dev_info[512];
+	int ret;
+
+	ret = mt753x_nl_list_devs(dev_info, sizeof(dev_info) - 1);
+	if (!ret) {
+		pr_info("No switch registered\n");
+		return -EINVAL;
+	}
+
+	ret = mt753x_nl_prepare_reply(info, MT753X_CMD_REPLY, &rep_skb);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_string(rep_skb, MT753X_ATTR_TYPE_MESG, dev_info);
+	if (ret < 0)
+		goto err;
+
+	return mt753x_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static int mt753x_nl_reply_read(struct genl_info *info, struct gsw_mt753x *gsw)
+{
+	struct sk_buff *rep_skb = NULL;
+	s32 phy, devad, reg;
+	int value;
+	int ret = 0;
+
+	phy = mt753x_nl_get_s32(info, MT753X_ATTR_TYPE_PHY, -1);
+	devad = mt753x_nl_get_s32(info, MT753X_ATTR_TYPE_DEVAD, -1);
+	reg = mt753x_nl_get_s32(info, MT753X_ATTR_TYPE_REG, -1);
+
+	if (reg < 0)
+		goto err;
+
+	ret = mt753x_nl_prepare_reply(info, MT753X_CMD_READ, &rep_skb);
+	if (ret < 0)
+		goto err;
+
+	if (phy >= 0) {
+		if (devad < 0)
+			value = gsw->mii_read(gsw, phy, reg);
+		else
+			value = gsw->mmd_read(gsw, phy, devad, reg);
+	} else {
+		value = mt753x_reg_read(gsw, reg);
+	}
+
+	ret = nla_put_s32(rep_skb, MT753X_ATTR_TYPE_REG, reg);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_s32(rep_skb, MT753X_ATTR_TYPE_VAL, value);
+	if (ret < 0)
+		goto err;
+
+	return mt753x_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static int mt753x_nl_reply_write(struct genl_info *info, struct gsw_mt753x *gsw)
+{
+	struct sk_buff *rep_skb = NULL;
+	s32 phy, devad, reg;
+	u32 value;
+	int ret = 0;
+
+	phy = mt753x_nl_get_s32(info, MT753X_ATTR_TYPE_PHY, -1);
+	devad = mt753x_nl_get_s32(info, MT753X_ATTR_TYPE_DEVAD, -1);
+	reg = mt753x_nl_get_s32(info, MT753X_ATTR_TYPE_REG, -1);
+
+	if (mt753x_nl_get_u32(info, MT753X_ATTR_TYPE_VAL, &value))
+		goto err;
+
+	if (reg < 0)
+		goto err;
+
+	ret = mt753x_nl_prepare_reply(info, MT753X_CMD_WRITE, &rep_skb);
+	if (ret < 0)
+		goto err;
+
+	if (phy >= 0) {
+		if (devad < 0)
+			gsw->mii_write(gsw, phy, reg, value);
+		else
+			gsw->mmd_write(gsw, phy, devad, reg, value);
+	} else {
+		mt753x_reg_write(gsw, reg, value);
+	}
+
+	ret = nla_put_s32(rep_skb, MT753X_ATTR_TYPE_REG, reg);
+	if (ret < 0)
+		goto err;
+
+	ret = nla_put_s32(rep_skb, MT753X_ATTR_TYPE_VAL, value);
+	if (ret < 0)
+		goto err;
+
+	return mt753x_nl_send_reply(rep_skb, info);
+
+err:
+	if (rep_skb)
+		nlmsg_free(rep_skb);
+
+	return ret;
+}
+
+static const enum mt753x_attr mt753x_nl_cmd_read_attrs[] = {
+	MT753X_ATTR_TYPE_REG
+};
+
+static const enum mt753x_attr mt753x_nl_cmd_write_attrs[] = {
+	MT753X_ATTR_TYPE_REG,
+	MT753X_ATTR_TYPE_VAL
+};
+
+static const struct mt753x_nl_cmd_item mt753x_nl_cmds[] = {
+	{
+		.cmd = MT753X_CMD_REQUEST,
+		.require_dev = false,
+		.process = mt753x_nl_get_swdevs
+	}, {
+		.cmd = MT753X_CMD_READ,
+		.require_dev = true,
+		.process = mt753x_nl_reply_read,
+		.required_attrs = mt753x_nl_cmd_read_attrs,
+		.nr_required_attrs = ARRAY_SIZE(mt753x_nl_cmd_read_attrs),
+	}, {
+		.cmd = MT753X_CMD_WRITE,
+		.require_dev = true,
+		.process = mt753x_nl_reply_write,
+		.required_attrs = mt753x_nl_cmd_write_attrs,
+		.nr_required_attrs = ARRAY_SIZE(mt753x_nl_cmd_write_attrs),
+	}
+};
+
+static int mt753x_nl_response(struct sk_buff *skb, struct genl_info *info)
+{
+	struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
+	const struct mt753x_nl_cmd_item *cmditem = NULL;
+	struct gsw_mt753x *gsw = NULL;
+	u32 sat_req_attrs = 0;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(mt753x_nl_cmds); i++) {
+		if (hdr->cmd == mt753x_nl_cmds[i].cmd) {
+			cmditem = &mt753x_nl_cmds[i];
+			break;
+		}
+	}
+
+	if (!cmditem) {
+		pr_info("mt753x-nl: unknown cmd %u\n", hdr->cmd);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < cmditem->nr_required_attrs; i++) {
+		if (info->attrs[cmditem->required_attrs[i]])
+			sat_req_attrs++;
+	}
+
+	if (sat_req_attrs != cmditem->nr_required_attrs) {
+		pr_info("mt753x-nl: missing required attr(s) for cmd %u\n",
+			hdr->cmd);
+		return -EINVAL;
+	}
+
+	if (cmditem->require_dev) {
+		gsw = mt753x_nl_parse_find_gsw(info);
+		if (!gsw) {
+			pr_info("mt753x-nl: failed to find switch dev\n");
+			return -EINVAL;
+		}
+	}
+
+	ret = cmditem->process(info, gsw);
+
+	mt753x_put_gsw();
+
+	return ret;
+}
+
+int __init mt753x_nl_init(void)
+{
+	int ret;
+
+	ret = genl_register_family(&mt753x_nl_family);
+	if (ret) {
+		pr_info("mt753x-nl: genl_register_family_with_ops failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+void __exit mt753x_nl_exit(void)
+{
+	genl_unregister_family(&mt753x_nl_family);
+}
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_nl.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_nl.h
new file mode 100644
index 0000000..3c06273
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_nl.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Sirui Zhao <Sirui.Zhao@mediatek.com>
+ */
+
+#ifndef _MT753X_NL_H_
+#define _MT753X_NL_H_
+
+#define MT753X_GENL_NAME		"mt753x"
+#define MT753X_GENL_VERSION		0x1
+
+enum mt753x_cmd {
+	MT753X_CMD_UNSPEC = 0,
+	MT753X_CMD_REQUEST,
+	MT753X_CMD_REPLY,
+	MT753X_CMD_READ,
+	MT753X_CMD_WRITE,
+
+	__MT753X_CMD_MAX,
+};
+
+enum mt753x_attr {
+	MT753X_ATTR_TYPE_UNSPEC = 0,
+	MT753X_ATTR_TYPE_MESG,
+	MT753X_ATTR_TYPE_PHY,
+	MT753X_ATTR_TYPE_DEVAD,
+	MT753X_ATTR_TYPE_REG,
+	MT753X_ATTR_TYPE_VAL,
+	MT753X_ATTR_TYPE_DEV_NAME,
+	MT753X_ATTR_TYPE_DEV_ID,
+
+	__MT753X_ATTR_TYPE_MAX,
+};
+
+#define MT753X_NR_ATTR_TYPE		(__MT753X_ATTR_TYPE_MAX - 1)
+
+#ifdef __KERNEL__
+int __init mt753x_nl_init(void);
+void __exit mt753x_nl_exit(void);
+#endif /* __KERNEL__ */
+
+#endif /* _MT753X_NL_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_phy.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_phy.c
new file mode 100755
index 0000000..6906e3c
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_phy.c
@@ -0,0 +1,1067 @@
+// SPDX-License-Identifier:	GPL-2.0+
+/*
+ * Common part for MediaTek MT753x gigabit switch
+ *
+ * Copyright (C) 2018 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "mt753x.h"
+#include "mt753x_regs.h"
+#include "mt753x_phy.h"
+
+u32 tc_phy_read_dev_reg(struct gsw_mt753x *gsw, u32 port_num, u32 dev_addr, u32 reg_addr)
+{
+	u32 phy_val;
+    phy_val = gsw->mmd_read(gsw, port_num, dev_addr, reg_addr);
+    
+    //printk("switch phy cl45 r %d 0x%x 0x%x = %x\n",port_num, dev_addr, reg_addr, phy_val);
+	//switch_phy_read_cl45(port_num, dev_addr, reg_addr, &phy_val);
+	return phy_val;
+}
+
+void tc_phy_write_dev_reg(struct gsw_mt753x *gsw, u32 port_num, u32 dev_addr, u32 reg_addr, u32 write_data)
+{
+	u32 phy_val;
+    gsw->mmd_write(gsw, port_num, dev_addr, reg_addr, write_data);
+    phy_val = gsw->mmd_read(gsw, port_num, dev_addr, reg_addr);
+    //printk("switch phy cl45 w %d 0x%x 0x%x 0x%x --> read back 0x%x\n",port_num, dev_addr, reg_addr, write_data, phy_val);
+	//switch_phy_write_cl45(port_num, dev_addr, reg_addr, write_data);
+}
+
+void switch_phy_write(struct gsw_mt753x *gsw, u32 port_num, u32 reg_addr, u32 write_data){
+	gsw->mii_write(gsw, port_num, reg_addr, write_data);
+}
+
+u32 switch_phy_read(struct gsw_mt753x *gsw, u32 port_num, u32 reg_addr){
+	return gsw->mii_read(gsw, port_num, reg_addr);
+}
+
+const u8 MT753x_ZCAL_TO_R50ohm_GE_TBL_100[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 127, 127, 123, 122, 117,
+	115, 112, 103, 100, 98, 87, 85, 83,
+	81, 72, 70, 68, 66, 64, 55, 53,
+	52, 50, 49, 48, 38, 36, 35, 34,
+	33, 32, 22, 21, 20, 19, 18, 17,
+	16, 7, 6, 5, 4, 3, 2, 1,
+	0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const u8 MT753x_TX_OFFSET_TBL[64] = {
+	0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18,
+	0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
+	0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
+	0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
+};
+
+u8 ge_cal_flag;
+
+u8 all_ge_ana_cal_wait(struct gsw_mt753x *gsw, u32 delay, u32 phyaddr) // for EN7512 
+{
+	u8 all_ana_cal_status;	
+	u32 cnt, tmp_1e_17c;
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017c, 0x0001);	// da_calin_flag pull high
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0x0001);
+	//printk("delay = %d\n", delay);
+	
+	cnt = 10000;
+	do {
+		udelay(delay);
+		cnt--;
+		all_ana_cal_status = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x17b) & 0x1;
+
+	} while ((all_ana_cal_status == 0) && (cnt != 0));
+
+
+	if(all_ana_cal_status == 1) {
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0);
+		return all_ana_cal_status;
+	} else {
+		tmp_1e_17c = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x17c);
+		if ((tmp_1e_17c & 0x1) != 1) {
+			pr_info("FIRST MDC/MDIO write error\n");
+			pr_info("FIRST 1e_17c = %x\n", tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x17c));
+
+		}
+		printk("re-K again\n");
+        
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0);
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0x0001);
+		cnt = 10000;
+		do {
+			udelay(delay);
+			cnt--;
+			tmp_1e_17c = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x17c);
+			if ((tmp_1e_17c & 0x1) != 1) {
+				pr_info("SECOND MDC/MDIO write error\n");
+				pr_info("SECOND 1e_17c = %x\n", tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x17c));
+				tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0x0001);
+				tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0x0001);
+				tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0x0001);
+			}
+		} while ((cnt != 0) && (tmp_1e_17c == 0));
+
+		cnt = 10000;
+		do {
+			udelay(delay);
+			cnt--;
+			all_ana_cal_status = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x17b) & 0x1;
+	
+		} while ((all_ana_cal_status == 0) && (cnt != 0));
+	
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x17c, 0);
+	}
+
+    if(all_ana_cal_status == 0){
+        pr_info("!!!!!!!!!!!! dev1Eh_reg17b ERROR\n");
+    }
+	
+	return all_ana_cal_status;
+}
+
+
+
+
+int ge_cal_rext(struct gsw_mt753x *gsw, u8 phyaddr, u32 delay)
+{
+	u8 rg_zcal_ctrl, all_ana_cal_status;
+	u16 ad_cal_comp_out_init;
+	u16 dev1e_e0_ana_cal_r5;
+	int calibration_polarity;
+	u8 cnt = 0;
+	u16 dev1e_17a_tmp, dev1e_e0_tmp;
+
+	/* *** Iext/Rext Cal start ************ */
+	all_ana_cal_status = ANACAL_INIT;
+	/* analog calibration enable, Rext calibration enable */
+	/* 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a */
+	/* 1e_dc[0]:rg_txvos_calen */
+	/* 1e_e1[4]:rg_cal_refsel(0:1.2V) */
+	//tc_phy_write_dev_reg(phyaddr, 0x1e, 0x00db, 0x1110)
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x1110);
+	//tc_phy_write_dev_reg(phyaddr, 0x1e, 0x00dc, 0x0000);
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0);
+	//tc_phy_write_dev_reg(phyaddr, 0x1e, 0x00e1, 0x0000);
+	//tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e1, 0x10);
+	
+	rg_zcal_ctrl = 0x20;/* start with 0 dB */
+	dev1e_e0_ana_cal_r5 = tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0xe0); // get default value
+	/* 1e_e0[5:0]:rg_zcal_ctrl */
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0xe0, rg_zcal_ctrl);
+	all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr);/* delay 20 usec */
+
+	if (all_ana_cal_status == 0) {
+		all_ana_cal_status = ANACAL_ERROR;
+		printk(" GE Rext AnaCal ERROR init!   \r\n");
+		return -1;
+	}
+	/* 1e_17a[8]:ad_cal_comp_out */
+	ad_cal_comp_out_init = (tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x017a) >> 8) & 0x1;
+	if (ad_cal_comp_out_init == 1)
+		calibration_polarity = -1;
+	else /* ad_cal_comp_out_init == 0 */
+		calibration_polarity = 1;
+	cnt = 0;
+	while (all_ana_cal_status < ANACAL_ERROR) {
+		cnt++;
+		rg_zcal_ctrl += calibration_polarity;
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0xe0, (rg_zcal_ctrl));
+		all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); /* delay 20 usec */
+		dev1e_17a_tmp = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x017a);
+		if (all_ana_cal_status == 0) {
+			all_ana_cal_status = ANACAL_ERROR;
+			printk("  GE Rext AnaCal ERROR 2!   \r\n");
+			return -1;
+		} else if (((dev1e_17a_tmp >> 8) & 0x1) != ad_cal_comp_out_init) {
+			all_ana_cal_status = ANACAL_FINISH;
+			//printk("  GE Rext AnaCal Done! (%d)(0x%x)  \r\n", cnt, rg_zcal_ctrl);
+		} else {
+			dev1e_17a_tmp = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x017a);
+			dev1e_e0_tmp =	tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0xe0);
+			if ((rg_zcal_ctrl == 0x3F) || (rg_zcal_ctrl == 0x00)) {
+				all_ana_cal_status = ANACAL_SATURATION;  /* need to FT(IC fail?) */
+				printk(" GE Rext AnaCal Saturation!  \r\n");
+				rg_zcal_ctrl = 0x20;  /* 0 dB */
+			} 
+		}
+	}
+
+	if (all_ana_cal_status == ANACAL_ERROR) {
+		rg_zcal_ctrl = 0x20;  /* 0 dB */
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e0, (dev1e_e0_ana_cal_r5 | rg_zcal_ctrl));
+	} else if(all_ana_cal_status == ANACAL_FINISH){
+		//tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e0, (dev1e_e0_ana_cal_r5 | rg_zcal_ctrl));
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e0, ((rg_zcal_ctrl << 8) | rg_zcal_ctrl));
+		printk("0x1e-e0 = %x\n", tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x00e0));
+		/* ****  1f_115[2:0] = rg_zcal_ctrl[5:3]  // Mog review */
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1f, 0x0115, ((rg_zcal_ctrl & 0x3f) >> 3));
+		printk("0x1f-115 = %x\n", tc_phy_read_dev_reg(gsw,  PHY0, 0x1f, 0x115));
+		printk("  GE Rext AnaCal Done! (%d)(0x%x)  \r\n", cnt, rg_zcal_ctrl);
+		ge_cal_flag = 1;
+	} else {
+		printk("GE Rxet cal something wrong2\n");
+	}
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x0000);
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0000);
+	
+	return 0;
+}
+
+//-----------------------------------------------------------------
+int ge_cal_r50(struct gsw_mt753x *gsw, u8 phyaddr, u32 delay)
+{
+	u8 rg_zcal_ctrl, all_ana_cal_status, calibration_pair;
+	u16 ad_cal_comp_out_init;
+	u16 dev1e_e0_ana_cal_r5;
+	int calibration_polarity;
+	u8 cnt = 0;
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x1100);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0000);	// 1e_dc[0]:rg_txvos_calen
+
+	for(calibration_pair = ANACAL_PAIR_A; calibration_pair <= ANACAL_PAIR_D; calibration_pair ++) {
+		rg_zcal_ctrl = 0x20;  						// start with 0 dB
+		dev1e_e0_ana_cal_r5 = (tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x00e0) & (~0x003f));
+		tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e0, (dev1e_e0_ana_cal_r5 | rg_zcal_ctrl));	// 1e_e0[5:0]:rg_zcal_ctrl
+		if(calibration_pair == ANACAL_PAIR_A)
+		{
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x1101);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0000);	
+			//printk("R50 pair A 1e_db=%x 1e_db=%x\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00db), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00dc));
+
+		}
+		else if(calibration_pair == ANACAL_PAIR_B)
+		{
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x1100);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x1000);	// 1e_dc[12]:rg_zcalen_b
+			//printk("R50 pair B 1e_db=%x 1e_db=%x\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00db),tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00dc));
+
+		}
+		else if(calibration_pair == ANACAL_PAIR_C)
+		{
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x1100);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0100);	// 1e_dc[8]:rg_zcalen_c
+			//printk("R50 pair C 1e_db=%x 1e_db=%x\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00db), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00dc));
+
+		}
+		else // if(calibration_pair == ANACAL_PAIR_D)
+		{
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x1100);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0010);	// 1e_dc[4]:rg_zcalen_d
+			//printk("R50 pair D 1e_db=%x 1e_db=%x\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00db), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x00dc));
+
+		}
+
+		all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); // delay 20 usec
+		if(all_ana_cal_status == 0)
+		{
+			all_ana_cal_status = ANACAL_ERROR;	
+			printk( "GE R50 AnaCal ERROR init!   \r\n");
+			return -1;
+		}
+	
+		ad_cal_comp_out_init = (tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x017a)>>8) & 0x1;		// 1e_17a[8]:ad_cal_comp_out	
+		if(ad_cal_comp_out_init == 1)
+			calibration_polarity = -1;
+		else
+			calibration_polarity = 1;
+
+		cnt = 0;
+		while(all_ana_cal_status < ANACAL_ERROR)
+		{
+			cnt ++;
+			rg_zcal_ctrl += calibration_polarity;
+			tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e0, (dev1e_e0_ana_cal_r5 | rg_zcal_ctrl));
+			all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); // delay 20 usec
+
+			if(all_ana_cal_status == 0)
+			{
+				all_ana_cal_status = ANACAL_ERROR;	
+				printk( "  GE R50 AnaCal ERROR 2!   \r\n");
+				return -1;
+			}
+			else if(((tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x017a)>>8)&0x1) != ad_cal_comp_out_init) 
+			{
+				all_ana_cal_status = ANACAL_FINISH;	
+			}
+			else {
+				if((rg_zcal_ctrl == 0x3F)||(rg_zcal_ctrl == 0x00))	
+				{
+					all_ana_cal_status = ANACAL_SATURATION;  // need to FT
+					printk( " GE R50 AnaCal Saturation!  \r\n");
+				}
+			}
+		}
+		
+		if(all_ana_cal_status == ANACAL_ERROR) {	
+			rg_zcal_ctrl = 0x20;  // 0 dB
+			//tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00e0, (dev1e_e0_ana_cal_r5 | rg_zcal_ctrl));
+		}
+		else {
+			rg_zcal_ctrl = MT753x_ZCAL_TO_R50ohm_GE_TBL_100[rg_zcal_ctrl - 9];	// wait Mog zcal/r50 mapping table
+			printk( " GE R50 AnaCal Done! (%d) (0x%x)(0x%x) \r\n", cnt, rg_zcal_ctrl, (rg_zcal_ctrl|0x80));
+		}
+		
+		if(calibration_pair == ANACAL_PAIR_A) {
+			ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174) & (~0x7f00);
+			//ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174);
+			//printk( " GE-a 1e_174(0x%x)(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), ad_cal_comp_out_init, tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0174, (ad_cal_comp_out_init | (((rg_zcal_ctrl<<8)&0xff00) | 0x8000)));	// 1e_174[15:8]
+			//printk( " GE-a 1e_174(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+		}
+		else if(calibration_pair == ANACAL_PAIR_B) {
+			ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174) & (~0x007f);
+			//ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174);
+			//printk( " GE-b 1e_174(0x%x)(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), ad_cal_comp_out_init, tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+			
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0174, (ad_cal_comp_out_init | (((rg_zcal_ctrl<<0)&0x00ff) | 0x0080)));	// 1e_174[7:0]
+			//printk( " GE-b 1e_174(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+		}
+		else if(calibration_pair == ANACAL_PAIR_C) {
+			ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175) & (~0x7f00);
+			//ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175);
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0175, (ad_cal_comp_out_init | (((rg_zcal_ctrl<<8)&0xff00) | 0x8000)));	// 1e_175[15:8]
+			//printk( " GE-c 1e_174(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+		} else {// if(calibration_pair == ANACAL_PAIR_D) 
+			ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175) & (~0x007f);
+			//ad_cal_comp_out_init = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175);
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0175, (ad_cal_comp_out_init | (((rg_zcal_ctrl<<0)&0x00ff) | 0x0080)));	// 1e_175[7:0]
+			//printk( " GE-d 1e_174(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+		}
+		//tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00e0, ((rg_zcal_ctrl<<8)|rg_zcal_ctrl));
+	}
+	
+	printk( " GE 1e_174(0x%x), 1e_175(0x%x)  \r\n", tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0174), tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x0175));
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x0000);
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0000);
+	
+	return 0;
+}
+
+int ge_cal_tx_offset(struct gsw_mt753x *gsw,  u8 phyaddr, u32 delay)
+{
+	u8 all_ana_cal_status, calibration_pair;
+	u16 ad_cal_comp_out_init;
+	int calibration_polarity, tx_offset_temp;
+	u8 tx_offset_reg_shift, tabl_idx, i;
+	u8 cnt = 0;
+	u16 tx_offset_reg, reg_temp, cal_temp;
+	//switch_phy_write(phyaddr, R0, 0x2100);//harry tmp
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x0100);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0001);	// 1e_dc[0]:rg_txvos_calen
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0096, 0x8000);	// 1e_96[15]:bypass_tx_offset_cal, Hw bypass, Fw cal
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x003e, 0xf808);	// 1e_3e
+	for(i = 0; i <= 4; i++)
+		tc_phy_write_dev_reg(gsw, i, 0x1e, 0x00dd, 0x0000);	
+	for(calibration_pair = ANACAL_PAIR_A; calibration_pair <= ANACAL_PAIR_D; calibration_pair ++)
+	{
+		tabl_idx = 31;
+		tx_offset_temp = MT753x_TX_OFFSET_TBL[tabl_idx];
+
+		if(calibration_pair == ANACAL_PAIR_A) {
+			//tc_phy_write_dev_reg(phyaddr, 0x1e, 0x145, 0x5010);
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x1000);				// 1e_dd[12]:rg_txg_calen_a
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017d, (0x8000|DAC_IN_0V));	// 1e_17d:dac_in0_a
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0181, (0x8000|DAC_IN_0V));	// 1e_181:dac_in1_a
+			//printk("tx offset pairA 1e_dd = %x, 1e_17d=%x, 1e_181=%x\n", tc_phy_read_dev_reg(phyaddr, 0x1e, 0x00dd), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x017d), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x0181));
+			reg_temp = (tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x0172) & (~0x3f00));
+			tx_offset_reg_shift = 8;									// 1e_172[13:8]
+			tx_offset_reg = 0x0172;
+
+			//tc_phy_write_dev_reg(phyaddr, 0x1e, tx_offset_reg, (reg_temp|(tx_offset_temp<<tx_offset_reg_shift)));
+		} else if(calibration_pair == ANACAL_PAIR_B) {
+			//tc_phy_write_dev_reg(phyaddr, 0x1e, 0x145, 0x5018);
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0100);				// 1e_dd[8]:rg_txg_calen_b
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017e, (0x8000|DAC_IN_0V));	// 1e_17e:dac_in0_b
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0182, (0x8000|DAC_IN_0V));	// 1e_182:dac_in1_b
+			//printk("tx offset pairB 1e_dd = %x, 1e_17d=%x, 1e_181=%x\n", tc_phy_read_dev_reg(phyaddr, 0x1e, 0x00dd), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x017d), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x0181));
+			reg_temp = (tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x0172) & (~0x003f));
+			tx_offset_reg_shift = 0;									// 1e_172[5:0]
+			tx_offset_reg = 0x0172;
+			//tc_phy_write_dev_reg(phyaddr, 0x1e, tx_offset_reg, (reg_temp|(tx_offset_temp<<tx_offset_reg_shift)));
+		} else if(calibration_pair == ANACAL_PAIR_C) {
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0010);				// 1e_dd[4]:rg_txg_calen_c
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017f, (0x8000|DAC_IN_0V));	// 1e_17f:dac_in0_c
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0183, (0x8000|DAC_IN_0V));	// 1e_183:dac_in1_c
+			reg_temp = (tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x0173) & (~0x3f00));
+			//printk("tx offset pairC 1e_dd = %x, 1e_17d=%x, 1e_181=%x\n", tc_phy_read_dev_reg(phyaddr, 0x1e, 0x00dd), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x017d), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x0181));
+			tx_offset_reg_shift = 8;									// 1e_173[13:8]
+			tx_offset_reg = 0x0173;
+			//tc_phy_write_dev_reg(phyaddr, 0x1e, tx_offset_reg, (reg_temp|(tx_offset_temp<<tx_offset_reg_shift)));
+		} else {// if(calibration_pair == ANACAL_PAIR_D)
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0001);				// 1e_dd[0]:rg_txg_calen_d
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0180, (0x8000|DAC_IN_0V));	// 1e_180:dac_in0_d
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0184, (0x8000|DAC_IN_0V));	// 1e_184:dac_in1_d
+			//printk("tx offset pairD 1e_dd = %x, 1e_17d=%x, 1e_181=%x\n", tc_phy_read_dev_reg(phyaddr, 0x1e, 0x00dd), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x017d), tc_phy_read_dev_reg(phyaddr, 0x1e, 0x0181));
+			reg_temp = (tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x0173) & (~0x003f));
+			tx_offset_reg_shift = 0;									// 1e_173[5:0]
+			tx_offset_reg = 0x0173;
+			//tc_phy_write_dev_reg(phyaddr, 0x1e, tx_offset_reg, (reg_temp|(tx_offset_temp<<tx_offset_reg_shift)));
+		}
+		tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_offset_reg, (reg_temp|(tx_offset_temp<<tx_offset_reg_shift)));	// 1e_172, 1e_173
+		all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); // delay 20 usec
+		if(all_ana_cal_status == 0) {
+			all_ana_cal_status = ANACAL_ERROR;	
+			printk( " GE Tx offset AnaCal ERROR init!   \r\n");
+			return -1;
+		}
+	
+		ad_cal_comp_out_init = (tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x017a)>>8) & 0x1;		// 1e_17a[8]:ad_cal_comp_out	
+		if(ad_cal_comp_out_init == 1)
+			calibration_polarity = 1;
+		else
+			calibration_polarity = -1;
+
+		cnt = 0;
+		//printk("TX offset cnt = %d, tabl_idx= %x, offset_val = %x\n", cnt, tabl_idx, MT753x_TX_OFFSET_TBL[tabl_idx]);
+		while(all_ana_cal_status < ANACAL_ERROR) {
+			
+			cnt ++;
+			tabl_idx += calibration_polarity;
+			//tx_offset_temp += calibration_polarity;
+			//cal_temp = tx_offset_temp;
+			cal_temp = MT753x_TX_OFFSET_TBL[tabl_idx];
+			//printk("TX offset cnt = %d, tabl_idx= %x, offset_val = %x\n", cnt, tabl_idx, MT753x_TX_OFFSET_TBL[tabl_idx]);
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_offset_reg, (reg_temp|(cal_temp<<tx_offset_reg_shift)));
+
+			all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); // delay 20 usec
+			if(all_ana_cal_status == 0) {
+				all_ana_cal_status = ANACAL_ERROR;	
+				printk( " GE Tx offset AnaCal ERROR init 2!   \r\n");
+				return -1;
+			} else if(((tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x017a)>>8)&0x1) != ad_cal_comp_out_init) {
+				all_ana_cal_status = ANACAL_FINISH;	
+			} else {
+				if((tabl_idx == 0)||(tabl_idx == 0x3f)) {
+					all_ana_cal_status = ANACAL_SATURATION;  // need to FT
+					printk( " GE Tx offset AnaCal Saturation!  \r\n");
+				}
+			}
+		}
+		
+		if(all_ana_cal_status == ANACAL_ERROR) {	
+			tx_offset_temp = TX_AMP_OFFSET_0MV;
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_offset_reg, (reg_temp|(tx_offset_temp<<tx_offset_reg_shift)));
+		} else {
+			printk( " GE Tx offset AnaCal Done! (pair-%d)(%d)(0x%x) 0x1e_%x=0x%x\n", calibration_pair, cnt, MT753x_TX_OFFSET_TBL[tabl_idx], tx_offset_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_offset_reg));
+		}
+	}
+
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017d, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017e, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017f, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0180, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0181, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0182, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0183, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0184, 0x0000);
+	
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x0000);	// disable analog calibration circuit
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0000);	// disable Tx offset calibration circuit
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x0000);	// disable analog calibration circuit
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0000);	// disable Tx offset calibration circuit
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x003e, 0x0000);	// disable Tx VLD force mode
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0000);	// disable Tx offset/amplitude calibration circuit
+	
+	return 0;
+}
+
+int ge_cal_tx_amp(struct gsw_mt753x *gsw, u8 phyaddr, u32 delay)
+{
+	u8	all_ana_cal_status, calibration_pair, i;
+	u16	ad_cal_comp_out_init;
+	int	calibration_polarity;
+	u32	tx_amp_reg_shift; 
+	u16	reg_temp;
+	u32	tx_amp_temp, tx_amp_reg, cnt=0, tx_amp_reg_100;
+	u32	debug_tmp, reg_backup, reg_tmp; 
+	u32	orig_1e_11, orig_1f_300;
+
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x1100);	// 1e_db[12]:rg_cal_ckinv, [8]:rg_ana_calen, [4]:rg_rext_calen, [0]:rg_zcalen_a
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0001);	// 1e_dc[0]:rg_txvos_calen
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e1, 0x0010);	// 1e_e1[4]:select 1V
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x003e, 0xf808);	// 1e_3e:enable Tx VLD
+
+	orig_1e_11 = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x11);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x11, 0xff00);
+//	tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x27a, 0x33);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0xc9, 0xffff);
+	orig_1f_300 = tc_phy_read_dev_reg(gsw, phyaddr, 0x1f, 0x300);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x300, 0x4);
+	for(i = 0; i <= 4; i++)
+		tc_phy_write_dev_reg(gsw, i, 0x1e, 0x00dd, 0x0000);
+	for(calibration_pair = ANACAL_PAIR_A; calibration_pair <= ANACAL_PAIR_D; calibration_pair ++) {
+		tx_amp_temp = 0x20;	// start with 0 dB
+
+		if(calibration_pair == ANACAL_PAIR_A) {
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x1000);				// 1e_dd[12]:tx_a amp calibration enable
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017d, (0x8000|DAC_IN_2V));	// 1e_17d:dac_in0_a	
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0181, (0x8000|DAC_IN_2V));	// 1e_181:dac_in1_a
+			reg_temp = (tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x012) & (~0xfc00));
+			tx_amp_reg_shift = 10;										// 1e_12[15:10]
+			tx_amp_reg = 0x12;
+			tx_amp_reg_100 = 0x16;
+		} else if(calibration_pair == ANACAL_PAIR_B) {
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0100);				// 1e_dd[8]:tx_b amp calibration enable
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017e, (0x8000|DAC_IN_2V));	// 1e_17e:dac_in0_b
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0182, (0x8000|DAC_IN_2V));	// 1e_182:dac_in1_b
+			reg_temp = (tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x017) & (~0x3f00));
+			tx_amp_reg_shift = 8;										// 1e_17[13:8]
+			tx_amp_reg = 0x17;
+			tx_amp_reg_100 = 0x18;
+		} else if(calibration_pair == ANACAL_PAIR_C) {
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0010);				// 1e_dd[4]:tx_c amp calibration enable
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017f, (0x8000|DAC_IN_2V));	// 1e_17f:dac_in0_c
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0183, (0x8000|DAC_IN_2V));	// 1e_183:dac_in1_c
+			reg_temp = (tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x019) & (~0x3f00));
+			tx_amp_reg_shift = 8;										// 1e_19[13:8]
+			tx_amp_reg = 0x19;
+			tx_amp_reg_100 = 0x20;
+		} else { //if(calibration_pair == ANACAL_PAIR_D)
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0001);				// 1e_dd[0]:tx_d amp calibration enable
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0180, (0x8000|DAC_IN_2V));	// 1e_180:dac_in0_d
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0184, (0x8000|DAC_IN_2V));	// 1e_184:dac_in1_d
+			reg_temp = (tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x021) & (~0x3f00));
+			tx_amp_reg_shift = 8;										// 1e_21[13:8]
+			tx_amp_reg = 0x21;
+			tx_amp_reg_100 = 0x22;
+		}
+		tc_phy_write_dev_reg( gsw, phyaddr, 0x1e, tx_amp_reg, (tx_amp_temp|(tx_amp_temp<<tx_amp_reg_shift)));	// 1e_12, 1e_17, 1e_19, 1e_21
+		tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100, (tx_amp_temp|(tx_amp_temp<<tx_amp_reg_shift)));
+		all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); 	// delay 20 usec
+		if(all_ana_cal_status == 0) {
+			all_ana_cal_status = ANACAL_ERROR;	
+			printk( " GE Tx amp AnaCal ERROR init init!   \r\n");
+			return -1;
+		}
+	
+		ad_cal_comp_out_init = (tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x017a)>>8) & 0x1;		// 1e_17a[8]:ad_cal_comp_out
+		if(ad_cal_comp_out_init == 1)
+			calibration_polarity = -1;
+		else
+			calibration_polarity = 1;
+
+		cnt =0;
+		while(all_ana_cal_status < ANACAL_ERROR) {
+			cnt ++;
+			tx_amp_temp += calibration_polarity;
+			//printk("tx_amp : %x, 1e %x = %x\n", tx_amp_temp, tx_amp_reg, (reg_temp|(tx_amp_temp<<tx_amp_reg_shift)));
+			tc_phy_write_dev_reg( gsw, phyaddr, 0x1e, tx_amp_reg, (tx_amp_temp|(tx_amp_temp<<tx_amp_reg_shift)));
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100, (tx_amp_temp|(tx_amp_temp<<tx_amp_reg_shift)));
+			all_ana_cal_status = all_ge_ana_cal_wait(gsw, delay, phyaddr); // delay 20 usec
+			if(all_ana_cal_status == 0) {
+				all_ana_cal_status = ANACAL_ERROR;	
+				printk( " GE Tx amp AnaCal ERROR 2!   \r\n");
+				return -1;
+			} else if(((tc_phy_read_dev_reg(gsw,  PHY0, 0x1e, 0x017a)>>8)&0x1) != ad_cal_comp_out_init) {
+				//printk("TX AMP ANACAL_FINISH\n");
+				all_ana_cal_status = ANACAL_FINISH;
+				if (phyaddr == 0) {
+					if (calibration_pair == ANACAL_PAIR_A)
+						tx_amp_temp = tx_amp_temp - 2;
+					else if(calibration_pair == ANACAL_PAIR_B)
+						tx_amp_temp = tx_amp_temp - 1;
+					else if(calibration_pair == ANACAL_PAIR_C)
+						tx_amp_temp = tx_amp_temp - 2;
+					else if(calibration_pair == ANACAL_PAIR_D)
+						tx_amp_temp = tx_amp_temp - 1;
+				} else if (phyaddr == 1) {
+					if (calibration_pair == ANACAL_PAIR_A)
+						tx_amp_temp = tx_amp_temp - 1;
+					else if(calibration_pair == ANACAL_PAIR_B)
+						tx_amp_temp = tx_amp_temp ;
+					else if(calibration_pair == ANACAL_PAIR_C)
+						tx_amp_temp = tx_amp_temp - 1;
+					else if(calibration_pair == ANACAL_PAIR_D)
+						tx_amp_temp = tx_amp_temp - 1;
+				} else if (phyaddr == 2) {
+					if (calibration_pair == ANACAL_PAIR_A)
+						tx_amp_temp = tx_amp_temp;
+					else if(calibration_pair == ANACAL_PAIR_B)
+						tx_amp_temp = tx_amp_temp - 1;
+					else if(calibration_pair == ANACAL_PAIR_C)
+						tx_amp_temp = tx_amp_temp;
+					else if(calibration_pair == ANACAL_PAIR_D)
+						tx_amp_temp = tx_amp_temp - 1;
+				} else if (phyaddr == 3) {
+					tx_amp_temp = tx_amp_temp;
+				} else if (phyaddr == 4) {
+					if (calibration_pair == ANACAL_PAIR_A)
+						tx_amp_temp = tx_amp_temp;
+					else if(calibration_pair == ANACAL_PAIR_B)
+						tx_amp_temp = tx_amp_temp - 1;
+					else if(calibration_pair == ANACAL_PAIR_C)
+						tx_amp_temp = tx_amp_temp;
+					else if(calibration_pair == ANACAL_PAIR_D)
+						tx_amp_temp = tx_amp_temp;
+				}								
+				reg_temp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, tx_amp_reg)&(~0xff00);
+				tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)));
+				tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, (tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)));
+				if (phyaddr == 0) {
+					if ((tx_amp_reg == 0x12) || (tx_amp_reg == 0x17)) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 7));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if (tx_amp_reg_100 == 0x16) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp+1+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+					if (tx_amp_reg_100 == 0x18) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+				} else if (phyaddr == 1) {
+					if (tx_amp_reg == 0x12) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 9));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if (tx_amp_reg == 0x17){
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 7));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if (tx_amp_reg_100 == 0x16) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+					if (tx_amp_reg_100 == 0x18) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp-1+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+				} else if (phyaddr == 2) {
+					if ((tx_amp_reg == 0x12) || (tx_amp_reg == 0x17)) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 6));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if ((tx_amp_reg_100 == 0x16) || (tx_amp_reg_100 == 0x18)) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp-1+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+				} else if (phyaddr == 3) {
+					if (tx_amp_reg == 0x12) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 4));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if (tx_amp_reg == 0x17) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 7));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if (tx_amp_reg_100 == 0x16) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp-2+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+					if (tx_amp_reg_100 == 0x18) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp-1+3)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+				} else if (phyaddr == 4) {
+					if ((tx_amp_reg == 0x12) || (tx_amp_reg == 0x17)) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, ((tx_amp_temp|((tx_amp_temp)<<tx_amp_reg_shift)) + 5));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+					}
+					if (tx_amp_reg_100 == 0x16) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp-2+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+					if (tx_amp_reg_100 == 0x18) {
+						//printk("before : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+						tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100,(tx_amp_temp|((tx_amp_temp-1+4)<<tx_amp_reg_shift)));
+						//printk("after : PORT[%d] 1e_%x = %x\n", phyaddr, tx_amp_reg_100, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg_100));
+					}
+				}	
+
+				if (calibration_pair == ANACAL_PAIR_A){
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x12);
+					reg_tmp = ((reg_backup & 0xfc00) >> 10);
+					reg_tmp -= 8;
+                                       reg_backup = 0x0000;
+                                       reg_backup |= ((reg_tmp << 10) | (reg_tmp << 0));
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x12, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x12);
+					//printk("PORT[%d] 1e.012 = %x (OFFSET_1000M_PAIR_A)\n", phyaddr, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x16);
+					reg_tmp = ((reg_backup & 0x3f) >> 0);
+					reg_tmp -= 8;
+					reg_backup = (reg_backup & (~0x3f));
+					reg_backup |= (reg_tmp << 0);
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x16, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x16);
+					//printk("PORT[%d] 1e.016 = %x (OFFSET_TESTMODE_1000M_PAIR_A)\n", phyaddr, reg_backup);
+				}
+				else if(calibration_pair == ANACAL_PAIR_B){
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x17);
+					reg_tmp = ((reg_backup & 0x3f00) >> 8);
+					reg_tmp -= 8;
+                                       reg_backup = 0x0000;
+                                       reg_backup |= ((reg_tmp << 8) | (reg_tmp << 0));
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x17, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x17);
+					//printk("PORT[%d] 1e.017 = %x (OFFSET_1000M_PAIR_B)\n", phyaddr, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x18);
+					reg_tmp = ((reg_backup & 0x3f) >> 0);
+					reg_tmp -= 8;
+					reg_backup = (reg_backup & (~0x3f));
+					reg_backup |= (reg_tmp << 0);
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x18, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x18);
+					//printk("PORT[%d] 1e.018 = %x (OFFSET_TESTMODE_1000M_PAIR_B)\n", phyaddr, reg_backup);
+				}
+				else if(calibration_pair == ANACAL_PAIR_C){
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x19);
+					reg_tmp = ((reg_backup & 0x3f00) >> 8);
+					reg_tmp -= 8;
+					reg_backup = (reg_backup & (~0x3f00));
+					reg_backup |= (reg_tmp << 8);
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x19, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x19);
+					//printk("PORT[%d] 1e.019 = %x (OFFSET_1000M_PAIR_C)\n", phyaddr, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x20);
+					reg_tmp = ((reg_backup & 0x3f) >> 0);
+					reg_tmp -= 8;
+					reg_backup = (reg_backup & (~0x3f));
+					reg_backup |= (reg_tmp << 0);
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x20, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x20);
+					//printk("PORT[%d] 1e.020 = %x (OFFSET_TESTMODE_1000M_PAIR_C)\n", phyaddr, reg_backup);
+				}
+				else if(calibration_pair == ANACAL_PAIR_D){
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x21);
+					reg_tmp = ((reg_backup & 0x3f00) >> 8);
+					reg_tmp -= 8;
+					reg_backup = (reg_backup & (~0x3f00));
+					reg_backup |= (reg_tmp << 8);
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x21, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x21);
+					//printk("PORT[%d] 1e.021 = %x (OFFSET_1000M_PAIR_D)\n", phyaddr, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x22);
+					reg_tmp = ((reg_backup & 0x3f) >> 0);
+					reg_tmp -= 8;
+					reg_backup = (reg_backup & (~0x3f));
+					reg_backup |= (reg_tmp << 0);
+					tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x22, reg_backup);
+					reg_backup = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x22);
+					//printk("PORT[%d] 1e.022 = %x (OFFSET_TESTMODE_1000M_PAIR_D)\n", phyaddr, reg_backup);
+				}
+
+				if (calibration_pair == ANACAL_PAIR_A){
+					//printk("PORT (%d) TX_AMP PAIR (A) FINAL CALIBRATION RESULT\n", phyaddr);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x12);
+					//printk("1e.012 = 0x%x\n", debug_tmp);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x16);
+					//printk("1e.016 = 0x%x\n", debug_tmp);
+				}
+	
+				else if(calibration_pair == ANACAL_PAIR_B){
+					//printk("PORT (%d) TX_AMP PAIR (A) FINAL CALIBRATION RESULT\n", phyaddr);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x17);
+					//printk("1e.017 = 0x%x\n", debug_tmp);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x18);
+					//printk("1e.018 = 0x%x\n", debug_tmp);
+				}
+				else if(calibration_pair == ANACAL_PAIR_C){
+					//printk("PORT (%d) TX_AMP PAIR (A) FINAL CALIBRATION RESULT\n", phyaddr);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x19);
+					//printk("1e.019 = 0x%x\n", debug_tmp);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x20);
+					//printk("1e.020 = 0x%x\n", debug_tmp);
+				}
+				else if(calibration_pair == ANACAL_PAIR_D){
+					//printk("PORT (%d) TX_AMP PAIR (A) FINAL CALIBRATION RESULT\n", phyaddr);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x21);
+					//printk("1e.021 = 0x%x\n", debug_tmp);
+					debug_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x22);
+					//printk("1e.022 = 0x%x\n", debug_tmp);
+				}
+
+
+				printk( " GE Tx amp AnaCal Done! (pair-%d)(1e_%x = 0x%x)\n", calibration_pair, tx_amp_reg, tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg));
+				
+			} else {
+				if((tx_amp_temp == 0x3f)||(tx_amp_temp == 0x00)) {
+					all_ana_cal_status = ANACAL_SATURATION;  // need to FT
+					printk( " GE Tx amp AnaCal Saturation!  \r\n");
+				}
+			}
+		}
+
+		if(all_ana_cal_status == ANACAL_ERROR) {	
+			tx_amp_temp = 0x20;
+			tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, tx_amp_reg, (reg_temp|(tx_amp_temp<<tx_amp_reg_shift)));
+		}
+	}
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017d, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017e, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x017f, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0180, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0181, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0182, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0183, 0x0000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x0184, 0x0000);
+	
+	/* disable analog calibration circuit */
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00db, 0x0000);
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00dc, 0x0000);	// disable Tx offset calibration circuit
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00db, 0x0000);	// disable analog calibration circuit
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dc, 0x0000);	// disable Tx offset calibration circuit
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x003e, 0x0000);	// disable Tx VLD force mode
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x00dd, 0x0000);	// disable Tx offset/amplitude calibration circuit
+	
+	
+
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x273, 0x2000);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0xc9, 0x0fff);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x145, 0x1000);
+
+	/* Restore CR to default */
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x11, orig_1e_11);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x300, orig_1f_300);
+	
+	return 0;
+}
+
+//-----------------------------------------------------------------
+
+int phy_calibration(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+	u32 reg_tmp;
+	u32 CALDLY = 40;
+	u32 orig_1e_11, orig_1e_185, orig_1e_e1, orig_1f_100;
+	int ret;
+	/* set [12]AN disable, [8]full duplex, [13/6]1000Mbps */
+	//tc_phy_write_dev_reg(phyaddr, 0x0,  0x0140);
+	switch_phy_write(gsw, phyaddr, R0, 0x140);
+
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x145, 0x1010);/* fix mdi */
+	orig_1e_185 = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, RG_185);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, RG_185, 0);/* disable tx slew control */
+	orig_1f_100 = tc_phy_read_dev_reg(gsw, phyaddr, 0x1f, 0x100);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x100, 0xc000);/* BG voltage output */
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x403, 0x1099); //bypass efuse
+
+#if (1)
+	//	1f_27c[12:8] cr_da_tx_i2mpb_10m	Trimming TX bias setup(@10M)
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x27c, 0x1f1f);
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x27c, 0x3300);
+
+	//reg_tmp1 = tc_phy_read_dev_reg(gsw,  PHY0, 0x1f, 0x27c);
+	//dev1Fh_reg273h TXVLD DA register	- Adjust voltage mode TX amplitude.
+	//tc_phy_write_dev_reg(phyaddr, 0x1f, 0x273, 0);
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x273, 0x1000);
+	//reg_tmp1 = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x273);
+	//printk("reg_tmp1273 = %x\n", reg_tmp1);
+	/*1e_11 TX  overshoot Enable (PAIR A/B/C/D) in gbe mode*/
+
+	orig_1e_11 = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x11);
+	reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x11);
+	reg_tmp = reg_tmp | (0xf << 12);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x11, reg_tmp);
+	orig_1e_e1 = tc_phy_read_dev_reg(gsw, PHY0, 0x1e, 0x00e1);
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e1, 0x10);
+	/* calibration start ============ */
+	printk("CALDLY = %d\n", CALDLY);
+	if(ge_cal_flag == 0){
+		ret = ge_cal_rext(gsw, 0, CALDLY);
+		if (ret == -1){
+			printk("ge_cal_rext error K port =%d\n", phyaddr);
+			return ret;
+		}
+		ge_cal_flag = 1;
+	}
+
+	/* *** R50 Cal start ***************************** */
+	/*phyaddress = 0*/
+	ret = ge_cal_r50(gsw, phyaddr, CALDLY);
+	if (ret == -1){
+		printk("R50 error K port =%d\n", phyaddr);
+		return ret;
+	}
+	/* *** R50 Cal end *** */
+	/* *** Tx offset Cal start *********************** */
+	ret = ge_cal_tx_offset(gsw, phyaddr, CALDLY);
+	if (ret == -1){
+		printk("ge_cal_tx_offset error K port =%d\n", phyaddr);
+		return ret;
+	}
+	/* *** Tx offset Cal end *** */
+
+	/* *** Tx Amp Cal start *** */
+	ret = ge_cal_tx_amp(gsw, phyaddr, CALDLY);
+	if (ret == -1){
+		printk("ge_cal_tx_amp error K port =%d\n", phyaddr);
+		return ret;
+	}
+	/* *** Tx Amp Cal end *** */
+	/*tmp maybe changed*/
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x27c, 0x1111);
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x27b, 0x47);
+	//tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x273, 0x2000);
+
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3a8, 0x0810);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3aa, 0x0008);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3ab, 0x0810);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3ad, 0x0008);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3ae, 0x0106);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3b0, 0x0001);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3b1, 0x0106);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3b3, 0x0001);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x18c, 0x0001);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x18d, 0x0001);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x18e, 0x0001);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x18f, 0x0001);
+
+	/*da_tx_bias1_b_tx_standby = 5'b10 (dev1eh_reg3aah[12:8])*/
+	reg_tmp = tc_phy_read_dev_reg(gsw, phyaddr, 0x1e, 0x3aa);
+	reg_tmp = reg_tmp & ~(0x1f00);
+	reg_tmp = reg_tmp | 0x2 << 8;
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3aa, reg_tmp);
+
+	/*da_tx_bias1_a_tx_standby = 5'b10 (dev1eh_reg3a9h[4:0])*/
+	reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1e, 0x3a9);
+	reg_tmp = reg_tmp & ~(0x1f);
+	reg_tmp = reg_tmp | 0x2;
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x3a9, reg_tmp);
+
+	/* Restore CR to default */
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, RG_185, orig_1e_185);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x100, orig_1f_100);
+	tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x11, orig_1e_11);
+	tc_phy_write_dev_reg(gsw, PHY0, 0x1e, 0x00e1, orig_1e_e1);
+#endif
+
+	return 0;
+}
+
+void rx_dc_offset(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+    pr_info("PORT %d RX_DC_OFFSET\n", phyaddr);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x96, 0x8000);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x37, 0x3);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x107, 0x4000);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x171, 0x1e5);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x39, 0x200f);
+    udelay(40);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x39, 0x000f);
+    udelay(40);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1e, 0x171, 0x65);
+}
+
+void check_rx_dc_offset_pair_a(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+    u32 reg_tmp;
+
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x114f);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;
+    pr_info("before pairA output = %x\n", reg_tmp);
+    udelay(40);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1142);
+    udelay(40);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;   
+    pr_info("after pairA output = %x\n", reg_tmp);
+    if ((reg_tmp & 0x80) != 0)
+        reg_tmp = (~reg_tmp) + 1;
+    if ((reg_tmp & 0xff) >4)
+        pr_info("pairA RX_DC_OFFSET error");
+}
+
+void check_rx_dc_offset_pair_b(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+    u32 reg_tmp;
+
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1151);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;
+    pr_info("before pairB output = %x\n", reg_tmp);
+    udelay(40);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1143);
+    udelay(40);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;   
+    pr_info("after pairB output = %x\n", reg_tmp);
+    if ((reg_tmp & 0x80) != 0)
+        reg_tmp = (~reg_tmp) + 1;
+    if ((reg_tmp & 0xff) >4)
+        pr_info("pairB RX_DC_OFFSET error");
+}
+
+void check_rx_dc_offset_pair_c(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+    u32 reg_tmp;
+
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1153);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;
+    pr_info("before pairC output = %x\n", reg_tmp);
+    udelay(40);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1144);
+    udelay(40);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;   
+    pr_info("after pairC output = %x\n", reg_tmp);
+    if ((reg_tmp & 0x80) != 0)
+        reg_tmp = (~reg_tmp) + 1;
+    if ((reg_tmp & 0xff) >4)
+        pr_info("pairC RX_DC_OFFSET error");
+}
+
+void check_rx_dc_offset_pair_d(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+    u32 reg_tmp;
+
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1155);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;
+    pr_info("before pairD output = %x\n", reg_tmp);
+    udelay(40);
+    tc_phy_write_dev_reg(gsw, phyaddr, 0x1f, 0x15, (phyaddr << 13) | 0x1145);
+    udelay(40);
+    reg_tmp = tc_phy_read_dev_reg(gsw,  phyaddr, 0x1f, 0x1a);
+    reg_tmp = reg_tmp & 0xff;   
+    pr_info("after pairD output = %x\n", reg_tmp);
+    if ((reg_tmp & 0x80) != 0)
+        reg_tmp = (~reg_tmp) + 1;
+    if ((reg_tmp & 0xff) >4)
+        pr_info("pairD RX_DC_OFFSET error");
+}
+
+
+int mt753x_phy_calibration(struct gsw_mt753x *gsw, u8 phyaddr)
+{
+	phy_calibration(gsw, phyaddr);
+
+	rx_dc_offset(gsw, phyaddr);
+	check_rx_dc_offset_pair_a(gsw, phyaddr);
+	check_rx_dc_offset_pair_b(gsw, phyaddr);
+	check_rx_dc_offset_pair_c(gsw, phyaddr);
+	check_rx_dc_offset_pair_d(gsw, phyaddr);
+
+	return 0;
+}
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_phy.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_phy.h
new file mode 100755
index 0000000..1b9e2ea
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_phy.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier:	GPL-2.0+ */
+/*
+ * Register definitions for MediaTek MT753x Gigabit switches
+ *
+ * Copyright (C) 2018 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT753X_PHY_H_
+#define _MT753X_PHY_H_
+
+#include <linux/bitops.h>
+
+/*phy calibration use*/
+#define DEV_1E				0x1E
+/*global device 0x1f, always set P0*/
+#define DEV_1F				0x1F
+
+
+/************IEXT/REXT CAL***************/
+/* bits range: for example BITS(16,23) = 0xFF0000*/
+#define BITS(m, n)   (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
+#define ANACAL_INIT			0x01
+#define ANACAL_ERROR			0xFD
+#define ANACAL_SATURATION		0xFE
+#define	ANACAL_FINISH			0xFF
+#define ANACAL_PAIR_A			0
+#define ANACAL_PAIR_B			1
+#define ANACAL_PAIR_C			2
+#define ANACAL_PAIR_D			3
+#define DAC_IN_0V			0x00
+#define DAC_IN_2V			0xf0
+#define TX_AMP_OFFSET_0MV		0x20
+#define TX_AMP_OFFSET_VALID_BITS	6
+
+#define R0				0
+#define PHY0				0
+#define PHY1				1
+#define PHY2				2
+#define PHY3				3
+#define PHY4				4
+#define ANA_TEST_MODE			BITS(8, 15)
+#define TST_TCLK_SEL			BITs(6, 7)
+#define ANA_TEST_VGA_RG			0x100
+
+#define FORCE_MDI_CROSS_OVER		BITS(3, 4)
+#define T10_TEST_CTL_RG			0x145
+#define RG_185				0x185
+#define RG_TX_SLEW			BIT(0)
+#define ANA_CAL_0			0xdb
+#define RG_CAL_CKINV			BIT(12)
+#define RG_ANA_CALEN			BIT(8)
+#define RG_REXT_CALEN			BIT(4)
+#define RG_ZCALEN_A			BIT(0)
+#define ANA_CAL_1			0xdc
+#define RG_ZCALEN_B			BIT(12)
+#define RG_ZCALEN_C			BIT(8)
+#define RG_ZCALEN_D			BIT(4)
+#define RG_TXVOS_CALEN			BIT(0)
+#define ANA_CAL_6			0xe1
+#define RG_CAL_REFSEL			BIT(4)
+#define RG_CAL_COMP_PWD			BIT(0)
+#define ANA_CAL_5			0xe0
+#define RG_REXT_TRIM			BITs(8, 13)
+#define RG_ZCAL_CTRL			BITs(0, 5)
+#define RG_17A				0x17a
+#define AD_CAL_COMP_OUT			BIT(8)
+#define RG_17B				0x17b
+#define AD_CAL_CLK			bit(0)
+#define RG_17C				0x17c
+#define DA_CALIN_FLAG			bit(0)
+/************R50 CAL****************************/
+#define RG_174				0x174
+#define RG_R50OHM_RSEL_TX_A_EN		BIT[15]
+#define CR_R50OHM_RSEL_TX_A		BITS[8:14]
+#define RG_R50OHM_RSEL_TX_B_EN		BIT[7]
+#define CR_R50OHM_RSEL_TX_B		BITS[6:0]
+#define RG_175				0x175
+#define RG_R50OHM_RSEL_TX_C_EN		BITS[15]
+#define CR_R50OHM_RSEL_TX_C		BITS[8:14]
+#define RG_R50OHM_RSEL_TX_D_EN		BIT[7]
+#define CR_R50OHM_RSEL_TX_D		BITS[0:6]
+/**********TX offset Calibration***************************/
+#define RG_95				0x96
+#define BYPASS_TX_OFFSET_CAL		BIT(15)
+#define RG_3E				0x3e
+#define BYPASS_PD_TXVLD_A		BIT(15)
+#define BYPASS_PD_TXVLD_B		BIT(14)
+#define BYPASS_PD_TXVLD_C		BIT(13)
+#define BYPASS_PD_TXVLD_D		BIT(12)
+#define BYPASS_PD_TX_10M		BIT(11)
+#define POWER_DOWN_TXVLD_A		BIT(7)
+#define POWER_DOWN_TXVLD_B		BIT(6)
+#define POWER_DOWN_TXVLD_C		BIT(5)
+#define POWER_DOWN_TXVLD_D		BIT(4)
+#define POWER_DOWN_TX_10M		BIT(3)
+#define RG_DD				0xdd
+#define RG_TXG_CALEN_A			BIT(12)
+#define RG_TXG_CALEN_B			BIT(8)
+#define RG_TXG_CALEN_C			BIT(4)
+#define RG_TXG_CALEN_D			BIT(0)
+#define RG_17D				0x17D
+#define FORCE_DASN_DAC_IN0_A		BIT(15)
+#define DASN_DAC_IN0_A			BITS(0, 9)
+#define RG_17E				0x17E
+#define FORCE_DASN_DAC_IN0_B		BIT(15)
+#define DASN_DAC_IN0_B			BITS(0, 9)
+#define RG_17F				0x17F
+
+#define FORCE_DASN_DAC_IN0_C		BIT(15)
+#define DASN_DAC_IN0_C			BITS(0, 9)
+#define RG_180				0x180
+#define FORCE_DASN_DAC_IN0_D		BIT(15)
+#define DASN_DAC_IN0_D			BITS(0, 9)
+
+#define RG_181				0x181
+#define FORCE_DASN_DAC_IN1_A		BIT(15)
+#define DASN_DAC_IN1_A			BITS(0, 9)
+#define RG_182				0x182
+#define FORCE_DASN_DAC_IN1_B		BIT(15)
+#define DASN_DAC_IN1_B			BITS(0, 9)
+#define RG_183				0x183
+#define FORCE_DASN_DAC_IN1_C		BIT15]
+#define DASN_DAC_IN1_C			BITS(0, 9)
+#define RG_184				0x184
+#define FORCE_DASN_DAC_IN1_D		BIT(15)
+#define DASN_DAC_IN1_D			BITS(0, 9)
+#define RG_172				0x172
+#define CR_TX_AMP_OFFSET_A		BITS(8, 13)
+#define CR_TX_AMP_OFFSET_B		BITS(0, 5)
+#define RG_173				0x173
+#define CR_TX_AMP_OFFSET_C		BITS(8, 13)
+#define CR_TX_AMP_OFFSET_D		BITS(0, 5)
+/**********TX Amp Calibration ***************************/
+#define RG_12				0x12
+#define DA_TX_I2MPB_A_GBE		BITS(10, 15)
+#define RG_17				0x17
+#define DA_TX_I2MPB_B_GBE		BITS(8, 13)
+#define RG_19				0x19
+#define DA_TX_I2MPB_C_GBE		BITS(8, 13)
+#define RG_21				0x21
+#define DA_TX_I2MPB_D_GBE		BITS(8, 13)
+
+#endif /* _MT753X_REGS_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_regs.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_regs.h
new file mode 100644
index 0000000..374f199
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_regs.h
@@ -0,0 +1,353 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT753X_REGS_H_
+#define _MT753X_REGS_H_
+
+#include <linux/bitops.h>
+
+/* Values of Egress TAG Control */
+#define ETAG_CTRL_UNTAG			0
+#define ETAG_CTRL_TAG			2
+#define ETAG_CTRL_SWAP			1
+#define ETAG_CTRL_STACK			3
+
+#define VTCR				0x90
+#define VAWD1				0x94
+#define VAWD2				0x98
+
+/* Fields of VTCR */
+#define VTCR_BUSY			BIT(31)
+#define IDX_INVLD			BIT(16)
+#define VTCR_FUNC_S			12
+#define VTCR_FUNC_M			0xf000
+#define VTCR_VID_S			0
+#define VTCR_VID_M			0xfff
+
+/* Values of VTCR_FUNC */
+#define VTCR_READ_VLAN_ENTRY		0
+#define VTCR_WRITE_VLAN_ENTRY		1
+#define VTCR_INVD_VLAN_ENTRY		2
+#define VTCR_ENABLE_VLAN_ENTRY		3
+#define VTCR_READ_ACL_ENTRY		4
+#define VTCR_WRITE_ACL_ENTRY		5
+#define VTCR_READ_TRTCM_TABLE		6
+#define VTCR_WRITE_TRTCM_TABLE		7
+#define VTCR_READ_ACL_MASK_ENTRY	8
+#define VTCR_WRITE_ACL_MASK_ENTRY	9
+#define VTCR_READ_ACL_RULE_ENTRY	10
+#define VTCR_WRITE_ACL_RULE_ENTRY	11
+#define VTCR_READ_ACL_RATE_ENTRY	12
+#define VTCR_WRITE_ACL_RATE_ENTRY	13
+
+/* VLAN entry fields */
+/* VAWD1 */
+#define PORT_STAG			BIT(31)
+#define IVL_MAC				BIT(30)
+#define EG_CON				BIT(29)
+#define VTAG_EN				BIT(28)
+#define COPY_PRI			BIT(27)
+#define USER_PRI_S			24
+#define USER_PRI_M			0x7000000
+#define PORT_MEM_S			16
+#define PORT_MEM_M			0xff0000
+#define S_TAG1_S			4
+#define S_TAG1_M			0xfff0
+#define FID_S				1
+#define FID_M				0x0e
+#define VENTRY_VALID			BIT(0)
+
+/* VAWD2 */
+#define S_TAG2_S			16
+#define S_TAG2_M			0xffff0000
+#define PORT_ETAG_S(p)			((p) * 2)
+#define PORT_ETAG_M			0x03
+
+#define PORT_CTRL_BASE			0x2000
+#define PORT_CTRL_PORT_OFFSET		0x100
+#define PORT_CTRL_REG(p, r)		(PORT_CTRL_BASE + \
+					(p) * PORT_CTRL_PORT_OFFSET +  (r))
+#define CKGCR(p)			PORT_CTRL_REG(p, 0x00)
+#define PCR(p)				PORT_CTRL_REG(p, 0x04)
+#define PIC(p)				PORT_CTRL_REG(p, 0x08)
+#define PSC(p)				PORT_CTRL_REG(p, 0x0c)
+#define PVC(p)				PORT_CTRL_REG(p, 0x10)
+#define PPBV1(p)			PORT_CTRL_REG(p, 0x14)
+#define PPBV2(p)			PORT_CTRL_REG(p, 0x18)
+#define BSR(p)				PORT_CTRL_REG(p, 0x1c)
+#define STAG01				PORT_CTRL_REG(p, 0x20)
+#define STAG23				PORT_CTRL_REG(p, 0x24)
+#define STAG45				PORT_CTRL_REG(p, 0x28)
+#define STAG67				PORT_CTRL_REG(p, 0x2c)
+
+#define PPBV(p, g)			(PPBV1(p) + ((g) / 2) * 4)
+
+/* Fields of PCR */
+#define MLDV2_EN			BIT(30)
+#define EG_TAG_S			28
+#define EG_TAG_M			0x30000000
+#define PORT_PRI_S			24
+#define PORT_PRI_M			0x7000000
+#define PORT_MATRIX_S			16
+#define PORT_MATRIX_M			0xff0000
+#define UP2DSCP_EN			BIT(12)
+#define UP2TAG_EN			BIT(11)
+#define ACL_EN				BIT(10)
+#define PORT_TX_MIR			BIT(9)
+#define PORT_RX_MIR			BIT(8)
+#define ACL_MIR				BIT(7)
+#define MIS_PORT_FW_S			4
+#define MIS_PORT_FW_M			0x70
+#define VLAN_MIS			BIT(2)
+#define PORT_VLAN_S			0
+#define PORT_VLAN_M			0x03
+
+/* Values of PORT_VLAN */
+#define PORT_MATRIX_MODE		0
+#define FALLBACK_MODE			1
+#define CHECK_MODE			2
+#define SECURITY_MODE			3
+
+/* Fields of PVC */
+#define STAG_VPID_S			16
+#define STAG_VPID_M			0xffff0000
+#define DIS_PVID			BIT(15)
+#define FORCE_PVID			BIT(14)
+#define PT_VPM				BIT(12)
+#define PT_OPTION			BIT(11)
+#define PVC_EG_TAG_S			8
+#define PVC_EG_TAG_M			0x700
+#define VLAN_ATTR_S			6
+#define VLAN_ATTR_M			0xc0
+#define PVC_PORT_STAG			BIT(5)
+#define BC_LKYV_EN			BIT(4)
+#define MC_LKYV_EN			BIT(3)
+#define UC_LKYV_EN			BIT(2)
+#define ACC_FRM_S			0
+#define ACC_FRM_M			0x03
+
+/* Values of VLAN_ATTR */
+#define VA_USER_PORT			0
+#define VA_STACK_PORT			1
+#define VA_TRANSLATION_PORT		2
+#define VA_TRANSPARENT_PORT		3
+
+/* Fields of PPBV */
+#define GRP_PORT_PRI_S(g)		(((g) % 2) * 16 + 13)
+#define GRP_PORT_PRI_M			0x07
+#define GRP_PORT_VID_S(g)		(((g) % 2) * 16)
+#define GRP_PORT_VID_M			0xfff
+
+#define PORT_MAC_CTRL_BASE		0x3000
+#define PORT_MAC_CTRL_PORT_OFFSET	0x100
+#define PORT_MAC_CTRL_REG(p, r)		(PORT_MAC_CTRL_BASE + \
+					(p) * PORT_MAC_CTRL_PORT_OFFSET + (r))
+#define PMCR(p)				PORT_MAC_CTRL_REG(p, 0x00)
+#define PMEEECR(p)			PORT_MAC_CTRL_REG(p, 0x04)
+#define PMSR(p)				PORT_MAC_CTRL_REG(p, 0x08)
+#define PINT_EN(p)			PORT_MAC_CTRL_REG(p, 0x10)
+#define PINT_STS(p)			PORT_MAC_CTRL_REG(p, 0x14)
+#define WOL(p)				PORT_MAC_CTRL_REG(p, 0x20)
+
+#define GMACCR				(PORT_MAC_CTRL_BASE + 0xe0)
+#define TXCRC_EN			BIT(19)
+#define RXCRC_EN			BIT(18)
+#define PRMBL_LMT_EN			BIT(17)
+#define MTCC_LMT_S			9
+#define MTCC_LMT_M			0x1e00
+#define MAX_RX_JUMBO_S			2
+#define MAX_RX_JUMBO_M			0x3c
+#define MAX_RX_PKT_LEN_S		0
+#define MAX_RX_PKT_LEN_M		0x3
+
+/* Values of MAX_RX_PKT_LEN */
+#define RX_PKT_LEN_1518			0
+#define RX_PKT_LEN_1536			1
+#define RX_PKT_LEN_1522			2
+#define RX_PKT_LEN_MAX_JUMBO		3
+
+/* Fields of PMCR */
+#define IPG_CFG_S			18
+#define IPG_CFG_M			0xc0000
+#define EXT_PHY				BIT(17)
+#define MAC_MODE			BIT(16)
+#define MAC_TX_EN			BIT(14)
+#define MAC_RX_EN			BIT(13)
+#define MAC_PRE				BIT(11)
+#define BKOFF_EN			BIT(9)
+#define BACKPR_EN			BIT(8)
+#define FORCE_EEE1G			BIT(7)
+#define FORCE_EEE1000			BIT(6)
+#define FORCE_RX_FC			BIT(5)
+#define FORCE_TX_FC			BIT(4)
+#define FORCE_SPD_S			2
+#define FORCE_SPD_M			0x0c
+#define FORCE_DPX			BIT(1)
+#define FORCE_LINK			BIT(0)
+
+/* Fields of PMSR */
+#define EEE1G_STS			BIT(7)
+#define EEE100_STS			BIT(6)
+#define RX_FC_STS			BIT(5)
+#define TX_FC_STS			BIT(4)
+#define MAC_SPD_STS_S			2
+#define MAC_SPD_STS_M			0x0c
+#define MAC_DPX_STS			BIT(1)
+#define MAC_LNK_STS			BIT(0)
+
+/* Fields of WOL */
+#define WOL_INT_STS			BIT(17)
+#define WOL_STS				BIT(16)
+#define SNP_PKT				BIT(3)
+#define CRC_DIS				BIT(2)
+#define WOL_INT_EN			BIT(1)
+#define WOL_EN				BIT(0)
+
+/* Values of MAC_SPD_STS */
+#define MAC_SPD_10			0
+#define MAC_SPD_100			1
+#define MAC_SPD_1000			2
+#define MAC_SPD_2500			3
+
+/* Values of IPG_CFG */
+#define IPG_96BIT			0
+#define IPG_96BIT_WITH_SHORT_IPG	1
+#define IPG_64BIT			2
+
+#define MIB_COUNTER_BASE		0x4000
+#define MIB_COUNTER_PORT_OFFSET		0x100
+#define MIB_COUNTER_REG(p, r)		(MIB_COUNTER_BASE + \
+					(p) * MIB_COUNTER_PORT_OFFSET + (r))
+#define STATS_TDPC			0x00
+#define STATS_TCRC			0x04
+#define STATS_TUPC			0x08
+#define STATS_TMPC			0x0C
+#define STATS_TBPC			0x10
+#define STATS_TCEC			0x14
+#define STATS_TSCEC			0x18
+#define STATS_TMCEC			0x1C
+#define STATS_TDEC			0x20
+#define STATS_TLCEC			0x24
+#define STATS_TXCEC			0x28
+#define STATS_TPPC			0x2C
+#define STATS_TL64PC			0x30
+#define STATS_TL65PC			0x34
+#define STATS_TL128PC			0x38
+#define STATS_TL256PC			0x3C
+#define STATS_TL512PC			0x40
+#define STATS_TL1024PC			0x44
+#define STATS_TOC			0x48
+#define STATS_RDPC			0x60
+#define STATS_RFPC			0x64
+#define STATS_RUPC			0x68
+#define STATS_RMPC			0x6C
+#define STATS_RBPC			0x70
+#define STATS_RAEPC			0x74
+#define STATS_RCEPC			0x78
+#define STATS_RUSPC			0x7C
+#define STATS_RFEPC			0x80
+#define STATS_ROSPC			0x84
+#define STATS_RJEPC			0x88
+#define STATS_RPPC			0x8C
+#define STATS_RL64PC			0x90
+#define STATS_RL65PC			0x94
+#define STATS_RL128PC			0x98
+#define STATS_RL256PC			0x9C
+#define STATS_RL512PC			0xA0
+#define STATS_RL1024PC			0xA4
+#define STATS_ROC			0xA8
+#define STATS_RDPC_CTRL			0xB0
+#define STATS_RDPC_ING			0xB4
+#define STATS_RDPC_ARL			0xB8
+
+#define SYS_CTRL			0x7000
+#define SW_PHY_RST			BIT(2)
+#define SW_SYS_RST			BIT(1)
+#define SW_REG_RST			BIT(0)
+
+#define SYS_INT_EN			0x7008
+#define SYS_INT_STS			0x700c
+#define MAC_PC_INT			BIT(16)
+#define PHY_INT(p)			BIT((p) + 8)
+#define PHY_LC_INT(p)			BIT(p)
+
+#define PHY_IAC				0x701c
+#define PHY_ACS_ST			BIT(31)
+#define MDIO_REG_ADDR_S			25
+#define MDIO_REG_ADDR_M			0x3e000000
+#define MDIO_PHY_ADDR_S			20
+#define MDIO_PHY_ADDR_M			0x1f00000
+#define MDIO_CMD_S			18
+#define MDIO_CMD_M			0xc0000
+#define MDIO_ST_S			16
+#define MDIO_ST_M			0x30000
+#define MDIO_RW_DATA_S			0
+#define MDIO_RW_DATA_M			0xffff
+
+/* MDIO_CMD: MDIO commands */
+#define MDIO_CMD_ADDR			0
+#define MDIO_CMD_WRITE			1
+#define MDIO_CMD_READ			2
+#define MDIO_CMD_READ_C45		3
+
+/* MDIO_ST: MDIO start field */
+#define MDIO_ST_C45			0
+#define MDIO_ST_C22			1
+
+#define HWSTRAP				0x7800
+#define MHWSTRAP			0x7804
+
+/* Internal GPHY Page Control Register */
+#define PHY_CL22_PAGE_CTRL		0x1f
+#define PHY_TR_PAGE			0x52b5
+
+/* Internal GPHY Token Ring Access Registers */
+#define PHY_TR_CTRL			0x10
+#define PHY_TR_LOW_DATA			0x11
+#define PHY_TR_HIGH_DATA		0x12
+
+/* Fields of PHY_TR_CTRL */
+#define PHY_TR_PKT_XMT_STA		BIT(15)
+#define PHY_TR_WR_S			13
+#define PHY_TR_CH_ADDR_S		11
+#define PHY_TR_NODE_ADDR_S		7
+#define PHY_TR_DATA_ADDR_S		1
+
+enum phy_tr_wr {
+	PHY_TR_WRITE = 0,
+	PHY_TR_READ = 1,
+};
+
+/* Helper macro for GPHY Token Ring Access */
+#define PHY_TR_LOW_VAL(x)		((x) & 0xffff)
+#define PHY_TR_HIGH_VAL(x)		(((x) & 0xff0000) >> 16)
+
+/* Token Ring Channels */
+#define PMA_CH				0x1
+#define DSP_CH				0x2
+
+/* Token Ring Nodes */
+#define PMA_NOD				0xf
+#define DSP_NOD				0xd
+
+/* Token Ring register range */
+enum tr_pma_reg_addr {
+	PMA_MIN = 0x0,
+	PMA_01  = 0x1,
+	PMA_17  = 0x17,
+	PMA_18  = 0x18,
+	PMA_MAX = 0x3d,
+};
+
+enum tr_dsp_reg_addr {
+	DSP_MIN = 0x0,
+	DSP_06  = 0x6,
+	DSP_08  = 0x8,
+	DSP_0f  = 0xf,
+	DSP_10  = 0x10,
+	DSP_MAX = 0x3e,
+};
+#endif /* _MT753X_REGS_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_swconfig.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_swconfig.c
new file mode 100755
index 0000000..4cd63ae
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_swconfig.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <linux/if.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/bitops.h>
+#include <net/genetlink.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/lockdep.h>
+#include <linux/workqueue.h>
+#include <linux/of_device.h>
+
+#include "mt753x.h"
+#include "mt753x_swconfig.h"
+#include "mt753x_regs.h"
+
+#define MT753X_PORT_MIB_TXB_ID	18	/* TxByte */
+#define MT753X_PORT_MIB_RXB_ID	37	/* RxByte */
+
+#define MIB_DESC(_s, _o, _n)   \
+	{                       \
+		.size = (_s),   \
+		.offset = (_o), \
+		.name = (_n),   \
+	}
+
+struct mt753x_mib_desc {
+	unsigned int size;
+	unsigned int offset;
+	const char *name;
+};
+
+static const struct mt753x_mib_desc mt753x_mibs[] = {
+	MIB_DESC(1, STATS_TDPC, "TxDrop"),
+	MIB_DESC(1, STATS_TCRC, "TxCRC"),
+	MIB_DESC(1, STATS_TUPC, "TxUni"),
+	MIB_DESC(1, STATS_TMPC, "TxMulti"),
+	MIB_DESC(1, STATS_TBPC, "TxBroad"),
+	MIB_DESC(1, STATS_TCEC, "TxCollision"),
+	MIB_DESC(1, STATS_TSCEC, "TxSingleCol"),
+	MIB_DESC(1, STATS_TMCEC, "TxMultiCol"),
+	MIB_DESC(1, STATS_TDEC, "TxDefer"),
+	MIB_DESC(1, STATS_TLCEC, "TxLateCol"),
+	MIB_DESC(1, STATS_TXCEC, "TxExcCol"),
+	MIB_DESC(1, STATS_TPPC, "TxPause"),
+	MIB_DESC(1, STATS_TL64PC, "Tx64Byte"),
+	MIB_DESC(1, STATS_TL65PC, "Tx65Byte"),
+	MIB_DESC(1, STATS_TL128PC, "Tx128Byte"),
+	MIB_DESC(1, STATS_TL256PC, "Tx256Byte"),
+	MIB_DESC(1, STATS_TL512PC, "Tx512Byte"),
+	MIB_DESC(1, STATS_TL1024PC, "Tx1024Byte"),
+	MIB_DESC(2, STATS_TOC, "TxByte"),
+	MIB_DESC(1, STATS_RDPC, "RxDrop"),
+	MIB_DESC(1, STATS_RFPC, "RxFiltered"),
+	MIB_DESC(1, STATS_RUPC, "RxUni"),
+	MIB_DESC(1, STATS_RMPC, "RxMulti"),
+	MIB_DESC(1, STATS_RBPC, "RxBroad"),
+	MIB_DESC(1, STATS_RAEPC, "RxAlignErr"),
+	MIB_DESC(1, STATS_RCEPC, "RxCRC"),
+	MIB_DESC(1, STATS_RUSPC, "RxUnderSize"),
+	MIB_DESC(1, STATS_RFEPC, "RxFragment"),
+	MIB_DESC(1, STATS_ROSPC, "RxOverSize"),
+	MIB_DESC(1, STATS_RJEPC, "RxJabber"),
+	MIB_DESC(1, STATS_RPPC, "RxPause"),
+	MIB_DESC(1, STATS_RL64PC, "Rx64Byte"),
+	MIB_DESC(1, STATS_RL65PC, "Rx65Byte"),
+	MIB_DESC(1, STATS_RL128PC, "Rx128Byte"),
+	MIB_DESC(1, STATS_RL256PC, "Rx256Byte"),
+	MIB_DESC(1, STATS_RL512PC, "Rx512Byte"),
+	MIB_DESC(1, STATS_RL1024PC, "Rx1024Byte"),
+	MIB_DESC(2, STATS_ROC, "RxByte"),
+	MIB_DESC(1, STATS_RDPC_CTRL, "RxCtrlDrop"),
+	MIB_DESC(1, STATS_RDPC_ING, "RxIngDrop"),
+	MIB_DESC(1, STATS_RDPC_ARL, "RxARLDrop")
+};
+
+enum {
+	/* Global attributes. */
+	MT753X_ATTR_ENABLE_VLAN,
+};
+
+static int mt753x_get_vlan_enable(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	val->value.i = gsw->global_vlan_enable;
+
+	return 0;
+}
+
+static int mt753x_set_vlan_enable(struct switch_dev *dev,
+				  const struct switch_attr *attr,
+				  struct switch_val *val)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	gsw->global_vlan_enable = val->value.i != 0;
+
+	return 0;
+}
+
+static int mt753x_get_port_pvid(struct switch_dev *dev, int port, int *val)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	if (port >= MT753X_NUM_PORTS)
+		return -EINVAL;
+
+	*val = mt753x_reg_read(gsw, PPBV1(port));
+	*val &= GRP_PORT_VID_M;
+
+	return 0;
+}
+
+static int mt753x_set_port_pvid(struct switch_dev *dev, int port, int pvid)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	if (port >= MT753X_NUM_PORTS)
+		return -EINVAL;
+
+	if (pvid < MT753X_MIN_VID || pvid > MT753X_MAX_VID)
+		return -EINVAL;
+
+	gsw->port_entries[port].pvid = pvid;
+
+	return 0;
+}
+
+static int mt753x_get_vlan_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+	u32 member;
+	u32 etags;
+	int i;
+
+	val->len = 0;
+
+	if (val->port_vlan < 0 || val->port_vlan >= MT753X_NUM_VLANS)
+		return -EINVAL;
+
+	mt753x_vlan_ctrl(gsw, VTCR_READ_VLAN_ENTRY, val->port_vlan);
+
+	member = mt753x_reg_read(gsw, VAWD1);
+	member &= PORT_MEM_M;
+	member >>= PORT_MEM_S;
+
+	etags = mt753x_reg_read(gsw, VAWD2);
+
+	for (i = 0; i < MT753X_NUM_PORTS; i++) {
+		struct switch_port *p;
+		int etag;
+
+		if (!(member & BIT(i)))
+			continue;
+
+		p = &val->value.ports[val->len++];
+		p->id = i;
+
+		etag = (etags >> PORT_ETAG_S(i)) & PORT_ETAG_M;
+
+		if (etag == ETAG_CTRL_TAG)
+			p->flags |= BIT(SWITCH_PORT_FLAG_TAGGED);
+		else if (etag != ETAG_CTRL_UNTAG)
+			dev_info(gsw->dev,
+				 "vlan egress tag control neither untag nor tag.\n");
+	}
+
+	return 0;
+}
+
+static int mt753x_set_vlan_ports(struct switch_dev *dev, struct switch_val *val)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+	u8 member = 0;
+	u8 etags = 0;
+	int i;
+
+	if (val->port_vlan < 0 || val->port_vlan >= MT753X_NUM_VLANS ||
+	    val->len > MT753X_NUM_PORTS)
+		return -EINVAL;
+
+	for (i = 0; i < val->len; i++) {
+		struct switch_port *p = &val->value.ports[i];
+
+		if (p->id >= MT753X_NUM_PORTS)
+			return -EINVAL;
+
+		member |= BIT(p->id);
+
+		if (p->flags & BIT(SWITCH_PORT_FLAG_TAGGED))
+			etags |= BIT(p->id);
+	}
+
+	gsw->vlan_entries[val->port_vlan].member = member;
+	gsw->vlan_entries[val->port_vlan].etags = etags;
+
+	return 0;
+}
+
+static int mt753x_set_vid(struct switch_dev *dev,
+			  const struct switch_attr *attr,
+			  struct switch_val *val)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+	int vlan;
+	u16 vid;
+
+	vlan = val->port_vlan;
+	vid = (u16)val->value.i;
+
+	if (vlan < 0 || vlan >= MT753X_NUM_VLANS)
+		return -EINVAL;
+
+	if (vid < MT753X_MIN_VID || vid > MT753X_MAX_VID)
+		return -EINVAL;
+
+	gsw->vlan_entries[vlan].vid = vid;
+	return 0;
+}
+
+static int mt753x_get_vid(struct switch_dev *dev,
+			  const struct switch_attr *attr,
+			  struct switch_val *val)
+{
+	val->value.i = val->port_vlan;
+	return 0;
+}
+
+static int mt753x_get_port_link(struct switch_dev *dev, int port,
+				struct switch_port_link *link)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+	u32 speed, pmsr;
+
+	if (port < 0 || port >= MT753X_NUM_PORTS)
+		return -EINVAL;
+
+	pmsr = mt753x_reg_read(gsw, PMSR(port));
+
+	link->link = pmsr & MAC_LNK_STS;
+	link->duplex = pmsr & MAC_DPX_STS;
+	speed = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S;
+
+	switch (speed) {
+	case MAC_SPD_10:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case MAC_SPD_100:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case MAC_SPD_1000:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	case MAC_SPD_2500:
+		/* TODO: swconfig has no support for 2500 now */
+		link->speed = SWITCH_PORT_SPEED_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int mt753x_set_port_link(struct switch_dev *dev, int port,
+				struct switch_port_link *link)
+{
+#ifndef MODULE
+	if (port >= MT753X_NUM_PHYS)
+		return -EINVAL;
+
+	return switch_generic_set_link(dev, port, link);
+#else
+	return -ENOTSUPP;
+#endif
+}
+
+static u64 get_mib_counter(struct gsw_mt753x *gsw, int i, int port)
+{
+	unsigned int offset;
+	u64 lo, hi, hi2;
+
+	offset = mt753x_mibs[i].offset;
+
+	if (mt753x_mibs[i].size == 1)
+		return mt753x_reg_read(gsw, MIB_COUNTER_REG(port, offset));
+
+	do {
+		hi = mt753x_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4));
+		lo = mt753x_reg_read(gsw, MIB_COUNTER_REG(port, offset));
+		hi2 = mt753x_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4));
+	} while (hi2 != hi);
+
+	return (hi << 32) | lo;
+}
+
+static int mt753x_get_port_mib(struct switch_dev *dev,
+			       const struct switch_attr *attr,
+			       struct switch_val *val)
+{
+	static char buf[4096];
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+	int i, len = 0;
+
+	if (val->port_vlan >= MT753X_NUM_PORTS)
+		return -EINVAL;
+
+	len += snprintf(buf + len, sizeof(buf) - len,
+			"Port %d MIB counters\n", val->port_vlan);
+
+	for (i = 0; i < ARRAY_SIZE(mt753x_mibs); ++i) {
+		u64 counter;
+
+		len += snprintf(buf + len, sizeof(buf) - len,
+				"%-11s: ", mt753x_mibs[i].name);
+		counter = get_mib_counter(gsw, i, val->port_vlan);
+		len += snprintf(buf + len, sizeof(buf) - len, "%llu\n",
+				counter);
+	}
+
+	val->value.s = buf;
+	val->len = len;
+	return 0;
+}
+
+static int mt753x_get_port_stats(struct switch_dev *dev, int port,
+				 struct switch_port_stats *stats)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	if (port < 0 || port >= MT753X_NUM_PORTS)
+		return -EINVAL;
+
+	stats->tx_bytes = get_mib_counter(gsw, MT753X_PORT_MIB_TXB_ID, port);
+	stats->rx_bytes = get_mib_counter(gsw, MT753X_PORT_MIB_RXB_ID, port);
+
+	return 0;
+}
+
+static void mt753x_port_isolation(struct gsw_mt753x *gsw)
+{
+	int i;
+
+	for (i = 0; i < MT753X_NUM_PORTS; i++)
+		mt753x_reg_write(gsw, PCR(i),
+				 BIT(gsw->cpu_port) << PORT_MATRIX_S);
+
+	mt753x_reg_write(gsw, PCR(gsw->cpu_port), PORT_MATRIX_M);
+
+	for (i = 0; i < MT753X_NUM_PORTS; i++) {
+		u32 pvc_mode = 0x8100 << STAG_VPID_S;
+
+		if ((gsw->port5_cfg.stag_on && i == 5) ||
+		    (gsw->port6_cfg.stag_on && i == 6))
+			pvc_mode |= PVC_PORT_STAG;
+		else
+			pvc_mode |= (VA_TRANSPARENT_PORT << VLAN_ATTR_S);
+
+		mt753x_reg_write(gsw, PVC(i), pvc_mode);
+	}
+}
+
+static int mt753x_apply_config(struct switch_dev *dev)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	if (!gsw->global_vlan_enable) {
+		mt753x_port_isolation(gsw);
+		return 0;
+	}
+
+	mt753x_apply_vlan_config(gsw);
+
+	return 0;
+}
+
+static int mt753x_apply_config_init(struct switch_dev *dev)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	if (!gsw->global_vlan_enable) {
+		mt753x_port_isolation(gsw);
+		return 0;
+	}
+
+	mt753x_apply_vlan_config_init(gsw);
+
+	return 0;
+}
+
+static int mt753x_reset_switch(struct switch_dev *dev)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+	int i;
+
+	memset(gsw->port_entries, 0, sizeof(gsw->port_entries));
+	memset(gsw->vlan_entries, 0, sizeof(gsw->vlan_entries));
+
+	/* set default vid of each vlan to the same number of vlan, so the vid
+	 * won't need be set explicitly.
+	 */
+	for (i = 0; i < MT753X_NUM_VLANS; i++)
+		gsw->vlan_entries[i].vid = i;
+
+	return 0;
+}
+
+static int mt753x_phy_read16(struct switch_dev *dev, int addr, u8 reg,
+			     u16 *value)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	*value = gsw->mii_read(gsw, addr, reg);
+
+	return 0;
+}
+
+static int mt753x_phy_write16(struct switch_dev *dev, int addr, u8 reg,
+			      u16 value)
+{
+	struct gsw_mt753x *gsw = container_of(dev, struct gsw_mt753x, swdev);
+
+	gsw->mii_write(gsw, addr, reg, value);
+
+	return 0;
+}
+
+static const struct switch_attr mt753x_global[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "VLAN mode (1:enabled)",
+		.max = 1,
+		.id = MT753X_ATTR_ENABLE_VLAN,
+		.get = mt753x_get_vlan_enable,
+		.set = mt753x_set_vlan_enable,
+	}
+};
+
+static const struct switch_attr mt753x_port[] = {
+	{
+		.type = SWITCH_TYPE_STRING,
+		.name = "mib",
+		.description = "Get MIB counters for port",
+		.get = mt753x_get_port_mib,
+		.set = NULL,
+	},
+};
+
+static const struct switch_attr mt753x_vlan[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "vid",
+		.description = "VLAN ID (0-4094)",
+		.set = mt753x_set_vid,
+		.get = mt753x_get_vid,
+		.max = 4094,
+	},
+};
+
+static const struct switch_dev_ops mt753x_swdev_ops = {
+	.attr_global = {
+		.attr = mt753x_global,
+		.n_attr = ARRAY_SIZE(mt753x_global),
+	},
+	.attr_port = {
+		.attr = mt753x_port,
+		.n_attr = ARRAY_SIZE(mt753x_port),
+	},
+	.attr_vlan = {
+		.attr = mt753x_vlan,
+		.n_attr = ARRAY_SIZE(mt753x_vlan),
+	},
+	.get_vlan_ports = mt753x_get_vlan_ports,
+	.set_vlan_ports = mt753x_set_vlan_ports,
+	.get_port_pvid = mt753x_get_port_pvid,
+	.set_port_pvid = mt753x_set_port_pvid,
+	.get_port_link = mt753x_get_port_link,
+	.set_port_link = mt753x_set_port_link,
+	.get_port_stats = mt753x_get_port_stats,
+	.apply_config = mt753x_apply_config,
+	.reset_switch = mt753x_reset_switch,
+	.phy_read16 = mt753x_phy_read16,
+	.phy_write16 = mt753x_phy_write16,
+};
+
+int mt753x_swconfig_init(struct gsw_mt753x *gsw)
+{
+	struct device_node *np = gsw->dev->of_node;
+	struct switch_dev *swdev;
+	int ret;
+
+	if (of_property_read_u32(np, "mediatek,cpuport", &gsw->cpu_port))
+		gsw->cpu_port = MT753X_DFL_CPU_PORT;
+
+	swdev = &gsw->swdev;
+
+	swdev->name = gsw->name;
+	swdev->alias = gsw->name;
+	swdev->cpu_port = gsw->cpu_port;
+	swdev->ports = MT753X_NUM_PORTS;
+	swdev->vlans = MT753X_NUM_VLANS;
+	swdev->ops = &mt753x_swdev_ops;
+
+	ret = register_switch(swdev, NULL);
+	if (ret) {
+		dev_notice(gsw->dev, "Failed to register switch %s\n",
+			   swdev->name);
+		return ret;
+	}
+
+	mt753x_apply_config_init(swdev);
+
+	return 0;
+}
+
+void mt753x_swconfig_destroy(struct gsw_mt753x *gsw)
+{
+	unregister_switch(&gsw->swdev);
+}
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_swconfig.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_swconfig.h
new file mode 100644
index 0000000..b877d89
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_swconfig.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT753X_SWCONFIG_H_
+#define _MT753X_SWCONFIG_H_
+
+#ifdef CONFIG_SWCONFIG
+#include <linux/switch.h>
+#include "mt753x.h"
+
+int mt753x_swconfig_init(struct gsw_mt753x *gsw);
+void mt753x_swconfig_destroy(struct gsw_mt753x *gsw);
+#else
+static inline int mt753x_swconfig_init(struct gsw_mt753x *gsw)
+{
+	mt753x_apply_vlan_config(gsw);
+
+	return 0;
+}
+
+static inline void mt753x_swconfig_destroy(struct gsw_mt753x *gsw)
+{
+}
+#endif
+
+#endif /* _MT753X_SWCONFIG_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_vlan.c b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_vlan.c
new file mode 100755
index 0000000..aa3b7f6
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_vlan.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ */
+
+#include "mt753x.h"
+#include "mt753x_regs.h"
+
+struct mt753x_mapping mt753x_def_mapping[] = {
+	{
+		.name = "llllw",
+		.pvids = { 1, 1, 1, 1, 2, 1, 2 },
+		.members = { 0, 0x2f, 0x50 },
+		.etags = { 0, 0, 0 },
+		.vids = { 0, 1, 2 },
+	}, {
+		.name = "wllll",
+		.pvids = { 2, 1, 1, 1, 1, 2, 1 },
+		.members = { 0, 0x5e, 0x21 },
+		.etags = { 0, 0, 0 },
+		.vids = { 0, 1, 2 },
+	}, {
+		.name = "lwlll",
+		.pvids = { 1, 2, 1, 1, 1, 2, 1 },
+		.members = { 0, 0x5d, 0x22 },
+		.etags = { 0, 0, 0 },
+		.vids = { 0, 1, 2 },
+	},
+};
+
+void mt753x_vlan_ctrl(struct gsw_mt753x *gsw, u32 cmd, u32 val)
+{
+	int i;
+
+	mt753x_reg_write(gsw, VTCR,
+			 VTCR_BUSY | ((cmd << VTCR_FUNC_S) & VTCR_FUNC_M) |
+			 (val & VTCR_VID_M));
+
+	for (i = 0; i < 300; i++) {
+		u32 val = mt753x_reg_read(gsw, VTCR);
+
+		if ((val & VTCR_BUSY) == 0)
+			break;
+
+		usleep_range(1000, 1100);
+	}
+
+	if (i == 300)
+		dev_info(gsw->dev, "vtcr timeout\n");
+}
+
+static void mt753x_write_vlan_entry(struct gsw_mt753x *gsw, int vlan, u16 vid,
+				    u8 ports, u8 etags)
+{
+	int port;
+	u32 val;
+
+	/* vlan port membership */
+	if (ports)
+		mt753x_reg_write(gsw, VAWD1,
+				 IVL_MAC | VTAG_EN | VENTRY_VALID |
+				 ((ports << PORT_MEM_S) & PORT_MEM_M));
+	else
+		mt753x_reg_write(gsw, VAWD1, 0);
+
+	/* egress mode */
+	val = 0;
+	for (port = 0; port < MT753X_NUM_PORTS; port++) {
+		if (etags & BIT(port))
+			val |= ETAG_CTRL_TAG << PORT_ETAG_S(port);
+		else
+			val |= ETAG_CTRL_UNTAG << PORT_ETAG_S(port);
+	}
+	mt753x_reg_write(gsw, VAWD2, val);
+
+	/* write to vlan table */
+	mt753x_vlan_ctrl(gsw, VTCR_WRITE_VLAN_ENTRY, vid);
+}
+
+void mt753x_apply_vlan_config(struct gsw_mt753x *gsw)
+{
+	int i, j;
+	u8 tag_ports;
+	u8 untag_ports;
+
+	/* set all ports as security mode */
+	for (i = 0; i < MT753X_NUM_PORTS; i++)
+		mt753x_reg_write(gsw, PCR(i),
+				 PORT_MATRIX_M | SECURITY_MODE);
+
+	/* check if a port is used in tag/untag vlan egress mode */
+	tag_ports = 0;
+	untag_ports = 0;
+
+	for (i = 0; i < MT753X_NUM_VLANS; i++) {
+		u8 member = gsw->vlan_entries[i].member;
+		u8 etags = gsw->vlan_entries[i].etags;
+
+		if (!member)
+			continue;
+
+		for (j = 0; j < MT753X_NUM_PORTS; j++) {
+			if (!(member & BIT(j)))
+				continue;
+
+			if (etags & BIT(j))
+				tag_ports |= 1u << j;
+			else
+				untag_ports |= 1u << j;
+		}
+	}
+
+	/* set all untag-only ports as transparent and the rest as user port */
+	for (i = 0; i < MT753X_NUM_PORTS; i++) {
+		u32 pvc_mode = 0x8100 << STAG_VPID_S;
+
+		if (untag_ports & BIT(i) && !(tag_ports & BIT(i)))
+			pvc_mode = (0x8100 << STAG_VPID_S) |
+				(VA_TRANSPARENT_PORT << VLAN_ATTR_S);
+
+		if ((gsw->port5_cfg.stag_on && i == 5) ||
+		    (gsw->port6_cfg.stag_on && i == 6))
+			pvc_mode = (((0x8100 << STAG_VPID_S)) & 0xffffffff) |
+				 PVC_PORT_STAG;
+
+		mt753x_reg_write(gsw, PVC(i), pvc_mode);
+	}
+
+	/* first clear the switch vlan table */
+	for (i = 0; i < MT753X_NUM_VLANS; i++)
+		mt753x_write_vlan_entry(gsw, i, i, 0, 0);
+
+	/* now program only vlans with members to avoid
+	 * clobbering remapped entries in later iterations
+	 */
+	for (i = 0; i < MT753X_NUM_VLANS; i++) {
+		u16 vid = gsw->vlan_entries[i].vid;
+		u8 member = gsw->vlan_entries[i].member;
+		u8 etags = gsw->vlan_entries[i].etags;
+
+		if (member)
+			mt753x_write_vlan_entry(gsw, i, vid, member, etags);
+	}
+
+	/* Port Default PVID */
+	for (i = 0; i < MT753X_NUM_PORTS; i++) {
+		int vlan = gsw->port_entries[i].pvid;
+		u16 pvid = 0;
+		u32 val;
+
+		if (vlan < MT753X_NUM_VLANS && gsw->vlan_entries[vlan].member)
+			pvid = gsw->vlan_entries[vlan].vid;
+
+		val = mt753x_reg_read(gsw, PPBV1(i));
+		val &= ~GRP_PORT_VID_M;
+		val |= pvid;
+		mt753x_reg_write(gsw, PPBV1(i), val);
+	}
+}
+
+void mt753x_apply_vlan_config_init(struct gsw_mt753x *gsw)
+{
+	int i, j;
+	u8 tag_ports;
+	u8 untag_ports;
+
+	/* set all ports as security mode */
+	for (i = 0; i < MT753X_NUM_PORTS; i++)
+		mt753x_reg_write(gsw, PCR(i),
+				 PORT_MATRIX_M | SECURITY_MODE);
+
+	/* check if a port is used in tag/untag vlan egress mode */
+	tag_ports = 0;
+	untag_ports = 0;
+
+	for (i = 0; i < MT753X_NUM_VLANS; i++) {
+		u8 member = gsw->vlan_entries[i].member;
+		u8 etags = gsw->vlan_entries[i].etags;
+
+		if (!member)
+			continue;
+
+		for (j = 0; j < MT753X_NUM_PORTS; j++) {
+			if (!(member & BIT(j)))
+				continue;
+
+			if (etags & BIT(j))
+				tag_ports |= 1u << j;
+			else
+				untag_ports |= 1u << j;
+		}
+	}
+
+	/* set all untag-only ports as transparent and the rest as user port */
+	for (i = 0; i < MT753X_NUM_PORTS; i++) {
+		u32 pvc_mode = 0x8100 << STAG_VPID_S;
+
+		if (untag_ports & BIT(i) && !(tag_ports & BIT(i)))
+			pvc_mode = (0x8100 << STAG_VPID_S) |
+				(VA_TRANSPARENT_PORT << VLAN_ATTR_S);
+
+		if ((gsw->port5_cfg.stag_on && i == 5) ||
+		    (gsw->port6_cfg.stag_on && i == 6))
+			pvc_mode = (((0x8100 << STAG_VPID_S)) & 0xffffffff) |
+				 PVC_PORT_STAG;
+
+		mt753x_reg_write(gsw, PVC(i), pvc_mode);
+	}
+
+	/* now program only vlans with members to avoid
+	 * clobbering remapped entries in later iterations
+	 */
+	for (i = 0; i < MT753X_NUM_VLANS; i++) {
+		u16 vid = gsw->vlan_entries[i].vid;
+		u8 member = gsw->vlan_entries[i].member;
+		u8 etags = gsw->vlan_entries[i].etags;
+
+		if (member)
+			mt753x_write_vlan_entry(gsw, i, vid, member, etags);
+	}
+
+	/* Port Default PVID */
+	for (i = 0; i < MT753X_NUM_PORTS; i++) {
+		int vlan = gsw->port_entries[i].pvid;
+		u16 pvid = 0;
+		u32 val;
+
+		if (vlan < MT753X_NUM_VLANS && gsw->vlan_entries[vlan].member)
+			pvid = gsw->vlan_entries[vlan].vid;
+
+		val = mt753x_reg_read(gsw, PPBV1(i));
+		val &= ~GRP_PORT_VID_M;
+		val |= pvid;
+		mt753x_reg_write(gsw, PPBV1(i), val);
+	}
+}
+struct mt753x_mapping *mt753x_find_mapping(struct device_node *np)
+{
+	const char *map;
+	int i;
+
+	if (of_property_read_string(np, "mediatek,portmap", &map))
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(mt753x_def_mapping); i++)
+		if (!strcmp(map, mt753x_def_mapping[i].name))
+			return &mt753x_def_mapping[i];
+
+	return NULL;
+}
+
+void mt753x_apply_mapping(struct gsw_mt753x *gsw, struct mt753x_mapping *map)
+{
+	int i = 0;
+
+	for (i = 0; i < MT753X_NUM_PORTS; i++)
+		gsw->port_entries[i].pvid = map->pvids[i];
+
+	for (i = 0; i < MT753X_NUM_VLANS; i++) {
+		gsw->vlan_entries[i].member = map->members[i];
+		gsw->vlan_entries[i].etags = map->etags[i];
+		gsw->vlan_entries[i].vid = map->vids[i];
+	}
+}
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_vlan.h b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_vlan.h
new file mode 100644
index 0000000..94022da
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mtk/mt753x/mt753x_vlan.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ */
+
+#ifndef _MT753X_VLAN_H_
+#define _MT753X_VLAN_H_
+
+#define MT753X_NUM_PORTS	7
+#define MT753X_NUM_VLANS	4095
+#define MT753X_MAX_VID		4095
+#define MT753X_MIN_VID		0
+
+struct gsw_mt753x;
+
+struct mt753x_port_entry {
+	u16	pvid;
+};
+
+struct mt753x_vlan_entry {
+	u16	vid;
+	u8	member;
+	u8	etags;
+};
+
+struct mt753x_mapping {
+	char	*name;
+	u16	pvids[MT753X_NUM_PORTS];
+	u8	members[MT753X_NUM_VLANS];
+	u8	etags[MT753X_NUM_VLANS];
+	u16	vids[MT753X_NUM_VLANS];
+};
+
+extern struct mt753x_mapping mt753x_defaults[];
+
+void mt753x_vlan_ctrl(struct gsw_mt753x *gsw, u32 cmd, u32 val);
+void mt753x_apply_vlan_config(struct gsw_mt753x *gsw);
+void mt753x_apply_vlan_config_init(struct gsw_mt753x *gsw);
+struct mt753x_mapping *mt753x_find_mapping(struct device_node *np);
+void mt753x_apply_mapping(struct gsw_mt753x *gsw, struct mt753x_mapping *map);
+#endif /* _MT753X_VLAN_H_ */
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mvsw61xx.c b/src/kernel/linux/v4.19/drivers/net/phy/mvsw61xx.c
new file mode 100644
index 0000000..253ebff
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mvsw61xx.c
@@ -0,0 +1,1093 @@
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (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/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include "mvsw61xx.h"
+
+MODULE_DESCRIPTION("Marvell 88E61xx Switch driver");
+MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>");
+MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mvsw61xx");
+
+/*
+ * Register access is done through direct or indirect addressing,
+ * depending on how the switch is physically connected.
+ *
+ * Direct addressing: all port and global registers directly
+ *   accessible via an address/register pair
+ *
+ * Indirect addressing: switch is mapped at a single address,
+ *   port and global registers accessible via a single command/data
+ *   register pair
+ */
+
+static int
+mvsw61xx_wait_mask_raw(struct mii_bus *bus, int addr,
+		int reg, u16 mask, u16 val)
+{
+	int i = 100;
+	u16 r;
+
+	do {
+		r = bus->read(bus, addr, reg);
+		if ((r & mask) == val)
+			return 0;
+	} while (--i > 0);
+
+	return -ETIMEDOUT;
+}
+
+static u16
+r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg)
+{
+	u16 ind_addr;
+
+	if (!indirect)
+		return bus->read(bus, addr, reg);
+
+	/* Indirect read: First, make sure switch is free */
+	mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD,
+			MV_INDIRECT_INPROGRESS, 0);
+
+	/* Load address and request read */
+	ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg;
+	bus->write(bus, base_addr, MV_INDIRECT_REG_CMD,
+			ind_addr);
+
+	/* Wait until it's ready */
+	mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD,
+			MV_INDIRECT_INPROGRESS, 0);
+
+	/* Read the requested data */
+	return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA);
+}
+
+static void
+w16(struct mii_bus *bus, bool indirect, int base_addr, int addr,
+		int reg, u16 val)
+{
+	u16 ind_addr;
+
+	if (!indirect) {
+		bus->write(bus, addr, reg, val);
+		return;
+	}
+
+	/* Indirect write: First, make sure switch is free */
+	mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD,
+			MV_INDIRECT_INPROGRESS, 0);
+
+	/* Load the data to be written */
+	bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val);
+
+	/* Wait again for switch to be free */
+	mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD,
+			MV_INDIRECT_INPROGRESS, 0);
+
+	/* Load address, and issue write command */
+	ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg;
+	bus->write(bus, base_addr, MV_INDIRECT_REG_CMD,
+			ind_addr);
+}
+
+/* swconfig support */
+
+static inline u16
+sr16(struct switch_dev *dev, int addr, int reg)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	return r16(state->bus, state->is_indirect, state->base_addr, addr, reg);
+}
+
+static inline void
+sw16(struct switch_dev *dev, int addr, int reg, u16 val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val);
+}
+
+static int
+mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr,
+		int reg, u16 mask, u16 val)
+{
+	int i = 100;
+	u16 r;
+
+	do {
+		r = sr16(dev, addr, reg) & mask;
+		if (r == val)
+			return 0;
+	} while (--i > 0);
+
+	return -ETIMEDOUT;
+}
+
+static int
+mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg)
+{
+	sw16(dev, MV_GLOBAL2REG(SMI_OP),
+	     MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg);
+
+	if (mvsw61xx_wait_mask_s(dev,  MV_GLOBAL2REG(SMI_OP),
+				 MV_INDIRECT_INPROGRESS, 0) < 0)
+		return -ETIMEDOUT;
+
+	return sr16(dev, MV_GLOBAL2REG(SMI_DATA));
+}
+
+static int
+mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val)
+{
+	sw16(dev, MV_GLOBAL2REG(SMI_DATA), val);
+
+	sw16(dev, MV_GLOBAL2REG(SMI_OP),
+	     MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg);
+
+	return mvsw61xx_wait_mask_s(dev,  MV_GLOBAL2REG(SMI_OP),
+				    MV_INDIRECT_INPROGRESS, 0) < 0;
+}
+
+static int
+mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg)
+{
+	int ret;
+
+	mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page);
+	ret = mvsw61xx_mdio_read(dev, port, reg);
+	mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0);
+
+	return ret;
+}
+
+static void
+mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg,
+			 u16 val)
+{
+	mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page);
+	mvsw61xx_mdio_write(dev, port, reg, val);
+	mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0);
+}
+
+static int
+mvsw61xx_get_port_mask(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	char *buf = state->buf;
+	int port, len, i;
+	u16 reg;
+
+	port = val->port_vlan;
+	reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK;
+
+	len = sprintf(buf, "0x%04x: ", reg);
+
+	for (i = 0; i < MV_PORTS; i++) {
+		if (reg & (1 << i))
+			len += sprintf(buf + len, "%d ", i);
+		else if (i == port)
+			len += sprintf(buf + len, "(%d) ", i);
+	}
+
+	val->value.s = buf;
+
+	return 0;
+}
+
+static int
+mvsw61xx_get_port_qmode(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	val->value.i = state->ports[val->port_vlan].qmode;
+
+	return 0;
+}
+
+static int
+mvsw61xx_set_port_qmode(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	state->ports[val->port_vlan].qmode = val->value.i;
+
+	return 0;
+}
+
+static int
+mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	*val = state->ports[port].pvid;
+
+	return 0;
+}
+
+static int
+mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	if (val < 0 || val >= MV_VLANS)
+		return -EINVAL;
+
+	state->ports[port].pvid = (u16)val;
+
+	return 0;
+}
+
+static int
+mvsw61xx_get_port_link(struct switch_dev *dev, int port,
+		struct switch_port_link *link)
+{
+	u16 status, speed;
+
+	status = sr16(dev, MV_PORTREG(STATUS, port));
+
+	link->link = status & MV_PORT_STATUS_LINK;
+	if (!link->link)
+		return 0;
+
+	link->duplex = status & MV_PORT_STATUS_FDX;
+
+	speed = (status & MV_PORT_STATUS_SPEED_MASK) >>
+			MV_PORT_STATUS_SPEED_SHIFT;
+
+	switch (speed) {
+	case MV_PORT_STATUS_SPEED_10:
+		link->speed = SWITCH_PORT_SPEED_10;
+		break;
+	case MV_PORT_STATUS_SPEED_100:
+		link->speed = SWITCH_PORT_SPEED_100;
+		break;
+	case MV_PORT_STATUS_SPEED_1000:
+		link->speed = SWITCH_PORT_SPEED_1000;
+		break;
+	}
+
+	return 0;
+}
+
+static int mvsw61xx_get_vlan_ports(struct switch_dev *dev,
+		struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int i, j, mode, vno;
+
+	vno = val->port_vlan;
+
+	if (vno <= 0 || vno >= dev->vlans)
+		return -EINVAL;
+
+	for (i = 0, j = 0; i < dev->ports; i++) {
+		if (state->vlans[vno].mask & (1 << i)) {
+			val->value.ports[j].id = i;
+
+			mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf;
+			if (mode == MV_VTUCTL_EGRESS_TAGGED)
+				val->value.ports[j].flags =
+					(1 << SWITCH_PORT_FLAG_TAGGED);
+			else
+				val->value.ports[j].flags = 0;
+
+			j++;
+		}
+	}
+
+	val->len = j;
+
+	return 0;
+}
+
+static int mvsw61xx_set_vlan_ports(struct switch_dev *dev,
+		struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int i, mode, pno, vno;
+
+	vno = val->port_vlan;
+
+	if (vno <= 0 || vno >= dev->vlans)
+		return -EINVAL;
+
+	state->vlans[vno].mask = 0;
+	state->vlans[vno].port_mode = 0;
+	state->vlans[vno].port_sstate = 0;
+
+	if(state->vlans[vno].vid == 0)
+		state->vlans[vno].vid = vno;
+
+	for (i = 0; i < val->len; i++) {
+		pno = val->value.ports[i].id;
+
+		state->vlans[vno].mask |= (1 << pno);
+		if (val->value.ports[i].flags &
+				(1 << SWITCH_PORT_FLAG_TAGGED))
+			mode = MV_VTUCTL_EGRESS_TAGGED;
+		else
+			mode = MV_VTUCTL_EGRESS_UNTAGGED;
+
+		state->vlans[vno].port_mode |= mode << (pno * 4);
+		state->vlans[vno].port_sstate |=
+			MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2);
+	}
+
+	/*
+	 * DISCARD is nonzero, so it must be explicitly
+	 * set on ports not in the VLAN.
+	 */
+	for (i = 0; i < dev->ports; i++)
+		if (!(state->vlans[vno].mask & (1 << i)))
+			state->vlans[vno].port_mode |=
+				MV_VTUCTL_DISCARD << (i * 4);
+
+	return 0;
+}
+
+static int mvsw61xx_get_vlan_port_based(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int vno = val->port_vlan;
+
+	if (vno <= 0 || vno >= dev->vlans)
+		return -EINVAL;
+
+	if (state->vlans[vno].port_based)
+		val->value.i = 1;
+	else
+		val->value.i = 0;
+
+	return 0;
+}
+
+static int mvsw61xx_set_vlan_port_based(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int vno = val->port_vlan;
+
+	if (vno <= 0 || vno >= dev->vlans)
+		return -EINVAL;
+
+	if (val->value.i == 1)
+		state->vlans[vno].port_based = true;
+	else
+		state->vlans[vno].port_based = false;
+
+	return 0;
+}
+
+static int mvsw61xx_get_vid(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int vno = val->port_vlan;
+
+	if (vno <= 0 || vno >= dev->vlans)
+		return -EINVAL;
+
+	val->value.i = state->vlans[vno].vid;
+
+	return 0;
+}
+
+static int mvsw61xx_set_vid(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int vno = val->port_vlan;
+
+	if (vno <= 0 || vno >= dev->vlans)
+		return -EINVAL;
+
+	state->vlans[vno].vid = val->value.i;
+
+	return 0;
+}
+
+static int mvsw61xx_get_enable_vlan(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	val->value.i = state->vlan_enabled;
+
+	return 0;
+}
+
+static int mvsw61xx_set_enable_vlan(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	state->vlan_enabled = val->value.i;
+
+	return 0;
+}
+
+static int mvsw61xx_get_mirror_rx_enable(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	val->value.i = state->mirror_rx;
+
+	return 0;
+}
+
+static int mvsw61xx_set_mirror_rx_enable(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	state->mirror_rx = val->value.i;
+
+	return 0;
+}
+
+static int mvsw61xx_get_mirror_tx_enable(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	val->value.i = state->mirror_tx;
+
+	return 0;
+}
+
+static int mvsw61xx_set_mirror_tx_enable(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	state->mirror_tx = val->value.i;
+
+	return 0;
+}
+
+static int mvsw61xx_get_mirror_monitor_port(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	val->value.i = state->monitor_port;
+
+	return 0;
+}
+
+static int mvsw61xx_set_mirror_monitor_port(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	state->monitor_port = val->value.i;
+
+	return 0;
+}
+
+static int mvsw61xx_get_mirror_source_port(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	val->value.i = state->source_port;
+
+	return 0;
+}
+
+static int mvsw61xx_set_mirror_source_port(struct switch_dev *dev,
+		const struct switch_attr *attr, struct switch_val *val)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+
+	state->source_port = val->value.i;
+
+	return 0;
+}
+
+static int mvsw61xx_vtu_program(struct switch_dev *dev)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	u16 v1, v2, s1, s2;
+	int i;
+
+	/* Flush */
+	mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP),
+			MV_VTUOP_INPROGRESS, 0);
+	sw16(dev, MV_GLOBALREG(VTU_OP),
+			MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE);
+
+	/* Write VLAN table */
+	for (i = 1; i < dev->vlans; i++) {
+		if (state->vlans[i].mask == 0 ||
+				state->vlans[i].vid == 0 ||
+				state->vlans[i].port_based == true)
+			continue;
+
+		mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP),
+				MV_VTUOP_INPROGRESS, 0);
+
+		/* Write per-VLAN port state into STU */
+		s1 = (u16) (state->vlans[i].port_sstate & 0xffff);
+		s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff);
+
+		sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID);
+		sw16(dev, MV_GLOBALREG(VTU_SID), i);
+		sw16(dev, MV_GLOBALREG(VTU_DATA1), s1);
+		sw16(dev, MV_GLOBALREG(VTU_DATA2), s2);
+		sw16(dev, MV_GLOBALREG(VTU_DATA3), 0);
+
+		sw16(dev, MV_GLOBALREG(VTU_OP),
+				MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD);
+		mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP),
+				MV_VTUOP_INPROGRESS, 0);
+
+		/* Write VLAN information into VTU */
+		v1 = (u16) (state->vlans[i].port_mode & 0xffff);
+		v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff);
+
+		sw16(dev, MV_GLOBALREG(VTU_VID),
+				MV_VTU_VID_VALID | state->vlans[i].vid);
+		sw16(dev, MV_GLOBALREG(VTU_SID), i);
+		sw16(dev, MV_GLOBALREG(VTU_FID), i);
+		sw16(dev, MV_GLOBALREG(VTU_DATA1), v1);
+		sw16(dev, MV_GLOBALREG(VTU_DATA2), v2);
+		sw16(dev, MV_GLOBALREG(VTU_DATA3), 0);
+
+		sw16(dev, MV_GLOBALREG(VTU_OP),
+				MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD);
+		mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP),
+				MV_VTUOP_INPROGRESS, 0);
+	}
+
+	return 0;
+}
+
+static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int i, mode;
+
+	for (i = 0; i < dev->ports; i++) {
+		if (!(state->vlans[vno].mask & (1 << i)))
+			continue;
+
+		mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf;
+
+		if(mode != MV_VTUCTL_EGRESS_TAGGED)
+			state->ports[i].pvid = state->vlans[vno].vid;
+
+		if (state->vlans[vno].port_based) {
+			state->ports[i].mask |= state->vlans[vno].mask;
+			state->ports[i].fdb = vno;
+		}
+		else
+			state->ports[i].qmode = MV_8021Q_MODE_SECURE;
+	}
+}
+
+static int mvsw61xx_update_state(struct switch_dev *dev)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int i;
+	u16 reg;
+
+	if (!state->registered)
+		return -EINVAL;
+
+	/*
+	 * Set 802.1q-only mode if vlan_enabled is true.
+	 *
+	 * Without this, even if 802.1q is enabled for
+	 * a port/VLAN, it still depends on the port-based
+	 * VLAN mask being set.
+	 *
+	 * With this setting, port-based VLANs are still
+	 * functional, provided the VID is not in the VTU.
+	 */
+	reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY));
+
+	if (state->vlan_enabled)
+		reg |= MV_8021Q_VLAN_ONLY;
+	else
+		reg &= ~MV_8021Q_VLAN_ONLY;
+
+	sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg);
+
+	/*
+	 * Set port-based VLAN masks on each port
+	 * based only on VLAN definitions known to
+	 * the driver (i.e. in state).
+	 *
+	 * This means any pre-existing port mapping is
+	 * wiped out once our driver is initialized.
+	 */
+	for (i = 0; i < dev->ports; i++) {
+		state->ports[i].mask = 0;
+		state->ports[i].qmode = MV_8021Q_MODE_DISABLE;
+	}
+
+	for (i = 0; i < dev->vlans; i++)
+		mvsw61xx_vlan_port_config(dev, i);
+
+	for (i = 0; i < dev->ports; i++) {
+		reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK;
+		reg |= state->ports[i].pvid;
+		sw16(dev, MV_PORTREG(VLANID, i), reg);
+
+		state->ports[i].mask &= ~(1 << i);
+
+		/* set default forwarding DB number and port mask */
+		reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK;
+		reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) &
+			MV_FDB_HI_MASK;
+		sw16(dev, MV_PORTREG(CONTROL1, i), reg);
+
+		reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) |
+			state->ports[i].mask;
+		sw16(dev, MV_PORTREG(VLANMAP, i), reg);
+
+		reg = sr16(dev, MV_PORTREG(CONTROL2, i)) &
+			~MV_8021Q_MODE_MASK;
+		reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT;
+		sw16(dev, MV_PORTREG(CONTROL2, i), reg);
+	}
+
+	mvsw61xx_vtu_program(dev);
+
+	/* port mirroring */
+	/* reset all mirror registers */
+	for (i = 0; i < dev->ports; i++) {
+		reg = sr16(dev, MV_PORTREG(CONTROL2, i));
+		reg &= ~(MV_MIRROR_RX_SRC_MASK | MV_MIRROR_TX_SRC_MASK);
+		sw16(dev, MV_PORTREG(CONTROL2, i), reg);
+	}
+	reg = sr16(dev, MV_GLOBALREG(MONITOR_CTRL));
+	reg |= MV_MIRROR_RX_DEST_MASK | MV_MIRROR_TX_DEST_MASK;
+	sw16(dev, MV_GLOBALREG(MONITOR_CTRL), reg);
+
+	/* now enable mirroring if necessary */
+	if (state->mirror_rx) {
+		/* set ingress monitor source */
+		reg = sr16(dev, MV_PORTREG(CONTROL2, state->source_port)) & ~MV_MIRROR_RX_SRC_MASK;
+		reg |= state->mirror_rx << MV_MIRROR_RX_SRC_SHIFT;
+		sw16(dev, MV_PORTREG(CONTROL2, state->source_port), reg);
+		/* set ingress monitor destination */
+		reg = sr16(dev, MV_GLOBALREG(MONITOR_CTRL)) & ~MV_MIRROR_RX_DEST_MASK;
+		reg |= state->monitor_port << MV_MIRROR_RX_DEST_SHIFT;
+		sw16(dev, MV_GLOBALREG(MONITOR_CTRL), reg);
+	}
+
+	if (state->mirror_tx) {
+		/* set egress monitor source */
+		reg = sr16(dev, MV_PORTREG(CONTROL2, state->source_port)) & ~MV_MIRROR_TX_SRC_MASK;
+		reg |= state->mirror_tx << MV_MIRROR_TX_SRC_SHIFT;
+		sw16(dev, MV_PORTREG(CONTROL2, state->source_port), reg);
+		/* set egress monitor destination */
+		reg = sr16(dev, MV_GLOBALREG(MONITOR_CTRL)) & ~MV_MIRROR_TX_DEST_MASK;
+		reg |= state->monitor_port << MV_MIRROR_TX_DEST_SHIFT;
+		sw16(dev, MV_GLOBALREG(MONITOR_CTRL), reg);
+	}
+
+	return 0;
+}
+
+static int mvsw61xx_apply(struct switch_dev *dev)
+{
+	return mvsw61xx_update_state(dev);
+}
+
+static void mvsw61xx_enable_serdes(struct switch_dev *dev)
+{
+	int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES,
+					   MV_PAGE_FIBER_SERDES, MII_BMCR);
+	if (bmcr < 0)
+		return;
+
+	if (bmcr & BMCR_PDOWN)
+		mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES,
+					 MV_PAGE_FIBER_SERDES, MII_BMCR,
+					 bmcr & ~BMCR_PDOWN);
+}
+
+static int _mvsw61xx_reset(struct switch_dev *dev, bool full)
+{
+	struct mvsw61xx_state *state = get_state(dev);
+	int i;
+	u16 reg;
+
+	/* Disable all ports before reset */
+	for (i = 0; i < dev->ports; i++) {
+		reg = sr16(dev, MV_PORTREG(CONTROL, i)) &
+			~MV_PORTCTRL_FORWARDING;
+		sw16(dev, MV_PORTREG(CONTROL, i), reg);
+	}
+
+	reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET;
+
+	sw16(dev, MV_GLOBALREG(CONTROL), reg);
+	if (mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(CONTROL),
+				MV_CONTROL_RESET, 0) < 0)
+		return -ETIMEDOUT;
+
+	for (i = 0; i < dev->ports; i++) {
+		state->ports[i].fdb = 0;
+		state->ports[i].qmode = 0;
+		state->ports[i].mask = 0;
+		state->ports[i].pvid = 0;
+
+		/* Force flow control off */
+		reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK;
+		reg |= MV_PHYCTL_FC_DISABLE;
+		sw16(dev, MV_PORTREG(PHYCTL, i), reg);
+
+		/* Set port association vector */
+		sw16(dev, MV_PORTREG(ASSOC, i), (1 << i));
+
+		/* power up phys */
+		if (full && i < 5) {
+			mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL,
+					    MV_SPEC_MDI_CROSS_AUTO |
+					    MV_SPEC_ENERGY_DETECT |
+					    MV_SPEC_DOWNSHIFT_COUNTER);
+			mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET |
+					    BMCR_ANENABLE | BMCR_FULLDPLX |
+					    BMCR_SPEED1000);
+		}
+
+		/* enable SerDes if necessary */
+		if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) {
+			u16 sts = sr16(dev, MV_PORTREG(STATUS, i));
+			u16 mode = sts & MV_PORT_STATUS_CMODE_MASK;
+
+			if (mode == MV_PORT_STATUS_CMODE_100BASE_X ||
+			    mode == MV_PORT_STATUS_CMODE_1000BASE_X ||
+			    mode == MV_PORT_STATUS_CMODE_SGMII) {
+				mvsw61xx_enable_serdes(dev);
+			}
+		}
+	}
+
+	for (i = 0; i < dev->vlans; i++) {
+		state->vlans[i].port_based = false;
+		state->vlans[i].mask = 0;
+		state->vlans[i].vid = 0;
+		state->vlans[i].port_mode = 0;
+		state->vlans[i].port_sstate = 0;
+	}
+
+	state->vlan_enabled = 0;
+
+	state->mirror_rx = false;
+	state->mirror_tx = false;
+	state->source_port = 0;
+	state->monitor_port = 0;
+
+	mvsw61xx_update_state(dev);
+
+	/* Re-enable ports */
+	for (i = 0; i < dev->ports; i++) {
+		reg = sr16(dev, MV_PORTREG(CONTROL, i)) |
+			MV_PORTCTRL_FORWARDING;
+		sw16(dev, MV_PORTREG(CONTROL, i), reg);
+	}
+
+	return 0;
+}
+
+static int mvsw61xx_reset(struct switch_dev *dev)
+{
+	return _mvsw61xx_reset(dev, false);
+}
+
+enum {
+	MVSW61XX_VLAN_PORT_BASED,
+	MVSW61XX_VLAN_ID,
+};
+
+enum {
+	MVSW61XX_PORT_MASK,
+	MVSW61XX_PORT_QMODE,
+};
+
+static const struct switch_attr mvsw61xx_global[] = {
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_vlan",
+		.description = "Enable 802.1q VLAN support",
+		.get = mvsw61xx_get_enable_vlan,
+		.set = mvsw61xx_set_enable_vlan,
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_rx",
+		.description = "Enable mirroring of RX packets",
+		.set = mvsw61xx_set_mirror_rx_enable,
+		.get = mvsw61xx_get_mirror_rx_enable,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "enable_mirror_tx",
+		.description = "Enable mirroring of TX packets",
+		.set = mvsw61xx_set_mirror_tx_enable,
+		.get = mvsw61xx_get_mirror_tx_enable,
+		.max = 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_monitor_port",
+		.description = "Mirror monitor port",
+		.set = mvsw61xx_set_mirror_monitor_port,
+		.get = mvsw61xx_get_mirror_monitor_port,
+		.max = MV_PORTS - 1
+	},
+	{
+		.type = SWITCH_TYPE_INT,
+		.name = "mirror_source_port",
+		.description = "Mirror source port",
+		.set = mvsw61xx_set_mirror_source_port,
+		.get = mvsw61xx_get_mirror_source_port,
+		.max = MV_PORTS - 1
+	},
+};
+
+static const struct switch_attr mvsw61xx_vlan[] = {
+	[MVSW61XX_VLAN_PORT_BASED] = {
+		.id = MVSW61XX_VLAN_PORT_BASED,
+		.type = SWITCH_TYPE_INT,
+		.name = "port_based",
+		.description = "Use port-based (non-802.1q) VLAN only",
+		.get = mvsw61xx_get_vlan_port_based,
+		.set = mvsw61xx_set_vlan_port_based,
+	},
+	[MVSW61XX_VLAN_ID] = {
+		.id = MVSW61XX_VLAN_ID,
+		.type = SWITCH_TYPE_INT,
+		.name = "vid",
+		.description = "Get/set VLAN ID",
+		.get = mvsw61xx_get_vid,
+		.set = mvsw61xx_set_vid,
+	},
+};
+
+static const struct switch_attr mvsw61xx_port[] = {
+	[MVSW61XX_PORT_MASK] = {
+		.id = MVSW61XX_PORT_MASK,
+		.type = SWITCH_TYPE_STRING,
+		.description = "Port-based VLAN mask",
+		.name = "mask",
+		.get = mvsw61xx_get_port_mask,
+		.set = NULL,
+	},
+	[MVSW61XX_PORT_QMODE] = {
+		.id = MVSW61XX_PORT_QMODE,
+		.type = SWITCH_TYPE_INT,
+		.description = "802.1q mode: 0=off/1=fallback/2=check/3=secure",
+		.name = "qmode",
+		.get = mvsw61xx_get_port_qmode,
+		.set = mvsw61xx_set_port_qmode,
+	},
+};
+
+static const struct switch_dev_ops mvsw61xx_ops = {
+	.attr_global = {
+		.attr = mvsw61xx_global,
+		.n_attr = ARRAY_SIZE(mvsw61xx_global),
+	},
+	.attr_vlan = {
+		.attr = mvsw61xx_vlan,
+		.n_attr = ARRAY_SIZE(mvsw61xx_vlan),
+	},
+	.attr_port = {
+		.attr = mvsw61xx_port,
+		.n_attr = ARRAY_SIZE(mvsw61xx_port),
+	},
+	.get_port_link = mvsw61xx_get_port_link,
+	.get_port_pvid = mvsw61xx_get_port_pvid,
+	.set_port_pvid = mvsw61xx_set_port_pvid,
+	.get_vlan_ports = mvsw61xx_get_vlan_ports,
+	.set_vlan_ports = mvsw61xx_set_vlan_ports,
+	.apply_config = mvsw61xx_apply,
+	.reset_switch = mvsw61xx_reset,
+};
+
+/* end swconfig stuff */
+
+static int mvsw61xx_probe(struct platform_device *pdev)
+{
+	struct mvsw61xx_state *state;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *mdio;
+	char *model_str;
+	u32 val;
+	int err;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	mdio = of_parse_phandle(np, "mii-bus", 0);
+	if (!mdio) {
+		dev_err(&pdev->dev, "Couldn't get MII bus handle\n");
+		err = -ENODEV;
+		goto out_err;
+	}
+
+	state->bus = of_mdio_find_bus(mdio);
+	if (!state->bus) {
+		dev_err(&pdev->dev, "Couldn't find MII bus from handle\n");
+		err = -ENODEV;
+		goto out_err;
+	}
+
+	state->is_indirect = of_property_read_bool(np, "is-indirect");
+
+	if (state->is_indirect) {
+		if (of_property_read_u32(np, "reg", &val)) {
+			dev_err(&pdev->dev, "Switch address not specified\n");
+			err = -ENODEV;
+			goto out_err;
+		}
+
+		state->base_addr = val;
+	} else {
+		state->base_addr = MV_BASE;
+	}
+
+	state->model = r16(state->bus, state->is_indirect, state->base_addr,
+				MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
+
+	switch(state->model) {
+	case MV_IDENT_VALUE_6171:
+		model_str = MV_IDENT_STR_6171;
+		break;
+	case MV_IDENT_VALUE_6172:
+		model_str = MV_IDENT_STR_6172;
+		break;
+	case MV_IDENT_VALUE_6176:
+		model_str = MV_IDENT_STR_6176;
+		break;
+	case MV_IDENT_VALUE_6352:
+		model_str = MV_IDENT_STR_6352;
+		break;
+	default:
+		dev_err(&pdev->dev, "No compatible switch found at 0x%02x\n",
+				state->base_addr);
+		err = -ENODEV;
+		goto out_err;
+	}
+
+	platform_set_drvdata(pdev, state);
+	dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str,
+			state->bus->id, state->base_addr);
+
+	dev_info(&pdev->dev, "Using %sdirect addressing\n",
+			(state->is_indirect ? "in" : ""));
+
+	if (of_property_read_u32(np, "cpu-port-0", &val)) {
+		dev_err(&pdev->dev, "CPU port not set\n");
+		err = -ENODEV;
+		goto out_err;
+	}
+
+	state->cpu_port0 = val;
+
+	if (!of_property_read_u32(np, "cpu-port-1", &val))
+		state->cpu_port1 = val;
+	else
+		state->cpu_port1 = -1;
+
+	state->dev.vlans = MV_VLANS;
+	state->dev.cpu_port = state->cpu_port0;
+	state->dev.ports = MV_PORTS;
+	state->dev.name = model_str;
+	state->dev.ops = &mvsw61xx_ops;
+	state->dev.alias = dev_name(&pdev->dev);
+
+	_mvsw61xx_reset(&state->dev, true);
+
+	err = register_switch(&state->dev, NULL);
+	if (err < 0)
+		goto out_err;
+
+	state->registered = true;
+
+	return 0;
+out_err:
+	kfree(state);
+	return err;
+}
+
+static int
+mvsw61xx_remove(struct platform_device *pdev)
+{
+	struct mvsw61xx_state *state = platform_get_drvdata(pdev);
+
+	if (state->registered)
+		unregister_switch(&state->dev);
+
+	kfree(state);
+
+	return 0;
+}
+
+static const struct of_device_id mvsw61xx_match[] = {
+	{ .compatible = "marvell,88e6171" },
+	{ .compatible = "marvell,88e6172" },
+	{ .compatible = "marvell,88e6176" },
+	{ .compatible = "marvell,88e6352" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mvsw61xx_match);
+
+static struct platform_driver mvsw61xx_driver = {
+	.probe = mvsw61xx_probe,
+	.remove = mvsw61xx_remove,
+	.driver = {
+		.name = "mvsw61xx",
+		.of_match_table = of_match_ptr(mvsw61xx_match),
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init mvsw61xx_module_init(void)
+{
+	return platform_driver_register(&mvsw61xx_driver);
+}
+late_initcall(mvsw61xx_module_init);
+
+static void __exit mvsw61xx_module_exit(void)
+{
+	platform_driver_unregister(&mvsw61xx_driver);
+}
+module_exit(mvsw61xx_module_exit);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mvsw61xx.h b/src/kernel/linux/v4.19/drivers/net/phy/mvsw61xx.h
new file mode 100644
index 0000000..545e2dd
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mvsw61xx.h
@@ -0,0 +1,309 @@
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (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 __MVSW61XX_H
+#define __MVSW61XX_H
+
+#define MV_PORTS			7
+#define MV_PORTS_MASK			((1 << MV_PORTS) - 1)
+
+#define MV_BASE				0x10
+
+#define MV_SWITCHPORT_BASE		0x10
+#define MV_SWITCHPORT(_n)		(MV_SWITCHPORT_BASE + (_n))
+#define MV_SWITCHREGS			(MV_BASE + 0xb)
+
+#define MV_VLANS			64
+
+enum {
+	MV_PORT_STATUS			= 0x00,
+	MV_PORT_PHYCTL			= 0x01,
+	MV_PORT_JAMCTL			= 0x02,
+	MV_PORT_IDENT			= 0x03,
+	MV_PORT_CONTROL			= 0x04,
+	MV_PORT_CONTROL1		= 0x05,
+	MV_PORT_VLANMAP			= 0x06,
+	MV_PORT_VLANID			= 0x07,
+	MV_PORT_CONTROL2		= 0x08,
+	MV_PORT_ASSOC			= 0x0b,
+	MV_PORT_RX_DISCARD_LOW		= 0x10,
+	MV_PORT_RX_DISCARD_HIGH		= 0x11,
+	MV_PORT_IN_FILTERED		= 0x12,
+	MV_PORT_OUT_ACCEPTED		= 0x13,
+};
+#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type
+
+enum {
+	MV_PORT_STATUS_FDX		= (1 << 10),
+	MV_PORT_STATUS_LINK		= (1 << 11),
+};
+
+enum {
+	MV_PORT_STATUS_CMODE_100BASE_X	= 0x8,
+	MV_PORT_STATUS_CMODE_1000BASE_X	= 0x9,
+	MV_PORT_STATUS_CMODE_SGMII	= 0xa,
+};
+
+#define MV_PORT_STATUS_CMODE_MASK	0xf
+
+enum {
+	MV_PORT_STATUS_SPEED_10		= 0x00,
+	MV_PORT_STATUS_SPEED_100	= 0x01,
+	MV_PORT_STATUS_SPEED_1000	= 0x02,
+};
+#define MV_PORT_STATUS_SPEED_SHIFT	8
+#define MV_PORT_STATUS_SPEED_MASK	(3 << 8)
+
+enum {
+	MV_PORTCTRL_DISABLED		= (0 << 0),
+	MV_PORTCTRL_BLOCKING		= (1 << 0),
+	MV_PORTCTRL_LEARNING		= (2 << 0),
+	MV_PORTCTRL_FORWARDING		= (3 << 0),
+	MV_PORTCTRL_VLANTUN		= (1 << 7),
+	MV_PORTCTRL_EGRESS		= (1 << 12),
+};
+
+#define MV_PHYCTL_FC_MASK		(3 << 6)
+
+enum {
+	MV_PHYCTL_FC_ENABLE		= (3 << 6),
+	MV_PHYCTL_FC_DISABLE		= (1 << 6),
+};
+
+enum {
+	MV_8021Q_EGRESS_UNMODIFIED	= 0x00,
+	MV_8021Q_EGRESS_UNTAGGED	= 0x01,
+	MV_8021Q_EGRESS_TAGGED		= 0x02,
+	MV_8021Q_EGRESS_ADDTAG		= 0x03,
+};
+
+#define MV_8021Q_MODE_SHIFT		10
+#define MV_8021Q_MODE_MASK		(0x3 << MV_8021Q_MODE_SHIFT)
+
+enum {
+	MV_8021Q_MODE_DISABLE		= 0x00,
+	MV_8021Q_MODE_FALLBACK		= 0x01,
+	MV_8021Q_MODE_CHECK		= 0x02,
+	MV_8021Q_MODE_SECURE		= 0x03,
+};
+
+enum {
+	MV_8021Q_VLAN_ONLY		= (1 << 15),
+};
+
+#define MV_PORTASSOC_MONITOR		(1 << 15)
+
+enum {
+	MV_SWITCH_ATU_FID0		= 0x01,
+	MV_SWITCH_ATU_FID1		= 0x02,
+	MV_SWITCH_ATU_SID		= 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,
+	MV_SWITCH_GLOBAL		= 0x1b,
+	MV_SWITCH_GLOBAL2		= 0x1c,
+};
+#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type
+
+enum {
+	MV_SWITCHCTL_EEIE		= (1 << 0),
+	MV_SWITCHCTL_PHYIE		= (1 << 1),
+	MV_SWITCHCTL_ATUDONE		= (1 << 2),
+	MV_SWITCHCTL_ATUIE		= (1 << 3),
+	MV_SWITCHCTL_CTRMODE		= (1 << 8),
+	MV_SWITCHCTL_RELOAD		= (1 << 9),
+	MV_SWITCHCTL_MSIZE		= (1 << 10),
+	MV_SWITCHCTL_DROP		= (1 << 13),
+};
+
+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),
+};
+
+enum {
+	MV_GLOBAL_STATUS		= 0x00,
+	MV_GLOBAL_ATU_FID		= 0x01,
+	MV_GLOBAL_VTU_FID		= 0x02,
+	MV_GLOBAL_VTU_SID		= 0x03,
+	MV_GLOBAL_CONTROL		= 0x04,
+	MV_GLOBAL_VTU_OP		= 0x05,
+	MV_GLOBAL_VTU_VID		= 0x06,
+	MV_GLOBAL_VTU_DATA1		= 0x07,
+	MV_GLOBAL_VTU_DATA2		= 0x08,
+	MV_GLOBAL_VTU_DATA3		= 0x09,
+	MV_GLOBAL_MONITOR_CTRL		= 0x1a,
+	MV_GLOBAL_CONTROL2		= 0x1c,
+};
+#define MV_GLOBALREG(_type) MV_SWITCH_GLOBAL, MV_GLOBAL_##_type
+
+enum {
+	MV_GLOBAL2_SMI_OP		= 0x18,
+	MV_GLOBAL2_SMI_DATA		= 0x19,
+	MV_GLOBAL2_SDET_POLARITY	= 0x1d,
+};
+#define MV_GLOBAL2REG(_type) MV_SWITCH_GLOBAL2, MV_GLOBAL2_##_type
+
+enum {
+	MV_VTU_VID_VALID		= (1 << 12),
+};
+
+enum {
+	MV_VTUOP_PURGE			= (1 << 12),
+	MV_VTUOP_LOAD			= (3 << 12),
+	MV_VTUOP_INPROGRESS		= (1 << 15),
+	MV_VTUOP_STULOAD		= (5 << 12),
+	MV_VTUOP_VTU_GET_NEXT		= (4 << 12),
+	MV_VTUOP_STU_GET_NEXT		= (6 << 12),
+	MV_VTUOP_GET_VIOLATION		= (7 << 12),
+};
+
+enum {
+	MV_CONTROL_RESET		= (1 << 15),
+	MV_CONTROL_PPU_ENABLE		= (1 << 14),
+};
+
+enum {
+	MV_VTUCTL_EGRESS_UNMODIFIED	= (0 << 0),
+	MV_VTUCTL_EGRESS_UNTAGGED	= (1 << 0),
+	MV_VTUCTL_EGRESS_TAGGED		= (2 << 0),
+	MV_VTUCTL_DISCARD		= (3 << 0),
+};
+
+enum {
+	MV_STUCTL_STATE_DISABLED	= (0 << 0),
+	MV_STUCTL_STATE_BLOCKING	= (1 << 0),
+	MV_STUCTL_STATE_LEARNING	= (2 << 0),
+	MV_STUCTL_STATE_FORWARDING	= (3 << 0),
+};
+
+enum {
+	MV_INDIRECT_REG_CMD		= 0,
+	MV_INDIRECT_REG_DATA		= 1,
+};
+
+enum {
+	MV_INDIRECT_INPROGRESS		= 0x8000,
+	MV_INDIRECT_WRITE		= 0x9400,
+	MV_INDIRECT_READ		= 0x9800,
+};
+#define MV_INDIRECT_ADDR_S		5
+
+#define MV_IDENT_MASK			0xfff0
+
+#define MV_IDENT_VALUE_6171		0x1710
+#define MV_IDENT_STR_6171		"MV88E6171"
+
+#define MV_IDENT_VALUE_6172		0x1720
+#define MV_IDENT_STR_6172		"MV88E6172"
+
+#define MV_IDENT_VALUE_6176		0x1760
+#define MV_IDENT_STR_6176		"MV88E6176"
+
+#define MV_IDENT_VALUE_6352		0x3520
+#define MV_IDENT_STR_6352		"MV88E6352"
+
+#define MV_PVID_MASK			0x0fff
+
+#define MV_FDB_HI_MASK			0x00ff
+#define MV_FDB_LO_MASK			0xf000
+#define MV_FDB_HI_SHIFT			4
+#define MV_FDB_LO_SHIFT			12
+
+#define MV_MIRROR_RX_DEST_MASK		0xf000
+#define MV_MIRROR_TX_DEST_MASK		0x0f00
+#define MV_MIRROR_RX_DEST_SHIFT		12
+#define MV_MIRROR_TX_DEST_SHIFT		8
+
+#define MV_MIRROR_RX_SRC_SHIFT		4
+#define MV_MIRROR_RX_SRC_MASK		(1 << MV_MIRROR_RX_SRC_SHIFT)
+#define MV_MIRROR_TX_SRC_SHIFT		5
+#define MV_MIRROR_TX_SRC_MASK		(1 << MV_MIRROR_TX_SRC_SHIFT)
+
+/* Marvell Specific PHY register */
+#define MII_MV_SPEC_CTRL		16
+enum {
+	MV_SPEC_MDI_CROSS_AUTO		= (0x6 << 4),
+	MV_SPEC_ENERGY_DETECT		= (0x3 << 8),
+	MV_SPEC_DOWNSHIFT_COUNTER	= (0x3 << 12),
+};
+
+#define MII_MV_PAGE			22
+
+#define MV_REG_FIBER_SERDES		0xf
+#define MV_PAGE_FIBER_SERDES		0x1
+
+struct mvsw61xx_state {
+	struct switch_dev dev;
+	struct mii_bus *bus;
+	int base_addr;
+	u16 model;
+
+	bool registered;
+	bool is_indirect;
+
+	int cpu_port0;
+	int cpu_port1;
+
+	int vlan_enabled;
+	struct port_state {
+		u16 fdb;
+		u16 pvid;
+		u16 mask;
+		u8 qmode;
+	} ports[MV_PORTS];
+
+	struct vlan_state {
+		bool port_based;
+
+		u16 mask;
+		u16 vid;
+		u32 port_mode;
+		u32 port_sstate;
+	} vlans[MV_VLANS];
+
+	/* mirroring */
+	bool mirror_rx;
+	bool mirror_tx;
+	int source_port;
+	int monitor_port;
+
+	char buf[128];
+};
+
+#define get_state(_dev) container_of((_dev), struct mvsw61xx_state, dev)
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/mvswitch.c b/src/kernel/linux/v4.19/drivers/net/phy/mvswitch.c
new file mode 100644
index 0000000..043978f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mvswitch.c
@@ -0,0 +1,444 @@
+/*
+ * 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 <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);
+	pdev->supported = ADVERTISED_100baseT_Full;
+	pdev->advertising = ADVERTISED_100baseT_Full;
+	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 --git a/src/kernel/linux/v4.19/drivers/net/phy/mvswitch.h b/src/kernel/linux/v4.19/drivers/net/phy/mvswitch.h
new file mode 100644
index 0000000..ab2a1a1
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/mvswitch.h
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/national.c b/src/kernel/linux/v4.19/drivers/net/phy/national.c
new file mode 100644
index 0000000..bf4070e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/national.c
@@ -0,0 +1,157 @@
+/*
+ * drivers/net/phy/national.c
+ *
+ * Driver for National Semiconductor PHYs
+ *
+ * Author: Stuart Menefy <stuart.menefy@st.com>
+ * Maintainer: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ *
+ * Copyright (c) 2008 STMicroelectronics Limited
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+
+#define DEBUG
+
+/* DP83865 phy identifier values */
+#define DP83865_PHY_ID	0x20005c7a
+
+#define DP83865_INT_STATUS	0x14
+#define DP83865_INT_MASK	0x15
+#define DP83865_INT_CLEAR	0x17
+
+#define DP83865_INT_REMOTE_FAULT 0x0008
+#define DP83865_INT_ANE_COMPLETED 0x0010
+#define DP83865_INT_LINK_CHANGE	0xe000
+#define DP83865_INT_MASK_DEFAULT (DP83865_INT_REMOTE_FAULT | \
+				DP83865_INT_ANE_COMPLETED | \
+				DP83865_INT_LINK_CHANGE)
+
+/* Advanced proprietary configuration */
+#define NS_EXP_MEM_CTL	0x16
+#define NS_EXP_MEM_DATA	0x1d
+#define NS_EXP_MEM_ADD	0x1e
+
+#define LED_CTRL_REG 0x13
+#define AN_FALLBACK_AN 0x0001
+#define AN_FALLBACK_CRC 0x0002
+#define AN_FALLBACK_IE 0x0004
+#define ALL_FALLBACK_ON (AN_FALLBACK_AN |  AN_FALLBACK_CRC | AN_FALLBACK_IE)
+
+enum hdx_loopback {
+	hdx_loopback_on = 0,
+	hdx_loopback_off = 1,
+};
+
+static u8 ns_exp_read(struct phy_device *phydev, u16 reg)
+{
+	phy_write(phydev, NS_EXP_MEM_ADD, reg);
+	return phy_read(phydev, NS_EXP_MEM_DATA);
+}
+
+static void ns_exp_write(struct phy_device *phydev, u16 reg, u8 data)
+{
+	phy_write(phydev, NS_EXP_MEM_ADD, reg);
+	phy_write(phydev, NS_EXP_MEM_DATA, data);
+}
+
+static int ns_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, DP83865_INT_MASK,
+				DP83865_INT_MASK_DEFAULT);
+	else
+		err = phy_write(phydev, DP83865_INT_MASK, 0);
+
+	return err;
+}
+
+static int ns_ack_interrupt(struct phy_device *phydev)
+{
+	int ret = phy_read(phydev, DP83865_INT_STATUS);
+	if (ret < 0)
+		return ret;
+
+	/* Clear the interrupt status bit by writing a “1”
+	 * to the corresponding bit in INT_CLEAR (2:0 are reserved) */
+	ret = phy_write(phydev, DP83865_INT_CLEAR, ret & ~0x7);
+
+	return ret;
+}
+
+static void ns_giga_speed_fallback(struct phy_device *phydev, int mode)
+{
+	int bmcr = phy_read(phydev, MII_BMCR);
+
+	phy_write(phydev, MII_BMCR, (bmcr | BMCR_PDOWN));
+
+	/* Enable 8 bit expended memory read/write (no auto increment) */
+	phy_write(phydev, NS_EXP_MEM_CTL, 0);
+	phy_write(phydev, NS_EXP_MEM_ADD, 0x1C0);
+	phy_write(phydev, NS_EXP_MEM_DATA, 0x0008);
+	phy_write(phydev, MII_BMCR, (bmcr & ~BMCR_PDOWN));
+	phy_write(phydev, LED_CTRL_REG, mode);
+}
+
+static void ns_10_base_t_hdx_loopack(struct phy_device *phydev, int disable)
+{
+	u16 lb_dis = BIT(1);
+
+	if (disable)
+		ns_exp_write(phydev, 0x1c0,
+			     ns_exp_read(phydev, 0x1c0) | lb_dis);
+	else
+		ns_exp_write(phydev, 0x1c0,
+			     ns_exp_read(phydev, 0x1c0) & ~lb_dis);
+
+	pr_debug("10BASE-T HDX loopback %s\n",
+		 (ns_exp_read(phydev, 0x1c0) & lb_dis) ? "off" : "on");
+}
+
+static int ns_config_init(struct phy_device *phydev)
+{
+	ns_giga_speed_fallback(phydev, ALL_FALLBACK_ON);
+	/* In the latest MAC or switches design, the 10 Mbps loopback
+	   is desired to be turned off. */
+	ns_10_base_t_hdx_loopack(phydev, hdx_loopback_off);
+	return ns_ack_interrupt(phydev);
+}
+
+static struct phy_driver dp83865_driver[] = { {
+	.phy_id = DP83865_PHY_ID,
+	.phy_id_mask = 0xfffffff0,
+	.name = "NatSemi DP83865",
+	.features = PHY_GBIT_FEATURES,
+	.flags = PHY_HAS_INTERRUPT,
+	.config_init = ns_config_init,
+	.ack_interrupt = ns_ack_interrupt,
+	.config_intr = ns_config_intr,
+} };
+
+module_phy_driver(dp83865_driver);
+
+MODULE_DESCRIPTION("NatSemi PHY driver");
+MODULE_AUTHOR("Stuart Menefy");
+MODULE_LICENSE("GPL");
+
+static struct mdio_device_id __maybe_unused ns_tbl[] = {
+	{ DP83865_PHY_ID, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, ns_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/nxp-tja1100.c b/src/kernel/linux/v4.19/drivers/net/phy/nxp-tja1100.c
new file mode 100644
index 0000000..ebdda3f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/nxp-tja1100.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#define PHY_ID_TJA1100	0x0180dc48
+#define TJA1100_LINKUP	BIT(15)
+
+#define PHY_ID_TJA1101  0x0180DD00U
+
+
+static int nxp_config_init(struct phy_device *phydev)
+{
+	phydev->supported = SUPPORTED_100baseT_Full;
+	phydev->advertising = SUPPORTED_100baseT_Full;
+	phydev->state = PHY_NOLINK;
+	phydev->autoneg = AUTONEG_DISABLE;
+
+	return 0;
+}
+
+static int nxp_read_status(struct phy_device *phydev)
+{
+	int val;
+
+	phydev->duplex = 1;
+	phydev->pause = 0;
+	phydev->speed = SPEED_100;
+
+	val = phy_read(phydev, MII_RESV1);
+	if (val < 0)
+		return val;
+
+	if (val & TJA1100_LINKUP)
+		phydev->link = 1;
+	else
+		phydev->link = 0;
+
+	return 0;
+}
+
+static struct phy_driver nxp_tja1100_driver[] = {
+	{
+		.phy_id		= PHY_ID_TJA1100,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "NXP TJA1100",
+		.config_init	= nxp_config_init,
+		.read_status	= nxp_read_status,
+	},
+	{
+		.phy_id		= PHY_ID_TJA1101,
+		.phy_id_mask	= 0xffffffff,
+		.name		= "NXP TJA1101",
+		.config_init	= nxp_config_init,
+		.read_status	= nxp_read_status,
+	}
+};
+
+module_phy_driver(nxp_tja1100_driver);
+
+static struct mdio_device_id __maybe_unused nxp_tbl[] = {
+	{ PHY_ID_TJA1100, 0xffffffff },
+	{ PHY_ID_TJA1101, 0xffffffff },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, nxp_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/phy-c45.c b/src/kernel/linux/v4.19/drivers/net/phy/phy-c45.c
new file mode 100644
index 0000000..0fe7fc7
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/phy-c45.c
@@ -0,0 +1,373 @@
+/*
+ * Clause 45 PHY support
+ */
+#include <linux/ethtool.h>
+#include <linux/export.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+/**
+ * genphy_c45_setup_forced - configures a forced speed
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_setup_forced(struct phy_device *phydev)
+{
+	int ctrl1, ctrl2, ret;
+
+	/* Half duplex is not supported */
+	if (phydev->duplex != DUPLEX_FULL)
+		return -EINVAL;
+
+	ctrl1 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+	if (ctrl1 < 0)
+		return ctrl1;
+
+	ctrl2 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2);
+	if (ctrl2 < 0)
+		return ctrl2;
+
+	ctrl1 &= ~MDIO_CTRL1_SPEEDSEL;
+	/*
+	 * PMA/PMD type selection is 1.7.5:0 not 1.7.3:0.  See 45.2.1.6.1
+	 * in 802.3-2012 and 802.3-2015.
+	 */
+	ctrl2 &= ~(MDIO_PMA_CTRL2_TYPE | 0x30);
+
+	switch (phydev->speed) {
+	case SPEED_10:
+		ctrl2 |= MDIO_PMA_CTRL2_10BT;
+		break;
+	case SPEED_100:
+		ctrl1 |= MDIO_PMA_CTRL1_SPEED100;
+		ctrl2 |= MDIO_PMA_CTRL2_100BTX;
+		break;
+	case SPEED_1000:
+		ctrl1 |= MDIO_PMA_CTRL1_SPEED1000;
+		/* Assume 1000base-T */
+		ctrl2 |= MDIO_PMA_CTRL2_1000BT;
+		break;
+	case SPEED_10000:
+		ctrl1 |= MDIO_CTRL1_SPEED10G;
+		/* Assume 10Gbase-T */
+		ctrl2 |= MDIO_PMA_CTRL2_10GBT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, ctrl1);
+	if (ret < 0)
+		return ret;
+
+	return phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2, ctrl2);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_setup_forced);
+
+/**
+ * genphy_c45_an_disable_aneg - disable auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * Disable auto-negotiation in the Clause 45 PHY. The link parameters
+ * parameters are controlled through the PMA/PMD MMD registers.
+ *
+ * Returns zero on success, negative errno code on failure.
+ */
+int genphy_c45_an_disable_aneg(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	val &= ~(MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
+
+	return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_an_disable_aneg);
+
+/**
+ * genphy_c45_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Enable and restart auto-negotiation.
+ */
+int genphy_c45_restart_aneg(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	val |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART;
+
+	return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_restart_aneg);
+
+/**
+ * genphy_c45_check_and_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: target phy_device struct
+ * @restart: whether aneg restart is requested
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Check, and restart auto-negotiation if needed.
+ */
+int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart)
+{
+	int ret = 0;
+
+	if (!restart) {
+		/* Configure and restart aneg if it wasn't set before */
+		ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+		if (ret < 0)
+			return ret;
+
+		if (!(ret & MDIO_AN_CTRL1_ENABLE))
+			restart = true;
+	}
+
+	if (restart)
+		ret = genphy_c45_restart_aneg(phydev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg);
+
+/**
+ * genphy_c45_aneg_done - return auto-negotiation complete status
+ * @phydev: target phy_device struct
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Reads the status register from the auto-negotiation MMD, returning:
+ * - positive if auto-negotiation is complete
+ * - negative errno code on error
+ * - zero otherwise
+ */
+int genphy_c45_aneg_done(struct phy_device *phydev)
+{
+	int val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+
+	return val < 0 ? val : val & MDIO_AN_STAT1_COMPLETE ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_aneg_done);
+
+/**
+ * genphy_c45_read_link - read the overall link status from the MMDs
+ * @phydev: target phy_device struct
+ * @mmd_mask: MMDs to read status from
+ *
+ * Read the link status from the specified MMDs, and if they all indicate
+ * that the link is up, return positive.  If an error is encountered,
+ * a negative errno will be returned, otherwise zero.
+ */
+int genphy_c45_read_link(struct phy_device *phydev, u32 mmd_mask)
+{
+	int val, devad;
+	bool link = true;
+
+	while (mmd_mask) {
+		devad = __ffs(mmd_mask);
+		mmd_mask &= ~BIT(devad);
+
+		/* The link state is latched low so that momentary link
+		 * drops can be detected. Do not double-read the status
+		 * in polling mode to detect such short link drops.
+		 */
+		if (!phy_polling_mode(phydev)) {
+			val = phy_read_mmd(phydev, devad, MDIO_STAT1);
+			if (val < 0)
+				return val;
+		}
+
+		val = phy_read_mmd(phydev, devad, MDIO_STAT1);
+		if (val < 0)
+			return val;
+
+		if (!(val & MDIO_STAT1_LSTATUS))
+			link = false;
+	}
+
+	return link;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_link);
+
+/**
+ * genphy_c45_read_lpa - read the link partner advertisement and pause
+ * @phydev: target phy_device struct
+ *
+ * Read the Clause 45 defined base (7.19) and 10G (7.33) status registers,
+ * filling in the link partner advertisement, pause and asym_pause members
+ * in @phydev.  This assumes that the auto-negotiation MMD is present, and
+ * the backplane bit (7.48.0) is clear.  Clause 45 PHY drivers are expected
+ * to fill in the remainder of the link partner advert from vendor registers.
+ */
+int genphy_c45_read_lpa(struct phy_device *phydev)
+{
+	int val;
+
+	/* Read the link partner's base page advertisement */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA);
+	if (val < 0)
+		return val;
+
+	phydev->lp_advertising = mii_lpa_to_ethtool_lpa_t(val);
+	phydev->pause = val & LPA_PAUSE_CAP ? 1 : 0;
+	phydev->asym_pause = val & LPA_PAUSE_ASYM ? 1 : 0;
+
+	/* Read the link partner's 10G advertisement */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_10GBT_STAT_LP10G)
+		phydev->lp_advertising |= ADVERTISED_10000baseT_Full;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_lpa);
+
+/**
+ * genphy_c45_read_pma - read link speed etc from PMA
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_read_pma(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	switch (val & MDIO_CTRL1_SPEEDSEL) {
+	case 0:
+		phydev->speed = SPEED_10;
+		break;
+	case MDIO_PMA_CTRL1_SPEED100:
+		phydev->speed = SPEED_100;
+		break;
+	case MDIO_PMA_CTRL1_SPEED1000:
+		phydev->speed = SPEED_1000;
+		break;
+	case MDIO_CTRL1_SPEED10G:
+		phydev->speed = SPEED_10000;
+		break;
+	default:
+		phydev->speed = SPEED_UNKNOWN;
+		break;
+	}
+
+	phydev->duplex = DUPLEX_FULL;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_pma);
+
+/**
+ * genphy_c45_read_mdix - read mdix status from PMA
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_read_mdix(struct phy_device *phydev)
+{
+	int val;
+
+	if (phydev->speed == SPEED_10000) {
+		val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
+				   MDIO_PMA_10GBT_SWAPPOL);
+		if (val < 0)
+			return val;
+
+		switch (val) {
+		case MDIO_PMA_10GBT_SWAPPOL_ABNX | MDIO_PMA_10GBT_SWAPPOL_CDNX:
+			phydev->mdix = ETH_TP_MDI;
+			break;
+
+		case 0:
+			phydev->mdix = ETH_TP_MDI_X;
+			break;
+
+		default:
+			phydev->mdix = ETH_TP_MDI_INVALID;
+			break;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_mdix);
+
+/* The gen10g_* functions are the old Clause 45 stub */
+
+int gen10g_config_aneg(struct phy_device *phydev)
+{
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gen10g_config_aneg);
+
+int gen10g_read_status(struct phy_device *phydev)
+{
+	u32 mmd_mask = phydev->c45_ids.devices_in_package;
+	int ret;
+
+	/* For now just lie and say it's 10G all the time */
+	phydev->speed = SPEED_10000;
+	phydev->duplex = DUPLEX_FULL;
+
+	/* Avoid reading the vendor MMDs */
+	mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2));
+
+	ret = genphy_c45_read_link(phydev, mmd_mask);
+
+	phydev->link = ret > 0 ? 1 : 0;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gen10g_read_status);
+
+int gen10g_no_soft_reset(struct phy_device *phydev)
+{
+	/* Do nothing for now */
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gen10g_no_soft_reset);
+
+int gen10g_config_init(struct phy_device *phydev)
+{
+	/* Temporarily just say we support everything */
+	phydev->supported = SUPPORTED_10000baseT_Full;
+	phydev->advertising = SUPPORTED_10000baseT_Full;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gen10g_config_init);
+
+int gen10g_suspend(struct phy_device *phydev)
+{
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gen10g_suspend);
+
+int gen10g_resume(struct phy_device *phydev)
+{
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gen10g_resume);
+
+struct phy_driver genphy_10g_driver = {
+	.phy_id         = 0xffffffff,
+	.phy_id_mask    = 0xffffffff,
+	.name           = "Generic 10G PHY",
+	.soft_reset	= gen10g_no_soft_reset,
+	.config_init    = gen10g_config_init,
+	.features       = 0,
+	.config_aneg    = gen10g_config_aneg,
+	.read_status    = gen10g_read_status,
+	.suspend        = gen10g_suspend,
+	.resume         = gen10g_resume,
+};
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/phy-core.c b/src/kernel/linux/v4.19/drivers/net/phy/phy-core.c
new file mode 100644
index 0000000..1000977
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/phy-core.c
@@ -0,0 +1,728 @@
+/*
+ * Core PHY library, taken from phy.c
+ *
+ * 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.
+ */
+#include <linux/export.h>
+#include <linux/phy.h>
+
+const char *phy_speed_to_str(int speed)
+{
+	switch (speed) {
+	case SPEED_10:
+		return "10Mbps";
+	case SPEED_100:
+		return "100Mbps";
+	case SPEED_1000:
+		return "1Gbps";
+	case SPEED_2500:
+		return "2.5Gbps";
+	case SPEED_5000:
+		return "5Gbps";
+	case SPEED_10000:
+		return "10Gbps";
+	case SPEED_14000:
+		return "14Gbps";
+	case SPEED_20000:
+		return "20Gbps";
+	case SPEED_25000:
+		return "25Gbps";
+	case SPEED_40000:
+		return "40Gbps";
+	case SPEED_50000:
+		return "50Gbps";
+	case SPEED_56000:
+		return "56Gbps";
+	case SPEED_100000:
+		return "100Gbps";
+	case SPEED_UNKNOWN:
+		return "Unknown";
+	default:
+		return "Unsupported (update phy-core.c)";
+	}
+}
+EXPORT_SYMBOL_GPL(phy_speed_to_str);
+
+const char *phy_duplex_to_str(unsigned int duplex)
+{
+	if (duplex == DUPLEX_HALF)
+		return "Half";
+	if (duplex == DUPLEX_FULL)
+		return "Full";
+	if (duplex == DUPLEX_UNKNOWN)
+		return "Unknown";
+	return "Unsupported (update phy-core.c)";
+}
+EXPORT_SYMBOL_GPL(phy_duplex_to_str);
+
+/* A mapping of all SUPPORTED settings to speed/duplex.  This table
+ * must be grouped by speed and sorted in descending match priority
+ * - iow, descending speed. */
+static const struct phy_setting settings[] = {
+	{
+		.speed = SPEED_10000,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+	},
+	{
+		.speed = SPEED_10000,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+	},
+	{
+		.speed = SPEED_10000,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+	},
+	{
+		.speed = SPEED_2500,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+	},
+	{
+		.speed = SPEED_1000,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+	},
+	{
+		.speed = SPEED_1000,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+	},
+	{
+		.speed = SPEED_1000,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+	},
+	{
+		.speed = SPEED_1000,
+		.duplex = DUPLEX_HALF,
+		.bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+	},
+	{
+		.speed = SPEED_100,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+	},
+	{
+		.speed = SPEED_100,
+		.duplex = DUPLEX_HALF,
+		.bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+	},
+	{
+		.speed = SPEED_10,
+		.duplex = DUPLEX_FULL,
+		.bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+	},
+	{
+		.speed = SPEED_10,
+		.duplex = DUPLEX_HALF,
+		.bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+	},
+};
+
+/**
+ * phy_lookup_setting - lookup a PHY setting
+ * @speed: speed to match
+ * @duplex: duplex to match
+ * @mask: allowed link modes
+ * @maxbit: bit size of link modes
+ * @exact: an exact match is required
+ *
+ * Search the settings array for a setting that matches the speed and
+ * duplex, and which is supported.
+ *
+ * If @exact is unset, either an exact match or %NULL for no match will
+ * be returned.
+ *
+ * If @exact is set, an exact match, the fastest supported setting at
+ * or below the specified speed, the slowest supported setting, or if
+ * they all fail, %NULL will be returned.
+ */
+const struct phy_setting *
+phy_lookup_setting(int speed, int duplex, const unsigned long *mask,
+		   size_t maxbit, bool exact)
+{
+	const struct phy_setting *p, *match = NULL, *last = NULL;
+	int i;
+
+	for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
+		if (p->bit < maxbit && test_bit(p->bit, mask)) {
+			last = p;
+			if (p->speed == speed && p->duplex == duplex) {
+				/* Exact match for speed and duplex */
+				match = p;
+				break;
+			} else if (!exact) {
+				if (!match && p->speed <= speed)
+					/* Candidate */
+					match = p;
+
+				if (p->speed < speed)
+					break;
+			}
+		}
+	}
+
+	if (!match && !exact)
+		match = last;
+
+	return match;
+}
+EXPORT_SYMBOL_GPL(phy_lookup_setting);
+
+size_t phy_speeds(unsigned int *speeds, size_t size,
+		  unsigned long *mask, size_t maxbit)
+{
+	size_t count;
+	int i;
+
+	for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++)
+		if (settings[i].bit < maxbit &&
+		    test_bit(settings[i].bit, mask) &&
+		    (count == 0 || speeds[count - 1] != settings[i].speed))
+			speeds[count++] = settings[i].speed;
+
+	return count;
+}
+
+/**
+ * phy_resolve_aneg_linkmode - resolve the advertisements into phy settings
+ * @phydev: The phy_device struct
+ *
+ * Resolve our and the link partner advertisements into their corresponding
+ * speed and duplex. If full duplex was negotiated, extract the pause mode
+ * from the link partner mask.
+ */
+void phy_resolve_aneg_linkmode(struct phy_device *phydev)
+{
+	u32 common = phydev->lp_advertising & phydev->advertising;
+
+	if (common & ADVERTISED_10000baseT_Full) {
+		phydev->speed = SPEED_10000;
+		phydev->duplex = DUPLEX_FULL;
+	} else if (common & ADVERTISED_1000baseT_Full) {
+		phydev->speed = SPEED_1000;
+		phydev->duplex = DUPLEX_FULL;
+	} else if (common & ADVERTISED_1000baseT_Half) {
+		phydev->speed = SPEED_1000;
+		phydev->duplex = DUPLEX_HALF;
+	} else if (common & ADVERTISED_100baseT_Full) {
+		phydev->speed = SPEED_100;
+		phydev->duplex = DUPLEX_FULL;
+	} else if (common & ADVERTISED_100baseT_Half) {
+		phydev->speed = SPEED_100;
+		phydev->duplex = DUPLEX_HALF;
+	} else if (common & ADVERTISED_10baseT_Full) {
+		phydev->speed = SPEED_10;
+		phydev->duplex = DUPLEX_FULL;
+	} else if (common & ADVERTISED_10baseT_Half) {
+		phydev->speed = SPEED_10;
+		phydev->duplex = DUPLEX_HALF;
+	}
+
+	if (phydev->duplex == DUPLEX_FULL) {
+		phydev->pause = !!(phydev->lp_advertising & ADVERTISED_Pause);
+		phydev->asym_pause = !!(phydev->lp_advertising &
+					ADVERTISED_Asym_Pause);
+	}
+}
+EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);
+
+static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
+			     u16 regnum)
+{
+	/* Write the desired MMD Devad */
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
+
+	/* Write the desired MMD register address */
+	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
+
+	/* Select the Function : DATA with no post increment */
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
+			devad | MII_MMD_CTRL_NOINCR);
+}
+
+/**
+ * __phy_read_mmd - Convenience function for reading a register
+ * from an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from (0..31)
+ * @regnum: The register on the MMD to read (0..65535)
+ *
+ * Same rules as for __phy_read();
+ */
+int __phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
+{
+	int val;
+
+	if (regnum > (u16)~0 || devad > 32)
+		return -EINVAL;
+
+	if (phydev->drv->read_mmd) {
+		val = phydev->drv->read_mmd(phydev, devad, regnum);
+	} else if (phydev->is_c45) {
+		u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);
+
+		val = __mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, addr);
+	} else {
+		struct mii_bus *bus = phydev->mdio.bus;
+		int phy_addr = phydev->mdio.addr;
+
+		mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+		/* Read the content of the MMD's selected register */
+		val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+	}
+	return val;
+}
+EXPORT_SYMBOL(__phy_read_mmd);
+
+/**
+ * phy_read_mmd - Convenience function for reading a register
+ * from an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from
+ * @regnum: The register on the MMD to read
+ *
+ * Same rules as for phy_read();
+ */
+int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
+{
+	int ret;
+
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	ret = __phy_read_mmd(phydev, devad, regnum);
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(phy_read_mmd);
+
+/**
+ * __phy_write_mmd - Convenience function for writing a register
+ * on an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from
+ * @regnum: The register on the MMD to read
+ * @val: value to write to @regnum
+ *
+ * Same rules as for __phy_write();
+ */
+int __phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+{
+	int ret;
+
+	if (regnum > (u16)~0 || devad > 32)
+		return -EINVAL;
+
+	if (phydev->drv->write_mmd) {
+		ret = phydev->drv->write_mmd(phydev, devad, regnum, val);
+	} else if (phydev->is_c45) {
+		u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);
+
+		ret = __mdiobus_write(phydev->mdio.bus, phydev->mdio.addr,
+				      addr, val);
+	} else {
+		struct mii_bus *bus = phydev->mdio.bus;
+		int phy_addr = phydev->mdio.addr;
+
+		mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+		/* Write the data into MMD's selected register */
+		__mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+
+		ret = 0;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(__phy_write_mmd);
+
+/**
+ * phy_write_mmd - Convenience function for writing a register
+ * on an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from
+ * @regnum: The register on the MMD to read
+ * @val: value to write to @regnum
+ *
+ * Same rules as for phy_write();
+ */
+int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+{
+	int ret;
+
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	ret = __phy_write_mmd(phydev, devad, regnum, val);
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(phy_write_mmd);
+
+/**
+ * __phy_modify_changed() - Convenience function for modifying a PHY register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * Unlocked helper function which allows a PHY register to be modified as
+ * new register value = (old register value & ~mask) | set
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int __phy_modify_changed(struct phy_device *phydev, u32 regnum, u16 mask,
+			 u16 set)
+{
+	int new, ret;
+
+	ret = __phy_read(phydev, regnum);
+	if (ret < 0)
+		return ret;
+
+	new = (ret & ~mask) | set;
+	if (new == ret)
+		return 0;
+
+	ret = __phy_write(phydev, regnum, new);
+
+	return ret < 0 ? ret : 1;
+}
+EXPORT_SYMBOL_GPL(__phy_modify_changed);
+
+/**
+ * phy_modify_changed - Function for modifying a PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int phy_modify_changed(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+	int ret;
+
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	ret = __phy_modify_changed(phydev, regnum, mask, set);
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify_changed);
+
+/**
+ * __phy_modify - Convenience function for modifying a PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int __phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+	int ret;
+
+	ret = __phy_modify_changed(phydev, regnum, mask, set);
+
+	return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(__phy_modify);
+
+/**
+ * phy_modify - Convenience function for modifying a given PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+	int ret;
+
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	ret = __phy_modify(phydev, regnum, mask, set);
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify);
+
+/**
+ * __phy_modify_mmd_changed - Function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * Unlocked helper function which allows a MMD register to be modified as
+ * new register value = (old register value & ~mask) | set
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int __phy_modify_mmd_changed(struct phy_device *phydev, int devad, u32 regnum,
+			     u16 mask, u16 set)
+{
+	int new, ret;
+
+	ret = __phy_read_mmd(phydev, devad, regnum);
+	if (ret < 0)
+		return ret;
+
+	new = (ret & ~mask) | set;
+	if (new == ret)
+		return 0;
+
+	ret = __phy_write_mmd(phydev, devad, regnum, new);
+
+	return ret < 0 ? ret : 1;
+}
+EXPORT_SYMBOL_GPL(__phy_modify_mmd_changed);
+
+/**
+ * phy_modify_mmd_changed - Function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int phy_modify_mmd_changed(struct phy_device *phydev, int devad, u32 regnum,
+			   u16 mask, u16 set)
+{
+	int ret;
+
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set);
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify_mmd_changed);
+
+/**
+ * __phy_modify_mmd - Convenience function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int __phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
+		     u16 mask, u16 set)
+{
+	int ret;
+
+	ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set);
+
+	return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(__phy_modify_mmd);
+
+/**
+ * phy_modify_mmd - Convenience function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
+		   u16 mask, u16 set)
+{
+	int ret;
+
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	ret = __phy_modify_mmd(phydev, devad, regnum, mask, set);
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify_mmd);
+
+static int __phy_read_page(struct phy_device *phydev)
+{
+	return phydev->drv->read_page(phydev);
+}
+
+static int __phy_write_page(struct phy_device *phydev, int page)
+{
+	return phydev->drv->write_page(phydev, page);
+}
+
+/**
+ * phy_save_page() - take the bus lock and save the current page
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * Take the MDIO bus lock, and return the current page number. On error,
+ * returns a negative errno. phy_restore_page() must always be called
+ * after this, irrespective of success or failure of this call.
+ */
+int phy_save_page(struct phy_device *phydev)
+{
+	mutex_lock(&phydev->mdio.bus->mdio_lock);
+	return __phy_read_page(phydev);
+}
+EXPORT_SYMBOL_GPL(phy_save_page);
+
+/**
+ * phy_select_page() - take the bus lock, save the current page, and set a page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: desired page
+ *
+ * Take the MDIO bus lock to protect against concurrent access, save the
+ * current PHY page, and set the current page.  On error, returns a
+ * negative errno, otherwise returns the previous page number.
+ * phy_restore_page() must always be called after this, irrespective
+ * of success or failure of this call.
+ */
+int phy_select_page(struct phy_device *phydev, int page)
+{
+	int ret, oldpage;
+
+	oldpage = ret = phy_save_page(phydev);
+	if (ret < 0)
+		return ret;
+
+	if (oldpage != page) {
+		ret = __phy_write_page(phydev, page);
+		if (ret < 0)
+			return ret;
+	}
+
+	return oldpage;
+}
+EXPORT_SYMBOL_GPL(phy_select_page);
+
+/**
+ * phy_restore_page() - restore the page register and release the bus lock
+ * @phydev: a pointer to a &struct phy_device
+ * @oldpage: the old page, return value from phy_save_page() or phy_select_page()
+ * @ret: operation's return code
+ *
+ * Release the MDIO bus lock, restoring @oldpage if it is a valid page.
+ * This function propagates the earliest error code from the group of
+ * operations.
+ *
+ * Returns:
+ *   @oldpage if it was a negative value, otherwise
+ *   @ret if it was a negative errno value, otherwise
+ *   phy_write_page()'s negative value if it were in error, otherwise
+ *   @ret.
+ */
+int phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
+{
+	int r;
+
+	if (oldpage >= 0) {
+		r = __phy_write_page(phydev, oldpage);
+
+		/* Propagate the operation return code if the page write
+		 * was successful.
+		 */
+		if (ret >= 0 && r < 0)
+			ret = r;
+	} else {
+		/* Propagate the phy page selection error code */
+		ret = oldpage;
+	}
+
+	mutex_unlock(&phydev->mdio.bus->mdio_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phy_restore_page);
+
+/**
+ * phy_read_paged() - Convenience function for reading a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ *
+ * Same rules as for phy_read().
+ */
+int phy_read_paged(struct phy_device *phydev, int page, u32 regnum)
+{
+	int ret = 0, oldpage;
+
+	oldpage = phy_select_page(phydev, page);
+	if (oldpage >= 0)
+		ret = __phy_read(phydev, regnum);
+
+	return phy_restore_page(phydev, oldpage, ret);
+}
+EXPORT_SYMBOL(phy_read_paged);
+
+/**
+ * phy_write_paged() - Convenience function for writing a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ * @val: value to write
+ *
+ * Same rules as for phy_write().
+ */
+int phy_write_paged(struct phy_device *phydev, int page, u32 regnum, u16 val)
+{
+	int ret = 0, oldpage;
+
+	oldpage = phy_select_page(phydev, page);
+	if (oldpage >= 0)
+		ret = __phy_write(phydev, regnum, val);
+
+	return phy_restore_page(phydev, oldpage, ret);
+}
+EXPORT_SYMBOL(phy_write_paged);
+
+/**
+ * phy_modify_paged() - Convenience function for modifying a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * Same rules as for phy_read() and phy_write().
+ */
+int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
+		     u16 mask, u16 set)
+{
+	int ret = 0, oldpage;
+
+	oldpage = phy_select_page(phydev, page);
+	if (oldpage >= 0)
+		ret = __phy_modify(phydev, regnum, mask, set);
+
+	return phy_restore_page(phydev, oldpage, ret);
+}
+EXPORT_SYMBOL(phy_modify_paged);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/phy.c b/src/kernel/linux/v4.19/drivers/net/phy/phy.c
new file mode 100644
index 0000000..b9d8bd7
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/phy.c
@@ -0,0 +1,1390 @@
+/* Framework for configuring and reading PHY devices
+ * Based on code in sungem_phy.c and gianfar_phy.c
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ * Copyright (c) 2006, 2007  Maciej W. Rozycki
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/phy_led_triggers.h>
+#include <linux/sfp.h>
+#include <linux/workqueue.h>
+#include <linux/mdio.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/atomic.h>
+
+#include <asm/irq.h>
+
+#define PHY_STATE_STR(_state)			\
+	case PHY_##_state:			\
+		return __stringify(_state);	\
+
+static const char *phy_state_to_str(enum phy_state st)
+{
+	switch (st) {
+	PHY_STATE_STR(DOWN)
+	PHY_STATE_STR(STARTING)
+	PHY_STATE_STR(READY)
+	PHY_STATE_STR(PENDING)
+	PHY_STATE_STR(UP)
+	PHY_STATE_STR(AN)
+	PHY_STATE_STR(RUNNING)
+	PHY_STATE_STR(NOLINK)
+	PHY_STATE_STR(FORCING)
+	PHY_STATE_STR(CHANGELINK)
+	PHY_STATE_STR(HALTED)
+	PHY_STATE_STR(RESUMING)
+	}
+
+	return NULL;
+}
+
+
+/**
+ * phy_print_status - Convenience function to print out the current phy status
+ * @phydev: the phy_device struct
+ */
+void phy_print_status(struct phy_device *phydev)
+{
+	if (phydev->link) {
+		netdev_info(phydev->attached_dev,
+			"Link is Up - %s/%s - flow control %s\n",
+			phy_speed_to_str(phydev->speed),
+			phy_duplex_to_str(phydev->duplex),
+			phydev->pause ? "rx/tx" : "off");
+	} else	{
+		netdev_info(phydev->attached_dev, "Link is Down\n");
+	}
+}
+EXPORT_SYMBOL(phy_print_status);
+
+/**
+ * phy_clear_interrupt - Ack the phy device's interrupt
+ * @phydev: the phy_device struct
+ *
+ * If the @phydev driver has an ack_interrupt function, call it to
+ * ack and clear the phy device's interrupt.
+ *
+ * Returns 0 on success or < 0 on error.
+ */
+static int phy_clear_interrupt(struct phy_device *phydev)
+{
+	if (phydev->drv->ack_interrupt)
+		return phydev->drv->ack_interrupt(phydev);
+
+	return 0;
+}
+
+/**
+ * phy_config_interrupt - configure the PHY device for the requested interrupts
+ * @phydev: the phy_device struct
+ * @interrupts: interrupt flags to configure for this @phydev
+ *
+ * Returns 0 on success or < 0 on error.
+ */
+static int phy_config_interrupt(struct phy_device *phydev, u32 interrupts)
+{
+	phydev->interrupts = interrupts;
+	if (phydev->drv->config_intr)
+		return phydev->drv->config_intr(phydev);
+
+	return 0;
+}
+
+/**
+ * phy_restart_aneg - restart auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * Restart the autonegotiation on @phydev.  Returns >= 0 on success or
+ * negative errno on error.
+ */
+int phy_restart_aneg(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))
+		ret = genphy_c45_restart_aneg(phydev);
+	else
+		ret = genphy_restart_aneg(phydev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phy_restart_aneg);
+
+/**
+ * phy_aneg_done - return auto-negotiation status
+ * @phydev: target phy_device struct
+ *
+ * Description: Return the auto-negotiation status from this @phydev
+ * Returns > 0 on success or < 0 on error. 0 means that auto-negotiation
+ * is still pending.
+ */
+int phy_aneg_done(struct phy_device *phydev)
+{
+	if (phydev->drv && phydev->drv->aneg_done)
+		return phydev->drv->aneg_done(phydev);
+
+	/* Avoid genphy_aneg_done() if the Clause 45 PHY does not
+	 * implement Clause 22 registers
+	 */
+	if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))
+		return -EINVAL;
+
+	return genphy_aneg_done(phydev);
+}
+EXPORT_SYMBOL(phy_aneg_done);
+
+/**
+ * phy_find_valid - find a PHY setting that matches the requested parameters
+ * @speed: desired speed
+ * @duplex: desired duplex
+ * @supported: mask of supported link modes
+ *
+ * Locate a supported phy setting that is, in priority order:
+ * - an exact match for the specified speed and duplex mode
+ * - a match for the specified speed, or slower speed
+ * - the slowest supported speed
+ * Returns the matched phy_setting entry, or %NULL if no supported phy
+ * settings were found.
+ */
+static const struct phy_setting *
+phy_find_valid(int speed, int duplex, u32 supported)
+{
+	unsigned long mask = supported;
+
+	return phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, false);
+}
+
+/**
+ * phy_supported_speeds - return all speeds currently supported by a phy device
+ * @phy: The phy device to return supported speeds of.
+ * @speeds: buffer to store supported speeds in.
+ * @size:   size of speeds buffer.
+ *
+ * Description: Returns the number of supported speeds, and fills the speeds
+ * buffer with the supported speeds. If speeds buffer is too small to contain
+ * all currently supported speeds, will return as many speeds as can fit.
+ */
+unsigned int phy_supported_speeds(struct phy_device *phy,
+				  unsigned int *speeds,
+				  unsigned int size)
+{
+	unsigned long supported = phy->supported;
+
+	return phy_speeds(speeds, size, &supported, BITS_PER_LONG);
+}
+
+/**
+ * phy_check_valid - check if there is a valid PHY setting which matches
+ *		     speed, duplex, and feature mask
+ * @speed: speed to match
+ * @duplex: duplex to match
+ * @features: A mask of the valid settings
+ *
+ * Description: Returns true if there is a valid setting, false otherwise.
+ */
+static inline bool phy_check_valid(int speed, int duplex, u32 features)
+{
+	unsigned long mask = features;
+
+	return !!phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, true);
+}
+
+/**
+ * phy_sanitize_settings - make sure the PHY is set to supported speed and duplex
+ * @phydev: the target phy_device struct
+ *
+ * Description: Make sure the PHY is set to supported speeds and
+ *   duplexes.  Drop down by one in this order:  1000/FULL,
+ *   1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF.
+ */
+static void phy_sanitize_settings(struct phy_device *phydev)
+{
+	const struct phy_setting *setting;
+	u32 features = phydev->supported;
+
+	/* Sanitize settings based on PHY capabilities */
+	if ((features & SUPPORTED_Autoneg) == 0)
+		phydev->autoneg = AUTONEG_DISABLE;
+
+	setting = phy_find_valid(phydev->speed, phydev->duplex, features);
+	if (setting) {
+		phydev->speed = setting->speed;
+		phydev->duplex = setting->duplex;
+	} else {
+		/* We failed to find anything (no supported speeds?) */
+		phydev->speed = SPEED_UNKNOWN;
+		phydev->duplex = DUPLEX_UNKNOWN;
+	}
+}
+
+/**
+ * phy_ethtool_sset - generic ethtool sset function, handles all the details
+ * @phydev: target phy_device struct
+ * @cmd: ethtool_cmd
+ *
+ * A few notes about parameter checking:
+ *
+ * - We don't set port or transceiver, so we don't care what they
+ *   were set to.
+ * - phy_start_aneg() will make sure forced settings are sane, and
+ *   choose the next best ones from the ones selected, so we don't
+ *   care if ethtool tries to give us bad values.
+ */
+int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd)
+{
+	u32 speed = ethtool_cmd_speed(cmd);
+
+	if (cmd->phy_address != phydev->mdio.addr)
+		return -EINVAL;
+
+	/* We make sure that we don't pass unsupported values in to the PHY */
+	cmd->advertising &= phydev->supported;
+
+	/* Verify the settings we care about. */
+	if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE)
+		return -EINVAL;
+
+	if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0)
+		return -EINVAL;
+
+	if (cmd->autoneg == AUTONEG_DISABLE &&
+	    ((speed != SPEED_1000 &&
+	      speed != SPEED_100 &&
+	      speed != SPEED_10) ||
+	     (cmd->duplex != DUPLEX_HALF &&
+	      cmd->duplex != DUPLEX_FULL)))
+		return -EINVAL;
+
+	phydev->autoneg = cmd->autoneg;
+
+	phydev->speed = speed;
+
+	phydev->advertising = cmd->advertising;
+
+	if (AUTONEG_ENABLE == cmd->autoneg)
+		phydev->advertising |= ADVERTISED_Autoneg;
+	else
+		phydev->advertising &= ~ADVERTISED_Autoneg;
+
+	phydev->duplex = cmd->duplex;
+
+	phydev->mdix_ctrl = cmd->eth_tp_mdix_ctrl;
+
+	/* Restart the PHY */
+	phy_start_aneg(phydev);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_sset);
+
+int phy_ethtool_ksettings_set(struct phy_device *phydev,
+			      const struct ethtool_link_ksettings *cmd)
+{
+	u8 autoneg = cmd->base.autoneg;
+	u8 duplex = cmd->base.duplex;
+	u32 speed = cmd->base.speed;
+	u32 advertising;
+
+	if (cmd->base.phy_address != phydev->mdio.addr)
+		return -EINVAL;
+
+	ethtool_convert_link_mode_to_legacy_u32(&advertising,
+						cmd->link_modes.advertising);
+
+	/* We make sure that we don't pass unsupported values in to the PHY */
+	advertising &= phydev->supported;
+
+	/* Verify the settings we care about. */
+	if (autoneg != AUTONEG_ENABLE && autoneg != AUTONEG_DISABLE)
+		return -EINVAL;
+
+	if (autoneg == AUTONEG_ENABLE && advertising == 0)
+		return -EINVAL;
+
+	if (autoneg == AUTONEG_DISABLE &&
+	    ((speed != SPEED_1000 &&
+	      speed != SPEED_100 &&
+	      speed != SPEED_10) ||
+	     (duplex != DUPLEX_HALF &&
+	      duplex != DUPLEX_FULL)))
+		return -EINVAL;
+
+	phydev->autoneg = autoneg;
+
+	phydev->speed = speed;
+
+	phydev->advertising = advertising;
+
+	if (autoneg == AUTONEG_ENABLE)
+		phydev->advertising |= ADVERTISED_Autoneg;
+	else
+		phydev->advertising &= ~ADVERTISED_Autoneg;
+
+	phydev->duplex = duplex;
+
+	phydev->mdix_ctrl = cmd->base.eth_tp_mdix_ctrl;
+
+	/* Restart the PHY */
+	phy_start_aneg(phydev);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_ksettings_set);
+
+void phy_ethtool_ksettings_get(struct phy_device *phydev,
+			       struct ethtool_link_ksettings *cmd)
+{
+	ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
+						phydev->supported);
+
+	ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising,
+						phydev->advertising);
+
+	ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.lp_advertising,
+						phydev->lp_advertising);
+
+	cmd->base.speed = phydev->speed;
+	cmd->base.duplex = phydev->duplex;
+	if (phydev->interface == PHY_INTERFACE_MODE_MOCA)
+		cmd->base.port = PORT_BNC;
+	else
+		cmd->base.port = PORT_MII;
+	cmd->base.transceiver = phy_is_internal(phydev) ?
+				XCVR_INTERNAL : XCVR_EXTERNAL;
+	cmd->base.phy_address = phydev->mdio.addr;
+	cmd->base.autoneg = phydev->autoneg;
+	cmd->base.eth_tp_mdix_ctrl = phydev->mdix_ctrl;
+	cmd->base.eth_tp_mdix = phydev->mdix;
+}
+EXPORT_SYMBOL(phy_ethtool_ksettings_get);
+
+/**
+ * phy_mii_ioctl - generic PHY MII ioctl interface
+ * @phydev: the phy_device struct
+ * @ifr: &struct ifreq for socket ioctl's
+ * @cmd: ioctl cmd to execute
+ *
+ * Note that this function is currently incompatible with the
+ * PHYCONTROL layer.  It changes registers without regard to
+ * current state.  Use at own risk.
+ */
+int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
+{
+	struct mii_ioctl_data *mii_data = if_mii(ifr);
+	u16 val = mii_data->val_in;
+	bool change_autoneg = false;
+	int prtad, devad;
+
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		mii_data->phy_id = phydev->mdio.addr;
+		/* fall through */
+
+	case SIOCGMIIREG:
+		if (mdio_phy_id_is_c45(mii_data->phy_id)) {
+			prtad = mdio_phy_id_prtad(mii_data->phy_id);
+			devad = mdio_phy_id_devad(mii_data->phy_id);
+			devad = MII_ADDR_C45 | devad << 16 | mii_data->reg_num;
+		} else {
+			prtad = mii_data->phy_id;
+			devad = mii_data->reg_num;
+		}
+		mii_data->val_out = mdiobus_read(phydev->mdio.bus, prtad,
+						 devad);
+		return 0;
+
+	case SIOCSMIIREG:
+		if (mdio_phy_id_is_c45(mii_data->phy_id)) {
+			prtad = mdio_phy_id_prtad(mii_data->phy_id);
+			devad = mdio_phy_id_devad(mii_data->phy_id);
+			devad = MII_ADDR_C45 | devad << 16 | mii_data->reg_num;
+		} else {
+			prtad = mii_data->phy_id;
+			devad = mii_data->reg_num;
+		}
+		if (prtad == phydev->mdio.addr) {
+			switch (devad) {
+			case MII_BMCR:
+				if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) {
+					if (phydev->autoneg == AUTONEG_ENABLE)
+						change_autoneg = true;
+					phydev->autoneg = AUTONEG_DISABLE;
+					if (val & BMCR_FULLDPLX)
+						phydev->duplex = DUPLEX_FULL;
+					else
+						phydev->duplex = DUPLEX_HALF;
+					if (val & BMCR_SPEED1000)
+						phydev->speed = SPEED_1000;
+					else if (val & BMCR_SPEED100)
+						phydev->speed = SPEED_100;
+					else phydev->speed = SPEED_10;
+				}
+				else {
+					if (phydev->autoneg == AUTONEG_DISABLE)
+						change_autoneg = true;
+					phydev->autoneg = AUTONEG_ENABLE;
+				}
+				break;
+			case MII_ADVERTISE:
+				phydev->advertising = mii_adv_to_ethtool_adv_t(val);
+				change_autoneg = true;
+				break;
+			default:
+				/* do nothing */
+				break;
+			}
+		}
+
+		mdiobus_write(phydev->mdio.bus, prtad, devad, val);
+
+		if (prtad == phydev->mdio.addr &&
+		    devad == MII_BMCR &&
+		    val & BMCR_RESET)
+			return phy_init_hw(phydev);
+
+		if (change_autoneg)
+			return phy_start_aneg(phydev);
+
+		return 0;
+
+	case SIOCSHWTSTAMP:
+		if (phydev->drv && phydev->drv->hwtstamp)
+			return phydev->drv->hwtstamp(phydev, ifr);
+		/* fall through */
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+EXPORT_SYMBOL(phy_mii_ioctl);
+
+static int phy_config_aneg(struct phy_device *phydev)
+{
+	if (phydev->drv->config_aneg)
+		return phydev->drv->config_aneg(phydev);
+
+	/* Clause 45 PHYs that don't implement Clause 22 registers are not
+	 * allowed to call genphy_config_aneg()
+	 */
+	if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))
+		return -EOPNOTSUPP;
+
+	return genphy_config_aneg(phydev);
+}
+
+/**
+ * phy_start_aneg_priv - start auto-negotiation for this PHY device
+ * @phydev: the phy_device struct
+ * @sync: indicate whether we should wait for the workqueue cancelation
+ *
+ * Description: Sanitizes the settings (if we're not autonegotiating
+ *   them), and then calls the driver's config_aneg function.
+ *   If the PHYCONTROL Layer is operating, we change the state to
+ *   reflect the beginning of Auto-negotiation or forcing.
+ */
+static int phy_start_aneg_priv(struct phy_device *phydev, bool sync)
+{
+	bool trigger = 0;
+	int err;
+
+	if (!phydev->drv)
+		return -EIO;
+
+	mutex_lock(&phydev->lock);
+
+	if (AUTONEG_DISABLE == phydev->autoneg)
+		phy_sanitize_settings(phydev);
+
+	/* Invalidate LP advertising flags */
+	phydev->lp_advertising = 0;
+
+	err = phy_config_aneg(phydev);
+	if (err < 0)
+		goto out_unlock;
+
+	if (phydev->state != PHY_HALTED) {
+		if (AUTONEG_ENABLE == phydev->autoneg) {
+			phydev->state = PHY_AN;
+			phydev->link_timeout = PHY_AN_TIMEOUT;
+		} else {
+			phydev->state = PHY_FORCING;
+			phydev->link_timeout = PHY_FORCE_TIMEOUT;
+		}
+	}
+
+	/* Re-schedule a PHY state machine to check PHY status because
+	 * negotiation may already be done and aneg interrupt may not be
+	 * generated.
+	 */
+	if (!phy_polling_mode(phydev) && phydev->state == PHY_AN) {
+		err = phy_aneg_done(phydev);
+		if (err > 0) {
+			trigger = true;
+			err = 0;
+		}
+	}
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+
+	if (trigger)
+		phy_trigger_machine(phydev, sync);
+
+	return err;
+}
+
+/**
+ * phy_start_aneg - start auto-negotiation for this PHY device
+ * @phydev: the phy_device struct
+ *
+ * Description: Sanitizes the settings (if we're not autonegotiating
+ *   them), and then calls the driver's config_aneg function.
+ *   If the PHYCONTROL Layer is operating, we change the state to
+ *   reflect the beginning of Auto-negotiation or forcing.
+ */
+int phy_start_aneg(struct phy_device *phydev)
+{
+	return phy_start_aneg_priv(phydev, true);
+}
+EXPORT_SYMBOL(phy_start_aneg);
+
+static int phy_poll_aneg_done(struct phy_device *phydev)
+{
+	unsigned int retries = 100;
+	int ret;
+
+	do {
+		msleep(100);
+		ret = phy_aneg_done(phydev);
+	} while (!ret && --retries);
+
+	if (!ret)
+		return -ETIMEDOUT;
+
+	return ret < 0 ? ret : 0;
+}
+
+/**
+ * phy_speed_down - set speed to lowest speed supported by both link partners
+ * @phydev: the phy_device struct
+ * @sync: perform action synchronously
+ *
+ * Description: Typically used to save energy when waiting for a WoL packet
+ *
+ * WARNING: Setting sync to false may cause the system being unable to suspend
+ * in case the PHY generates an interrupt when finishing the autonegotiation.
+ * This interrupt may wake up the system immediately after suspend.
+ * Therefore use sync = false only if you're sure it's safe with the respective
+ * network chip.
+ */
+int phy_speed_down(struct phy_device *phydev, bool sync)
+{
+	u32 adv = phydev->lp_advertising & phydev->supported;
+	u32 adv_old = phydev->advertising;
+	int ret;
+
+	if (phydev->autoneg != AUTONEG_ENABLE)
+		return 0;
+
+	if (adv & PHY_10BT_FEATURES)
+		phydev->advertising &= ~(PHY_100BT_FEATURES |
+					 PHY_1000BT_FEATURES);
+	else if (adv & PHY_100BT_FEATURES)
+		phydev->advertising &= ~PHY_1000BT_FEATURES;
+
+	if (phydev->advertising == adv_old)
+		return 0;
+
+	ret = phy_config_aneg(phydev);
+	if (ret)
+		return ret;
+
+	return sync ? phy_poll_aneg_done(phydev) : 0;
+}
+EXPORT_SYMBOL_GPL(phy_speed_down);
+
+/**
+ * phy_speed_up - (re)set advertised speeds to all supported speeds
+ * @phydev: the phy_device struct
+ *
+ * Description: Used to revert the effect of phy_speed_down
+ */
+int phy_speed_up(struct phy_device *phydev)
+{
+	u32 mask = PHY_10BT_FEATURES | PHY_100BT_FEATURES | PHY_1000BT_FEATURES;
+	u32 adv_old = phydev->advertising;
+
+	if (phydev->autoneg != AUTONEG_ENABLE)
+		return 0;
+
+	phydev->advertising = (adv_old & ~mask) | (phydev->supported & mask);
+
+	if (phydev->advertising == adv_old)
+		return 0;
+
+	return phy_config_aneg(phydev);
+}
+EXPORT_SYMBOL_GPL(phy_speed_up);
+
+/**
+ * phy_start_machine - start PHY state machine tracking
+ * @phydev: the phy_device struct
+ *
+ * Description: The PHY infrastructure can run a state machine
+ *   which tracks whether the PHY is starting up, negotiating,
+ *   etc.  This function starts the delayed workqueue which tracks
+ *   the state of the PHY. If you want to maintain your own state machine,
+ *   do not call this function.
+ */
+void phy_start_machine(struct phy_device *phydev)
+{
+	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ);
+}
+EXPORT_SYMBOL_GPL(phy_start_machine);
+
+/**
+ * phy_trigger_machine - trigger the state machine to run
+ *
+ * @phydev: the phy_device struct
+ * @sync: indicate whether we should wait for the workqueue cancelation
+ *
+ * Description: There has been a change in state which requires that the
+ *   state machine runs.
+ */
+
+void phy_trigger_machine(struct phy_device *phydev, bool sync)
+{
+	if (sync)
+		cancel_delayed_work_sync(&phydev->state_queue);
+	else
+		cancel_delayed_work(&phydev->state_queue);
+	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, 0);
+}
+
+/**
+ * phy_stop_machine - stop the PHY state machine tracking
+ * @phydev: target phy_device struct
+ *
+ * Description: Stops the state machine delayed workqueue, sets the
+ *   state to UP (unless it wasn't up yet). This function must be
+ *   called BEFORE phy_detach.
+ */
+void phy_stop_machine(struct phy_device *phydev)
+{
+	cancel_delayed_work_sync(&phydev->state_queue);
+
+	mutex_lock(&phydev->lock);
+	if (phydev->state > PHY_UP && phydev->state != PHY_HALTED)
+		phydev->state = PHY_UP;
+	mutex_unlock(&phydev->lock);
+}
+
+/**
+ * phy_error - enter HALTED state for this PHY device
+ * @phydev: target phy_device struct
+ *
+ * Moves the PHY to the HALTED state in response to a read
+ * or write error, and tells the controller the link is down.
+ * Must not be called from interrupt context, or while the
+ * phydev->lock is held.
+ */
+static void phy_error(struct phy_device *phydev)
+{
+	mutex_lock(&phydev->lock);
+	phydev->state = PHY_HALTED;
+	mutex_unlock(&phydev->lock);
+
+	phy_trigger_machine(phydev, false);
+}
+
+/**
+ * phy_disable_interrupts - Disable the PHY interrupts from the PHY side
+ * @phydev: target phy_device struct
+ */
+static int phy_disable_interrupts(struct phy_device *phydev)
+{
+	int err;
+
+	/* Disable PHY interrupts */
+	err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
+	if (err)
+		return err;
+
+	/* Clear the interrupt */
+	return phy_clear_interrupt(phydev);
+}
+
+/**
+ * phy_change - Called by the phy_interrupt to handle PHY changes
+ * @phydev: phy_device struct that interrupted
+ */
+static irqreturn_t phy_change(struct phy_device *phydev)
+{
+	if (phy_interrupt_is_valid(phydev)) {
+		if (phydev->drv->did_interrupt &&
+		    !phydev->drv->did_interrupt(phydev))
+			return IRQ_NONE;
+
+		if (phydev->state == PHY_HALTED)
+			if (phy_disable_interrupts(phydev))
+				goto phy_err;
+	}
+
+	mutex_lock(&phydev->lock);
+	if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state))
+		phydev->state = PHY_CHANGELINK;
+	mutex_unlock(&phydev->lock);
+
+	/* reschedule state queue work to run as soon as possible */
+	phy_trigger_machine(phydev, true);
+
+	if (phy_interrupt_is_valid(phydev) && phy_clear_interrupt(phydev))
+		goto phy_err;
+	return IRQ_HANDLED;
+
+phy_err:
+	phy_error(phydev);
+	return IRQ_NONE;
+}
+
+/**
+ * phy_change_work - Scheduled by the phy_mac_interrupt to handle PHY changes
+ * @work: work_struct that describes the work to be done
+ */
+void phy_change_work(struct work_struct *work)
+{
+	struct phy_device *phydev =
+		container_of(work, struct phy_device, phy_queue);
+
+	phy_change(phydev);
+}
+
+/**
+ * phy_interrupt - PHY interrupt handler
+ * @irq: interrupt line
+ * @phy_dat: phy_device pointer
+ *
+ * Description: When a PHY interrupt occurs, the handler disables
+ * interrupts, and uses phy_change to handle the interrupt.
+ */
+static irqreturn_t phy_interrupt(int irq, void *phy_dat)
+{
+	struct phy_device *phydev = phy_dat;
+
+	if (PHY_HALTED == phydev->state)
+		return IRQ_NONE;		/* It can't be ours.  */
+
+	return phy_change(phydev);
+}
+
+/**
+ * phy_enable_interrupts - Enable the interrupts from the PHY side
+ * @phydev: target phy_device struct
+ */
+static int phy_enable_interrupts(struct phy_device *phydev)
+{
+	int err = phy_clear_interrupt(phydev);
+
+	if (err < 0)
+		return err;
+
+	return phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
+}
+
+/**
+ * phy_start_interrupts - request and enable interrupts for a PHY device
+ * @phydev: target phy_device struct
+ *
+ * Description: Request the interrupt for the given PHY.
+ *   If this fails, then we set irq to PHY_POLL.
+ *   Otherwise, we enable the interrupts in the PHY.
+ *   This should only be called with a valid IRQ number.
+ *   Returns 0 on success or < 0 on error.
+ */
+int phy_start_interrupts(struct phy_device *phydev)
+{
+	if (request_threaded_irq(phydev->irq, NULL, phy_interrupt,
+				 IRQF_ONESHOT | IRQF_SHARED,
+				 phydev_name(phydev), phydev) < 0) {
+		pr_warn("%s: Can't get IRQ %d (PHY)\n",
+			phydev->mdio.bus->name, phydev->irq);
+		phydev->irq = PHY_POLL;
+		return 0;
+	}
+
+	return phy_enable_interrupts(phydev);
+}
+EXPORT_SYMBOL(phy_start_interrupts);
+
+/**
+ * phy_stop_interrupts - disable interrupts from a PHY device
+ * @phydev: target phy_device struct
+ */
+int phy_stop_interrupts(struct phy_device *phydev)
+{
+	int err = phy_disable_interrupts(phydev);
+
+	if (err)
+		phy_error(phydev);
+
+	free_irq(phydev->irq, phydev);
+
+	return err;
+}
+EXPORT_SYMBOL(phy_stop_interrupts);
+
+/**
+ * phy_stop - Bring down the PHY link, and stop checking the status
+ * @phydev: target phy_device struct
+ */
+void phy_stop(struct phy_device *phydev)
+{
+	mutex_lock(&phydev->lock);
+
+	if (PHY_HALTED == phydev->state)
+		goto out_unlock;
+
+	if (phy_interrupt_is_valid(phydev))
+		phy_disable_interrupts(phydev);
+
+	if (phydev->sfp_bus)
+		sfp_upstream_stop(phydev->sfp_bus);
+
+	phydev->state = PHY_HALTED;
+
+out_unlock:
+	mutex_unlock(&phydev->lock);
+
+	/* Cannot call flush_scheduled_work() here as desired because
+	 * of rtnl_lock(), but PHY_HALTED shall guarantee phy_change()
+	 * will not reenable interrupts.
+	 */
+}
+EXPORT_SYMBOL(phy_stop);
+
+/**
+ * phy_start - start or restart a PHY device
+ * @phydev: target phy_device struct
+ *
+ * Description: Indicates the attached device's readiness to
+ *   handle PHY-related work.  Used during startup to start the
+ *   PHY, and after a call to phy_stop() to resume operation.
+ *   Also used to indicate the MDIO bus has cleared an error
+ *   condition.
+ */
+void phy_start(struct phy_device *phydev)
+{
+	int err = 0;
+
+	mutex_lock(&phydev->lock);
+
+	if (phydev->sfp_bus)
+		sfp_upstream_start(phydev->sfp_bus);
+
+	switch (phydev->state) {
+	case PHY_STARTING:
+		phydev->state = PHY_PENDING;
+		break;
+	case PHY_READY:
+		phydev->state = PHY_UP;
+		break;
+	case PHY_HALTED:
+		/* if phy was suspended, bring the physical link up again */
+		__phy_resume(phydev);
+
+		/* make sure interrupts are re-enabled for the PHY */
+		if (phy_interrupt_is_valid(phydev)) {
+			err = phy_enable_interrupts(phydev);
+			if (err < 0)
+				break;
+		}
+
+		phydev->state = PHY_RESUMING;
+		break;
+	default:
+		break;
+	}
+	mutex_unlock(&phydev->lock);
+
+	phy_trigger_machine(phydev, true);
+}
+EXPORT_SYMBOL(phy_start);
+
+static void phy_link_up(struct phy_device *phydev)
+{
+	phydev->phy_link_change(phydev, true, true);
+	phy_led_trigger_change_speed(phydev);
+}
+
+static void phy_link_down(struct phy_device *phydev, bool do_carrier)
+{
+	phydev->phy_link_change(phydev, false, do_carrier);
+	phy_led_trigger_change_speed(phydev);
+}
+
+/**
+ * phy_state_machine - Handle the state machine
+ * @work: work_struct that describes the work to be done
+ */
+void phy_state_machine(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct phy_device *phydev =
+			container_of(dwork, struct phy_device, state_queue);
+	bool needs_aneg = false, do_suspend = false;
+	enum phy_state old_state;
+	int err = 0;
+	int old_link;
+
+	mutex_lock(&phydev->lock);
+
+	old_state = phydev->state;
+
+	if (phydev->drv && phydev->drv->link_change_notify)
+		phydev->drv->link_change_notify(phydev);
+
+	switch (phydev->state) {
+	case PHY_DOWN:
+	case PHY_STARTING:
+	case PHY_READY:
+	case PHY_PENDING:
+		break;
+	case PHY_UP:
+		needs_aneg = true;
+
+		phydev->link_timeout = PHY_AN_TIMEOUT;
+
+		break;
+	case PHY_AN:
+		err = phy_read_status(phydev);
+		if (err < 0)
+			break;
+
+		/* If the link is down, give up on negotiation for now */
+		if (!phydev->link) {
+			phydev->state = PHY_NOLINK;
+			phy_link_down(phydev, true);
+			break;
+		}
+
+		/* Check if negotiation is done.  Break if there's an error */
+		err = phy_aneg_done(phydev);
+		if (err < 0)
+			break;
+
+		/* If AN is done, we're running */
+		if (err > 0) {
+			phydev->state = PHY_RUNNING;
+			phy_link_up(phydev);
+		} else if (0 == phydev->link_timeout--)
+			needs_aneg = true;
+		break;
+	case PHY_NOLINK:
+		if (!phy_polling_mode(phydev))
+			break;
+
+		err = phy_read_status(phydev);
+		if (err)
+			break;
+
+		if (phydev->link) {
+			if (AUTONEG_ENABLE == phydev->autoneg) {
+				err = phy_aneg_done(phydev);
+				if (err < 0)
+					break;
+
+				if (!err) {
+					phydev->state = PHY_AN;
+					phydev->link_timeout = PHY_AN_TIMEOUT;
+					break;
+				}
+			}
+			phydev->state = PHY_RUNNING;
+			phy_link_up(phydev);
+		}
+		break;
+	case PHY_FORCING:
+		err = genphy_update_link(phydev);
+		if (err)
+			break;
+
+		if (phydev->link) {
+			phydev->state = PHY_RUNNING;
+			phy_link_up(phydev);
+		} else {
+			if (0 == phydev->link_timeout--)
+				needs_aneg = true;
+			phy_link_down(phydev, false);
+		}
+		break;
+	case PHY_RUNNING:
+		/* Only register a CHANGE if we are polling and link changed
+		 * since latest checking.
+		 */
+		if (phy_polling_mode(phydev)) {
+			old_link = phydev->link;
+			err = phy_read_status(phydev);
+			if (err)
+				break;
+
+			if (old_link != phydev->link)
+				phydev->state = PHY_CHANGELINK;
+		}
+		/*
+		 * Failsafe: check that nobody set phydev->link=0 between two
+		 * poll cycles, otherwise we won't leave RUNNING state as long
+		 * as link remains down.
+		 */
+		if (!phydev->link && phydev->state == PHY_RUNNING) {
+			phydev->state = PHY_CHANGELINK;
+			phydev_err(phydev, "no link in PHY_RUNNING\n");
+		}
+		break;
+	case PHY_CHANGELINK:
+		err = phy_read_status(phydev);
+		if (err)
+			break;
+
+		if (phydev->link) {
+			phydev->state = PHY_RUNNING;
+			phy_link_up(phydev);
+		} else {
+			phydev->state = PHY_NOLINK;
+			phy_link_down(phydev, true);
+		}
+		break;
+	case PHY_HALTED:
+		if (phydev->link) {
+			phydev->link = 0;
+			phy_link_down(phydev, true);
+			do_suspend = true;
+		}
+		break;
+	case PHY_RESUMING:
+		if (AUTONEG_ENABLE == phydev->autoneg) {
+			err = phy_aneg_done(phydev);
+			if (err < 0)
+				break;
+
+			/* err > 0 if AN is done.
+			 * Otherwise, it's 0, and we're  still waiting for AN
+			 */
+			if (err > 0) {
+				err = phy_read_status(phydev);
+				if (err)
+					break;
+
+				if (phydev->link) {
+					phydev->state = PHY_RUNNING;
+					phy_link_up(phydev);
+				} else	{
+					phydev->state = PHY_NOLINK;
+					phy_link_down(phydev, false);
+				}
+			} else {
+				phydev->state = PHY_AN;
+				phydev->link_timeout = PHY_AN_TIMEOUT;
+			}
+		} else {
+			err = phy_read_status(phydev);
+			if (err)
+				break;
+
+			if (phydev->link) {
+				phydev->state = PHY_RUNNING;
+				phy_link_up(phydev);
+			} else	{
+				phydev->state = PHY_NOLINK;
+				phy_link_down(phydev, false);
+			}
+		}
+		break;
+	}
+
+	mutex_unlock(&phydev->lock);
+
+	if (needs_aneg)
+		err = phy_start_aneg_priv(phydev, false);
+	else if (do_suspend)
+		phy_suspend(phydev);
+
+	if (err < 0)
+		phy_error(phydev);
+
+	if (old_state != phydev->state)
+		phydev_dbg(phydev, "PHY state change %s -> %s\n",
+			   phy_state_to_str(old_state),
+			   phy_state_to_str(phydev->state));
+
+	/* Only re-schedule a PHY state machine change if we are polling the
+	 * PHY, if PHY_IGNORE_INTERRUPT is set, then we will be moving
+	 * between states from phy_mac_interrupt()
+	 */
+	if (phy_polling_mode(phydev))
+		queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,
+				   PHY_STATE_TIME * HZ);
+}
+
+/**
+ * phy_mac_interrupt - MAC says the link has changed
+ * @phydev: phy_device struct with changed link
+ *
+ * The MAC layer is able to indicate there has been a change in the PHY link
+ * status. Trigger the state machine and work a work queue.
+ */
+void phy_mac_interrupt(struct phy_device *phydev)
+{
+	/* Trigger a state machine change */
+	queue_work(system_power_efficient_wq, &phydev->phy_queue);
+}
+EXPORT_SYMBOL(phy_mac_interrupt);
+
+/**
+ * phy_init_eee - init and check the EEE feature
+ * @phydev: target phy_device struct
+ * @clk_stop_enable: PHY may stop the clock during LPI
+ *
+ * Description: it checks if the Energy-Efficient Ethernet (EEE)
+ * is supported by looking at the MMD registers 3.20 and 7.60/61
+ * and it programs the MMD register 3.0 setting the "Clock stop enable"
+ * bit if required.
+ */
+int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable)
+{
+	if (!phydev->drv)
+		return -EIO;
+
+	/* According to 802.3az,the EEE is supported only in full duplex-mode.
+	 */
+	if (phydev->duplex == DUPLEX_FULL) {
+		int eee_lp, eee_cap, eee_adv;
+		u32 lp, cap, adv;
+		int status;
+
+		/* Read phy status to properly get the right settings */
+		status = phy_read_status(phydev);
+		if (status)
+			return status;
+
+		/* First check if the EEE ability is supported */
+		eee_cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
+		if (eee_cap <= 0)
+			goto eee_exit_err;
+
+		cap = mmd_eee_cap_to_ethtool_sup_t(eee_cap);
+		if (!cap)
+			goto eee_exit_err;
+
+		/* Check which link settings negotiated and verify it in
+		 * the EEE advertising registers.
+		 */
+		eee_lp = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
+		if (eee_lp <= 0)
+			goto eee_exit_err;
+
+		eee_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
+		if (eee_adv <= 0)
+			goto eee_exit_err;
+
+		adv = mmd_eee_adv_to_ethtool_adv_t(eee_adv);
+		lp = mmd_eee_adv_to_ethtool_adv_t(eee_lp);
+		if (!phy_check_valid(phydev->speed, phydev->duplex, lp & adv))
+			goto eee_exit_err;
+
+		if (clk_stop_enable) {
+			/* Configure the PHY to stop receiving xMII
+			 * clock while it is signaling LPI.
+			 */
+			int val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1);
+			if (val < 0)
+				return val;
+
+			val |= MDIO_PCS_CTRL1_CLKSTOP_EN;
+			phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, val);
+		}
+
+		return 0; /* EEE supported */
+	}
+eee_exit_err:
+	return -EPROTONOSUPPORT;
+}
+EXPORT_SYMBOL(phy_init_eee);
+
+/**
+ * phy_get_eee_err - report the EEE wake error count
+ * @phydev: target phy_device struct
+ *
+ * Description: it is to report the number of time where the PHY
+ * failed to complete its normal wake sequence.
+ */
+int phy_get_eee_err(struct phy_device *phydev)
+{
+	if (!phydev->drv)
+		return -EIO;
+
+	return phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR);
+}
+EXPORT_SYMBOL(phy_get_eee_err);
+
+/**
+ * phy_ethtool_get_eee - get EEE supported and status
+ * @phydev: target phy_device struct
+ * @data: ethtool_eee data
+ *
+ * Description: it reportes the Supported/Advertisement/LP Advertisement
+ * capabilities.
+ */
+int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_eee *data)
+{
+	int val;
+
+	if (!phydev->drv)
+		return -EIO;
+
+	/* Get Supported EEE */
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
+	if (val < 0)
+		return val;
+	data->supported = mmd_eee_cap_to_ethtool_sup_t(val);
+
+	/* Get advertisement EEE */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
+	if (val < 0)
+		return val;
+	data->advertised = mmd_eee_adv_to_ethtool_adv_t(val);
+
+	/* Get LP advertisement EEE */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
+	if (val < 0)
+		return val;
+	data->lp_advertised = mmd_eee_adv_to_ethtool_adv_t(val);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_get_eee);
+
+/**
+ * phy_ethtool_set_eee - set EEE supported and status
+ * @phydev: target phy_device struct
+ * @data: ethtool_eee data
+ *
+ * Description: it is to program the Advertisement EEE register.
+ */
+int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data)
+{
+	int cap, old_adv, adv, ret;
+
+	if (!phydev->drv)
+		return -EIO;
+
+	/* Get Supported EEE */
+	cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
+	if (cap < 0)
+		return cap;
+
+	old_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
+	if (old_adv < 0)
+		return old_adv;
+
+	adv = ethtool_adv_to_mmd_eee_adv_t(data->advertised) & cap;
+
+	/* Mask prohibited EEE modes */
+	adv &= ~phydev->eee_broken_modes;
+
+	if (old_adv != adv) {
+		ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv);
+		if (ret < 0)
+			return ret;
+
+		/* Restart autonegotiation so the new modes get sent to the
+		 * link partner.
+		 */
+		ret = phy_restart_aneg(phydev);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_set_eee);
+
+int phy_ethtool_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+	if (phydev->drv && phydev->drv->set_wol)
+		return phydev->drv->set_wol(phydev, wol);
+
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(phy_ethtool_set_wol);
+
+void phy_ethtool_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+	if (phydev->drv && phydev->drv->get_wol)
+		phydev->drv->get_wol(phydev, wol);
+}
+EXPORT_SYMBOL(phy_ethtool_get_wol);
+
+int phy_ethtool_get_link_ksettings(struct net_device *ndev,
+				   struct ethtool_link_ksettings *cmd)
+{
+	struct phy_device *phydev = ndev->phydev;
+
+	if (!phydev)
+		return -ENODEV;
+
+	phy_ethtool_ksettings_get(phydev, cmd);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_get_link_ksettings);
+
+int phy_ethtool_set_link_ksettings(struct net_device *ndev,
+				   const struct ethtool_link_ksettings *cmd)
+{
+	struct phy_device *phydev = ndev->phydev;
+
+	if (!phydev)
+		return -ENODEV;
+
+	return phy_ethtool_ksettings_set(phydev, cmd);
+}
+EXPORT_SYMBOL(phy_ethtool_set_link_ksettings);
+
+int phy_ethtool_nway_reset(struct net_device *ndev)
+{
+	struct phy_device *phydev = ndev->phydev;
+
+	if (!phydev)
+		return -ENODEV;
+
+	if (!phydev->drv)
+		return -EIO;
+
+	return phy_restart_aneg(phydev);
+}
+EXPORT_SYMBOL(phy_ethtool_nway_reset);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/phy_device.c b/src/kernel/linux/v4.19/drivers/net/phy/phy_device.c
new file mode 100644
index 0000000..5cdd75a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/phy_device.c
@@ -0,0 +1,2154 @@
+/* Framework for finding and configuring PHYs.
+ * Also contains generic PHY driver
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/phy_led_triggers.h>
+#include <linux/sfp.h>
+#include <linux/mdio.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/of.h>
+
+#include <asm/irq.h>
+
+MODULE_DESCRIPTION("PHY library");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+void phy_device_free(struct phy_device *phydev)
+{
+	put_device(&phydev->mdio.dev);
+}
+EXPORT_SYMBOL(phy_device_free);
+
+static void phy_mdio_device_free(struct mdio_device *mdiodev)
+{
+	struct phy_device *phydev;
+
+	phydev = container_of(mdiodev, struct phy_device, mdio);
+	phy_device_free(phydev);
+}
+
+static void phy_device_release(struct device *dev)
+{
+	kfree(to_phy_device(dev));
+}
+
+static void phy_mdio_device_remove(struct mdio_device *mdiodev)
+{
+	struct phy_device *phydev;
+
+	phydev = container_of(mdiodev, struct phy_device, mdio);
+	phy_device_remove(phydev);
+}
+
+static struct phy_driver genphy_driver;
+extern struct phy_driver genphy_10g_driver;
+
+static LIST_HEAD(phy_fixup_list);
+static DEFINE_MUTEX(phy_fixup_lock);
+
+#ifdef CONFIG_PM
+static bool mdio_bus_phy_may_suspend(struct phy_device *phydev)
+{
+	struct device_driver *drv = phydev->mdio.dev.driver;
+	struct phy_driver *phydrv = to_phy_driver(drv);
+	struct net_device *netdev = phydev->attached_dev;
+
+	if (!drv || !phydrv->suspend)
+		return false;
+
+	/* PHY not attached? May suspend if the PHY has not already been
+	 * suspended as part of a prior call to phy_disconnect() ->
+	 * phy_detach() -> phy_suspend() because the parent netdev might be the
+	 * MDIO bus driver and clock gated at this point.
+	 */
+	if (!netdev)
+		return !phydev->suspended;
+
+	if (netdev->wol_enabled)
+		return false;
+
+	/* As long as not all affected network drivers support the
+	 * wol_enabled flag, let's check for hints that WoL is enabled.
+	 * Don't suspend PHY if the attached netdev parent may wake up.
+	 * The parent may point to a PCI device, as in tg3 driver.
+	 */
+	if (netdev->dev.parent && device_may_wakeup(netdev->dev.parent))
+		return false;
+
+	/* Also don't suspend PHY if the netdev itself may wakeup. This
+	 * is the case for devices w/o underlaying pwr. mgmt. aware bus,
+	 * e.g. SoC devices.
+	 */
+	if (device_may_wakeup(&netdev->dev))
+		return false;
+
+	return true;
+}
+
+static int mdio_bus_phy_suspend(struct device *dev)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+
+	/* We must stop the state machine manually, otherwise it stops out of
+	 * control, possibly with the phydev->lock held. Upon resume, netdev
+	 * may call phy routines that try to grab the same lock, and that may
+	 * lead to a deadlock.
+	 */
+	if (phydev->attached_dev && phydev->adjust_link)
+		phy_stop_machine(phydev);
+
+	if (!mdio_bus_phy_may_suspend(phydev))
+		return 0;
+
+	return phy_suspend(phydev);
+}
+
+static int mdio_bus_phy_resume(struct device *dev)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+	int ret;
+
+	if (!mdio_bus_phy_may_suspend(phydev))
+		goto no_resume;
+
+	ret = phy_resume(phydev);
+	if (ret < 0)
+		return ret;
+
+no_resume:
+	if (phydev->attached_dev && phydev->adjust_link)
+		phy_start_machine(phydev);
+
+	return 0;
+}
+
+static int mdio_bus_phy_restore(struct device *dev)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+	struct net_device *netdev = phydev->attached_dev;
+	int ret;
+
+	if (!netdev)
+		return 0;
+
+	ret = phy_init_hw(phydev);
+	if (ret < 0)
+		return ret;
+
+	if (phydev->attached_dev && phydev->adjust_link)
+		phy_start_machine(phydev);
+
+	return 0;
+}
+
+static const struct dev_pm_ops mdio_bus_phy_pm_ops = {
+	.suspend = mdio_bus_phy_suspend,
+	.resume = mdio_bus_phy_resume,
+	.freeze = mdio_bus_phy_suspend,
+	.thaw = mdio_bus_phy_resume,
+	.restore = mdio_bus_phy_restore,
+};
+
+#define MDIO_BUS_PHY_PM_OPS (&mdio_bus_phy_pm_ops)
+
+#else
+
+#define MDIO_BUS_PHY_PM_OPS NULL
+
+#endif /* CONFIG_PM */
+
+/**
+ * phy_register_fixup - creates a new phy_fixup and adds it to the list
+ * @bus_id: A string which matches phydev->mdio.dev.bus_id (or PHY_ANY_ID)
+ * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY)
+ *	It can also be PHY_ANY_UID
+ * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before
+ *	comparison
+ * @run: The actual code to be run when a matching PHY is found
+ */
+int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask,
+		       int (*run)(struct phy_device *))
+{
+	struct phy_fixup *fixup = kzalloc(sizeof(*fixup), GFP_KERNEL);
+
+	if (!fixup)
+		return -ENOMEM;
+
+	strlcpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id));
+	fixup->phy_uid = phy_uid;
+	fixup->phy_uid_mask = phy_uid_mask;
+	fixup->run = run;
+
+	mutex_lock(&phy_fixup_lock);
+	list_add_tail(&fixup->list, &phy_fixup_list);
+	mutex_unlock(&phy_fixup_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_register_fixup);
+
+/* Registers a fixup to be run on any PHY with the UID in phy_uid */
+int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask,
+			       int (*run)(struct phy_device *))
+{
+	return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run);
+}
+EXPORT_SYMBOL(phy_register_fixup_for_uid);
+
+/* Registers a fixup to be run on the PHY with id string bus_id */
+int phy_register_fixup_for_id(const char *bus_id,
+			      int (*run)(struct phy_device *))
+{
+	return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run);
+}
+EXPORT_SYMBOL(phy_register_fixup_for_id);
+
+/**
+ * phy_unregister_fixup - remove a phy_fixup from the list
+ * @bus_id: A string matches fixup->bus_id (or PHY_ANY_ID) in phy_fixup_list
+ * @phy_uid: A phy id matches fixup->phy_id (or PHY_ANY_UID) in phy_fixup_list
+ * @phy_uid_mask: Applied to phy_uid and fixup->phy_uid before comparison
+ */
+int phy_unregister_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask)
+{
+	struct list_head *pos, *n;
+	struct phy_fixup *fixup;
+	int ret;
+
+	ret = -ENODEV;
+
+	mutex_lock(&phy_fixup_lock);
+	list_for_each_safe(pos, n, &phy_fixup_list) {
+		fixup = list_entry(pos, struct phy_fixup, list);
+
+		if ((!strcmp(fixup->bus_id, bus_id)) &&
+		    ((fixup->phy_uid & phy_uid_mask) ==
+		     (phy_uid & phy_uid_mask))) {
+			list_del(&fixup->list);
+			kfree(fixup);
+			ret = 0;
+			break;
+		}
+	}
+	mutex_unlock(&phy_fixup_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(phy_unregister_fixup);
+
+/* Unregisters a fixup of any PHY with the UID in phy_uid */
+int phy_unregister_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask)
+{
+	return phy_unregister_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask);
+}
+EXPORT_SYMBOL(phy_unregister_fixup_for_uid);
+
+/* Unregisters a fixup of the PHY with id string bus_id */
+int phy_unregister_fixup_for_id(const char *bus_id)
+{
+	return phy_unregister_fixup(bus_id, PHY_ANY_UID, 0xffffffff);
+}
+EXPORT_SYMBOL(phy_unregister_fixup_for_id);
+
+/* Returns 1 if fixup matches phydev in bus_id and phy_uid.
+ * Fixups can be set to match any in one or more fields.
+ */
+static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup)
+{
+	if (strcmp(fixup->bus_id, phydev_name(phydev)) != 0)
+		if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0)
+			return 0;
+
+	if ((fixup->phy_uid & fixup->phy_uid_mask) !=
+	    (phydev->phy_id & fixup->phy_uid_mask))
+		if (fixup->phy_uid != PHY_ANY_UID)
+			return 0;
+
+	return 1;
+}
+
+/* Runs any matching fixups for this phydev */
+static int phy_scan_fixups(struct phy_device *phydev)
+{
+	struct phy_fixup *fixup;
+
+	mutex_lock(&phy_fixup_lock);
+	list_for_each_entry(fixup, &phy_fixup_list, list) {
+		if (phy_needs_fixup(phydev, fixup)) {
+			int err = fixup->run(phydev);
+
+			if (err < 0) {
+				mutex_unlock(&phy_fixup_lock);
+				return err;
+			}
+			phydev->has_fixups = true;
+		}
+	}
+	mutex_unlock(&phy_fixup_lock);
+
+	return 0;
+}
+
+static int phy_bus_match(struct device *dev, struct device_driver *drv)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+	struct phy_driver *phydrv = to_phy_driver(drv);
+	const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
+	int i;
+
+	if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
+		return 0;
+
+	if (phydrv->match_phy_device)
+		return phydrv->match_phy_device(phydev);
+
+	if (phydev->is_c45) {
+		for (i = 1; i < num_ids; i++) {
+			if (phydev->c45_ids.device_ids[i] == 0xffffffff)
+				continue;
+
+			if ((phydrv->phy_id & phydrv->phy_id_mask) ==
+			    (phydev->c45_ids.device_ids[i] &
+			     phydrv->phy_id_mask))
+				return 1;
+		}
+		return 0;
+	} else {
+		return (phydrv->phy_id & phydrv->phy_id_mask) ==
+			(phydev->phy_id & phydrv->phy_id_mask);
+	}
+}
+
+static ssize_t
+phy_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+
+	return sprintf(buf, "0x%.8lx\n", (unsigned long)phydev->phy_id);
+}
+static DEVICE_ATTR_RO(phy_id);
+
+static ssize_t
+phy_interface_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+	const char *mode = NULL;
+
+	if (phy_is_internal(phydev))
+		mode = "internal";
+	else
+		mode = phy_modes(phydev->interface);
+
+	return sprintf(buf, "%s\n", mode);
+}
+static DEVICE_ATTR_RO(phy_interface);
+
+static ssize_t
+phy_has_fixups_show(struct device *dev, struct device_attribute *attr,
+		    char *buf)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+
+	return sprintf(buf, "%d\n", phydev->has_fixups);
+}
+static DEVICE_ATTR_RO(phy_has_fixups);
+
+static struct attribute *phy_dev_attrs[] = {
+	&dev_attr_phy_id.attr,
+	&dev_attr_phy_interface.attr,
+	&dev_attr_phy_has_fixups.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(phy_dev);
+
+static const struct device_type mdio_bus_phy_type = {
+	.name = "PHY",
+	.groups = phy_dev_groups,
+	.release = phy_device_release,
+	.pm = MDIO_BUS_PHY_PM_OPS,
+};
+
+struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,
+				     bool is_c45,
+				     struct phy_c45_device_ids *c45_ids)
+{
+	struct phy_device *dev;
+	struct mdio_device *mdiodev;
+
+	/* We allocate the device, and initialize the default values */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return ERR_PTR(-ENOMEM);
+
+	mdiodev = &dev->mdio;
+	mdiodev->dev.parent = &bus->dev;
+	mdiodev->dev.bus = &mdio_bus_type;
+	mdiodev->dev.type = &mdio_bus_phy_type;
+	mdiodev->bus = bus;
+	mdiodev->bus_match = phy_bus_match;
+	mdiodev->addr = addr;
+	mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
+	mdiodev->device_free = phy_mdio_device_free;
+	mdiodev->device_remove = phy_mdio_device_remove;
+
+	dev->speed = SPEED_UNKNOWN;
+	dev->duplex = DUPLEX_UNKNOWN;
+	dev->pause = 0;
+	dev->asym_pause = 0;
+	dev->link = 0;
+	dev->interface = PHY_INTERFACE_MODE_GMII;
+
+	dev->autoneg = AUTONEG_ENABLE;
+
+	dev->is_c45 = is_c45;
+	dev->phy_id = phy_id;
+	if (c45_ids)
+		dev->c45_ids = *c45_ids;
+	dev->irq = bus->irq[addr];
+	dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
+
+	dev->state = PHY_DOWN;
+
+	mutex_init(&dev->lock);
+	INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
+	INIT_WORK(&dev->phy_queue, phy_change_work);
+
+	/* Request the appropriate module unconditionally; don't
+	 * bother trying to do so only if it isn't already loaded,
+	 * because that gets complicated. A hotplug event would have
+	 * done an unconditional modprobe anyway.
+	 * We don't do normal hotplug because it won't work for MDIO
+	 * -- because it relies on the device staying around for long
+	 * enough for the driver to get loaded. With MDIO, the NIC
+	 * driver will get bored and give up as soon as it finds that
+	 * there's no driver _already_ loaded.
+	 */
+	request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));
+
+	device_initialize(&mdiodev->dev);
+
+	return dev;
+}
+EXPORT_SYMBOL(phy_device_create);
+
+/* get_phy_c45_devs_in_pkg - reads a MMD's devices in package registers.
+ * @bus: the target MII bus
+ * @addr: PHY address on the MII bus
+ * @dev_addr: MMD address in the PHY.
+ * @devices_in_package: where to store the devices in package information.
+ *
+ * Description: reads devices in package registers of a MMD at @dev_addr
+ * from PHY at @addr on @bus.
+ *
+ * Returns: 0 on success, -EIO on failure.
+ */
+static int get_phy_c45_devs_in_pkg(struct mii_bus *bus, int addr, int dev_addr,
+				   u32 *devices_in_package)
+{
+	int phy_reg, reg_addr;
+
+	reg_addr = MII_ADDR_C45 | dev_addr << 16 | MDIO_DEVS2;
+	phy_reg = mdiobus_read(bus, addr, reg_addr);
+	if (phy_reg < 0)
+		return -EIO;
+	*devices_in_package = (phy_reg & 0xffff) << 16;
+
+	reg_addr = MII_ADDR_C45 | dev_addr << 16 | MDIO_DEVS1;
+	phy_reg = mdiobus_read(bus, addr, reg_addr);
+	if (phy_reg < 0)
+		return -EIO;
+	*devices_in_package |= (phy_reg & 0xffff);
+
+	return 0;
+}
+
+/**
+ * get_phy_c45_ids - reads the specified addr for its 802.3-c45 IDs.
+ * @bus: the target MII bus
+ * @addr: PHY address on the MII bus
+ * @phy_id: where to store the ID retrieved.
+ * @c45_ids: where to store the c45 ID information.
+ *
+ *   If the PHY devices-in-package appears to be valid, it and the
+ *   corresponding identifiers are stored in @c45_ids, zero is stored
+ *   in @phy_id.  Otherwise 0xffffffff is stored in @phy_id.  Returns
+ *   zero on success.
+ *
+ */
+static int get_phy_c45_ids(struct mii_bus *bus, int addr, u32 *phy_id,
+			   struct phy_c45_device_ids *c45_ids) {
+	int phy_reg;
+	int i, reg_addr;
+	const int num_ids = ARRAY_SIZE(c45_ids->device_ids);
+	u32 *devs = &c45_ids->devices_in_package;
+
+	/* Find first non-zero Devices In package. Device zero is reserved
+	 * for 802.3 c45 complied PHYs, so don't probe it at first.
+	 */
+	for (i = 1; i < num_ids && *devs == 0; i++) {
+		phy_reg = get_phy_c45_devs_in_pkg(bus, addr, i, devs);
+		if (phy_reg < 0)
+			return -EIO;
+
+		if ((*devs & 0x1fffffff) == 0x1fffffff) {
+			/*  If mostly Fs, there is no device there,
+			 *  then let's continue to probe more, as some
+			 *  10G PHYs have zero Devices In package,
+			 *  e.g. Cortina CS4315/CS4340 PHY.
+			 */
+			phy_reg = get_phy_c45_devs_in_pkg(bus, addr, 0, devs);
+			if (phy_reg < 0)
+				return -EIO;
+			/* no device there, let's get out of here */
+			if ((*devs & 0x1fffffff) == 0x1fffffff) {
+				*phy_id = 0xffffffff;
+				return 0;
+			} else {
+				break;
+			}
+		}
+	}
+
+	/* Now probe Device Identifiers for each device present. */
+	for (i = 1; i < num_ids; i++) {
+		if (!(c45_ids->devices_in_package & (1 << i)))
+			continue;
+
+		reg_addr = MII_ADDR_C45 | i << 16 | MII_PHYSID1;
+		phy_reg = mdiobus_read(bus, addr, reg_addr);
+		if (phy_reg < 0)
+			return -EIO;
+		c45_ids->device_ids[i] = (phy_reg & 0xffff) << 16;
+
+		reg_addr = MII_ADDR_C45 | i << 16 | MII_PHYSID2;
+		phy_reg = mdiobus_read(bus, addr, reg_addr);
+		if (phy_reg < 0)
+			return -EIO;
+		c45_ids->device_ids[i] |= (phy_reg & 0xffff);
+	}
+	*phy_id = 0;
+	return 0;
+}
+
+/**
+ * get_phy_id - reads the specified addr for its ID.
+ * @bus: the target MII bus
+ * @addr: PHY address on the MII bus
+ * @phy_id: where to store the ID retrieved.
+ * @is_c45: If true the PHY uses the 802.3 clause 45 protocol
+ * @c45_ids: where to store the c45 ID information.
+ *
+ * Description: In the case of a 802.3-c22 PHY, reads the ID registers
+ *   of the PHY at @addr on the @bus, stores it in @phy_id and returns
+ *   zero on success.
+ *
+ *   In the case of a 802.3-c45 PHY, get_phy_c45_ids() is invoked, and
+ *   its return value is in turn returned.
+ *
+ */
+static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id,
+		      bool is_c45, struct phy_c45_device_ids *c45_ids)
+{
+	int phy_reg;
+
+	if (is_c45)
+		return get_phy_c45_ids(bus, addr, phy_id, c45_ids);
+
+	/* Grab the bits from PHYIR1, and put them in the upper half */
+	phy_reg = mdiobus_read(bus, addr, MII_PHYSID1);
+	if (phy_reg < 0) {
+		/* if there is no device, return without an error so scanning
+		 * the bus works properly
+		 */
+		if (phy_reg == -EIO || phy_reg == -ENODEV) {
+			*phy_id = 0xffffffff;
+			return 0;
+		}
+
+		return -EIO;
+	}
+
+	*phy_id = (phy_reg & 0xffff) << 16;
+
+	/* Grab the bits from PHYIR2, and put them in the lower half */
+	phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);
+	if (phy_reg < 0)
+		return -EIO;
+
+	*phy_id |= (phy_reg & 0xffff);
+
+	return 0;
+}
+
+/**
+ * get_phy_device - reads the specified PHY device and returns its @phy_device
+ *		    struct
+ * @bus: the target MII bus
+ * @addr: PHY address on the MII bus
+ * @is_c45: If true the PHY uses the 802.3 clause 45 protocol
+ *
+ * Description: Reads the ID registers of the PHY at @addr on the
+ *   @bus, then allocates and returns the phy_device to represent it.
+ */
+struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
+{
+	struct phy_c45_device_ids c45_ids;
+	u32 phy_id = 0;
+	int r;
+
+	c45_ids.devices_in_package = 0;
+	memset(c45_ids.device_ids, 0xff, sizeof(c45_ids.device_ids));
+
+	r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);
+	if (r)
+		return ERR_PTR(r);
+
+	/* If the phy_id is mostly Fs, there is no device there */
+	if ((phy_id & 0x1fffffff) == 0x1fffffff)
+		return ERR_PTR(-ENODEV);
+
+	return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
+}
+EXPORT_SYMBOL(get_phy_device);
+
+/**
+ * phy_device_register - Register the phy device on the MDIO bus
+ * @phydev: phy_device structure to be added to the MDIO bus
+ */
+int phy_device_register(struct phy_device *phydev)
+{
+	int err;
+
+	err = mdiobus_register_device(&phydev->mdio);
+	if (err)
+		return err;
+
+	/* Deassert the reset signal */
+	phy_device_reset(phydev, 0);
+
+	/* Run all of the fixups for this PHY */
+	err = phy_scan_fixups(phydev);
+	if (err) {
+		pr_err("PHY %d failed to initialize\n", phydev->mdio.addr);
+		goto out;
+	}
+
+	err = device_add(&phydev->mdio.dev);
+	if (err) {
+		pr_err("PHY %d failed to add\n", phydev->mdio.addr);
+		goto out;
+	}
+
+	return 0;
+
+ out:
+	/* Assert the reset signal */
+	phy_device_reset(phydev, 1);
+
+	mdiobus_unregister_device(&phydev->mdio);
+	return err;
+}
+EXPORT_SYMBOL(phy_device_register);
+
+/**
+ * phy_device_remove - Remove a previously registered phy device from the MDIO bus
+ * @phydev: phy_device structure to remove
+ *
+ * This doesn't free the phy_device itself, it merely reverses the effects
+ * of phy_device_register(). Use phy_device_free() to free the device
+ * after calling this function.
+ */
+void phy_device_remove(struct phy_device *phydev)
+{
+	device_del(&phydev->mdio.dev);
+
+	/* Assert the reset signal */
+	phy_device_reset(phydev, 1);
+
+	mdiobus_unregister_device(&phydev->mdio);
+}
+EXPORT_SYMBOL(phy_device_remove);
+
+/**
+ * phy_find_first - finds the first PHY device on the bus
+ * @bus: the target MII bus
+ */
+struct phy_device *phy_find_first(struct mii_bus *bus)
+{
+	struct phy_device *phydev;
+	int addr;
+
+	for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+		phydev = mdiobus_get_phy(bus, addr);
+		if (phydev)
+			return phydev;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL(phy_find_first);
+
+static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier)
+{
+	struct net_device *netdev = phydev->attached_dev;
+
+	if (do_carrier) {
+		if (up)
+			netif_carrier_on(netdev);
+		else
+			netif_carrier_off(netdev);
+	}
+	phydev->adjust_link(netdev);
+}
+
+/**
+ * phy_prepare_link - prepares the PHY layer to monitor link status
+ * @phydev: target phy_device struct
+ * @handler: callback function for link status change notifications
+ *
+ * Description: Tells the PHY infrastructure to handle the
+ *   gory details on monitoring link status (whether through
+ *   polling or an interrupt), and to call back to the
+ *   connected device driver when the link status changes.
+ *   If you want to monitor your own link state, don't call
+ *   this function.
+ */
+static void phy_prepare_link(struct phy_device *phydev,
+			     void (*handler)(struct net_device *))
+{
+	phydev->adjust_link = handler;
+}
+
+/**
+ * phy_connect_direct - connect an ethernet device to a specific phy_device
+ * @dev: the network device to connect
+ * @phydev: the pointer to the phy device
+ * @handler: callback function for state change notifications
+ * @interface: PHY device's interface
+ */
+int phy_connect_direct(struct net_device *dev, struct phy_device *phydev,
+		       void (*handler)(struct net_device *),
+		       phy_interface_t interface)
+{
+	int rc;
+
+	if (!dev)
+		return -EINVAL;
+
+	rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);
+	if (rc)
+		return rc;
+
+	phy_prepare_link(phydev, handler);
+	phy_start_machine(phydev);
+	if (phydev->irq > 0)
+		phy_start_interrupts(phydev);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_connect_direct);
+
+/**
+ * phy_connect - connect an ethernet device to a PHY device
+ * @dev: the network device to connect
+ * @bus_id: the id string of the PHY device to connect
+ * @handler: callback function for state change notifications
+ * @interface: PHY device's interface
+ *
+ * Description: Convenience function for connecting ethernet
+ *   devices to PHY devices.  The default behavior is for
+ *   the PHY infrastructure to handle everything, and only notify
+ *   the connected driver when the link status changes.  If you
+ *   don't want, or can't use the provided functionality, you may
+ *   choose to call only the subset of functions which provide
+ *   the desired functionality.
+ */
+struct phy_device *phy_connect(struct net_device *dev, const char *bus_id,
+			       void (*handler)(struct net_device *),
+			       phy_interface_t interface)
+{
+	struct phy_device *phydev;
+	struct device *d;
+	int rc;
+
+	/* Search the list of PHY devices on the mdio bus for the
+	 * PHY with the requested name
+	 */
+	d = bus_find_device_by_name(&mdio_bus_type, NULL, bus_id);
+	if (!d) {
+		pr_err("PHY %s not found\n", bus_id);
+		return ERR_PTR(-ENODEV);
+	}
+	phydev = to_phy_device(d);
+
+	rc = phy_connect_direct(dev, phydev, handler, interface);
+	put_device(d);
+	if (rc)
+		return ERR_PTR(rc);
+
+	return phydev;
+}
+EXPORT_SYMBOL(phy_connect);
+
+/**
+ * phy_disconnect - disable interrupts, stop state machine, and detach a PHY
+ *		    device
+ * @phydev: target phy_device struct
+ */
+void phy_disconnect(struct phy_device *phydev)
+{
+	if (phydev->irq > 0)
+		phy_stop_interrupts(phydev);
+
+	phy_stop_machine(phydev);
+
+	phydev->adjust_link = NULL;
+
+	phy_detach(phydev);
+}
+EXPORT_SYMBOL(phy_disconnect);
+
+/**
+ * phy_poll_reset - Safely wait until a PHY reset has properly completed
+ * @phydev: The PHY device to poll
+ *
+ * Description: According to IEEE 802.3, Section 2, Subsection 22.2.4.1.1, as
+ *   published in 2008, a PHY reset may take up to 0.5 seconds.  The MII BMCR
+ *   register must be polled until the BMCR_RESET bit clears.
+ *
+ *   Furthermore, any attempts to write to PHY registers may have no effect
+ *   or even generate MDIO bus errors until this is complete.
+ *
+ *   Some PHYs (such as the Marvell 88E1111) don't entirely conform to the
+ *   standard and do not fully reset after the BMCR_RESET bit is set, and may
+ *   even *REQUIRE* a soft-reset to properly restart autonegotiation.  In an
+ *   effort to support such broken PHYs, this function is separate from the
+ *   standard phy_init_hw() which will zero all the other bits in the BMCR
+ *   and reapply all driver-specific and board-specific fixups.
+ */
+static int phy_poll_reset(struct phy_device *phydev)
+{
+	/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
+	unsigned int retries = 12;
+	int ret;
+
+	do {
+		msleep(50);
+		ret = phy_read(phydev, MII_BMCR);
+		if (ret < 0)
+			return ret;
+	} while (ret & BMCR_RESET && --retries);
+	if (ret & BMCR_RESET)
+		return -ETIMEDOUT;
+
+	/* Some chips (smsc911x) may still need up to another 1ms after the
+	 * BMCR_RESET bit is cleared before they are usable.
+	 */
+	msleep(1);
+	return 0;
+}
+
+int phy_init_hw(struct phy_device *phydev)
+{
+	int ret = 0;
+
+	/* Deassert the reset signal */
+	phy_device_reset(phydev, 0);
+
+	if (!phydev->drv || !phydev->drv->config_init)
+		return 0;
+
+	if (phydev->drv->soft_reset)
+		ret = phydev->drv->soft_reset(phydev);
+
+	if (ret < 0)
+		return ret;
+
+	ret = phy_scan_fixups(phydev);
+	if (ret < 0)
+		return ret;
+
+	return phydev->drv->config_init(phydev);
+}
+EXPORT_SYMBOL(phy_init_hw);
+
+void phy_attached_info(struct phy_device *phydev)
+{
+	phy_attached_print(phydev, NULL);
+}
+EXPORT_SYMBOL(phy_attached_info);
+
+#define ATTACHED_FMT "attached PHY driver [%s] (mii_bus:phy_addr=%s, irq=%s)"
+void phy_attached_print(struct phy_device *phydev, const char *fmt, ...)
+{
+	const char *drv_name = phydev->drv ? phydev->drv->name : "unbound";
+	char *irq_str;
+	char irq_num[8];
+
+	switch(phydev->irq) {
+	case PHY_POLL:
+		irq_str = "POLL";
+		break;
+	case PHY_IGNORE_INTERRUPT:
+		irq_str = "IGNORE";
+		break;
+	default:
+		snprintf(irq_num, sizeof(irq_num), "%d", phydev->irq);
+		irq_str = irq_num;
+		break;
+	}
+
+
+	if (!fmt) {
+		dev_info(&phydev->mdio.dev, ATTACHED_FMT "\n",
+			 drv_name, phydev_name(phydev),
+			 irq_str);
+	} else {
+		va_list ap;
+
+		dev_info(&phydev->mdio.dev, ATTACHED_FMT,
+			 drv_name, phydev_name(phydev),
+			 irq_str);
+
+		va_start(ap, fmt);
+		vprintk(fmt, ap);
+		va_end(ap);
+	}
+}
+EXPORT_SYMBOL(phy_attached_print);
+
+/**
+ * phy_sfp_attach - attach the SFP bus to the PHY upstream network device
+ * @upstream: pointer to the phy device
+ * @bus: sfp bus representing cage being attached
+ *
+ * This is used to fill in the sfp_upstream_ops .attach member.
+ */
+void phy_sfp_attach(void *upstream, struct sfp_bus *bus)
+{
+	struct phy_device *phydev = upstream;
+
+	if (phydev->attached_dev)
+		phydev->attached_dev->sfp_bus = bus;
+	phydev->sfp_bus_attached = true;
+}
+EXPORT_SYMBOL(phy_sfp_attach);
+
+/**
+ * phy_sfp_detach - detach the SFP bus from the PHY upstream network device
+ * @upstream: pointer to the phy device
+ * @bus: sfp bus representing cage being attached
+ *
+ * This is used to fill in the sfp_upstream_ops .detach member.
+ */
+void phy_sfp_detach(void *upstream, struct sfp_bus *bus)
+{
+	struct phy_device *phydev = upstream;
+
+	if (phydev->attached_dev)
+		phydev->attached_dev->sfp_bus = NULL;
+	phydev->sfp_bus_attached = false;
+}
+EXPORT_SYMBOL(phy_sfp_detach);
+
+/**
+ * phy_sfp_probe - probe for a SFP cage attached to this PHY device
+ * @phydev: Pointer to phy_device
+ * @ops: SFP's upstream operations
+ */
+int phy_sfp_probe(struct phy_device *phydev,
+		  const struct sfp_upstream_ops *ops)
+{
+	struct sfp_bus *bus;
+	int ret;
+
+	if (phydev->mdio.dev.fwnode) {
+		bus = sfp_bus_find_fwnode(phydev->mdio.dev.fwnode);
+		if (IS_ERR(bus))
+			return PTR_ERR(bus);
+
+		phydev->sfp_bus = bus;
+
+		ret = sfp_bus_add_upstream(bus, phydev, ops);
+		sfp_bus_put(bus);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(phy_sfp_probe);
+
+/**
+ * phy_attach_direct - attach a network device to a given PHY device pointer
+ * @dev: network device to attach
+ * @phydev: Pointer to phy_device to attach
+ * @flags: PHY device's dev_flags
+ * @interface: PHY device's interface
+ *
+ * Description: Called by drivers to attach to a particular PHY
+ *     device. The phy_device is found, and properly hooked up
+ *     to the phy_driver.  If no driver is attached, then a
+ *     generic driver is used.  The phy_device is given a ptr to
+ *     the attaching device, and given a callback for link status
+ *     change.  The phy_device is returned to the attaching driver.
+ *     This function takes a reference on the phy device.
+ */
+int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
+		      u32 flags, phy_interface_t interface)
+{
+	struct module *ndev_owner = dev->dev.parent->driver->owner;
+	struct mii_bus *bus = phydev->mdio.bus;
+	struct device *d = &phydev->mdio.dev;
+	bool using_genphy = false;
+	int err;
+
+	/* For Ethernet device drivers that register their own MDIO bus, we
+	 * will have bus->owner match ndev_mod, so we do not want to increment
+	 * our own module->refcnt here, otherwise we would not be able to
+	 * unload later on.
+	 */
+	if (ndev_owner != bus->owner && !try_module_get(bus->owner)) {
+		dev_err(&dev->dev, "failed to get the bus module\n");
+		return -EIO;
+	}
+
+	get_device(d);
+
+	/* Assume that if there is no driver, that it doesn't
+	 * exist, and we should use the genphy driver.
+	 */
+	if (!d->driver) {
+		if (phydev->is_c45)
+			d->driver = &genphy_10g_driver.mdiodrv.driver;
+		else
+			d->driver = &genphy_driver.mdiodrv.driver;
+
+		using_genphy = true;
+	}
+
+	if (!try_module_get(d->driver->owner)) {
+		dev_err(&dev->dev, "failed to get the device driver module\n");
+		err = -EIO;
+		goto error_put_device;
+	}
+
+	if (using_genphy) {
+		err = d->driver->probe(d);
+		if (err >= 0)
+			err = device_bind_driver(d);
+
+		if (err)
+			goto error_module_put;
+	}
+
+	if (phydev->attached_dev) {
+		dev_err(&dev->dev, "PHY already attached\n");
+		err = -EBUSY;
+		goto error;
+	}
+
+	phydev->phy_link_change = phy_link_change;
+	phydev->attached_dev = dev;
+	dev->phydev = phydev;
+
+	if (phydev->sfp_bus_attached)
+		dev->sfp_bus = phydev->sfp_bus;
+
+	/* Some Ethernet drivers try to connect to a PHY device before
+	 * calling register_netdevice() -> netdev_register_kobject() and
+	 * does the dev->dev.kobj initialization. Here we only check for
+	 * success which indicates that the network device kobject is
+	 * ready. Once we do that we still need to keep track of whether
+	 * links were successfully set up or not for phy_detach() to
+	 * remove them accordingly.
+	 */
+	phydev->sysfs_links = false;
+
+	err = sysfs_create_link(&phydev->mdio.dev.kobj, &dev->dev.kobj,
+				"attached_dev");
+	if (!err) {
+		err = sysfs_create_link_nowarn(&dev->dev.kobj,
+					       &phydev->mdio.dev.kobj,
+					       "phydev");
+		if (err) {
+			dev_err(&dev->dev, "could not add device link to %s err %d\n",
+				kobject_name(&phydev->mdio.dev.kobj),
+				err);
+			/* non-fatal - some net drivers can use one netdevice
+			 * with more then one phy
+			 */
+		}
+
+		phydev->sysfs_links = true;
+	}
+
+	phydev->dev_flags = flags;
+
+	phydev->interface = interface;
+
+	phydev->state = PHY_READY;
+
+	/* Initial carrier state is off as the phy is about to be
+	 * (re)initialized.
+	 */
+	netif_carrier_off(phydev->attached_dev);
+
+	/* Do initial configuration here, now that
+	 * we have certain key parameters
+	 * (dev_flags and interface)
+	 */
+	err = phy_init_hw(phydev);
+	if (err)
+		goto error;
+
+	phy_resume(phydev);
+	phy_led_triggers_register(phydev);
+
+	return err;
+
+error:
+	/* phy_detach() does all of the cleanup below */
+	phy_detach(phydev);
+	return err;
+
+error_module_put:
+	module_put(d->driver->owner);
+error_put_device:
+	put_device(d);
+	if (ndev_owner != bus->owner)
+		module_put(bus->owner);
+	return err;
+}
+EXPORT_SYMBOL(phy_attach_direct);
+
+/**
+ * phy_attach - attach a network device to a particular PHY device
+ * @dev: network device to attach
+ * @bus_id: Bus ID of PHY device to attach
+ * @interface: PHY device's interface
+ *
+ * Description: Same as phy_attach_direct() except that a PHY bus_id
+ *     string is passed instead of a pointer to a struct phy_device.
+ */
+struct phy_device *phy_attach(struct net_device *dev, const char *bus_id,
+			      phy_interface_t interface)
+{
+	struct bus_type *bus = &mdio_bus_type;
+	struct phy_device *phydev;
+	struct device *d;
+	int rc;
+
+	if (!dev)
+		return ERR_PTR(-EINVAL);
+
+	/* Search the list of PHY devices on the mdio bus for the
+	 * PHY with the requested name
+	 */
+	d = bus_find_device_by_name(bus, NULL, bus_id);
+	if (!d) {
+		pr_err("PHY %s not found\n", bus_id);
+		return ERR_PTR(-ENODEV);
+	}
+	phydev = to_phy_device(d);
+
+	rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);
+	put_device(d);
+	if (rc)
+		return ERR_PTR(rc);
+
+	return phydev;
+}
+EXPORT_SYMBOL(phy_attach);
+
+/**
+ * phy_detach - detach a PHY device from its network device
+ * @phydev: target phy_device struct
+ *
+ * This detaches the phy device from its network device and the phy
+ * driver, and drops the reference count taken in phy_attach_direct().
+ */
+void phy_detach(struct phy_device *phydev)
+{
+	struct net_device *dev = phydev->attached_dev;
+	struct module *ndev_owner = dev->dev.parent->driver->owner;
+	struct mii_bus *bus;
+
+	if (phydev->drv && phydev->drv->detach)
+		phydev->drv->detach(phydev);
+
+	if (phydev->sysfs_links) {
+		sysfs_remove_link(&dev->dev.kobj, "phydev");
+		sysfs_remove_link(&phydev->mdio.dev.kobj, "attached_dev");
+	}
+	phy_suspend(phydev);
+	phydev->attached_dev->phydev = NULL;
+	phydev->attached_dev = NULL;
+	phydev->phylink = NULL;
+
+	phy_led_triggers_unregister(phydev);
+
+	module_put(phydev->mdio.dev.driver->owner);
+
+	/* If the device had no specific driver before (i.e. - it
+	 * was using the generic driver), we unbind the device
+	 * from the generic driver so that there's a chance a
+	 * real driver could be loaded
+	 */
+	if (phydev->mdio.dev.driver == &genphy_10g_driver.mdiodrv.driver ||
+	    phydev->mdio.dev.driver == &genphy_driver.mdiodrv.driver)
+		device_release_driver(&phydev->mdio.dev);
+
+	/*
+	 * The phydev might go away on the put_device() below, so avoid
+	 * a use-after-free bug by reading the underlying bus first.
+	 */
+	bus = phydev->mdio.bus;
+
+	put_device(&phydev->mdio.dev);
+	if (ndev_owner != bus->owner)
+		module_put(bus->owner);
+
+	/* Assert the reset signal */
+	phy_device_reset(phydev, 1);
+}
+EXPORT_SYMBOL(phy_detach);
+
+int phy_suspend(struct phy_device *phydev)
+{
+	struct phy_driver *phydrv = to_phy_driver(phydev->mdio.dev.driver);
+	struct net_device *netdev = phydev->attached_dev;
+	struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
+	int ret = 0;
+
+	/* If the device has WOL enabled, we cannot suspend the PHY */
+	phy_ethtool_get_wol(phydev, &wol);
+	if (wol.wolopts || (netdev && netdev->wol_enabled))
+		return -EBUSY;
+
+	if (phydev->drv && phydrv->suspend)
+		ret = phydrv->suspend(phydev);
+
+	if (ret)
+		return ret;
+
+	phydev->suspended = true;
+
+	return ret;
+}
+EXPORT_SYMBOL(phy_suspend);
+
+int __phy_resume(struct phy_device *phydev)
+{
+	struct phy_driver *phydrv = to_phy_driver(phydev->mdio.dev.driver);
+	int ret = 0;
+
+	WARN_ON(!mutex_is_locked(&phydev->lock));
+
+	if (phydev->drv && phydrv->resume)
+		ret = phydrv->resume(phydev);
+
+	if (ret)
+		return ret;
+
+	phydev->suspended = false;
+
+	return ret;
+}
+EXPORT_SYMBOL(__phy_resume);
+
+int phy_resume(struct phy_device *phydev)
+{
+	int ret;
+
+	mutex_lock(&phydev->lock);
+	ret = __phy_resume(phydev);
+	mutex_unlock(&phydev->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(phy_resume);
+
+int phy_loopback(struct phy_device *phydev, bool enable)
+{
+	struct phy_driver *phydrv = to_phy_driver(phydev->mdio.dev.driver);
+	int ret = 0;
+
+	mutex_lock(&phydev->lock);
+
+	if (enable && phydev->loopback_enabled) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	if (!enable && !phydev->loopback_enabled) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (phydev->drv && phydrv->set_loopback)
+		ret = phydrv->set_loopback(phydev, enable);
+	else
+		ret = -EOPNOTSUPP;
+
+	if (ret)
+		goto out;
+
+	phydev->loopback_enabled = enable;
+
+out:
+	mutex_unlock(&phydev->lock);
+	return ret;
+}
+EXPORT_SYMBOL(phy_loopback);
+
+/**
+ * phy_reset_after_clk_enable - perform a PHY reset if needed
+ * @phydev: target phy_device struct
+ *
+ * Description: Some PHYs are known to need a reset after their refclk was
+ *   enabled. This function evaluates the flags and perform the reset if it's
+ *   needed. Returns < 0 on error, 0 if the phy wasn't reset and 1 if the phy
+ *   was reset.
+ */
+int phy_reset_after_clk_enable(struct phy_device *phydev)
+{
+	if (!phydev || !phydev->drv)
+		return -ENODEV;
+
+	if (phydev->drv->flags & PHY_RST_AFTER_CLK_EN) {
+		phy_device_reset(phydev, 1);
+		phy_device_reset(phydev, 0);
+		return 1;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_reset_after_clk_enable);
+
+/* Generic PHY support and helper functions */
+
+/**
+ * genphy_config_advert - sanitize and advertise auto-negotiation parameters
+ * @phydev: target phy_device struct
+ *
+ * Description: Writes MII_ADVERTISE with the appropriate values,
+ *   after sanitizing the values to make sure we only advertise
+ *   what is supported.  Returns < 0 on error, 0 if the PHY's advertisement
+ *   hasn't changed, and > 0 if it has changed.
+ */
+static int genphy_config_advert(struct phy_device *phydev)
+{
+	u32 advertise;
+	int oldadv, adv, bmsr;
+	int err, changed = 0;
+
+	/* Only allow advertising what this PHY supports */
+	phydev->advertising &= phydev->supported;
+	advertise = phydev->advertising;
+
+	/* Setup standard advertisement */
+	adv = phy_read(phydev, MII_ADVERTISE);
+	if (adv < 0)
+		return adv;
+
+	oldadv = adv;
+	adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP |
+		 ADVERTISE_PAUSE_ASYM);
+	adv |= ethtool_adv_to_mii_adv_t(advertise);
+
+	if (adv != oldadv) {
+		err = phy_write(phydev, MII_ADVERTISE, adv);
+
+		if (err < 0)
+			return err;
+		changed = 1;
+	}
+
+	bmsr = phy_read(phydev, MII_BMSR);
+	if (bmsr < 0)
+		return bmsr;
+
+	/* Per 802.3-2008, Section 22.2.4.2.16 Extended status all
+	 * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a
+	 * logical 1.
+	 */
+	if (!(bmsr & BMSR_ESTATEN))
+		return changed;
+
+	/* Configure gigabit if it's supported */
+	adv = phy_read(phydev, MII_CTRL1000);
+	if (adv < 0)
+		return adv;
+
+	oldadv = adv;
+	adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF);
+
+	if (phydev->supported & (SUPPORTED_1000baseT_Half |
+				 SUPPORTED_1000baseT_Full)) {
+		adv |= ethtool_adv_to_mii_ctrl1000_t(advertise);
+	}
+
+	if (adv != oldadv)
+		changed = 1;
+
+	err = phy_write(phydev, MII_CTRL1000, adv);
+	if (err < 0)
+		return err;
+
+	return changed;
+}
+
+/**
+ * genphy_config_eee_advert - disable unwanted eee mode advertisement
+ * @phydev: target phy_device struct
+ *
+ * Description: Writes MDIO_AN_EEE_ADV after disabling unsupported energy
+ *   efficent ethernet modes. Returns 0 if the PHY's advertisement hasn't
+ *   changed, and 1 if it has changed.
+ */
+static int genphy_config_eee_advert(struct phy_device *phydev)
+{
+	int broken = phydev->eee_broken_modes;
+	int old_adv, adv;
+
+	/* Nothing to disable */
+	if (!broken)
+		return 0;
+
+	/* If the following call fails, we assume that EEE is not
+	 * supported by the phy. If we read 0, EEE is not advertised
+	 * In both case, we don't need to continue
+	 */
+	adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
+	if (adv <= 0)
+		return 0;
+
+	old_adv = adv;
+	adv &= ~broken;
+
+	/* Advertising remains unchanged with the broken mask */
+	if (old_adv == adv)
+		return 0;
+
+	phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv);
+
+	return 1;
+}
+
+/**
+ * genphy_setup_forced - configures/forces speed/duplex from @phydev
+ * @phydev: target phy_device struct
+ *
+ * Description: Configures MII_BMCR to force speed/duplex
+ *   to the values in phydev. Assumes that the values are valid.
+ *   Please see phy_sanitize_settings().
+ */
+int genphy_setup_forced(struct phy_device *phydev)
+{
+	u16 ctl = 0;
+
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+
+	if (SPEED_1000 == phydev->speed)
+		ctl |= BMCR_SPEED1000;
+	else if (SPEED_100 == phydev->speed)
+		ctl |= BMCR_SPEED100;
+
+	if (DUPLEX_FULL == phydev->duplex)
+		ctl |= BMCR_FULLDPLX;
+
+	return phy_modify(phydev, MII_BMCR,
+			  ~(BMCR_LOOPBACK | BMCR_ISOLATE | BMCR_PDOWN), ctl);
+}
+EXPORT_SYMBOL(genphy_setup_forced);
+
+/**
+ * genphy_restart_aneg - Enable and Restart Autonegotiation
+ * @phydev: target phy_device struct
+ */
+int genphy_restart_aneg(struct phy_device *phydev)
+{
+	/* Don't isolate the PHY if we're negotiating */
+	return phy_modify(phydev, MII_BMCR, BMCR_ISOLATE,
+			  BMCR_ANENABLE | BMCR_ANRESTART);
+}
+EXPORT_SYMBOL(genphy_restart_aneg);
+
+/**
+ * genphy_config_aneg - restart auto-negotiation or write BMCR
+ * @phydev: target phy_device struct
+ *
+ * Description: If auto-negotiation is enabled, we configure the
+ *   advertising, and then restart auto-negotiation.  If it is not
+ *   enabled, then we write the BMCR.
+ */
+int genphy_config_aneg(struct phy_device *phydev)
+{
+	int err, changed;
+
+	changed = genphy_config_eee_advert(phydev);
+
+	if (AUTONEG_ENABLE != phydev->autoneg)
+		return genphy_setup_forced(phydev);
+
+	err = genphy_config_advert(phydev);
+	if (err < 0) /* error */
+		return err;
+
+	changed |= err;
+
+	if (changed == 0) {
+		/* Advertisement hasn't changed, but maybe aneg was never on to
+		 * begin with?  Or maybe phy was isolated?
+		 */
+		int ctl = phy_read(phydev, MII_BMCR);
+
+		if (ctl < 0)
+			return ctl;
+
+		if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE))
+			changed = 1; /* do restart aneg */
+	}
+
+	/* Only restart aneg if we are advertising something different
+	 * than we were before.
+	 */
+	if (changed > 0)
+		return genphy_restart_aneg(phydev);
+
+	return 0;
+}
+EXPORT_SYMBOL(genphy_config_aneg);
+
+/**
+ * genphy_aneg_done - return auto-negotiation status
+ * @phydev: target phy_device struct
+ *
+ * Description: Reads the status register and returns 0 either if
+ *   auto-negotiation is incomplete, or if there was an error.
+ *   Returns BMSR_ANEGCOMPLETE if auto-negotiation is done.
+ */
+int genphy_aneg_done(struct phy_device *phydev)
+{
+	int retval = phy_read(phydev, MII_BMSR);
+
+	return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE);
+}
+EXPORT_SYMBOL(genphy_aneg_done);
+
+/**
+ * genphy_update_link - update link status in @phydev
+ * @phydev: target phy_device struct
+ *
+ * Description: Update the value in phydev->link to reflect the
+ *   current link value.  In order to do this, we need to read
+ *   the status register twice, keeping the second value.
+ */
+int genphy_update_link(struct phy_device *phydev)
+{
+	int status;
+
+	if (phydev->drv && phydev->drv->update_link)
+		return phydev->drv->update_link(phydev);
+
+	/* The link state is latched low so that momentary link
+	 * drops can be detected. Do not double-read the status
+	 * in polling mode to detect such short link drops.
+	 */
+	if (!phy_polling_mode(phydev)) {
+		status = phy_read(phydev, MII_BMSR);
+		if (status < 0)
+			return status;
+	}
+
+	/* Read link and autonegotiation status */
+	status = phy_read(phydev, MII_BMSR);
+	if (status < 0)
+		return status;
+
+	if ((status & BMSR_LSTATUS) == 0)
+		phydev->link = 0;
+	else
+		phydev->link = 1;
+
+	return 0;
+}
+EXPORT_SYMBOL(genphy_update_link);
+
+/**
+ * genphy_read_status - check the link status and update current link state
+ * @phydev: target phy_device struct
+ *
+ * Description: Check the link, then figure out the current state
+ *   by comparing what we advertise with what the link partner
+ *   advertises.  Start by checking the gigabit possibilities,
+ *   then move on to 10/100.
+ */
+int genphy_read_status(struct phy_device *phydev)
+{
+	int adv;
+	int err;
+	int lpa;
+	int lpagb = 0;
+	int common_adv;
+	int common_adv_gb = 0;
+
+	/* Update the link, but return if there was an error */
+	err = genphy_update_link(phydev);
+	if (err)
+		return err;
+
+	phydev->lp_advertising = 0;
+
+	if (AUTONEG_ENABLE == phydev->autoneg) {
+		if (phydev->supported & (SUPPORTED_1000baseT_Half
+					| SUPPORTED_1000baseT_Full)) {
+			lpagb = phy_read(phydev, MII_STAT1000);
+			if (lpagb < 0)
+				return lpagb;
+
+			adv = phy_read(phydev, MII_CTRL1000);
+			if (adv < 0)
+				return adv;
+
+			if (lpagb & LPA_1000MSFAIL) {
+				if (adv & CTL1000_ENABLE_MASTER)
+					phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting manual settings?\n");
+				else
+					phydev_err(phydev, "Master/Slave resolution failed\n");
+				return -ENOLINK;
+			}
+
+			phydev->lp_advertising =
+				mii_stat1000_to_ethtool_lpa_t(lpagb);
+			common_adv_gb = lpagb & adv << 2;
+		}
+
+		lpa = phy_read(phydev, MII_LPA);
+		if (lpa < 0)
+			return lpa;
+
+		phydev->lp_advertising |= mii_lpa_to_ethtool_lpa_t(lpa);
+
+		adv = phy_read(phydev, MII_ADVERTISE);
+		if (adv < 0)
+			return adv;
+
+		common_adv = lpa & adv;
+
+		phydev->speed = SPEED_10;
+		phydev->duplex = DUPLEX_HALF;
+		phydev->pause = 0;
+		phydev->asym_pause = 0;
+
+		if (common_adv_gb & (LPA_1000FULL | LPA_1000HALF)) {
+			phydev->speed = SPEED_1000;
+
+			if (common_adv_gb & LPA_1000FULL)
+				phydev->duplex = DUPLEX_FULL;
+		} else if (common_adv & (LPA_100FULL | LPA_100HALF)) {
+			phydev->speed = SPEED_100;
+
+			if (common_adv & LPA_100FULL)
+				phydev->duplex = DUPLEX_FULL;
+		} else
+			if (common_adv & LPA_10FULL)
+				phydev->duplex = DUPLEX_FULL;
+
+		if (phydev->duplex == DUPLEX_FULL) {
+			phydev->pause = common_adv & LPA_PAUSE_CAP ? 1 : 0;
+			phydev->asym_pause = common_adv &
+					     LPA_PAUSE_ASYM ? 1 : 0;
+		}
+	} else {
+		int bmcr = phy_read(phydev, MII_BMCR);
+
+		if (bmcr < 0)
+			return bmcr;
+
+		if (bmcr & BMCR_FULLDPLX)
+			phydev->duplex = DUPLEX_FULL;
+		else
+			phydev->duplex = DUPLEX_HALF;
+
+		if (bmcr & BMCR_SPEED1000)
+			phydev->speed = SPEED_1000;
+		else if (bmcr & BMCR_SPEED100)
+			phydev->speed = SPEED_100;
+		else
+			phydev->speed = SPEED_10;
+
+		phydev->pause = 0;
+		phydev->asym_pause = 0;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(genphy_read_status);
+
+/**
+ * genphy_soft_reset - software reset the PHY via BMCR_RESET bit
+ * @phydev: target phy_device struct
+ *
+ * Description: Perform a software PHY reset using the standard
+ * BMCR_RESET bit and poll for the reset bit to be cleared.
+ *
+ * Returns: 0 on success, < 0 on failure
+ */
+int genphy_soft_reset(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_write(phydev, MII_BMCR, BMCR_RESET);
+	if (ret < 0)
+		return ret;
+
+	return phy_poll_reset(phydev);
+}
+EXPORT_SYMBOL(genphy_soft_reset);
+
+int genphy_config_init(struct phy_device *phydev)
+{
+	int val;
+	u32 features;
+
+	features = (SUPPORTED_TP | SUPPORTED_MII
+			| SUPPORTED_AUI | SUPPORTED_FIBRE |
+			SUPPORTED_BNC | SUPPORTED_Pause | SUPPORTED_Asym_Pause);
+
+	/* Do we support autonegotiation? */
+	val = phy_read(phydev, MII_BMSR);
+	if (val < 0)
+		return val;
+
+	if (val & BMSR_ANEGCAPABLE)
+		features |= SUPPORTED_Autoneg;
+
+	if (val & BMSR_100FULL)
+		features |= SUPPORTED_100baseT_Full;
+	if (val & BMSR_100HALF)
+		features |= SUPPORTED_100baseT_Half;
+	if (val & BMSR_10FULL)
+		features |= SUPPORTED_10baseT_Full;
+	if (val & BMSR_10HALF)
+		features |= SUPPORTED_10baseT_Half;
+
+	if (val & BMSR_ESTATEN) {
+		val = phy_read(phydev, MII_ESTATUS);
+		if (val < 0)
+			return val;
+
+		if (val & ESTATUS_1000_TFULL)
+			features |= SUPPORTED_1000baseT_Full;
+		if (val & ESTATUS_1000_THALF)
+			features |= SUPPORTED_1000baseT_Half;
+	}
+
+	phydev->supported &= features;
+	phydev->advertising &= features;
+
+	return 0;
+}
+EXPORT_SYMBOL(genphy_config_init);
+
+/* This is used for the phy device which doesn't support the MMD extended
+ * register access, but it does have side effect when we are trying to access
+ * the MMD register via indirect method.
+ */
+int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad, u16 regnum)
+{
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(genphy_read_mmd_unsupported);
+
+int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
+				 u16 regnum, u16 val)
+{
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(genphy_write_mmd_unsupported);
+
+int genphy_suspend(struct phy_device *phydev)
+{
+	return phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
+}
+EXPORT_SYMBOL(genphy_suspend);
+
+int genphy_resume(struct phy_device *phydev)
+{
+	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+}
+EXPORT_SYMBOL(genphy_resume);
+
+int genphy_loopback(struct phy_device *phydev, bool enable)
+{
+	return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
+			  enable ? BMCR_LOOPBACK : 0);
+}
+EXPORT_SYMBOL(genphy_loopback);
+
+static int __set_phy_supported(struct phy_device *phydev, u32 max_speed)
+{
+	switch (max_speed) {
+	case SPEED_10:
+		phydev->supported &= ~PHY_100BT_FEATURES;
+		/* fall through */
+	case SPEED_100:
+		phydev->supported &= ~PHY_1000BT_FEATURES;
+		break;
+	case SPEED_1000:
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+int phy_set_max_speed(struct phy_device *phydev, u32 max_speed)
+{
+	int err;
+
+	err = __set_phy_supported(phydev, max_speed);
+	if (err)
+		return err;
+
+	phydev->advertising = phydev->supported;
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_set_max_speed);
+
+/**
+ * phy_support_asym_pause - Enable support of asym pause
+ * @phydev: target phy_device struct
+ *
+ * Description: Called by the MAC to indicate is supports Asym Pause.
+ */
+void phy_support_asym_pause(struct phy_device *phydev)
+{
+	phydev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
+	phydev->advertising = phydev->supported;
+}
+EXPORT_SYMBOL(phy_support_asym_pause);
+
+/**
+ * phy_set_asym_pause - Configure Pause and Asym Pause
+ * @phydev: target phy_device struct
+ * @rx: Receiver Pause is supported
+ * @tx: Transmit Pause is supported
+ *
+ * Description: Configure advertised Pause support depending on if
+ * transmit and receiver pause is supported. If there has been a
+ * change in adverting, trigger a new autoneg. Generally called from
+ * the set_pauseparam .ndo.
+ */
+void phy_set_asym_pause(struct phy_device *phydev, bool rx, bool tx)
+{
+	u16 oldadv = phydev->advertising;
+	u16 newadv = oldadv &= ~(SUPPORTED_Pause | SUPPORTED_Asym_Pause);
+
+	if (rx)
+		newadv |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
+	if (tx)
+		newadv ^= SUPPORTED_Asym_Pause;
+
+	if (oldadv != newadv) {
+		phydev->advertising = newadv;
+
+		if (phydev->autoneg)
+			phy_start_aneg(phydev);
+	}
+}
+EXPORT_SYMBOL(phy_set_asym_pause);
+
+static void of_set_phy_supported(struct phy_device *phydev)
+{
+	struct device_node *node = phydev->mdio.dev.of_node;
+	u32 max_speed;
+
+	if (!IS_ENABLED(CONFIG_OF_MDIO))
+		return;
+
+	if (!node)
+		return;
+
+	if (!of_property_read_u32(node, "max-speed", &max_speed))
+		__set_phy_supported(phydev, max_speed);
+}
+
+static void of_set_phy_eee_broken(struct phy_device *phydev)
+{
+	struct device_node *node = phydev->mdio.dev.of_node;
+	u32 broken = 0;
+
+	if (!IS_ENABLED(CONFIG_OF_MDIO))
+		return;
+
+	if (!node)
+		return;
+
+	if (of_property_read_bool(node, "eee-broken-100tx"))
+		broken |= MDIO_EEE_100TX;
+	if (of_property_read_bool(node, "eee-broken-1000t"))
+		broken |= MDIO_EEE_1000T;
+	if (of_property_read_bool(node, "eee-broken-10gt"))
+		broken |= MDIO_EEE_10GT;
+	if (of_property_read_bool(node, "eee-broken-1000kx"))
+		broken |= MDIO_EEE_1000KX;
+	if (of_property_read_bool(node, "eee-broken-10gkx4"))
+		broken |= MDIO_EEE_10GKX4;
+	if (of_property_read_bool(node, "eee-broken-10gkr"))
+		broken |= MDIO_EEE_10GKR;
+
+	phydev->eee_broken_modes = broken;
+}
+
+/**
+ * phy_probe - probe and init a PHY device
+ * @dev: device to probe and init
+ *
+ * Description: Take care of setting up the phy_device structure,
+ *   set the state to READY (the driver's init function should
+ *   set it to STARTING if needed).
+ */
+static int phy_probe(struct device *dev)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+	struct device_driver *drv = phydev->mdio.dev.driver;
+	struct phy_driver *phydrv = to_phy_driver(drv);
+	int err = 0;
+
+	phydev->drv = phydrv;
+
+	/* Disable the interrupt if the PHY doesn't support it
+	 * but the interrupt is still a valid one
+	 */
+	if (!(phydrv->flags & PHY_HAS_INTERRUPT) &&
+	    phy_interrupt_is_valid(phydev))
+		phydev->irq = PHY_POLL;
+
+	if (phydrv->flags & PHY_IS_INTERNAL)
+		phydev->is_internal = true;
+
+	mutex_lock(&phydev->lock);
+
+	/* Start out supporting everything. Eventually,
+	 * a controller will attach, and may modify one
+	 * or both of these values
+	 */
+	phydev->supported = phydrv->features;
+	of_set_phy_supported(phydev);
+	phydev->advertising = phydev->supported;
+
+	/* Get the EEE modes we want to prohibit. We will ask
+	 * the PHY stop advertising these mode later on
+	 */
+	of_set_phy_eee_broken(phydev);
+
+	/* The Pause Frame bits indicate that the PHY can support passing
+	 * pause frames. During autonegotiation, the PHYs will determine if
+	 * they should allow pause frames to pass.  The MAC driver should then
+	 * use that result to determine whether to enable flow control via
+	 * pause frames.
+	 *
+	 * Normally, PHY drivers should not set the Pause bits, and instead
+	 * allow phylib to do that.  However, there may be some situations
+	 * (e.g. hardware erratum) where the driver wants to set only one
+	 * of these bits.
+	 */
+	if (phydrv->features & (SUPPORTED_Pause | SUPPORTED_Asym_Pause)) {
+		phydev->supported &= ~(SUPPORTED_Pause | SUPPORTED_Asym_Pause);
+		phydev->supported |= phydrv->features &
+				     (SUPPORTED_Pause | SUPPORTED_Asym_Pause);
+	} else {
+		phydev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
+	}
+
+	/* Set the state to READY by default */
+	phydev->state = PHY_READY;
+
+	if (phydev->drv->probe) {
+		/* Deassert the reset signal */
+		phy_device_reset(phydev, 0);
+
+		err = phydev->drv->probe(phydev);
+		if (err) {
+			/* Assert the reset signal */
+			phy_device_reset(phydev, 1);
+		}
+	}
+
+	mutex_unlock(&phydev->lock);
+
+	return err;
+}
+
+static int phy_remove(struct device *dev)
+{
+	struct phy_device *phydev = to_phy_device(dev);
+
+	cancel_delayed_work_sync(&phydev->state_queue);
+
+	mutex_lock(&phydev->lock);
+	phydev->state = PHY_DOWN;
+	mutex_unlock(&phydev->lock);
+
+	sfp_bus_del_upstream(phydev->sfp_bus);
+	phydev->sfp_bus = NULL;
+
+	if (phydev->drv && phydev->drv->remove) {
+		phydev->drv->remove(phydev);
+
+		/* Assert the reset signal */
+		phy_device_reset(phydev, 1);
+	}
+	phydev->drv = NULL;
+
+	return 0;
+}
+
+/**
+ * phy_driver_register - register a phy_driver with the PHY layer
+ * @new_driver: new phy_driver to register
+ * @owner: module owning this PHY
+ */
+int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
+{
+	int retval;
+
+	new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
+	new_driver->mdiodrv.driver.name = new_driver->name;
+	new_driver->mdiodrv.driver.bus = &mdio_bus_type;
+	new_driver->mdiodrv.driver.probe = phy_probe;
+	new_driver->mdiodrv.driver.remove = phy_remove;
+	new_driver->mdiodrv.driver.owner = owner;
+
+	/* The following works around an issue where the PHY driver doesn't bind
+	 * to the device, resulting in the genphy driver being used instead of
+	 * the dedicated driver. The root cause of the issue isn't known yet
+	 * and seems to be in the base driver core. Once this is fixed we may
+	 * remove this workaround.
+	 */
+	new_driver->mdiodrv.driver.probe_type = PROBE_FORCE_SYNCHRONOUS;
+
+	retval = driver_register(&new_driver->mdiodrv.driver);
+	if (retval) {
+		pr_err("%s: Error %d in registering driver\n",
+		       new_driver->name, retval);
+
+		return retval;
+	}
+
+	pr_debug("%s: Registered new driver\n", new_driver->name);
+
+	return 0;
+}
+EXPORT_SYMBOL(phy_driver_register);
+
+int phy_drivers_register(struct phy_driver *new_driver, int n,
+			 struct module *owner)
+{
+	int i, ret = 0;
+
+	for (i = 0; i < n; i++) {
+		ret = phy_driver_register(new_driver + i, owner);
+		if (ret) {
+			while (i-- > 0)
+				phy_driver_unregister(new_driver + i);
+			break;
+		}
+	}
+	return ret;
+}
+EXPORT_SYMBOL(phy_drivers_register);
+
+void phy_driver_unregister(struct phy_driver *drv)
+{
+	driver_unregister(&drv->mdiodrv.driver);
+}
+EXPORT_SYMBOL(phy_driver_unregister);
+
+void phy_drivers_unregister(struct phy_driver *drv, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		phy_driver_unregister(drv + i);
+}
+EXPORT_SYMBOL(phy_drivers_unregister);
+
+static struct phy_driver genphy_driver = {
+	.phy_id		= 0xffffffff,
+	.phy_id_mask	= 0xffffffff,
+	.name		= "Generic PHY",
+	.soft_reset	= genphy_no_soft_reset,
+	.config_init	= genphy_config_init,
+	.features	= PHY_GBIT_FEATURES | SUPPORTED_MII |
+			  SUPPORTED_AUI | SUPPORTED_FIBRE |
+			  SUPPORTED_BNC,
+	.aneg_done	= genphy_aneg_done,
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+	.set_loopback   = genphy_loopback,
+};
+
+static int __init phy_init(void)
+{
+	int rc;
+
+	rc = mdio_bus_init();
+	if (rc)
+		return rc;
+
+	rc = phy_driver_register(&genphy_10g_driver, THIS_MODULE);
+	if (rc)
+		goto err_10g;
+
+	rc = phy_driver_register(&genphy_driver, THIS_MODULE);
+	if (rc) {
+		phy_driver_unregister(&genphy_10g_driver);
+err_10g:
+		mdio_bus_exit();
+	}
+
+	return rc;
+}
+
+static void __exit phy_exit(void)
+{
+	phy_driver_unregister(&genphy_10g_driver);
+	phy_driver_unregister(&genphy_driver);
+	mdio_bus_exit();
+}
+
+subsys_initcall(phy_init);
+module_exit(phy_exit);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/phy_led_triggers.c b/src/kernel/linux/v4.19/drivers/net/phy/phy_led_triggers.c
new file mode 100644
index 0000000..7278eca
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/phy_led_triggers.c
@@ -0,0 +1,177 @@
+/* Copyright (C) 2016 National Instruments Corp.
+ *
+ * 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/leds.h>
+#include <linux/phy.h>
+#include <linux/phy_led_triggers.h>
+#include <linux/netdevice.h>
+
+static struct phy_led_trigger *phy_speed_to_led_trigger(struct phy_device *phy,
+							unsigned int speed)
+{
+	unsigned int i;
+
+	for (i = 0; i < phy->phy_num_led_triggers; i++) {
+		if (phy->phy_led_triggers[i].speed == speed)
+			return &phy->phy_led_triggers[i];
+	}
+	return NULL;
+}
+
+static void phy_led_trigger_no_link(struct phy_device *phy)
+{
+	if (phy->last_triggered) {
+		led_trigger_event(&phy->last_triggered->trigger, LED_OFF);
+		led_trigger_event(&phy->led_link_trigger->trigger, LED_OFF);
+		phy->last_triggered = NULL;
+	}
+}
+
+void phy_led_trigger_change_speed(struct phy_device *phy)
+{
+	struct phy_led_trigger *plt;
+
+	if (!phy->link)
+		return phy_led_trigger_no_link(phy);
+
+	if (phy->speed == 0)
+		return;
+
+	plt = phy_speed_to_led_trigger(phy, phy->speed);
+	if (!plt) {
+		netdev_alert(phy->attached_dev,
+			     "No phy led trigger registered for speed(%d)\n",
+			     phy->speed);
+		return phy_led_trigger_no_link(phy);
+	}
+
+	if (plt != phy->last_triggered) {
+		if (!phy->last_triggered)
+			led_trigger_event(&phy->led_link_trigger->trigger,
+					  LED_FULL);
+		else
+			led_trigger_event(&phy->last_triggered->trigger, LED_OFF);
+
+		led_trigger_event(&plt->trigger, LED_FULL);
+		phy->last_triggered = plt;
+	}
+}
+EXPORT_SYMBOL_GPL(phy_led_trigger_change_speed);
+
+static void phy_led_trigger_format_name(struct phy_device *phy, char *buf,
+					size_t size, char *suffix)
+{
+	snprintf(buf, size, PHY_ID_FMT ":%s",
+		 phy->mdio.bus->id, phy->mdio.addr, suffix);
+}
+
+static int phy_led_trigger_register(struct phy_device *phy,
+				    struct phy_led_trigger *plt,
+				    unsigned int speed)
+{
+	char name_suffix[PHY_LED_TRIGGER_SPEED_SUFFIX_SIZE];
+
+	plt->speed = speed;
+
+	if (speed < SPEED_1000)
+		snprintf(name_suffix, sizeof(name_suffix), "%dMbps", speed);
+	else if (speed == SPEED_2500)
+		snprintf(name_suffix, sizeof(name_suffix), "2.5Gbps");
+	else
+		snprintf(name_suffix, sizeof(name_suffix), "%dGbps",
+			 DIV_ROUND_CLOSEST(speed, 1000));
+
+	phy_led_trigger_format_name(phy, plt->name, sizeof(plt->name),
+				    name_suffix);
+	plt->trigger.name = plt->name;
+
+	return led_trigger_register(&plt->trigger);
+}
+
+static void phy_led_trigger_unregister(struct phy_led_trigger *plt)
+{
+	led_trigger_unregister(&plt->trigger);
+}
+
+int phy_led_triggers_register(struct phy_device *phy)
+{
+	int i, err;
+	unsigned int speeds[50];
+
+	phy->phy_num_led_triggers = phy_supported_speeds(phy, speeds,
+							 ARRAY_SIZE(speeds));
+	if (!phy->phy_num_led_triggers)
+		return 0;
+
+	phy->led_link_trigger = devm_kzalloc(&phy->mdio.dev,
+					     sizeof(*phy->led_link_trigger),
+					     GFP_KERNEL);
+	if (!phy->led_link_trigger) {
+		err = -ENOMEM;
+		goto out_clear;
+	}
+
+	phy_led_trigger_format_name(phy, phy->led_link_trigger->name,
+				    sizeof(phy->led_link_trigger->name),
+				    "link");
+	phy->led_link_trigger->trigger.name = phy->led_link_trigger->name;
+
+	err = led_trigger_register(&phy->led_link_trigger->trigger);
+	if (err)
+		goto out_free_link;
+
+	phy->phy_led_triggers = devm_kcalloc(&phy->mdio.dev,
+					    phy->phy_num_led_triggers,
+					    sizeof(struct phy_led_trigger),
+					    GFP_KERNEL);
+	if (!phy->phy_led_triggers) {
+		err = -ENOMEM;
+		goto out_unreg_link;
+	}
+
+	for (i = 0; i < phy->phy_num_led_triggers; i++) {
+		err = phy_led_trigger_register(phy, &phy->phy_led_triggers[i],
+					       speeds[i]);
+		if (err)
+			goto out_unreg;
+	}
+
+	phy->last_triggered = NULL;
+	phy_led_trigger_change_speed(phy);
+
+	return 0;
+out_unreg:
+	while (i--)
+		phy_led_trigger_unregister(&phy->phy_led_triggers[i]);
+	devm_kfree(&phy->mdio.dev, phy->phy_led_triggers);
+out_unreg_link:
+	phy_led_trigger_unregister(phy->led_link_trigger);
+out_free_link:
+	devm_kfree(&phy->mdio.dev, phy->led_link_trigger);
+	phy->led_link_trigger = NULL;
+out_clear:
+	phy->phy_num_led_triggers = 0;
+	return err;
+}
+EXPORT_SYMBOL_GPL(phy_led_triggers_register);
+
+void phy_led_triggers_unregister(struct phy_device *phy)
+{
+	int i;
+
+	for (i = 0; i < phy->phy_num_led_triggers; i++)
+		phy_led_trigger_unregister(&phy->phy_led_triggers[i]);
+
+	if (phy->led_link_trigger)
+		phy_led_trigger_unregister(phy->led_link_trigger);
+}
+EXPORT_SYMBOL_GPL(phy_led_triggers_unregister);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/phylink.c b/src/kernel/linux/v4.19/drivers/net/phy/phylink.c
new file mode 100644
index 0000000..7495342
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/phylink.c
@@ -0,0 +1,1897 @@
+/*
+ * phylink models the MAC to optional PHY connection, supporting
+ * technologies such as SFP cages where the PHY is hot-pluggable.
+ *
+ * Copyright (C) 2015 Russell King
+ *
+ * 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/ethtool.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+#include <linux/phylink.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+
+#include "sfp.h"
+#include "swphy.h"
+
+#define SUPPORTED_INTERFACES \
+	(SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \
+	 SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane)
+#define ADVERTISED_INTERFACES \
+	(ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
+	 ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
+
+enum {
+	PHYLINK_DISABLE_STOPPED,
+	PHYLINK_DISABLE_LINK,
+};
+
+/**
+ * struct phylink - internal data type for phylink
+ */
+struct phylink {
+	/* private: */
+	struct net_device *netdev;
+	const struct phylink_mac_ops *ops;
+
+	unsigned long phylink_disable_state; /* bitmask of disables */
+	struct phy_device *phydev;
+	phy_interface_t link_interface;	/* PHY_INTERFACE_xxx */
+	u8 cfg_link_an_mode;		/* MLO_AN_xxx */
+	u8 cur_link_an_mode;
+	u8 link_port;			/* The current non-phy ethtool port */
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+
+	/* The link configuration settings */
+	struct phylink_link_state link_config;
+
+	/* The current settings */
+	phy_interface_t cur_interface;
+
+	struct gpio_desc *link_gpio;
+	unsigned int link_irq;
+	struct timer_list link_poll;
+	void (*get_fixed_state)(struct net_device *dev,
+				struct phylink_link_state *s);
+
+	struct mutex state_mutex;
+	struct phylink_link_state phy_state;
+	struct work_struct resolve;
+
+	bool mac_link_dropped;
+
+	struct sfp_bus *sfp_bus;
+	bool sfp_may_have_phy;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+	u8 sfp_port;
+};
+
+static inline void linkmode_zero(unsigned long *dst)
+{
+	bitmap_zero(dst, __ETHTOOL_LINK_MODE_MASK_NBITS);
+}
+
+static inline void linkmode_copy(unsigned long *dst, const unsigned long *src)
+{
+	bitmap_copy(dst, src, __ETHTOOL_LINK_MODE_MASK_NBITS);
+}
+
+static inline void linkmode_and(unsigned long *dst, const unsigned long *a,
+				const unsigned long *b)
+{
+	bitmap_and(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS);
+}
+
+static inline void linkmode_or(unsigned long *dst, const unsigned long *a,
+				const unsigned long *b)
+{
+	bitmap_or(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS);
+}
+
+static inline bool linkmode_empty(const unsigned long *src)
+{
+	return bitmap_empty(src, __ETHTOOL_LINK_MODE_MASK_NBITS);
+}
+
+/**
+ * phylink_set_port_modes() - set the port type modes in the ethtool mask
+ * @mask: ethtool link mode mask
+ *
+ * Sets all the port type modes in the ethtool mask.  MAC drivers should
+ * use this in their 'validate' callback.
+ */
+void phylink_set_port_modes(unsigned long *mask)
+{
+	phylink_set(mask, TP);
+	phylink_set(mask, AUI);
+	phylink_set(mask, MII);
+	phylink_set(mask, FIBRE);
+	phylink_set(mask, BNC);
+	phylink_set(mask, Backplane);
+}
+EXPORT_SYMBOL_GPL(phylink_set_port_modes);
+
+static int phylink_is_empty_linkmode(const unsigned long *linkmode)
+{
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(tmp) = { 0, };
+
+	phylink_set_port_modes(tmp);
+	phylink_set(tmp, Autoneg);
+	phylink_set(tmp, Pause);
+	phylink_set(tmp, Asym_Pause);
+
+	bitmap_andnot(tmp, linkmode, tmp, __ETHTOOL_LINK_MODE_MASK_NBITS);
+
+	return linkmode_empty(tmp);
+}
+
+static const char *phylink_an_mode_str(unsigned int mode)
+{
+	static const char *modestr[] = {
+		[MLO_AN_PHY] = "phy",
+		[MLO_AN_FIXED] = "fixed",
+		[MLO_AN_INBAND] = "inband",
+	};
+
+	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+}
+
+static int phylink_validate(struct phylink *pl, unsigned long *supported,
+			    struct phylink_link_state *state)
+{
+	pl->ops->validate(pl->netdev, supported, state);
+
+	return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+}
+
+static int phylink_parse_fixedlink(struct phylink *pl,
+				   struct fwnode_handle *fwnode)
+{
+	struct fwnode_handle *fixed_node;
+	const struct phy_setting *s;
+	struct gpio_desc *desc;
+	u32 speed;
+	int ret;
+
+	fixed_node = fwnode_get_named_child_node(fwnode, "fixed-link");
+	if (fixed_node) {
+		ret = fwnode_property_read_u32(fixed_node, "speed", &speed);
+
+		pl->link_config.speed = speed;
+		pl->link_config.duplex = DUPLEX_HALF;
+
+		if (fwnode_property_read_bool(fixed_node, "full-duplex"))
+			pl->link_config.duplex = DUPLEX_FULL;
+
+		/* We treat the "pause" and "asym-pause" terminology as
+		 * defining the link partner's ability. */
+		if (fwnode_property_read_bool(fixed_node, "pause"))
+			pl->link_config.pause |= MLO_PAUSE_SYM;
+		if (fwnode_property_read_bool(fixed_node, "asym-pause"))
+			pl->link_config.pause |= MLO_PAUSE_ASYM;
+
+		if (ret == 0) {
+			desc = fwnode_get_named_gpiod(fixed_node, "link-gpios",
+						      0, GPIOD_IN, "?");
+
+			if (!IS_ERR(desc))
+				pl->link_gpio = desc;
+			else if (desc == ERR_PTR(-EPROBE_DEFER))
+				ret = -EPROBE_DEFER;
+		}
+		fwnode_handle_put(fixed_node);
+
+		if (ret)
+			return ret;
+	} else {
+		u32 prop[5];
+
+		ret = fwnode_property_read_u32_array(fwnode, "fixed-link",
+						     NULL, 0);
+		if (ret != ARRAY_SIZE(prop)) {
+			netdev_err(pl->netdev, "broken fixed-link?\n");
+			return -EINVAL;
+		}
+
+		ret = fwnode_property_read_u32_array(fwnode, "fixed-link",
+						     prop, ARRAY_SIZE(prop));
+		if (!ret) {
+			pl->link_config.duplex = prop[1] ?
+						DUPLEX_FULL : DUPLEX_HALF;
+			pl->link_config.speed = prop[2];
+			if (prop[3])
+				pl->link_config.pause |= MLO_PAUSE_SYM;
+			if (prop[4])
+				pl->link_config.pause |= MLO_PAUSE_ASYM;
+		}
+	}
+
+	if (pl->link_config.speed > SPEED_1000 &&
+	    pl->link_config.duplex != DUPLEX_FULL)
+		netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n",
+			    pl->link_config.speed);
+
+	bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+	linkmode_copy(pl->link_config.advertising, pl->supported);
+	phylink_validate(pl, pl->supported, &pl->link_config);
+
+	s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
+			       pl->supported,
+			       __ETHTOOL_LINK_MODE_MASK_NBITS, true);
+	linkmode_zero(pl->supported);
+	phylink_set(pl->supported, MII);
+	phylink_set(pl->supported, Pause);
+	phylink_set(pl->supported, Asym_Pause);
+	if (s) {
+		__set_bit(s->bit, pl->supported);
+	} else {
+		netdev_warn(pl->netdev, "fixed link %s duplex %dMbps not recognised\n",
+			    pl->link_config.duplex == DUPLEX_FULL ? "full" : "half",
+			    pl->link_config.speed);
+	}
+
+	linkmode_and(pl->link_config.advertising, pl->link_config.advertising,
+		     pl->supported);
+
+	pl->link_config.link = 1;
+	pl->link_config.an_complete = 1;
+
+	return 0;
+}
+
+static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode)
+{
+	struct fwnode_handle *dn;
+	const char *managed;
+
+	dn = fwnode_get_named_child_node(fwnode, "fixed-link");
+	if (dn || fwnode_property_present(fwnode, "fixed-link"))
+		pl->cfg_link_an_mode = MLO_AN_FIXED;
+	fwnode_handle_put(dn);
+
+	if (fwnode_property_read_string(fwnode, "managed", &managed) == 0 &&
+	    strcmp(managed, "in-band-status") == 0) {
+		if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+			netdev_err(pl->netdev,
+				   "can't use both fixed-link and in-band-status\n");
+			return -EINVAL;
+		}
+
+		linkmode_zero(pl->supported);
+		phylink_set(pl->supported, MII);
+		phylink_set(pl->supported, Autoneg);
+		phylink_set(pl->supported, Asym_Pause);
+		phylink_set(pl->supported, Pause);
+		pl->link_config.an_enabled = true;
+		pl->cfg_link_an_mode = MLO_AN_INBAND;
+
+		switch (pl->link_config.interface) {
+		case PHY_INTERFACE_MODE_SGMII:
+			phylink_set(pl->supported, 10baseT_Half);
+			phylink_set(pl->supported, 10baseT_Full);
+			phylink_set(pl->supported, 100baseT_Half);
+			phylink_set(pl->supported, 100baseT_Full);
+			phylink_set(pl->supported, 1000baseT_Half);
+			phylink_set(pl->supported, 1000baseT_Full);
+			break;
+
+		case PHY_INTERFACE_MODE_1000BASEX:
+			phylink_set(pl->supported, 1000baseX_Full);
+			break;
+
+		case PHY_INTERFACE_MODE_2500BASEX:
+			phylink_set(pl->supported, 2500baseX_Full);
+			break;
+
+		case PHY_INTERFACE_MODE_10GKR:
+			phylink_set(pl->supported, 10baseT_Half);
+			phylink_set(pl->supported, 10baseT_Full);
+			phylink_set(pl->supported, 100baseT_Half);
+			phylink_set(pl->supported, 100baseT_Full);
+			phylink_set(pl->supported, 1000baseT_Half);
+			phylink_set(pl->supported, 1000baseT_Full);
+			phylink_set(pl->supported, 1000baseX_Full);
+			phylink_set(pl->supported, 10000baseKR_Full);
+			phylink_set(pl->supported, 10000baseCR_Full);
+			phylink_set(pl->supported, 10000baseSR_Full);
+			phylink_set(pl->supported, 10000baseLR_Full);
+			phylink_set(pl->supported, 10000baseLRM_Full);
+			phylink_set(pl->supported, 10000baseER_Full);
+			break;
+
+		default:
+			netdev_err(pl->netdev,
+				   "incorrect link mode %s for in-band status\n",
+				   phy_modes(pl->link_config.interface));
+			return -EINVAL;
+		}
+
+		linkmode_copy(pl->link_config.advertising, pl->supported);
+
+		if (phylink_validate(pl, pl->supported, &pl->link_config)) {
+			netdev_err(pl->netdev,
+				   "failed to validate link configuration for in-band status\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static void phylink_mac_config(struct phylink *pl,
+			       const struct phylink_link_state *state)
+{
+	netdev_dbg(pl->netdev,
+		   "%s: mode=%s/%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n",
+		   __func__, phylink_an_mode_str(pl->cur_link_an_mode),
+		   phy_modes(state->interface),
+		   phy_speed_to_str(state->speed),
+		   phy_duplex_to_str(state->duplex),
+		   __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising,
+		   state->pause, state->link, state->an_enabled);
+
+	pl->ops->mac_config(pl->netdev, pl->cur_link_an_mode, state);
+}
+
+static void phylink_mac_config_up(struct phylink *pl,
+				  const struct phylink_link_state *state)
+{
+	if (state->link)
+		phylink_mac_config(pl, state);
+}
+
+static void phylink_mac_an_restart(struct phylink *pl)
+{
+	if (pl->link_config.an_enabled &&
+	    phy_interface_mode_is_8023z(pl->link_config.interface))
+		pl->ops->mac_an_restart(pl->netdev);
+}
+
+static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state)
+{
+	struct net_device *ndev = pl->netdev;
+
+	linkmode_copy(state->advertising, pl->link_config.advertising);
+	linkmode_zero(state->lp_advertising);
+	state->interface = pl->link_config.interface;
+	state->an_enabled = pl->link_config.an_enabled;
+	state->speed = SPEED_UNKNOWN;
+	state->duplex = DUPLEX_UNKNOWN;
+	state->pause = MLO_PAUSE_NONE;
+	state->an_complete = 0;
+	state->link = 1;
+
+	return pl->ops->mac_link_state(ndev, state);
+}
+
+/* The fixed state is... fixed except for the link state,
+ * which may be determined by a GPIO or a callback.
+ */
+static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state)
+{
+	*state = pl->link_config;
+	if (pl->get_fixed_state)
+		pl->get_fixed_state(pl->netdev, state);
+	else if (pl->link_gpio)
+		state->link = !!gpiod_get_value_cansleep(pl->link_gpio);
+}
+
+/* Flow control is resolved according to our and the link partners
+ * advertisements using the following drawn from the 802.3 specs:
+ *  Local device  Link partner
+ *  Pause AsymDir Pause AsymDir Result
+ *    1     X       1     X     TX+RX
+ *    0     1       1     1     TX
+ *    1     1       0     1     RX
+ */
+static void phylink_resolve_flow(struct phylink *pl,
+				 struct phylink_link_state *state)
+{
+	int new_pause = 0;
+
+	if (pl->link_config.pause & MLO_PAUSE_AN) {
+		int pause = 0;
+
+		if (phylink_test(pl->link_config.advertising, Pause))
+			pause |= MLO_PAUSE_SYM;
+		if (phylink_test(pl->link_config.advertising, Asym_Pause))
+			pause |= MLO_PAUSE_ASYM;
+
+		pause &= state->pause;
+
+		if (pause & MLO_PAUSE_SYM)
+			new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX;
+		else if (pause & MLO_PAUSE_ASYM)
+			new_pause = state->pause & MLO_PAUSE_SYM ?
+				 MLO_PAUSE_TX : MLO_PAUSE_RX;
+	} else {
+		new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK;
+	}
+
+	state->pause &= ~MLO_PAUSE_TXRX_MASK;
+	state->pause |= new_pause;
+}
+
+static const char *phylink_pause_to_str(int pause)
+{
+	switch (pause & MLO_PAUSE_TXRX_MASK) {
+	case MLO_PAUSE_TX | MLO_PAUSE_RX:
+		return "rx/tx";
+	case MLO_PAUSE_TX:
+		return "tx";
+	case MLO_PAUSE_RX:
+		return "rx";
+	default:
+		return "off";
+	}
+}
+
+static void phylink_resolve(struct work_struct *w)
+{
+	struct phylink *pl = container_of(w, struct phylink, resolve);
+	struct phylink_link_state link_state;
+	struct net_device *ndev = pl->netdev;
+
+	mutex_lock(&pl->state_mutex);
+	if (pl->phylink_disable_state) {
+		pl->mac_link_dropped = false;
+		link_state.link = false;
+	} else if (pl->mac_link_dropped) {
+		link_state.link = false;
+	} else {
+		switch (pl->cur_link_an_mode) {
+		case MLO_AN_PHY:
+			link_state = pl->phy_state;
+			phylink_resolve_flow(pl, &link_state);
+			phylink_mac_config_up(pl, &link_state);
+			break;
+
+		case MLO_AN_FIXED:
+			phylink_get_fixed_state(pl, &link_state);
+			phylink_mac_config_up(pl, &link_state);
+			break;
+
+		case MLO_AN_INBAND:
+			phylink_get_mac_state(pl, &link_state);
+
+			/* If we have a phy, the "up" state is the union of
+			 * both the PHY and the MAC */
+			if (pl->phydev)
+				link_state.link &= pl->phy_state.link;
+
+			/* Only update if the PHY link is up */
+			if (pl->phydev && pl->phy_state.link) {
+				link_state.interface = pl->phy_state.interface;
+
+				/* If we have a PHY, we need to update with
+				 * the pause mode bits. */
+				link_state.pause |= pl->phy_state.pause;
+				phylink_resolve_flow(pl, &link_state);
+				phylink_mac_config(pl, &link_state);
+			}
+			break;
+		}
+	}
+
+	if (link_state.link != netif_carrier_ok(ndev)) {
+		if (!link_state.link) {
+			netif_carrier_off(ndev);
+			pl->ops->mac_link_down(ndev, pl->cur_link_an_mode,
+					       pl->cur_interface);
+			netdev_info(ndev, "Link is Down\n");
+		} else {
+			pl->cur_interface = link_state.interface;
+			pl->ops->mac_link_up(ndev, pl->cur_link_an_mode,
+					     pl->cur_interface, pl->phydev);
+
+			netif_carrier_on(ndev);
+
+			netdev_info(ndev,
+				    "Link is Up - %s/%s - flow control %s\n",
+				    phy_speed_to_str(link_state.speed),
+				    phy_duplex_to_str(link_state.duplex),
+				    phylink_pause_to_str(link_state.pause));
+		}
+	}
+	if (!link_state.link && pl->mac_link_dropped) {
+		pl->mac_link_dropped = false;
+		queue_work(system_power_efficient_wq, &pl->resolve);
+	}
+	mutex_unlock(&pl->state_mutex);
+}
+
+static void phylink_run_resolve(struct phylink *pl)
+{
+	if (!pl->phylink_disable_state)
+		queue_work(system_power_efficient_wq, &pl->resolve);
+}
+
+static void phylink_run_resolve_and_disable(struct phylink *pl, int bit)
+{
+	unsigned long state = pl->phylink_disable_state;
+
+	set_bit(bit, &pl->phylink_disable_state);
+	if (state == 0) {
+		queue_work(system_power_efficient_wq, &pl->resolve);
+		flush_work(&pl->resolve);
+	}
+}
+
+static void phylink_fixed_poll(struct timer_list *t)
+{
+	struct phylink *pl = container_of(t, struct phylink, link_poll);
+
+	mod_timer(t, jiffies + HZ);
+
+	phylink_run_resolve(pl);
+}
+
+static const struct sfp_upstream_ops sfp_phylink_ops;
+
+static int phylink_register_sfp(struct phylink *pl,
+				struct fwnode_handle *fwnode)
+{
+	struct sfp_bus *bus;
+	int ret;
+
+	bus = sfp_bus_find_fwnode(fwnode);
+	if (IS_ERR(bus)) {
+		ret = PTR_ERR(bus);
+		netdev_err(pl->netdev, "unable to attach SFP bus: %d\n", ret);
+		return ret;
+	}
+
+	pl->sfp_bus = bus;
+
+	ret = sfp_bus_add_upstream(bus, pl, &sfp_phylink_ops);
+	sfp_bus_put(bus);
+
+	return ret;
+}
+
+/**
+ * phylink_create() - create a phylink instance
+ * @ndev: a pointer to the &struct net_device
+ * @fwnode: a pointer to a &struct fwnode_handle describing the network
+ *	interface
+ * @iface: the desired link mode defined by &typedef phy_interface_t
+ * @ops: a pointer to a &struct phylink_mac_ops for the MAC.
+ *
+ * Create a new phylink instance, and parse the link parameters found in @np.
+ * This will parse in-band modes, fixed-link or SFP configuration.
+ *
+ * Returns a pointer to a &struct phylink, or an error-pointer value. Users
+ * must use IS_ERR() to check for errors from this function.
+ */
+struct phylink *phylink_create(struct net_device *ndev,
+			       struct fwnode_handle *fwnode,
+			       phy_interface_t iface,
+			       const struct phylink_mac_ops *ops)
+{
+	struct phylink *pl;
+	int ret;
+
+	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+	if (!pl)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&pl->state_mutex);
+	INIT_WORK(&pl->resolve, phylink_resolve);
+	pl->netdev = ndev;
+	pl->phy_state.interface = iface;
+	pl->link_interface = iface;
+	if (iface == PHY_INTERFACE_MODE_MOCA)
+		pl->link_port = PORT_BNC;
+	else
+		pl->link_port = PORT_MII;
+	pl->link_config.interface = iface;
+	pl->link_config.pause = MLO_PAUSE_AN;
+	pl->link_config.speed = SPEED_UNKNOWN;
+	pl->link_config.duplex = DUPLEX_UNKNOWN;
+	pl->link_config.an_enabled = true;
+	pl->ops = ops;
+	__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+	timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
+
+	bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+	linkmode_copy(pl->link_config.advertising, pl->supported);
+	phylink_validate(pl, pl->supported, &pl->link_config);
+
+	ret = phylink_parse_mode(pl, fwnode);
+	if (ret < 0) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+		ret = phylink_parse_fixedlink(pl, fwnode);
+		if (ret < 0) {
+			kfree(pl);
+			return ERR_PTR(ret);
+		}
+	}
+
+	pl->cur_link_an_mode = pl->cfg_link_an_mode;
+
+	ret = phylink_register_sfp(pl, fwnode);
+	if (ret < 0) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	return pl;
+}
+EXPORT_SYMBOL_GPL(phylink_create);
+
+/**
+ * phylink_destroy() - cleanup and destroy the phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Destroy a phylink instance. Any PHY that has been attached must have been
+ * cleaned up via phylink_disconnect_phy() prior to calling this function.
+ */
+void phylink_destroy(struct phylink *pl)
+{
+	sfp_bus_del_upstream(pl->sfp_bus);
+	if (pl->link_gpio)
+		gpiod_put(pl->link_gpio);
+
+	cancel_work_sync(&pl->resolve);
+	kfree(pl);
+}
+EXPORT_SYMBOL_GPL(phylink_destroy);
+
+static void phylink_phy_change(struct phy_device *phydev, bool up,
+			       bool do_carrier)
+{
+	struct phylink *pl = phydev->phylink;
+
+	mutex_lock(&pl->state_mutex);
+	pl->phy_state.speed = phydev->speed;
+	pl->phy_state.duplex = phydev->duplex;
+	pl->phy_state.pause = MLO_PAUSE_NONE;
+	if (phydev->pause)
+		pl->phy_state.pause |= MLO_PAUSE_SYM;
+	if (phydev->asym_pause)
+		pl->phy_state.pause |= MLO_PAUSE_ASYM;
+	pl->phy_state.interface = phydev->interface;
+	pl->phy_state.link = up;
+	mutex_unlock(&pl->state_mutex);
+
+	phylink_run_resolve(pl);
+
+	netdev_dbg(pl->netdev, "phy link %s %s/%s/%s\n", up ? "up" : "down",
+		   phy_modes(phydev->interface),
+		   phy_speed_to_str(phydev->speed),
+		   phy_duplex_to_str(phydev->duplex));
+}
+
+static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
+			       phy_interface_t interface)
+{
+	struct phylink_link_state config;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+	u32 advertising;
+	int ret;
+
+	/*
+	 * This is the new way of dealing with flow control for PHYs,
+	 * as described by Timur Tabi in commit 529ed1275263 ("net: phy:
+	 * phy drivers should not set SUPPORTED_[Asym_]Pause") except
+	 * using our validate call to the MAC, we rely upon the MAC
+	 * clearing the bits from both supported and advertising fields.
+	 */
+	phy_support_asym_pause(phy);
+
+	memset(&config, 0, sizeof(config));
+	ethtool_convert_legacy_u32_to_link_mode(supported, phy->supported);
+	ethtool_convert_legacy_u32_to_link_mode(config.advertising,
+						phy->advertising);
+	config.interface = interface;
+
+	ret = phylink_validate(pl, supported, &config);
+	if (ret)
+		return ret;
+
+	phy->phylink = pl;
+	phy->phy_link_change = phylink_phy_change;
+
+	netdev_info(pl->netdev,
+		    "PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev),
+		    phy->drv->name);
+
+	mutex_lock(&phy->lock);
+	mutex_lock(&pl->state_mutex);
+	pl->phydev = phy;
+	pl->phy_state.interface = interface;
+	linkmode_copy(pl->supported, supported);
+	linkmode_copy(pl->link_config.advertising, config.advertising);
+
+	/* Restrict the phy advertisement according to the MAC support. */
+	ethtool_convert_link_mode_to_legacy_u32(&advertising, config.advertising);
+	phy->advertising = advertising;
+	mutex_unlock(&pl->state_mutex);
+	mutex_unlock(&phy->lock);
+
+	netdev_dbg(pl->netdev,
+		   "phy: setting supported %*pb advertising 0x%08x\n",
+		   __ETHTOOL_LINK_MODE_MASK_NBITS, pl->supported,
+		   phy->advertising);
+
+	phy_start_machine(phy);
+	if (phy->irq > 0)
+		phy_start_interrupts(phy);
+
+	return 0;
+}
+
+static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
+			      phy_interface_t interface)
+{
+	if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED ||
+		    (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+		     phy_interface_mode_is_8023z(interface))))
+		return -EINVAL;
+
+	if (pl->phydev)
+		return -EBUSY;
+
+	return phy_attach_direct(pl->netdev, phy, 0, interface);
+}
+
+/**
+ * phylink_connect_phy() - connect a PHY to the phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @phy: a pointer to a &struct phy_device.
+ *
+ * Connect @phy to the phylink instance specified by @pl by calling
+ * phy_attach_direct(). Configure the @phy according to the MAC driver's
+ * capabilities, start the PHYLIB state machine and enable any interrupts
+ * that the PHY supports.
+ *
+ * This updates the phylink's ethtool supported and advertising link mode
+ * masks.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
+{
+	int ret;
+
+	/* Use PHY device/driver interface */
+	if (pl->link_interface == PHY_INTERFACE_MODE_NA) {
+		pl->link_interface = phy->interface;
+		pl->link_config.interface = pl->link_interface;
+	}
+
+	ret = phylink_attach_phy(pl, phy, pl->link_interface);
+	if (ret < 0)
+		return ret;
+
+	ret = phylink_bringup_phy(pl, phy, pl->link_config.interface);
+	if (ret)
+		phy_detach(phy);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_connect_phy);
+
+/**
+ * phylink_of_phy_connect() - connect the PHY specified in the DT mode.
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @dn: a pointer to a &struct device_node.
+ * @flags: PHY-specific flags to communicate to the PHY device driver
+ *
+ * Connect the phy specified in the device node @dn to the phylink instance
+ * specified by @pl. Actions specified in phylink_connect_phy() will be
+ * performed.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn,
+			   u32 flags)
+{
+	struct device_node *phy_node;
+	struct phy_device *phy_dev;
+	int ret;
+
+	/* Fixed links and 802.3z are handled without needing a PHY */
+	if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
+	    (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+	     phy_interface_mode_is_8023z(pl->link_interface)))
+		return 0;
+
+	phy_node = of_parse_phandle(dn, "phy-handle", 0);
+	if (!phy_node)
+		phy_node = of_parse_phandle(dn, "phy", 0);
+	if (!phy_node)
+		phy_node = of_parse_phandle(dn, "phy-device", 0);
+
+	if (!phy_node) {
+		if (pl->cfg_link_an_mode == MLO_AN_PHY)
+			return -ENODEV;
+		return 0;
+	}
+
+	phy_dev = of_phy_attach(pl->netdev, phy_node, flags,
+				pl->link_interface);
+	/* We're done with the phy_node handle */
+	of_node_put(phy_node);
+
+	if (!phy_dev)
+		return -ENODEV;
+
+	ret = phylink_bringup_phy(pl, phy_dev, pl->link_config.interface);
+	if (ret)
+		phy_detach(phy_dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
+
+/**
+ * phylink_disconnect_phy() - disconnect any PHY attached to the phylink
+ *   instance.
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Disconnect any current PHY from the phylink instance described by @pl.
+ */
+void phylink_disconnect_phy(struct phylink *pl)
+{
+	struct phy_device *phy;
+
+	ASSERT_RTNL();
+
+	phy = pl->phydev;
+	if (phy) {
+		mutex_lock(&phy->lock);
+		mutex_lock(&pl->state_mutex);
+		pl->phydev = NULL;
+		mutex_unlock(&pl->state_mutex);
+		mutex_unlock(&phy->lock);
+		flush_work(&pl->resolve);
+
+		phy_disconnect(phy);
+	}
+}
+EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
+
+/**
+ * phylink_fixed_state_cb() - allow setting a fixed link callback
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @cb: callback to execute to determine the fixed link state.
+ *
+ * The MAC driver should call this driver when the state of its link
+ * can be determined through e.g: an out of band MMIO register.
+ */
+int phylink_fixed_state_cb(struct phylink *pl,
+			   void (*cb)(struct net_device *dev,
+				      struct phylink_link_state *state))
+{
+	/* It does not make sense to let the link be overriden unless we use
+	 * MLO_AN_FIXED
+	 */
+	if (pl->cfg_link_an_mode != MLO_AN_FIXED)
+		return -EINVAL;
+
+	mutex_lock(&pl->state_mutex);
+	pl->get_fixed_state = cb;
+	mutex_unlock(&pl->state_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(phylink_fixed_state_cb);
+
+/**
+ * phylink_mac_change() - notify phylink of a change in MAC state
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @up: indicates whether the link is currently up.
+ *
+ * The MAC driver should call this driver when the state of its link
+ * changes (eg, link failure, new negotiation results, etc.)
+ */
+void phylink_mac_change(struct phylink *pl, bool up)
+{
+	if (!up)
+		pl->mac_link_dropped = true;
+	phylink_run_resolve(pl);
+	netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down");
+}
+EXPORT_SYMBOL_GPL(phylink_mac_change);
+
+static irqreturn_t phylink_link_handler(int irq, void *data)
+{
+	struct phylink *pl = data;
+
+	phylink_run_resolve(pl);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * phylink_start() - start a phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Start the phylink instance specified by @pl, configuring the MAC for the
+ * desired link mode(s) and negotiation style. This should be called from the
+ * network device driver's &struct net_device_ops ndo_open() method.
+ */
+void phylink_start(struct phylink *pl)
+{
+	ASSERT_RTNL();
+
+	netdev_info(pl->netdev, "configuring for %s/%s link mode\n",
+		    phylink_an_mode_str(pl->cur_link_an_mode),
+		    phy_modes(pl->link_config.interface));
+
+	/* Always set the carrier off */
+	netif_carrier_off(pl->netdev);
+
+	/* Apply the link configuration to the MAC when starting. This allows
+	 * a fixed-link to start with the correct parameters, and also
+	 * ensures that we set the appropriate advertisement for Serdes links.
+	 */
+	phylink_resolve_flow(pl, &pl->link_config);
+	phylink_mac_config(pl, &pl->link_config);
+
+	/* Restart autonegotiation if using 802.3z to ensure that the link
+	 * parameters are properly negotiated.  This is necessary for DSA
+	 * switches using 802.3z negotiation to ensure they see our modes.
+	 */
+	phylink_mac_an_restart(pl);
+
+	clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+	phylink_run_resolve(pl);
+
+	if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+		int irq = gpiod_to_irq(pl->link_gpio);
+
+		if (irq > 0) {
+			if (!request_irq(irq, phylink_link_handler,
+					 IRQF_TRIGGER_RISING |
+					 IRQF_TRIGGER_FALLING,
+					 "netdev link", pl))
+				pl->link_irq = irq;
+			else
+				irq = 0;
+		}
+		if (irq <= 0)
+			mod_timer(&pl->link_poll, jiffies + HZ);
+	}
+	if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->get_fixed_state)
+		mod_timer(&pl->link_poll, jiffies + HZ);
+	if (pl->phydev)
+		phy_start(pl->phydev);
+	if (pl->sfp_bus)
+		sfp_upstream_start(pl->sfp_bus);
+}
+EXPORT_SYMBOL_GPL(phylink_start);
+
+/**
+ * phylink_stop() - stop a phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Stop the phylink instance specified by @pl. This should be called from the
+ * network device driver's &struct net_device_ops ndo_stop() method.  The
+ * network device's carrier state should not be changed prior to calling this
+ * function.
+ */
+void phylink_stop(struct phylink *pl)
+{
+	ASSERT_RTNL();
+
+	if (pl->sfp_bus)
+		sfp_upstream_stop(pl->sfp_bus);
+	if (pl->phydev)
+		phy_stop(pl->phydev);
+	del_timer_sync(&pl->link_poll);
+	if (pl->link_irq) {
+		free_irq(pl->link_irq, pl);
+		pl->link_irq = 0;
+	}
+
+	phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_STOPPED);
+}
+EXPORT_SYMBOL_GPL(phylink_stop);
+
+/**
+ * phylink_ethtool_get_wol() - get the wake on lan parameters for the PHY
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @wol: a pointer to &struct ethtool_wolinfo to hold the read parameters
+ *
+ * Read the wake on lan parameters from the PHY attached to the phylink
+ * instance specified by @pl. If no PHY is currently attached, report no
+ * support for wake on lan.
+ */
+void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol)
+{
+	ASSERT_RTNL();
+
+	wol->supported = 0;
+	wol->wolopts = 0;
+
+	if (pl->phydev)
+		phy_ethtool_get_wol(pl->phydev, wol);
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol);
+
+/**
+ * phylink_ethtool_set_wol() - set wake on lan parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @wol: a pointer to &struct ethtool_wolinfo for the desired parameters
+ *
+ * Set the wake on lan parameters for the PHY attached to the phylink
+ * instance specified by @pl. If no PHY is attached, returns %EOPNOTSUPP
+ * error.
+ *
+ * Returns zero on success or negative errno code.
+ */
+int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol)
+{
+	int ret = -EOPNOTSUPP;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev)
+		ret = phy_ethtool_set_wol(pl->phydev, wol);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol);
+
+static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b)
+{
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask);
+
+	linkmode_zero(mask);
+	phylink_set_port_modes(mask);
+
+	linkmode_and(dst, dst, mask);
+	linkmode_or(dst, dst, b);
+}
+
+static void phylink_get_ksettings(const struct phylink_link_state *state,
+				  struct ethtool_link_ksettings *kset)
+{
+	phylink_merge_link_mode(kset->link_modes.advertising, state->advertising);
+	linkmode_copy(kset->link_modes.lp_advertising, state->lp_advertising);
+	kset->base.speed = state->speed;
+	kset->base.duplex = state->duplex;
+	kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE :
+				AUTONEG_DISABLE;
+}
+
+/**
+ * phylink_ethtool_ksettings_get() - get the current link settings
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @kset: a pointer to a &struct ethtool_link_ksettings to hold link settings
+ *
+ * Read the current link settings for the phylink instance specified by @pl.
+ * This will be the link settings read from the MAC, PHY or fixed link
+ * settings depending on the current negotiation mode.
+ */
+int phylink_ethtool_ksettings_get(struct phylink *pl,
+				  struct ethtool_link_ksettings *kset)
+{
+	struct phylink_link_state link_state;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev) {
+		phy_ethtool_ksettings_get(pl->phydev, kset);
+	} else {
+		kset->base.port = pl->link_port;
+	}
+
+	linkmode_copy(kset->link_modes.supported, pl->supported);
+
+	switch (pl->cur_link_an_mode) {
+	case MLO_AN_FIXED:
+		/* We are using fixed settings. Report these as the
+		 * current link settings - and note that these also
+		 * represent the supported speeds/duplex/pause modes.
+		 */
+		phylink_get_fixed_state(pl, &link_state);
+		phylink_get_ksettings(&link_state, kset);
+		break;
+
+	case MLO_AN_INBAND:
+		/* If there is a phy attached, then use the reported
+		 * settings from the phy with no modification.
+		 */
+		if (pl->phydev)
+			break;
+
+		phylink_get_mac_state(pl, &link_state);
+
+		/* The MAC is reporting the link results from its own PCS
+		 * layer via in-band status. Report these as the current
+		 * link settings.
+		 */
+		phylink_get_ksettings(&link_state, kset);
+		break;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
+
+/**
+ * phylink_ethtool_ksettings_set() - set the link settings
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @kset: a pointer to a &struct ethtool_link_ksettings for the desired modes
+ */
+int phylink_ethtool_ksettings_set(struct phylink *pl,
+				  const struct ethtool_link_ksettings *kset)
+{
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+	struct ethtool_link_ksettings our_kset;
+	struct phylink_link_state config;
+	int ret;
+
+	ASSERT_RTNL();
+
+	if (kset->base.autoneg != AUTONEG_DISABLE &&
+	    kset->base.autoneg != AUTONEG_ENABLE)
+		return -EINVAL;
+
+	linkmode_copy(support, pl->supported);
+	config = pl->link_config;
+
+	/* Mask out unsupported advertisements */
+	linkmode_and(config.advertising, kset->link_modes.advertising,
+		     support);
+
+	/* FIXME: should we reject autoneg if phy/mac does not support it? */
+	if (kset->base.autoneg == AUTONEG_DISABLE) {
+		const struct phy_setting *s;
+
+		/* Autonegotiation disabled, select a suitable speed and
+		 * duplex.
+		 */
+		s = phy_lookup_setting(kset->base.speed, kset->base.duplex,
+				       support,
+				       __ETHTOOL_LINK_MODE_MASK_NBITS, false);
+		if (!s)
+			return -EINVAL;
+
+		/* If we have a fixed link (as specified by firmware), refuse
+		 * to change link parameters.
+		 */
+		if (pl->cur_link_an_mode == MLO_AN_FIXED &&
+		    (s->speed != pl->link_config.speed ||
+		     s->duplex != pl->link_config.duplex))
+			return -EINVAL;
+
+		config.speed = s->speed;
+		config.duplex = s->duplex;
+		config.an_enabled = false;
+
+		__clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising);
+	} else {
+		/* If we have a fixed link, refuse to enable autonegotiation */
+		if (pl->cur_link_an_mode == MLO_AN_FIXED)
+			return -EINVAL;
+
+		config.speed = SPEED_UNKNOWN;
+		config.duplex = DUPLEX_UNKNOWN;
+		config.an_enabled = true;
+
+		__set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising);
+	}
+
+	if (phylink_validate(pl, support, &config))
+		return -EINVAL;
+
+	/* If autonegotiation is enabled, we must have an advertisement */
+	if (config.an_enabled && phylink_is_empty_linkmode(config.advertising))
+		return -EINVAL;
+
+	our_kset = *kset;
+	linkmode_copy(our_kset.link_modes.advertising, config.advertising);
+	our_kset.base.speed = config.speed;
+	our_kset.base.duplex = config.duplex;
+
+	/* If we have a PHY, configure the phy */
+	if (pl->phydev) {
+		ret = phy_ethtool_ksettings_set(pl->phydev, &our_kset);
+		if (ret)
+			return ret;
+	}
+
+	mutex_lock(&pl->state_mutex);
+	/* Configure the MAC to match the new settings */
+	linkmode_copy(pl->link_config.advertising, our_kset.link_modes.advertising);
+	pl->link_config.interface = config.interface;
+	pl->link_config.speed = our_kset.base.speed;
+	pl->link_config.duplex = our_kset.base.duplex;
+	pl->link_config.an_enabled = our_kset.base.autoneg != AUTONEG_DISABLE;
+
+	/* If we have a PHY, phylib will call our link state function if the
+	 * mode has changed, which will trigger a resolve and update the MAC
+	 * configuration. For a fixed link, this isn't able to change any
+	 * parameters, which just leaves inband mode.
+	 */
+	if (pl->cur_link_an_mode == MLO_AN_INBAND &&
+	    !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) {
+		phylink_mac_config(pl, &pl->link_config);
+		phylink_mac_an_restart(pl);
+	}
+	mutex_unlock(&pl->state_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set);
+
+/**
+ * phylink_ethtool_nway_reset() - restart negotiation
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Restart negotiation for the phylink instance specified by @pl. This will
+ * cause any attached phy to restart negotiation with the link partner, and
+ * if the MAC is in a BaseX mode, the MAC will also be requested to restart
+ * negotiation.
+ *
+ * Returns zero on success, or negative error code.
+ */
+int phylink_ethtool_nway_reset(struct phylink *pl)
+{
+	int ret = 0;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev)
+		ret = phy_restart_aneg(pl->phydev);
+	phylink_mac_an_restart(pl);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
+
+/**
+ * phylink_ethtool_get_pauseparam() - get the current pause parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @pause: a pointer to a &struct ethtool_pauseparam
+ */
+void phylink_ethtool_get_pauseparam(struct phylink *pl,
+				    struct ethtool_pauseparam *pause)
+{
+	ASSERT_RTNL();
+
+	pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN);
+	pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX);
+	pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX);
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);
+
+/**
+ * phylink_ethtool_set_pauseparam() - set the current pause parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @pause: a pointer to a &struct ethtool_pauseparam
+ */
+int phylink_ethtool_set_pauseparam(struct phylink *pl,
+				   struct ethtool_pauseparam *pause)
+{
+	struct phylink_link_state *config = &pl->link_config;
+
+	ASSERT_RTNL();
+
+	if (!phylink_test(pl->supported, Pause) &&
+	    !phylink_test(pl->supported, Asym_Pause))
+		return -EOPNOTSUPP;
+
+	if (!phylink_test(pl->supported, Asym_Pause) &&
+	    !pause->autoneg && pause->rx_pause != pause->tx_pause)
+		return -EINVAL;
+
+	config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK);
+
+	if (pause->autoneg)
+		config->pause |= MLO_PAUSE_AN;
+	if (pause->rx_pause)
+		config->pause |= MLO_PAUSE_RX;
+	if (pause->tx_pause)
+		config->pause |= MLO_PAUSE_TX;
+
+	/* If we have a PHY, phylib will call our link state function if the
+	 * mode has changed, which will trigger a resolve and update the MAC
+	 * configuration.
+	 */
+	if (pl->phydev) {
+		phy_set_asym_pause(pl->phydev, pause->rx_pause,
+				   pause->tx_pause);
+	} else if (!test_bit(PHYLINK_DISABLE_STOPPED,
+			     &pl->phylink_disable_state)) {
+		switch (pl->cur_link_an_mode) {
+		case MLO_AN_FIXED:
+			/* Should we allow fixed links to change against the config? */
+			phylink_resolve_flow(pl, config);
+			phylink_mac_config(pl, config);
+			break;
+
+		case MLO_AN_INBAND:
+			phylink_mac_config(pl, config);
+			phylink_mac_an_restart(pl);
+			break;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
+
+/**
+ * phylink_ethtool_get_eee_err() - read the energy efficient ethernet error
+ *   counter
+ * @pl: a pointer to a &struct phylink returned from phylink_create().
+ *
+ * Read the Energy Efficient Ethernet error counter from the PHY associated
+ * with the phylink instance specified by @pl.
+ *
+ * Returns positive error counter value, or negative error code.
+ */
+int phylink_get_eee_err(struct phylink *pl)
+{
+	int ret = 0;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev)
+		ret = phy_get_eee_err(pl->phydev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_get_eee_err);
+
+/**
+ * phylink_ethtool_get_eee() - read the energy efficient ethernet parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @eee: a pointer to a &struct ethtool_eee for the read parameters
+ */
+int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee)
+{
+	int ret = -EOPNOTSUPP;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev)
+		ret = phy_ethtool_get_eee(pl->phydev, eee);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
+
+/**
+ * phylink_ethtool_set_eee() - set the energy efficient ethernet parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @eee: a pointer to a &struct ethtool_eee for the desired parameters
+ */
+int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee)
+{
+	int ret = -EOPNOTSUPP;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev)
+		ret = phy_ethtool_set_eee(pl->phydev, eee);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee);
+
+/* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+ * FIXME: should deal with negotiation state too.
+ */
+static int phylink_mii_emul_read(unsigned int reg,
+				 struct phylink_link_state *state)
+{
+	struct fixed_phy_status fs;
+	int val;
+
+	fs.link = state->link;
+	fs.speed = state->speed;
+	fs.duplex = state->duplex;
+	fs.pause = state->pause & MLO_PAUSE_SYM;
+	fs.asym_pause = state->pause & MLO_PAUSE_ASYM;
+
+	val = swphy_read_reg(reg, &fs);
+	if (reg == MII_BMSR) {
+		if (!state->an_complete)
+			val &= ~BMSR_ANEGCOMPLETE;
+	}
+	return val;
+}
+
+static int phylink_phy_read(struct phylink *pl, unsigned int phy_id,
+			    unsigned int reg)
+{
+	struct phy_device *phydev = pl->phydev;
+	int prtad, devad;
+
+	if (mdio_phy_id_is_c45(phy_id)) {
+		prtad = mdio_phy_id_prtad(phy_id);
+		devad = mdio_phy_id_devad(phy_id);
+		devad = MII_ADDR_C45 | devad << 16 | reg;
+	} else if (phydev->is_c45) {
+		switch (reg) {
+		case MII_BMCR:
+		case MII_BMSR:
+		case MII_PHYSID1:
+		case MII_PHYSID2:
+			devad = __ffs(phydev->c45_ids.devices_in_package);
+			break;
+		case MII_ADVERTISE:
+		case MII_LPA:
+			if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN))
+				return -EINVAL;
+			devad = MDIO_MMD_AN;
+			if (reg == MII_ADVERTISE)
+				reg = MDIO_AN_ADVERTISE;
+			else
+				reg = MDIO_AN_LPA;
+			break;
+		default:
+			return -EINVAL;
+		}
+		prtad = phy_id;
+		devad = MII_ADDR_C45 | devad << 16 | reg;
+	} else {
+		prtad = phy_id;
+		devad = reg;
+	}
+	return mdiobus_read(pl->phydev->mdio.bus, prtad, devad);
+}
+
+static int phylink_phy_write(struct phylink *pl, unsigned int phy_id,
+			     unsigned int reg, unsigned int val)
+{
+	struct phy_device *phydev = pl->phydev;
+	int prtad, devad;
+
+	if (mdio_phy_id_is_c45(phy_id)) {
+		prtad = mdio_phy_id_prtad(phy_id);
+		devad = mdio_phy_id_devad(phy_id);
+		devad = MII_ADDR_C45 | devad << 16 | reg;
+	} else if (phydev->is_c45) {
+		switch (reg) {
+		case MII_BMCR:
+		case MII_BMSR:
+		case MII_PHYSID1:
+		case MII_PHYSID2:
+			devad = __ffs(phydev->c45_ids.devices_in_package);
+			break;
+		case MII_ADVERTISE:
+		case MII_LPA:
+			if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN))
+				return -EINVAL;
+			devad = MDIO_MMD_AN;
+			if (reg == MII_ADVERTISE)
+				reg = MDIO_AN_ADVERTISE;
+			else
+				reg = MDIO_AN_LPA;
+			break;
+		default:
+			return -EINVAL;
+		}
+		prtad = phy_id;
+		devad = MII_ADDR_C45 | devad << 16 | reg;
+	} else {
+		prtad = phy_id;
+		devad = reg;
+	}
+
+	return mdiobus_write(phydev->mdio.bus, prtad, devad, val);
+}
+
+static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
+			    unsigned int reg)
+{
+	struct phylink_link_state state;
+	int val = 0xffff;
+
+	switch (pl->cur_link_an_mode) {
+	case MLO_AN_FIXED:
+		if (phy_id == 0) {
+			phylink_get_fixed_state(pl, &state);
+			val = phylink_mii_emul_read(reg, &state);
+		}
+		break;
+
+	case MLO_AN_PHY:
+		return -EOPNOTSUPP;
+
+	case MLO_AN_INBAND:
+		if (phy_id == 0) {
+			val = phylink_get_mac_state(pl, &state);
+			if (val < 0)
+				return val;
+
+			val = phylink_mii_emul_read(reg, &state);
+		}
+		break;
+	}
+
+	return val & 0xffff;
+}
+
+static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
+			     unsigned int reg, unsigned int val)
+{
+	switch (pl->cur_link_an_mode) {
+	case MLO_AN_FIXED:
+		break;
+
+	case MLO_AN_PHY:
+		return -EOPNOTSUPP;
+
+	case MLO_AN_INBAND:
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * phylink_mii_ioctl() - generic mii ioctl interface
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @ifr: a pointer to a &struct ifreq for socket ioctls
+ * @cmd: ioctl cmd to execute
+ *
+ * Perform the specified MII ioctl on the PHY attached to the phylink instance
+ * specified by @pl. If no PHY is attached, emulate the presence of the PHY.
+ *
+ * Returns: zero on success or negative error code.
+ *
+ * %SIOCGMIIPHY:
+ *  read register from the current PHY.
+ * %SIOCGMIIREG:
+ *  read register from the specified PHY.
+ * %SIOCSMIIREG:
+ *  set a register on the specified PHY.
+ */
+int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
+{
+	struct mii_ioctl_data *mii = if_mii(ifr);
+	int  ret;
+
+	ASSERT_RTNL();
+
+	if (pl->phydev) {
+		/* PHYs only exist for MLO_AN_PHY and SGMII */
+		switch (cmd) {
+		case SIOCGMIIPHY:
+			mii->phy_id = pl->phydev->mdio.addr;
+			/* fall through */
+
+		case SIOCGMIIREG:
+			ret = phylink_phy_read(pl, mii->phy_id, mii->reg_num);
+			if (ret >= 0) {
+				mii->val_out = ret;
+				ret = 0;
+			}
+			break;
+
+		case SIOCSMIIREG:
+			ret = phylink_phy_write(pl, mii->phy_id, mii->reg_num,
+						mii->val_in);
+			break;
+
+		default:
+			ret = phy_mii_ioctl(pl->phydev, ifr, cmd);
+			break;
+		}
+	} else {
+		switch (cmd) {
+		case SIOCGMIIPHY:
+			mii->phy_id = 0;
+			/* fall through */
+
+		case SIOCGMIIREG:
+			ret = phylink_mii_read(pl, mii->phy_id, mii->reg_num);
+			if (ret >= 0) {
+				mii->val_out = ret;
+				ret = 0;
+			}
+			break;
+
+		case SIOCSMIIREG:
+			ret = phylink_mii_write(pl, mii->phy_id, mii->reg_num,
+						mii->val_in);
+			break;
+
+		default:
+			ret = -EOPNOTSUPP;
+			break;
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
+
+static void phylink_sfp_attach(void *upstream, struct sfp_bus *bus)
+{
+	struct phylink *pl = upstream;
+
+	pl->netdev->sfp_bus = bus;
+}
+
+static void phylink_sfp_detach(void *upstream, struct sfp_bus *bus)
+{
+	struct phylink *pl = upstream;
+
+	pl->netdev->sfp_bus = NULL;
+}
+
+static int phylink_sfp_config(struct phylink *pl, u8 mode,
+			      const unsigned long *supported,
+			      const unsigned long *advertising)
+{
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(support1);
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+	struct phylink_link_state config;
+	phy_interface_t iface;
+	bool changed;
+	int ret;
+
+	linkmode_copy(support, supported);
+
+	memset(&config, 0, sizeof(config));
+	linkmode_copy(config.advertising, advertising);
+	config.interface = PHY_INTERFACE_MODE_NA;
+	config.speed = SPEED_UNKNOWN;
+	config.duplex = DUPLEX_UNKNOWN;
+	config.pause = MLO_PAUSE_AN;
+	config.an_enabled = pl->link_config.an_enabled;
+
+	/* Ignore errors if we're expecting a PHY to attach later */
+	ret = phylink_validate(pl, support, &config);
+	if (ret) {
+		netdev_err(pl->netdev, "validation with support %*pb failed: %d\n",
+			   __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret);
+		return ret;
+	}
+
+	iface = sfp_select_interface(pl->sfp_bus, config.advertising);
+	if (iface == PHY_INTERFACE_MODE_NA) {
+		netdev_err(pl->netdev,
+			   "selection of interface failed, advertisement %*pb\n",
+			   __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising);
+		return -EINVAL;
+	}
+
+	config.interface = iface;
+	linkmode_copy(support1, support);
+	ret = phylink_validate(pl, support1, &config);
+	if (ret) {
+		netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n",
+			   phylink_an_mode_str(mode),
+			   phy_modes(config.interface),
+			   __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret);
+		return ret;
+	}
+
+	netdev_dbg(pl->netdev, "requesting link mode %s/%s with support %*pb\n",
+		   phylink_an_mode_str(mode), phy_modes(config.interface),
+		   __ETHTOOL_LINK_MODE_MASK_NBITS, support);
+
+	if (phy_interface_mode_is_8023z(iface) && pl->phydev)
+		return -EINVAL;
+
+	changed = !bitmap_equal(pl->supported, support,
+				__ETHTOOL_LINK_MODE_MASK_NBITS);
+	if (changed) {
+		linkmode_copy(pl->supported, support);
+		linkmode_copy(pl->link_config.advertising, config.advertising);
+	}
+
+	if (pl->cur_link_an_mode != mode ||
+	    pl->link_config.interface != config.interface) {
+		pl->link_config.interface = config.interface;
+		pl->cur_link_an_mode = mode;
+
+		changed = true;
+
+		netdev_info(pl->netdev, "switched to %s/%s link mode\n",
+			    phylink_an_mode_str(mode),
+			    phy_modes(config.interface));
+	}
+
+	pl->link_port = pl->sfp_port;
+
+	if (changed && !test_bit(PHYLINK_DISABLE_STOPPED,
+				 &pl->phylink_disable_state))
+		phylink_mac_config(pl, &pl->link_config);
+
+	return ret;
+}
+
+static int phylink_sfp_module_insert(void *upstream,
+				     const struct sfp_eeprom_id *id)
+{
+	struct phylink *pl = upstream;
+	unsigned long *support = pl->sfp_support;
+
+	ASSERT_RTNL();
+
+	linkmode_zero(support);
+	sfp_parse_support(pl->sfp_bus, id, support);
+	pl->sfp_port = sfp_parse_port(pl->sfp_bus, id, support);
+
+	/* If this module may have a PHY connecting later, defer until later */
+	pl->sfp_may_have_phy = sfp_may_have_phy(pl->sfp_bus, id);
+	if (pl->sfp_may_have_phy)
+		return 0;
+
+	return phylink_sfp_config(pl, MLO_AN_INBAND, support, support);
+}
+
+static int phylink_sfp_module_start(void *upstream)
+{
+	struct phylink *pl = upstream;
+
+	/* If this SFP module has a PHY, start the PHY now. */
+	if (pl->phydev) {
+		phy_start(pl->phydev);
+		return 0;
+	}
+
+	/* If the module may have a PHY but we didn't detect one we
+	 * need to configure the MAC here.
+	 */
+	if (!pl->sfp_may_have_phy)
+		return 0;
+
+	return phylink_sfp_config(pl, MLO_AN_INBAND,
+				  pl->sfp_support, pl->sfp_support);
+}
+
+static void phylink_sfp_module_stop(void *upstream)
+{
+	struct phylink *pl = upstream;
+
+	/* If this SFP module has a PHY, stop it. */
+	if (pl->phydev)
+		phy_stop(pl->phydev);
+}
+
+static void phylink_sfp_link_down(void *upstream)
+{
+	struct phylink *pl = upstream;
+
+	ASSERT_RTNL();
+
+	phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_LINK);
+}
+
+static void phylink_sfp_link_up(void *upstream)
+{
+	struct phylink *pl = upstream;
+
+	ASSERT_RTNL();
+
+	clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+	phylink_run_resolve(pl);
+}
+
+/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
+ * or 802.3z control word, so inband will not work.
+ */
+static bool phylink_phy_no_inband(struct phy_device *phy)
+{
+	return phy->is_c45 &&
+		(phy->c45_ids.device_ids[1] & 0xfffffff0) == 0xae025150;
+}
+
+static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+{
+	struct phylink *pl = upstream;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
+	phy_interface_t interface;
+	u8 mode;
+	int ret;
+
+	/*
+	 * This is the new way of dealing with flow control for PHYs,
+	 * as described by Timur Tabi in commit 529ed1275263 ("net: phy:
+	 * phy drivers should not set SUPPORTED_[Asym_]Pause") except
+	 * using our validate call to the MAC, we rely upon the MAC
+	 * clearing the bits from both supported and advertising fields.
+	 */
+	phy_support_asym_pause(phy);
+
+	ethtool_convert_legacy_u32_to_link_mode(supported, phy->supported);
+	ethtool_convert_legacy_u32_to_link_mode(advertising, phy->advertising);
+
+	if (phylink_phy_no_inband(phy))
+		mode = MLO_AN_PHY;
+	else
+		mode = MLO_AN_INBAND;
+
+	/* Do the initial configuration */
+	ret = phylink_sfp_config(pl, mode, supported, advertising);
+	if (ret < 0)
+		return ret;
+
+	interface = pl->link_config.interface;
+	ret = phylink_attach_phy(pl, phy, interface);
+	if (ret < 0)
+		return ret;
+
+	/* Clause 45 PHYs switch their Serdes lane between several different
+	 * modes, normally 10GBASE-R, SGMII. Some use 2500BASE-X for 2.5G
+	 * speeds.  We really need to know which interface modes the PHY and
+	 * MAC supports to properly work out which linkmodes can be supported.
+	 */
+	if (phy->is_c45)
+		interface = PHY_INTERFACE_MODE_NA;
+
+	ret = phylink_bringup_phy(pl, phy, interface);
+	if (ret)
+		phy_detach(phy);
+
+	return ret;
+}
+
+static void phylink_sfp_disconnect_phy(void *upstream)
+{
+	phylink_disconnect_phy(upstream);
+}
+
+static const struct sfp_upstream_ops sfp_phylink_ops = {
+	.attach = phylink_sfp_attach,
+	.detach = phylink_sfp_detach,
+	.module_insert = phylink_sfp_module_insert,
+	.module_start = phylink_sfp_module_start,
+	.module_stop = phylink_sfp_module_stop,
+	.link_up = phylink_sfp_link_up,
+	.link_down = phylink_sfp_link_down,
+	.connect_phy = phylink_sfp_connect_phy,
+	.disconnect_phy = phylink_sfp_disconnect_phy,
+};
+
+/* Helpers for MAC drivers */
+
+/**
+ * phylink_helper_basex_speed() - 1000BaseX/2500BaseX helper
+ * @state: a pointer to a &struct phylink_link_state
+ *
+ * Inspect the interface mode, advertising mask or forced speed and
+ * decide whether to run at 2.5Gbit or 1Gbit appropriately, switching
+ * the interface mode to suit.  @state->interface is appropriately
+ * updated, and the advertising mask has the "other" baseX_Full flag
+ * cleared.
+ */
+void phylink_helper_basex_speed(struct phylink_link_state *state)
+{
+	if (phy_interface_mode_is_8023z(state->interface)) {
+		bool want_2500 = state->an_enabled ?
+			phylink_test(state->advertising, 2500baseX_Full) :
+			state->speed == SPEED_2500;
+
+		if (want_2500) {
+			phylink_clear(state->advertising, 1000baseX_Full);
+			state->interface = PHY_INTERFACE_MODE_2500BASEX;
+		} else {
+			phylink_clear(state->advertising, 2500baseX_Full);
+			state->interface = PHY_INTERFACE_MODE_1000BASEX;
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(phylink_helper_basex_speed);
+
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/psb6970.c b/src/kernel/linux/v4.19/drivers/net/phy/psb6970.c
new file mode 100644
index 0000000..c1a381c
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/psb6970.c
@@ -0,0 +1,441 @@
+/*
+ * 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>
+
+#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;
+	}
+
+	pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full;
+
+	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;
+	}
+
+	dev->phy_ptr = priv;
+
+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 --git a/src/kernel/linux/v4.19/drivers/net/phy/qsemi.c b/src/kernel/linux/v4.19/drivers/net/phy/qsemi.c
new file mode 100644
index 0000000..889a4dc
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/qsemi.c
@@ -0,0 +1,132 @@
+/*
+ * drivers/net/phy/qsemi.c
+ *
+ * Driver for Quality Semiconductor PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.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 <asm/io.h>
+#include <asm/irq.h>
+#include <linux/uaccess.h>
+
+/* ------------------------------------------------------------------------- */
+/* The Quality Semiconductor QS6612 is used on the RPX CLLF                  */
+
+/* register definitions */
+
+#define MII_QS6612_MCR		17  /* Mode Control Register      */
+#define MII_QS6612_FTR		27  /* Factory Test Register      */
+#define MII_QS6612_MCO		28  /* Misc. Control Register     */
+#define MII_QS6612_ISR		29  /* Interrupt Source Register  */
+#define MII_QS6612_IMR		30  /* Interrupt Mask Register    */
+#define MII_QS6612_IMR_INIT	0x003a
+#define MII_QS6612_PCR		31  /* 100BaseTx PHY Control Reg. */
+
+#define QS6612_PCR_AN_COMPLETE	0x1000
+#define QS6612_PCR_RLBEN	0x0200
+#define QS6612_PCR_DCREN	0x0100
+#define QS6612_PCR_4B5BEN	0x0040
+#define QS6612_PCR_TX_ISOLATE	0x0020
+#define QS6612_PCR_MLT3_DIS	0x0002
+#define QS6612_PCR_SCRM_DESCRM	0x0001
+
+MODULE_DESCRIPTION("Quality Semiconductor PHY driver");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+/* Returns 0, unless there's a write error */
+static int qs6612_config_init(struct phy_device *phydev)
+{
+	/* The PHY powers up isolated on the RPX,
+	 * so send a command to allow operation.
+	 * XXX - My docs indicate this should be 0x0940
+	 * ...or something.  The current value sets three
+	 * reserved bits, bit 11, which specifies it should be
+	 * set to one, bit 10, which specifies it should be set
+	 * to 0, and bit 7, which doesn't specify.  However, my
+	 * docs are preliminary, and I will leave it like this
+	 * until someone more knowledgable corrects me or it.
+	 * -- Andy Fleming
+	 */
+	return phy_write(phydev, MII_QS6612_PCR, 0x0dc0);
+}
+
+static int qs6612_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, MII_QS6612_ISR);
+
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_BMSR);
+
+	if (err < 0)
+		return err;
+
+	err = phy_read(phydev, MII_EXPANSION);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int qs6612_config_intr(struct phy_device *phydev)
+{
+	int err;
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, MII_QS6612_IMR,
+				MII_QS6612_IMR_INIT);
+	else
+		err = phy_write(phydev, MII_QS6612_IMR, 0);
+
+	return err;
+
+}
+
+static struct phy_driver qs6612_driver[] = { {
+	.phy_id		= 0x00181440,
+	.name		= "QS6612",
+	.phy_id_mask	= 0xfffffff0,
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= qs6612_config_init,
+	.ack_interrupt	= qs6612_ack_interrupt,
+	.config_intr	= qs6612_config_intr,
+} };
+
+module_phy_driver(qs6612_driver);
+
+static struct mdio_device_id __maybe_unused qs6612_tbl[] = {
+	{ 0x00181440, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, qs6612_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/realtek.c b/src/kernel/linux/v4.19/drivers/net/phy/realtek.c
new file mode 100644
index 0000000..2645b26
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/realtek.c
@@ -0,0 +1,323 @@
+/*
+ * drivers/net/phy/realtek.c
+ *
+ * Driver for Realtek PHYs
+ *
+ * Author: Johnson Leung <r58129@freescale.com>
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+#include <linux/bitops.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+
+#define RTL821x_PHYSR				0x11
+#define RTL821x_PHYSR_DUPLEX			BIT(13)
+#define RTL821x_PHYSR_SPEED			GENMASK(15, 14)
+
+#define RTL821x_INER				0x12
+#define RTL8211B_INER_INIT			0x6400
+#define RTL8211E_INER_LINK_STATUS		BIT(10)
+#define RTL8211F_INER_LINK_STATUS		BIT(4)
+
+#define RTL821x_INSR				0x13
+
+#define RTL821x_PAGE_SELECT			0x1f
+
+#define RTL8211F_INSR				0x1d
+
+#define RTL8211F_TX_DELAY			BIT(8)
+
+#define RTL8201F_ISR				0x1e
+#define RTL8201F_IER				0x13
+
+#define RTL8366RB_POWER_SAVE			0x15
+#define RTL8366RB_POWER_SAVE_ON			BIT(12)
+
+MODULE_DESCRIPTION("Realtek PHY driver");
+MODULE_AUTHOR("Johnson Leung");
+MODULE_LICENSE("GPL");
+
+static int rtl821x_read_page(struct phy_device *phydev)
+{
+	return __phy_read(phydev, RTL821x_PAGE_SELECT);
+}
+
+static int rtl821x_write_page(struct phy_device *phydev, int page)
+{
+	return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
+}
+
+static int rtl8201_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, RTL8201F_ISR);
+
+	return (err < 0) ? err : 0;
+}
+
+static int rtl821x_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read(phydev, RTL821x_INSR);
+
+	return (err < 0) ? err : 0;
+}
+
+static int rtl8211f_ack_interrupt(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
+
+	return (err < 0) ? err : 0;
+}
+
+static int rtl8201_config_intr(struct phy_device *phydev)
+{
+	u16 val;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		val = BIT(13) | BIT(12) | BIT(11);
+	else
+		val = 0;
+
+	return phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
+}
+
+static int rtl8211b_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, RTL821x_INER,
+				RTL8211B_INER_INIT);
+	else
+		err = phy_write(phydev, RTL821x_INER, 0);
+
+	return err;
+}
+
+static int rtl8211e_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, RTL821x_INER,
+				RTL8211E_INER_LINK_STATUS);
+	else
+		err = phy_write(phydev, RTL821x_INER, 0);
+
+	return err;
+}
+
+static int rtl8211f_config_intr(struct phy_device *phydev)
+{
+	u16 val;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		val = RTL8211F_INER_LINK_STATUS;
+	else
+		val = 0;
+
+	return phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
+}
+
+static int rtl8211_config_aneg(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_config_aneg(phydev);
+	if (ret < 0)
+		return ret;
+
+	/* Quirk was copied from vendor driver. Unfortunately it includes no
+	 * description of the magic numbers.
+	 */
+	if (phydev->speed == SPEED_100 && phydev->autoneg == AUTONEG_DISABLE) {
+		phy_write(phydev, 0x17, 0x2138);
+		phy_write(phydev, 0x0e, 0x0260);
+	} else {
+		phy_write(phydev, 0x17, 0x2108);
+		phy_write(phydev, 0x0e, 0x0000);
+	}
+
+	return 0;
+}
+
+static int rtl8211c_config_init(struct phy_device *phydev)
+{
+	/* RTL8211C has an issue when operating in Gigabit slave mode */
+	phy_set_bits(phydev, MII_CTRL1000,
+		     CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
+
+	return genphy_config_init(phydev);
+}
+
+static int rtl8211f_config_init(struct phy_device *phydev)
+{
+	int ret;
+	u16 val = 0;
+
+	ret = genphy_config_init(phydev);
+	if (ret < 0)
+		return ret;
+
+	/* enable TX-delay for rgmii-id and rgmii-txid, otherwise disable it */
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+		val = RTL8211F_TX_DELAY;
+	
+	phy_write_paged(phydev, 0x0A43, 0x1B, 0xdc84);/*modify by chencheng*/
+	phy_write_paged(phydev, 0x0A43, 0x1C,0x7180);/*modify by chencheng*/	
+
+	return phy_modify_paged(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, val);
+}
+
+static int rtl8211b_suspend(struct phy_device *phydev)
+{
+	phy_write(phydev, MII_MMD_DATA, BIT(9));
+
+	return genphy_suspend(phydev);
+}
+
+static int rtl8211b_resume(struct phy_device *phydev)
+{
+	phy_write(phydev, MII_MMD_DATA, 0);
+
+	return genphy_resume(phydev);
+}
+
+static int rtl8366rb_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_config_init(phydev);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_set_bits(phydev, RTL8366RB_POWER_SAVE,
+			   RTL8366RB_POWER_SAVE_ON);
+	if (ret) {
+		dev_err(&phydev->mdio.dev,
+			"error enabling power management\n");
+	}
+
+	return ret;
+}
+
+static struct phy_driver realtek_drvs[] = {
+	{
+		.phy_id         = 0x00008201,
+		.name           = "RTL8201CP Ethernet",
+		.phy_id_mask    = 0x0000ffff,
+		.features       = PHY_BASIC_FEATURES,
+		.flags          = PHY_HAS_INTERRUPT,
+	}, {
+		.phy_id		= 0x001cc816,
+		.name		= "RTL8201F Fast Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.ack_interrupt	= &rtl8201_ack_interrupt,
+		.config_intr	= &rtl8201_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+		.read_page	= rtl821x_read_page,
+		.write_page	= rtl821x_write_page,
+	}, {
+		.phy_id		= 0x001cc910,
+		.name		= "RTL8211 Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.config_aneg	= rtl8211_config_aneg,
+		.read_mmd	= &genphy_read_mmd_unsupported,
+		.write_mmd	= &genphy_write_mmd_unsupported,
+	}, {
+		.phy_id		= 0x001cc912,
+		.name		= "RTL8211B Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.ack_interrupt	= &rtl821x_ack_interrupt,
+		.config_intr	= &rtl8211b_config_intr,
+		.read_mmd	= &genphy_read_mmd_unsupported,
+		.write_mmd	= &genphy_write_mmd_unsupported,
+		.suspend	= rtl8211b_suspend,
+		.resume		= rtl8211b_resume,
+	}, {
+		.phy_id		= 0x001cc913,
+		.name		= "RTL8211C Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.config_init	= rtl8211c_config_init,
+		.read_mmd	= &genphy_read_mmd_unsupported,
+		.write_mmd	= &genphy_write_mmd_unsupported,
+	}, {
+		.phy_id		= 0x001cc914,
+		.name		= "RTL8211DN Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.ack_interrupt	= rtl821x_ack_interrupt,
+		.config_intr	= rtl8211e_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= 0x001cc915,
+		.name		= "RTL8211E Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.ack_interrupt	= &rtl821x_ack_interrupt,
+		.config_intr	= &rtl8211e_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	}, {
+		.phy_id		= 0x001cc916,
+		.name		= "RTL8211F Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= &rtl8211f_config_init,
+		.ack_interrupt	= &rtl8211f_ack_interrupt,
+		.config_intr	= &rtl8211f_config_intr,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+		.read_page	= rtl821x_read_page,
+		.write_page	= rtl821x_write_page,
+	}, {
+		.phy_id		= 0x001cc961,
+		.name		= "RTL8366RB Gigabit Ethernet",
+		.phy_id_mask	= 0x001fffff,
+		.features	= PHY_GBIT_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+		.config_init	= &rtl8366rb_config_init,
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	},
+};
+
+module_phy_driver(realtek_drvs);
+
+static struct mdio_device_id __maybe_unused realtek_tbl[] = {
+	{ 0x001cc816, 0x001fffff },
+	{ 0x001cc910, 0x001fffff },
+	{ 0x001cc912, 0x001fffff },
+	{ 0x001cc913, 0x001fffff },
+	{ 0x001cc914, 0x001fffff },
+	{ 0x001cc915, 0x001fffff },
+	{ 0x001cc916, 0x001fffff },
+	{ 0x001cc961, 0x001fffff },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, realtek_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/rockchip.c b/src/kernel/linux/v4.19/drivers/net/phy/rockchip.c
new file mode 100644
index 0000000..f1da70b
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rockchip.c
@@ -0,0 +1,232 @@
+/**
+ * drivers/net/phy/rockchip.c
+ *
+ * Driver for ROCKCHIP Ethernet PHYs
+ *
+ * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * David Wu <david.wu@rock-chips.com>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+
+#define INTERNAL_EPHY_ID			0x1234d400
+
+#define MII_INTERNAL_CTRL_STATUS		17
+#define SMI_ADDR_TSTCNTL			20
+#define SMI_ADDR_TSTREAD1			21
+#define SMI_ADDR_TSTREAD2			22
+#define SMI_ADDR_TSTWRITE			23
+#define MII_SPECIAL_CONTROL_STATUS		31
+
+#define MII_AUTO_MDIX_EN			BIT(7)
+#define MII_MDIX_EN				BIT(6)
+
+#define MII_SPEED_10				BIT(2)
+#define MII_SPEED_100				BIT(3)
+
+#define TSTCNTL_RD				(BIT(15) | BIT(10))
+#define TSTCNTL_WR				(BIT(14) | BIT(10))
+
+#define TSTMODE_ENABLE				0x400
+#define TSTMODE_DISABLE				0x0
+
+#define WR_ADDR_A7CFG				0x18
+
+static int rockchip_init_tstmode(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Enable access to Analog and DSP register banks */
+	ret = phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_DISABLE);
+	if (ret)
+		return ret;
+
+	return phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_ENABLE);
+}
+
+static int rockchip_close_tstmode(struct phy_device *phydev)
+{
+	/* Back to basic register bank */
+	return phy_write(phydev, SMI_ADDR_TSTCNTL, TSTMODE_DISABLE);
+}
+
+static int rockchip_integrated_phy_analog_init(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = rockchip_init_tstmode(phydev);
+	if (ret)
+		return ret;
+
+	/*
+	 * Adjust tx amplitude to make sginal better,
+	 * the default value is 0x8.
+	 */
+	ret = phy_write(phydev, SMI_ADDR_TSTWRITE, 0xB);
+	if (ret)
+		return ret;
+	ret = phy_write(phydev, SMI_ADDR_TSTCNTL, TSTCNTL_WR | WR_ADDR_A7CFG);
+	if (ret)
+		return ret;
+
+	return rockchip_close_tstmode(phydev);
+}
+
+static int rockchip_integrated_phy_config_init(struct phy_device *phydev)
+{
+	int val, ret;
+
+	/*
+	 * The auto MIDX has linked problem on some board,
+	 * workround to disable auto MDIX.
+	 */
+	val = phy_read(phydev, MII_INTERNAL_CTRL_STATUS);
+	if (val < 0)
+		return val;
+	val &= ~MII_AUTO_MDIX_EN;
+	ret = phy_write(phydev, MII_INTERNAL_CTRL_STATUS, val);
+	if (ret)
+		return ret;
+
+	return rockchip_integrated_phy_analog_init(phydev);
+}
+
+static void rockchip_link_change_notify(struct phy_device *phydev)
+{
+	int speed = SPEED_10;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		int reg = phy_read(phydev, MII_SPECIAL_CONTROL_STATUS);
+
+		if (reg < 0) {
+			phydev_err(phydev, "phy_read err: %d.\n", reg);
+			return;
+		}
+
+		if (reg & MII_SPEED_100)
+			speed = SPEED_100;
+		else if (reg & MII_SPEED_10)
+			speed = SPEED_10;
+	} else {
+		int bmcr = phy_read(phydev, MII_BMCR);
+
+		if (bmcr < 0) {
+			phydev_err(phydev, "phy_read err: %d.\n", bmcr);
+			return;
+		}
+
+		if (bmcr & BMCR_SPEED100)
+			speed = SPEED_100;
+		else
+			speed = SPEED_10;
+	}
+
+	/*
+	 * If mode switch happens from 10BT to 100BT, all DSP/AFE
+	 * registers are set to default values. So any AFE/DSP
+	 * registers have to be re-initialized in this case.
+	 */
+	if ((phydev->speed == SPEED_10) && (speed == SPEED_100)) {
+		int ret = rockchip_integrated_phy_analog_init(phydev);
+		if (ret)
+			phydev_err(phydev, "rockchip_integrated_phy_analog_init err: %d.\n",
+				   ret);
+	}
+}
+
+static int rockchip_set_polarity(struct phy_device *phydev, int polarity)
+{
+	int reg, err, val;
+
+	/* get the current settings */
+	reg = phy_read(phydev, MII_INTERNAL_CTRL_STATUS);
+	if (reg < 0)
+		return reg;
+
+	reg &= ~MII_AUTO_MDIX_EN;
+	val = reg;
+	switch (polarity) {
+	case ETH_TP_MDI:
+		val &= ~MII_MDIX_EN;
+		break;
+	case ETH_TP_MDI_X:
+		val |= MII_MDIX_EN;
+		break;
+	case ETH_TP_MDI_AUTO:
+	case ETH_TP_MDI_INVALID:
+	default:
+		return 0;
+	}
+
+	if (val != reg) {
+		/* Set the new polarity value in the register */
+		err = phy_write(phydev, MII_INTERNAL_CTRL_STATUS, val);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int rockchip_config_aneg(struct phy_device *phydev)
+{
+	int err;
+
+	err = rockchip_set_polarity(phydev, phydev->mdix);
+	if (err < 0)
+		return err;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int rockchip_phy_resume(struct phy_device *phydev)
+{
+	genphy_resume(phydev);
+
+	return rockchip_integrated_phy_config_init(phydev);
+}
+
+static struct phy_driver rockchip_phy_driver[] = {
+{
+	.phy_id			= INTERNAL_EPHY_ID,
+	.phy_id_mask		= 0xfffffff0,
+	.name			= "Rockchip integrated EPHY",
+	.features		= PHY_BASIC_FEATURES,
+	.flags			= 0,
+	.link_change_notify	= rockchip_link_change_notify,
+	.soft_reset		= genphy_soft_reset,
+	.config_init		= rockchip_integrated_phy_config_init,
+	.config_aneg		= rockchip_config_aneg,
+	.suspend		= genphy_suspend,
+	.resume			= rockchip_phy_resume,
+},
+};
+
+module_phy_driver(rockchip_phy_driver);
+
+static struct mdio_device_id __maybe_unused rockchip_phy_tbl[] = {
+	{ INTERNAL_EPHY_ID, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, rockchip_phy_tbl);
+
+MODULE_AUTHOR("David Wu <david.wu@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip Ethernet PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8306.c b/src/kernel/linux/v4.19/drivers/net/phy/rtl8306.c
new file mode 100644
index 0000000..6d09c10
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8306.c
@@ -0,0 +1,1066 @@
+/*
+ * 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",
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0))
+	.flags		= PHY_HAS_MAGICANEG,
+#endif
+	.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 --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8366_smi.c b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366_smi.c
new file mode 100644
index 0000000..2c4d53f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366_smi.c
@@ -0,0 +1,1632 @@
+/*
+ * 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 = MDC_REALTEK_PHY_ADDR;
+	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 = MDC_REALTEK_PHY_ADDR;
+	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);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
+	{
+		int i;
+		smi->mii_bus->irq = smi->mii_irq;
+		for (i = 0; i < PHY_MAX_ADDR; i++)
+			smi->mii_irq[i] = PHY_POLL;
+	}
+#endif
+
+#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;
+	}
+
+	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;
+
+	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 --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8366_smi.h b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366_smi.h
new file mode 100644
index 0000000..d1d988a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366_smi.h
@@ -0,0 +1,160 @@
+/*
+ * 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;
+};
+
+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 --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8366rb.c b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366rb.c
new file mode 100644
index 0000000..0e01160
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366rb.c
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8366s.c b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366s.c
new file mode 100644
index 0000000..8c74677
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8366s.c
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8367.c b/src/kernel/linux/v4.19/drivers/net/phy/rtl8367.c
new file mode 100644
index 0000000..7f0569d
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8367.c
@@ -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 --git a/src/kernel/linux/v4.19/drivers/net/phy/rtl8367b.c b/src/kernel/linux/v4.19/drivers/net/phy/rtl8367b.c
new file mode 100644
index 0000000..be9f74f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/rtl8367b.c
@@ -0,0 +1,1614 @@
+/*
+ * 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/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_CHIP_DEBUG0_DUMMY0(_x)	BIT(8 + (_x))
+
+#define RTL8367B_CHIP_DEBUG1_REG		0x1304
+
+#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_EXT_RGMXF_REG(_x)		(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)		(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_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)
+
+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 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_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_VER_REG, &chip_ver);
+
+	rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) &
+		RTL8367B_CHIP_VER_RLVID_MASK;
+
+	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:
+		dev_err(smi->parent, "unknow rlvid %u\n", rlvid);
+		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:
+	case RTL8367_EXTIF_MODE_RGMII_33V:
+		REG_WR(smi, RTL8367B_CHIP_DEBUG0_REG, 0x0367);
+		REG_WR(smi, RTL8367B_CHIP_DEBUG1_REG, 0x7777);
+		break;
+
+	case RTL8367_EXTIF_MODE_TMII_MAC:
+	case RTL8367_EXTIF_MODE_TMII_PHY:
+		REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG,
+			BIT((id + 1) % 2), BIT((id + 1) % 2));
+		break;
+
+	case RTL8367_EXTIF_MODE_GMII:
+		REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG,
+		        RTL8367B_CHIP_DEBUG0_DUMMY0(id),
+			RTL8367B_CHIP_DEBUG0_DUMMY0(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 + 1) % 2), 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;
+	}
+
+	REG_RMW(smi, RTL8367B_DIS_REG,
+		RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id),
+		mode << RTL8367B_DIS_RGMII_SHIFT(id));
+
+	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;
+	} 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);
+
+	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;
+	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;
+	}
+
+	switch (chip_ver) {
+	case 0x1000:
+		chip_name = "8367RB";
+		break;
+	case 0x1010:
+		chip_name = "8367R-VB";
+		break;
+	default:
+		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,
+};
+
+static int  rtl8367b_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 = &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);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id rtl8367b_match[] = {
+	{ .compatible = "realtek,rtl8367b" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtl8367b_match);
+#endif
+
+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 --git a/src/kernel/linux/v4.19/drivers/net/phy/sfp-bus.c b/src/kernel/linux/v4.19/drivers/net/phy/sfp-bus.c
new file mode 100644
index 0000000..75ef872
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/sfp-bus.c
@@ -0,0 +1,809 @@
+#include <linux/export.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/phylink.h>
+#include <linux/property.h>
+#include <linux/rtnetlink.h>
+#include <linux/slab.h>
+
+#include "sfp.h"
+
+struct sfp_quirk {
+	const char *vendor;
+	const char *part;
+	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
+};
+
+/**
+ * struct sfp_bus - internal representation of a sfp bus
+ */
+struct sfp_bus {
+	/* private: */
+	struct kref kref;
+	struct list_head node;
+	struct fwnode_handle *fwnode;
+
+	const struct sfp_socket_ops *socket_ops;
+	struct device *sfp_dev;
+	struct sfp *sfp;
+	const struct sfp_quirk *sfp_quirk;
+
+	const struct sfp_upstream_ops *upstream_ops;
+	void *upstream;
+	struct phy_device *phydev;
+
+	bool registered;
+	bool started;
+};
+
+static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+				unsigned long *modes)
+{
+	phylink_set(modes, 2500baseX_Full);
+}
+
+static const struct sfp_quirk sfp_quirks[] = {
+	{
+		// Alcatel Lucent G-010S-P can operate at 2500base-X, but
+		// incorrectly report 2500MBd NRZ in their EEPROM
+		.vendor = "ALCATELLUCENT",
+		.part = "G010SP",
+		.modes = sfp_quirk_2500basex,
+	}, {
+		// Alcatel Lucent G-010S-A can operate at 2500base-X, but
+		// report 3.2GBd NRZ in their EEPROM
+		.vendor = "ALCATELLUCENT",
+		.part = "3FE46541AA",
+		.modes = sfp_quirk_2500basex,
+	}, {
+		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
+		// NRZ in their EEPROM
+		.vendor = "HUAWEI",
+		.part = "MA5671A",
+		.modes = sfp_quirk_2500basex,
+	},
+};
+
+static size_t sfp_strlen(const char *str, size_t maxlen)
+{
+	size_t size, i;
+
+	/* Trailing characters should be filled with space chars */
+	for (i = 0, size = 0; i < maxlen; i++)
+		if (str[i] != ' ')
+			size = i + 1;
+
+	return size;
+}
+
+static bool sfp_match(const char *qs, const char *str, size_t len)
+{
+	if (!qs)
+		return true;
+	if (strlen(qs) != len)
+		return false;
+	return !strncmp(qs, str, len);
+}
+
+static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
+{
+	const struct sfp_quirk *q;
+	unsigned int i;
+	size_t vs, ps;
+
+	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
+	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
+
+	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
+		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
+		    sfp_match(q->part, id->base.vendor_pn, ps))
+			return q;
+
+	return NULL;
+}
+
+/**
+ * sfp_parse_port() - Parse the EEPROM base ID, setting the port type
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ * @support: optional pointer to an array of unsigned long for the
+ *   ethtool support mask
+ *
+ * Parse the EEPROM identification given in @id, and return one of
+ * %PORT_TP, %PORT_FIBRE or %PORT_OTHER. If @support is non-%NULL,
+ * also set the ethtool %ETHTOOL_LINK_MODE_xxx_BIT corresponding with
+ * the connector type.
+ *
+ * If the port type is not known, returns %PORT_OTHER.
+ */
+int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+		   unsigned long *support)
+{
+	int port;
+
+	/* port is the physical connector, set this from the connector field. */
+	switch (id->base.connector) {
+	case SFF8024_CONNECTOR_SC:
+	case SFF8024_CONNECTOR_FIBERJACK:
+	case SFF8024_CONNECTOR_LC:
+	case SFF8024_CONNECTOR_MT_RJ:
+	case SFF8024_CONNECTOR_MU:
+	case SFF8024_CONNECTOR_OPTICAL_PIGTAIL:
+	case SFF8024_CONNECTOR_MPO_1X12:
+	case SFF8024_CONNECTOR_MPO_2X16:
+		port = PORT_FIBRE;
+		break;
+
+	case SFF8024_CONNECTOR_RJ45:
+		port = PORT_TP;
+		break;
+
+	case SFF8024_CONNECTOR_COPPER_PIGTAIL:
+		port = PORT_DA;
+		break;
+
+	case SFF8024_CONNECTOR_UNSPEC:
+		if (id->base.e1000_base_t) {
+			port = PORT_TP;
+			break;
+		}
+		/* fallthrough */
+	case SFF8024_CONNECTOR_SG: /* guess */
+	case SFF8024_CONNECTOR_HSSDC_II:
+	case SFF8024_CONNECTOR_NOSEPARATE:
+	case SFF8024_CONNECTOR_MXC_2X16:
+		port = PORT_OTHER;
+		break;
+	default:
+		dev_warn(bus->sfp_dev, "SFP: unknown connector id 0x%02x\n",
+			 id->base.connector);
+		port = PORT_OTHER;
+		break;
+	}
+
+	if (support) {
+		switch (port) {
+		case PORT_FIBRE:
+			phylink_set(support, FIBRE);
+			break;
+
+		case PORT_TP:
+			phylink_set(support, TP);
+			break;
+		}
+	}
+
+	return port;
+}
+EXPORT_SYMBOL_GPL(sfp_parse_port);
+
+/**
+ * sfp_may_have_phy() - indicate whether the module may have a PHY
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ *
+ * Parse the EEPROM identification given in @id, and return whether
+ * this module may have a PHY.
+ */
+bool sfp_may_have_phy(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
+{
+	if (id->base.e1000_base_t)
+		return true;
+
+	if (id->base.phys_id != SFF8024_ID_DWDM_SFP) {
+		switch (id->base.extended_cc) {
+		case SFF8024_ECC_10GBASE_T_SFI:
+		case SFF8024_ECC_10GBASE_T_SR:
+		case SFF8024_ECC_5GBASE_T:
+		case SFF8024_ECC_2_5GBASE_T:
+			return true;
+		}
+	}
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(sfp_may_have_phy);
+
+/**
+ * sfp_parse_support() - Parse the eeprom id for supported link modes
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ * @support: pointer to an array of unsigned long for the ethtool support mask
+ *
+ * Parse the EEPROM identification information and derive the supported
+ * ethtool link modes for the module.
+ */
+void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+		       unsigned long *support)
+{
+	unsigned int br_min, br_nom, br_max;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { 0, };
+
+	/* Decode the bitrate information to MBd */
+	br_min = br_nom = br_max = 0;
+	if (id->base.br_nominal) {
+		if (id->base.br_nominal != 255) {
+			br_nom = id->base.br_nominal * 100;
+			br_min = br_nom - id->base.br_nominal * id->ext.br_min;
+			br_max = br_nom + id->base.br_nominal * id->ext.br_max;
+		} else if (id->ext.br_max) {
+			br_nom = 250 * id->ext.br_max;
+			br_max = br_nom + br_nom * id->ext.br_min / 100;
+			br_min = br_nom - br_nom * id->ext.br_min / 100;
+		}
+
+		/* When using passive cables, in case neither BR,min nor BR,max
+		 * are specified, set br_min to 0 as the nominal value is then
+		 * used as the maximum.
+		 */
+		if (br_min == br_max && id->base.sfp_ct_passive)
+			br_min = 0;
+	}
+
+	/* Set ethtool support from the compliance fields. */
+	if (id->base.e10g_base_sr)
+		phylink_set(modes, 10000baseSR_Full);
+	if (id->base.e10g_base_lr)
+		phylink_set(modes, 10000baseLR_Full);
+	if (id->base.e10g_base_lrm)
+		phylink_set(modes, 10000baseLRM_Full);
+	if (id->base.e10g_base_er)
+		phylink_set(modes, 10000baseER_Full);
+	if (id->base.e1000_base_sx ||
+	    id->base.e1000_base_lx ||
+	    id->base.e1000_base_cx)
+		phylink_set(modes, 1000baseX_Full);
+	if (id->base.e1000_base_t) {
+		phylink_set(modes, 1000baseT_Half);
+		phylink_set(modes, 1000baseT_Full);
+	}
+
+	/* 1000Base-PX or 1000Base-BX10 */
+	if ((id->base.e_base_px || id->base.e_base_bx10) &&
+	    br_min <= 1300 && br_max >= 1200)
+		phylink_set(modes, 1000baseX_Full);
+
+	/* For active or passive cables, select the link modes
+	 * based on the bit rates and the cable compliance bytes.
+	 */
+	if ((id->base.sfp_ct_passive || id->base.sfp_ct_active) && br_nom) {
+		/* This may look odd, but some manufacturers use 12000MBd */
+		if (br_min <= 12000 && br_max >= 10300)
+			phylink_set(modes, 10000baseCR_Full);
+		if (br_min <= 3200 && br_max >= 3100)
+			phylink_set(modes, 2500baseX_Full);
+		if (br_min <= 1300 && br_max >= 1200)
+			phylink_set(modes, 1000baseX_Full);
+	}
+	if (id->base.sfp_ct_passive) {
+		if (id->base.passive.sff8431_app_e)
+			phylink_set(modes, 10000baseCR_Full);
+	}
+	if (id->base.sfp_ct_active) {
+		if (id->base.active.sff8431_app_e ||
+		    id->base.active.sff8431_lim) {
+			phylink_set(modes, 10000baseCR_Full);
+		}
+	}
+
+	switch (id->base.extended_cc) {
+	case SFF8024_ECC_UNSPEC:
+		break;
+	case SFF8024_ECC_100GBASE_SR4_25GBASE_SR:
+		phylink_set(modes, 100000baseSR4_Full);
+		phylink_set(modes, 25000baseSR_Full);
+		break;
+	case SFF8024_ECC_100GBASE_LR4_25GBASE_LR:
+	case SFF8024_ECC_100GBASE_ER4_25GBASE_ER:
+		phylink_set(modes, 100000baseLR4_ER4_Full);
+		break;
+	case SFF8024_ECC_100GBASE_CR4:
+		phylink_set(modes, 100000baseCR4_Full);
+		/* fallthrough */
+	case SFF8024_ECC_25GBASE_CR_S:
+	case SFF8024_ECC_25GBASE_CR_N:
+		phylink_set(modes, 25000baseCR_Full);
+		break;
+	case SFF8024_ECC_10GBASE_T_SFI:
+	case SFF8024_ECC_10GBASE_T_SR:
+		phylink_set(modes, 10000baseT_Full);
+		break;
+	case SFF8024_ECC_5GBASE_T:
+		phylink_set(modes, 5000baseT_Full);
+		break;
+	case SFF8024_ECC_2_5GBASE_T:
+		phylink_set(modes, 2500baseT_Full);
+		break;
+	default:
+		dev_warn(bus->sfp_dev,
+			 "Unknown/unsupported extended compliance code: 0x%02x\n",
+			 id->base.extended_cc);
+		break;
+	}
+
+	/* For fibre channel SFP, derive possible BaseX modes */
+	if (id->base.fc_speed_100 ||
+	    id->base.fc_speed_200 ||
+	    id->base.fc_speed_400) {
+		if (id->base.br_nominal >= 31)
+			phylink_set(modes, 2500baseX_Full);
+		if (id->base.br_nominal >= 12)
+			phylink_set(modes, 1000baseX_Full);
+	}
+
+	/* If we haven't discovered any modes that this module supports, try
+	 * the encoding and bitrate to determine supported modes. Some BiDi
+	 * modules (eg, 1310nm/1550nm) are not 1000BASE-BX compliant due to
+	 * the differing wavelengths, so do not set any transceiver bits.
+	 */
+	if (bitmap_empty(modes, __ETHTOOL_LINK_MODE_MASK_NBITS)) {
+		/* If the encoding and bit rate allows 1000baseX */
+		if (id->base.encoding == SFF8024_ENCODING_8B10B && br_nom &&
+		    br_min <= 1300 && br_max >= 1200)
+			phylink_set(modes, 1000baseX_Full);
+	}
+
+	if (bus->sfp_quirk)
+		bus->sfp_quirk->modes(id, modes);
+
+	bitmap_or(support, support, modes, __ETHTOOL_LINK_MODE_MASK_NBITS);
+
+	phylink_set(support, Autoneg);
+	phylink_set(support, Pause);
+	phylink_set(support, Asym_Pause);
+}
+EXPORT_SYMBOL_GPL(sfp_parse_support);
+
+/**
+ * sfp_select_interface() - Select appropriate phy_interface_t mode
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @link_modes: ethtool link modes mask
+ *
+ * Derive the phy_interface_t mode for the SFP module from the link
+ * modes mask.
+ */
+phy_interface_t sfp_select_interface(struct sfp_bus *bus,
+				     unsigned long *link_modes)
+{
+	if (phylink_test(link_modes, 10000baseCR_Full) ||
+	    phylink_test(link_modes, 10000baseSR_Full) ||
+	    phylink_test(link_modes, 10000baseLR_Full) ||
+	    phylink_test(link_modes, 10000baseLRM_Full) ||
+	    phylink_test(link_modes, 10000baseER_Full) ||
+	    phylink_test(link_modes, 10000baseT_Full))
+		return PHY_INTERFACE_MODE_10GKR;
+
+	if (phylink_test(link_modes, 2500baseX_Full))
+		return PHY_INTERFACE_MODE_2500BASEX;
+
+	if (phylink_test(link_modes, 1000baseT_Half) ||
+	    phylink_test(link_modes, 1000baseT_Full))
+		return PHY_INTERFACE_MODE_SGMII;
+
+	if (phylink_test(link_modes, 1000baseX_Full))
+		return PHY_INTERFACE_MODE_1000BASEX;
+
+	dev_warn(bus->sfp_dev, "Unable to ascertain link mode\n");
+
+	return PHY_INTERFACE_MODE_NA;
+}
+EXPORT_SYMBOL_GPL(sfp_select_interface);
+
+static LIST_HEAD(sfp_buses);
+static DEFINE_MUTEX(sfp_mutex);
+
+static const struct sfp_upstream_ops *sfp_get_upstream_ops(struct sfp_bus *bus)
+{
+	return bus->registered ? bus->upstream_ops : NULL;
+}
+
+static struct sfp_bus *sfp_bus_get(struct fwnode_handle *fwnode)
+{
+	struct sfp_bus *sfp, *new, *found = NULL;
+
+	new = kzalloc(sizeof(*new), GFP_KERNEL);
+
+	mutex_lock(&sfp_mutex);
+
+	list_for_each_entry(sfp, &sfp_buses, node) {
+		if (sfp->fwnode == fwnode) {
+			kref_get(&sfp->kref);
+			found = sfp;
+			break;
+		}
+	}
+
+	if (!found && new) {
+		kref_init(&new->kref);
+		new->fwnode = fwnode;
+		list_add(&new->node, &sfp_buses);
+		found = new;
+		new = NULL;
+	}
+
+	mutex_unlock(&sfp_mutex);
+
+	kfree(new);
+
+	return found;
+}
+
+static void sfp_bus_release(struct kref *kref)
+{
+	struct sfp_bus *bus = container_of(kref, struct sfp_bus, kref);
+
+	list_del(&bus->node);
+	mutex_unlock(&sfp_mutex);
+	kfree(bus);
+}
+
+/**
+ * sfp_bus_put() - put a reference on the &struct sfp_bus
+ * @bus: the &struct sfp_bus found via sfp_bus_find_fwnode()
+ *
+ * Put a reference on the &struct sfp_bus and free the underlying structure
+ * if this was the last reference.
+ */
+void sfp_bus_put(struct sfp_bus *bus)
+{
+	if (bus)
+		kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex);
+}
+EXPORT_SYMBOL_GPL(sfp_bus_put);
+
+static int sfp_register_bus(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = bus->upstream_ops;
+	int ret;
+
+	if (ops) {
+		if (ops->link_down)
+			ops->link_down(bus->upstream);
+		if (ops->connect_phy && bus->phydev) {
+			ret = ops->connect_phy(bus->upstream, bus->phydev);
+			if (ret)
+				return ret;
+		}
+	}
+	bus->registered = true;
+	bus->socket_ops->attach(bus->sfp);
+	if (bus->started)
+		bus->socket_ops->start(bus->sfp);
+	bus->upstream_ops->attach(bus->upstream, bus);
+	return 0;
+}
+
+static void sfp_unregister_bus(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = bus->upstream_ops;
+
+	if (bus->registered) {
+		bus->upstream_ops->detach(bus->upstream, bus);
+		if (bus->started)
+			bus->socket_ops->stop(bus->sfp);
+		bus->socket_ops->detach(bus->sfp);
+		if (bus->phydev && ops && ops->disconnect_phy)
+			ops->disconnect_phy(bus->upstream);
+	}
+	bus->registered = false;
+}
+
+/**
+ * sfp_get_module_info() - Get the ethtool_modinfo for a SFP module
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @modinfo: a &struct ethtool_modinfo
+ *
+ * Fill in the type and eeprom_len parameters in @modinfo for a module on
+ * the sfp bus specified by @bus.
+ *
+ * Returns 0 on success or a negative errno number.
+ */
+int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo)
+{
+	return bus->socket_ops->module_info(bus->sfp, modinfo);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_info);
+
+/**
+ * sfp_get_module_eeprom() - Read the SFP module EEPROM
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @ee: a &struct ethtool_eeprom
+ * @data: buffer to contain the EEPROM data (must be at least @ee->len bytes)
+ *
+ * Read the EEPROM as specified by the supplied @ee. See the documentation
+ * for &struct ethtool_eeprom for the region to be read.
+ *
+ * Returns 0 on success or a negative errno number.
+ */
+int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
+			  u8 *data)
+{
+	return bus->socket_ops->module_eeprom(bus->sfp, ee, data);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_eeprom);
+
+/**
+ * sfp_upstream_start() - Inform the SFP that the network device is up
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Inform the SFP socket that the network device is now up, so that the
+ * module can be enabled by allowing TX_DISABLE to be deasserted. This
+ * should be called from the network device driver's &struct net_device_ops
+ * ndo_open() method.
+ */
+void sfp_upstream_start(struct sfp_bus *bus)
+{
+	if (bus->registered)
+		bus->socket_ops->start(bus->sfp);
+	bus->started = true;
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_start);
+
+/**
+ * sfp_upstream_stop() - Inform the SFP that the network device is down
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Inform the SFP socket that the network device is now up, so that the
+ * module can be disabled by asserting TX_DISABLE, disabling the laser
+ * in optical modules. This should be called from the network device
+ * driver's &struct net_device_ops ndo_stop() method.
+ */
+void sfp_upstream_stop(struct sfp_bus *bus)
+{
+	if (bus->registered)
+		bus->socket_ops->stop(bus->sfp);
+	bus->started = false;
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_stop);
+
+static void sfp_upstream_clear(struct sfp_bus *bus)
+{
+	bus->upstream_ops = NULL;
+	bus->upstream = NULL;
+}
+
+/**
+ * sfp_bus_find_fwnode() - parse and locate the SFP bus from fwnode
+ * @fwnode: firmware node for the parent device (MAC or PHY)
+ *
+ * Parse the parent device's firmware node for a SFP bus, and locate
+ * the sfp_bus structure, incrementing its reference count.  This must
+ * be put via sfp_bus_put() when done.
+ *
+ * Returns: on success, a pointer to the sfp_bus structure,
+ *	    %NULL if no SFP is specified,
+ * 	    on failure, an error pointer value:
+ * 		corresponding to the errors detailed for
+ * 		fwnode_property_get_reference_args().
+ * 	        %-ENOMEM if we failed to allocate the bus.
+ *		an error from the upstream's connect_phy() method.
+ */
+struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode)
+{
+	struct fwnode_reference_args ref;
+	struct sfp_bus *bus;
+	int ret;
+
+	ret = fwnode_property_get_reference_args(fwnode, "sfp", NULL,
+						 0, 0, &ref);
+	if (ret == -ENOENT)
+		return NULL;
+	else if (ret < 0)
+		return ERR_PTR(ret);
+
+	bus = sfp_bus_get(ref.fwnode);
+	fwnode_handle_put(ref.fwnode);
+	if (!bus)
+		return ERR_PTR(-ENOMEM);
+
+	return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_bus_find_fwnode);
+
+/**
+ * sfp_bus_add_upstream() - parse and register the neighbouring device
+ * @bus: the &struct sfp_bus found via sfp_bus_find_fwnode()
+ * @upstream: the upstream private data
+ * @ops: the upstream's &struct sfp_upstream_ops
+ *
+ * Add upstream driver for the SFP bus, and if the bus is complete, register
+ * the SFP bus using sfp_register_upstream().  This takes a reference on the
+ * bus, so it is safe to put the bus after this call.
+ *
+ * Returns: on success, a pointer to the sfp_bus structure,
+ *	    %NULL if no SFP is specified,
+ * 	    on failure, an error pointer value:
+ * 		corresponding to the errors detailed for
+ * 		fwnode_property_get_reference_args().
+ * 	        %-ENOMEM if we failed to allocate the bus.
+ *		an error from the upstream's connect_phy() method.
+ */
+int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
+			 const struct sfp_upstream_ops *ops)
+{
+	int ret;
+
+	/* If no bus, return success */
+	if (!bus)
+		return 0;
+
+	rtnl_lock();
+	kref_get(&bus->kref);
+	bus->upstream_ops = ops;
+	bus->upstream = upstream;
+
+	if (bus->sfp) {
+		ret = sfp_register_bus(bus);
+		if (ret)
+			sfp_upstream_clear(bus);
+	} else {
+		ret = 0;
+	}
+	rtnl_unlock();
+
+	if (ret)
+		sfp_bus_put(bus);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_bus_add_upstream);
+
+/**
+ * sfp_bus_del_upstream() - Delete a sfp bus
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Delete a previously registered upstream connection for the SFP
+ * module. @bus should have been added by sfp_bus_add_upstream().
+ */
+void sfp_bus_del_upstream(struct sfp_bus *bus)
+{
+	if (bus) {
+		rtnl_lock();
+		if (bus->sfp)
+			sfp_unregister_bus(bus);
+		sfp_upstream_clear(bus);
+		rtnl_unlock();
+
+		sfp_bus_put(bus);
+	}
+}
+EXPORT_SYMBOL_GPL(sfp_bus_del_upstream);
+
+/* Socket driver entry points */
+int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+	int ret = 0;
+
+	if (ops && ops->connect_phy)
+		ret = ops->connect_phy(bus->upstream, phydev);
+
+	if (ret == 0)
+		bus->phydev = phydev;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_add_phy);
+
+void sfp_remove_phy(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+	if (ops && ops->disconnect_phy)
+		ops->disconnect_phy(bus->upstream);
+	bus->phydev = NULL;
+}
+EXPORT_SYMBOL_GPL(sfp_remove_phy);
+
+void sfp_link_up(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+	if (ops && ops->link_up)
+		ops->link_up(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_link_up);
+
+void sfp_link_down(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+	if (ops && ops->link_down)
+		ops->link_down(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_link_down);
+
+int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+	int ret = 0;
+
+	bus->sfp_quirk = sfp_lookup_quirk(id);
+
+	if (ops && ops->module_insert)
+		ret = ops->module_insert(bus->upstream, id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_module_insert);
+
+void sfp_module_remove(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+	if (ops && ops->module_remove)
+		ops->module_remove(bus->upstream);
+
+	bus->sfp_quirk = NULL;
+}
+EXPORT_SYMBOL_GPL(sfp_module_remove);
+
+int sfp_module_start(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+	int ret = 0;
+
+	if (ops && ops->module_start)
+		ret = ops->module_start(bus->upstream);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_module_start);
+
+void sfp_module_stop(struct sfp_bus *bus)
+{
+	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+	if (ops && ops->module_stop)
+		ops->module_stop(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_module_stop);
+
+static void sfp_socket_clear(struct sfp_bus *bus)
+{
+	bus->sfp_dev = NULL;
+	bus->sfp = NULL;
+	bus->socket_ops = NULL;
+}
+
+struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
+				    const struct sfp_socket_ops *ops)
+{
+	struct sfp_bus *bus = sfp_bus_get(dev->fwnode);
+	int ret = 0;
+
+	if (bus) {
+		rtnl_lock();
+		bus->sfp_dev = dev;
+		bus->sfp = sfp;
+		bus->socket_ops = ops;
+
+		if (bus->upstream_ops) {
+			ret = sfp_register_bus(bus);
+			if (ret)
+				sfp_socket_clear(bus);
+		}
+		rtnl_unlock();
+	}
+
+	if (ret) {
+		sfp_bus_put(bus);
+		bus = NULL;
+	}
+
+	return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_register_socket);
+
+void sfp_unregister_socket(struct sfp_bus *bus)
+{
+	rtnl_lock();
+	if (bus->upstream_ops)
+		sfp_unregister_bus(bus);
+	sfp_socket_clear(bus);
+	rtnl_unlock();
+
+	sfp_bus_put(bus);
+}
+EXPORT_SYMBOL_GPL(sfp_unregister_socket);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/sfp.c b/src/kernel/linux/v4.19/drivers/net/phy/sfp.c
new file mode 100644
index 0000000..7d08b69
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/sfp.c
@@ -0,0 +1,2348 @@
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/rtnetlink.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "mdio-i2c.h"
+#include "sfp.h"
+#include "swphy.h"
+
+enum {
+	GPIO_MODDEF0,
+	GPIO_LOS,
+	GPIO_TX_FAULT,
+	GPIO_TX_DISABLE,
+	GPIO_RATE_SELECT,
+	GPIO_MAX,
+
+	SFP_F_PRESENT = BIT(GPIO_MODDEF0),
+	SFP_F_LOS = BIT(GPIO_LOS),
+	SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
+	SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
+	SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
+
+	SFP_E_INSERT = 0,
+	SFP_E_REMOVE,
+	SFP_E_DEV_ATTACH,
+	SFP_E_DEV_DETACH,
+	SFP_E_DEV_DOWN,
+	SFP_E_DEV_UP,
+	SFP_E_TX_FAULT,
+	SFP_E_TX_CLEAR,
+	SFP_E_LOS_HIGH,
+	SFP_E_LOS_LOW,
+	SFP_E_TIMEOUT,
+
+	SFP_MOD_EMPTY = 0,
+	SFP_MOD_ERROR,
+	SFP_MOD_PROBE,
+	SFP_MOD_WAITDEV,
+	SFP_MOD_HPOWER,
+	SFP_MOD_WAITPWR,
+	SFP_MOD_PRESENT,
+
+	SFP_DEV_DETACHED = 0,
+	SFP_DEV_DOWN,
+	SFP_DEV_UP,
+
+	SFP_S_DOWN = 0,
+	SFP_S_FAIL,
+	SFP_S_WAIT,
+	SFP_S_INIT,
+	SFP_S_INIT_PHY,
+	SFP_S_INIT_TX_FAULT,
+	SFP_S_WAIT_LOS,
+	SFP_S_LINK_UP,
+	SFP_S_TX_FAULT,
+	SFP_S_REINIT,
+	SFP_S_TX_DISABLE,
+};
+
+static const char  * const mod_state_strings[] = {
+	[SFP_MOD_EMPTY] = "empty",
+	[SFP_MOD_ERROR] = "error",
+	[SFP_MOD_PROBE] = "probe",
+	[SFP_MOD_WAITDEV] = "waitdev",
+	[SFP_MOD_HPOWER] = "hpower",
+	[SFP_MOD_WAITPWR] = "waitpwr",
+	[SFP_MOD_PRESENT] = "present",
+};
+
+static const char *mod_state_to_str(unsigned short mod_state)
+{
+	if (mod_state >= ARRAY_SIZE(mod_state_strings))
+		return "Unknown module state";
+	return mod_state_strings[mod_state];
+}
+
+static const char * const dev_state_strings[] = {
+	[SFP_DEV_DETACHED] = "detached",
+	[SFP_DEV_DOWN] = "down",
+	[SFP_DEV_UP] = "up",
+};
+
+static const char *dev_state_to_str(unsigned short dev_state)
+{
+	if (dev_state >= ARRAY_SIZE(dev_state_strings))
+		return "Unknown device state";
+	return dev_state_strings[dev_state];
+}
+
+static const char * const event_strings[] = {
+	[SFP_E_INSERT] = "insert",
+	[SFP_E_REMOVE] = "remove",
+	[SFP_E_DEV_ATTACH] = "dev_attach",
+	[SFP_E_DEV_DETACH] = "dev_detach",
+	[SFP_E_DEV_DOWN] = "dev_down",
+	[SFP_E_DEV_UP] = "dev_up",
+	[SFP_E_TX_FAULT] = "tx_fault",
+	[SFP_E_TX_CLEAR] = "tx_clear",
+	[SFP_E_LOS_HIGH] = "los_high",
+	[SFP_E_LOS_LOW] = "los_low",
+	[SFP_E_TIMEOUT] = "timeout",
+};
+
+static const char *event_to_str(unsigned short event)
+{
+	if (event >= ARRAY_SIZE(event_strings))
+		return "Unknown event";
+	return event_strings[event];
+}
+
+static const char * const sm_state_strings[] = {
+	[SFP_S_DOWN] = "down",
+	[SFP_S_FAIL] = "fail",
+	[SFP_S_WAIT] = "wait",
+	[SFP_S_INIT] = "init",
+	[SFP_S_INIT_PHY] = "init_phy",
+	[SFP_S_INIT_TX_FAULT] = "init_tx_fault",
+	[SFP_S_WAIT_LOS] = "wait_los",
+	[SFP_S_LINK_UP] = "link_up",
+	[SFP_S_TX_FAULT] = "tx_fault",
+	[SFP_S_REINIT] = "reinit",
+	[SFP_S_TX_DISABLE] = "rx_disable",
+};
+
+static const char *sm_state_to_str(unsigned short sm_state)
+{
+	if (sm_state >= ARRAY_SIZE(sm_state_strings))
+		return "Unknown state";
+	return sm_state_strings[sm_state];
+}
+
+static const char *gpio_of_names[] = {
+	"mod-def0",
+	"los",
+	"tx-fault",
+	"tx-disable",
+	"rate-select0",
+};
+
+static const enum gpiod_flags gpio_flags[] = {
+	GPIOD_IN,
+	GPIOD_IN,
+	GPIOD_IN,
+	GPIOD_ASIS,
+	GPIOD_ASIS,
+};
+
+/* t_start_up (SFF-8431) or t_init (SFF-8472) is the time required for a
+ * non-cooled module to initialise its laser safety circuitry. We wait
+ * an initial T_WAIT period before we check the tx fault to give any PHY
+ * on board (for a copper SFP) time to initialise.
+ */
+#define T_WAIT			msecs_to_jiffies(50)
+#define T_START_UP		msecs_to_jiffies(300)
+#define T_START_UP_BAD_GPON	msecs_to_jiffies(60000)
+
+/* t_reset is the time required to assert the TX_DISABLE signal to reset
+ * an indicated TX_FAULT.
+ */
+#define T_RESET_US		10
+#define T_FAULT_RECOVER		msecs_to_jiffies(1000)
+
+/* N_FAULT_INIT is the number of recovery attempts at module initialisation
+ * time. If the TX_FAULT signal is not deasserted after this number of
+ * attempts at clearing it, we decide that the module is faulty.
+ * N_FAULT is the same but after the module has initialised.
+ */
+#define N_FAULT_INIT		5
+#define N_FAULT			5
+
+/* T_PHY_RETRY is the time interval between attempts to probe the PHY.
+ * R_PHY_RETRY is the number of attempts.
+ */
+#define T_PHY_RETRY		msecs_to_jiffies(50)
+#define R_PHY_RETRY		12
+
+/* SFP module presence detection is poor: the three MOD DEF signals are
+ * the same length on the PCB, which means it's possible for MOD DEF 0 to
+ * connect before the I2C bus on MOD DEF 1/2.
+ *
+ * The SFF-8472 specifies t_serial ("Time from power on until module is
+ * ready for data transmission over the two wire serial bus.") as 300ms.
+ */
+#define T_SERIAL		msecs_to_jiffies(300)
+#define T_HPOWER_LEVEL		msecs_to_jiffies(300)
+#define T_PROBE_RETRY_INIT	msecs_to_jiffies(100)
+#define R_PROBE_RETRY_INIT	10
+#define T_PROBE_RETRY_SLOW	msecs_to_jiffies(5000)
+#define R_PROBE_RETRY_SLOW	12
+
+/* SFP modules appear to always have their PHY configured for bus address
+ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
+ */
+#define SFP_PHY_ADDR	22
+
+struct sff_data {
+	unsigned int gpios;
+	bool (*module_supported)(const struct sfp_eeprom_id *id);
+};
+
+struct sfp {
+	struct device *dev;
+	struct i2c_adapter *i2c;
+	struct mii_bus *i2c_mii;
+	struct sfp_bus *sfp_bus;
+	struct phy_device *mod_phy;
+	const struct sff_data *type;
+	u32 max_power_mW;
+
+	unsigned int (*get_state)(struct sfp *);
+	void (*set_state)(struct sfp *, unsigned int);
+	int (*read)(struct sfp *, bool, u8, void *, size_t);
+	int (*write)(struct sfp *, bool, u8, void *, size_t);
+
+	struct gpio_desc *gpio[GPIO_MAX];
+	int gpio_irq[GPIO_MAX];
+
+	bool need_poll;
+
+	struct mutex st_mutex;			/* Protects state */
+	unsigned int state_soft_mask;
+	unsigned int state;
+	struct delayed_work poll;
+	struct delayed_work timeout;
+	struct mutex sm_mutex;			/* Protects state machine */
+	unsigned char sm_mod_state;
+	unsigned char sm_mod_tries_init;
+	unsigned char sm_mod_tries;
+	unsigned char sm_dev_state;
+	unsigned short sm_state;
+	unsigned char sm_fault_retries;
+	unsigned char sm_phy_retries;
+
+	struct sfp_eeprom_id id;
+	unsigned int module_power_mW;
+	unsigned int module_t_start_up;
+
+#if IS_ENABLED(CONFIG_HWMON)
+	struct sfp_diag diag;
+	struct delayed_work hwmon_probe;
+	unsigned int hwmon_tries;
+	struct device *hwmon_dev;
+	char *hwmon_name;
+#endif
+
+};
+
+static bool sff_module_supported(const struct sfp_eeprom_id *id)
+{
+	return id->base.phys_id == SFF8024_ID_SFF_8472 &&
+	       id->base.phys_ext_id == SFP_PHYS_EXT_ID_SFP;
+}
+
+static const struct sff_data sff_data = {
+	.gpios = SFP_F_LOS | SFP_F_TX_FAULT | SFP_F_TX_DISABLE,
+	.module_supported = sff_module_supported,
+};
+
+static bool sfp_module_supported(const struct sfp_eeprom_id *id)
+{
+	return id->base.phys_id == SFF8024_ID_SFP &&
+	       id->base.phys_ext_id == SFP_PHYS_EXT_ID_SFP;
+}
+
+static const struct sff_data sfp_data = {
+	.gpios = SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT |
+		 SFP_F_TX_DISABLE | SFP_F_RATE_SELECT,
+	.module_supported = sfp_module_supported,
+};
+
+static const struct of_device_id sfp_of_match[] = {
+	{ .compatible = "sff,sff", .data = &sff_data, },
+	{ .compatible = "sff,sfp", .data = &sfp_data, },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sfp_of_match);
+
+static unsigned long poll_jiffies;
+
+static unsigned int sfp_gpio_get_state(struct sfp *sfp)
+{
+	unsigned int i, state, v;
+
+	for (i = state = 0; i < GPIO_MAX; i++) {
+		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+			continue;
+
+		v = gpiod_get_value_cansleep(sfp->gpio[i]);
+		if (v)
+			state |= BIT(i);
+	}
+
+	return state;
+}
+
+static unsigned int sff_gpio_get_state(struct sfp *sfp)
+{
+	return sfp_gpio_get_state(sfp) | SFP_F_PRESENT;
+}
+
+static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
+{
+	if (state & SFP_F_PRESENT) {
+		/* If the module is present, drive the signals */
+		if (sfp->gpio[GPIO_TX_DISABLE])
+			gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
+					       state & SFP_F_TX_DISABLE);
+		if (state & SFP_F_RATE_SELECT)
+			gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
+					       state & SFP_F_RATE_SELECT);
+	} else {
+		/* Otherwise, let them float to the pull-ups */
+		if (sfp->gpio[GPIO_TX_DISABLE])
+			gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
+		if (state & SFP_F_RATE_SELECT)
+			gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
+	}
+}
+
+static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
+			size_t len)
+{
+	struct i2c_msg msgs[2];
+	u8 bus_addr = a2 ? 0x51 : 0x50;
+	size_t this_len;
+	int ret;
+
+	msgs[0].addr = bus_addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 1;
+	msgs[0].buf = &dev_addr;
+	msgs[1].addr = bus_addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = buf;
+
+	while (len) {
+		this_len = len;
+		if (this_len > 16)
+			this_len = 16;
+
+		msgs[1].len = this_len;
+
+		ret = i2c_transfer(sfp->i2c, msgs, ARRAY_SIZE(msgs));
+		if (ret < 0)
+			return ret;
+
+		if (ret != ARRAY_SIZE(msgs))
+			break;
+
+		msgs[1].buf += this_len;
+		dev_addr += this_len;
+		len -= this_len;
+	}
+
+	return msgs[1].buf - (u8 *)buf;
+}
+
+static int sfp_i2c_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
+	size_t len)
+{
+	struct i2c_msg msgs[1];
+	u8 bus_addr = a2 ? 0x51 : 0x50;
+	int ret;
+
+	msgs[0].addr = bus_addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 1 + len;
+	msgs[0].buf = kmalloc(1 + len, GFP_KERNEL);
+	if (!msgs[0].buf)
+		return -ENOMEM;
+
+	msgs[0].buf[0] = dev_addr;
+	memcpy(&msgs[0].buf[1], buf, len);
+
+	ret = i2c_transfer(sfp->i2c, msgs, ARRAY_SIZE(msgs));
+
+	kfree(msgs[0].buf);
+
+	if (ret < 0)
+		return ret;
+
+	return ret == ARRAY_SIZE(msgs) ? len : 0;
+}
+
+static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+{
+	struct mii_bus *i2c_mii;
+	int ret;
+
+	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+		return -EINVAL;
+
+	sfp->i2c = i2c;
+	sfp->read = sfp_i2c_read;
+	sfp->write = sfp_i2c_write;
+
+	i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
+	if (IS_ERR(i2c_mii))
+		return PTR_ERR(i2c_mii);
+
+	i2c_mii->name = "SFP I2C Bus";
+	i2c_mii->phy_mask = ~0;
+
+	ret = mdiobus_register(i2c_mii);
+	if (ret < 0) {
+		mdiobus_free(i2c_mii);
+		return ret;
+	}
+
+	sfp->i2c_mii = i2c_mii;
+
+	return 0;
+}
+
+/* Interface */
+static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+{
+	return sfp->read(sfp, a2, addr, buf, len);
+}
+
+static int sfp_write(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+{
+	return sfp->write(sfp, a2, addr, buf, len);
+}
+
+static unsigned int sfp_soft_get_state(struct sfp *sfp)
+{
+	unsigned int state = 0;
+	u8 status;
+
+	if (sfp_read(sfp, true, SFP_STATUS, &status, sizeof(status)) ==
+		     sizeof(status)) {
+		if (status & SFP_STATUS_RX_LOS)
+			state |= SFP_F_LOS;
+		if (status & SFP_STATUS_TX_FAULT)
+			state |= SFP_F_TX_FAULT;
+	}
+
+	return state & sfp->state_soft_mask;
+}
+
+static void sfp_soft_set_state(struct sfp *sfp, unsigned int state)
+{
+	u8 status;
+
+	if (sfp_read(sfp, true, SFP_STATUS, &status, sizeof(status)) ==
+		     sizeof(status)) {
+		if (state & SFP_F_TX_DISABLE)
+			status |= SFP_STATUS_TX_DISABLE_FORCE;
+		else
+			status &= ~SFP_STATUS_TX_DISABLE_FORCE;
+
+		sfp_write(sfp, true, SFP_STATUS, &status, sizeof(status));
+	}
+}
+
+static void sfp_soft_start_poll(struct sfp *sfp)
+{
+	const struct sfp_eeprom_id *id = &sfp->id;
+
+	sfp->state_soft_mask = 0;
+	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE &&
+	    !sfp->gpio[GPIO_TX_DISABLE])
+		sfp->state_soft_mask |= SFP_F_TX_DISABLE;
+	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT &&
+	    !sfp->gpio[GPIO_TX_FAULT])
+		sfp->state_soft_mask |= SFP_F_TX_FAULT;
+	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS &&
+	    !sfp->gpio[GPIO_LOS])
+		sfp->state_soft_mask |= SFP_F_LOS;
+
+	if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) &&
+	    !sfp->need_poll)
+		mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+}
+
+static void sfp_soft_stop_poll(struct sfp *sfp)
+{
+	sfp->state_soft_mask = 0;
+}
+
+static unsigned int sfp_get_state(struct sfp *sfp)
+{
+	unsigned int state = sfp->get_state(sfp);
+
+	if (state & SFP_F_PRESENT &&
+	    sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT))
+		state |= sfp_soft_get_state(sfp);
+
+	return state;
+}
+
+static void sfp_set_state(struct sfp *sfp, unsigned int state)
+{
+	sfp->set_state(sfp, state);
+
+	if (state & SFP_F_PRESENT &&
+	    sfp->state_soft_mask & SFP_F_TX_DISABLE)
+		sfp_soft_set_state(sfp, state);
+}
+
+static unsigned int sfp_check(void *buf, size_t len)
+{
+	u8 *p, check;
+
+	for (p = buf, check = 0; len; p++, len--)
+		check += *p;
+
+	return check;
+}
+
+/* hwmon */
+#if IS_ENABLED(CONFIG_HWMON)
+static umode_t sfp_hwmon_is_visible(const void *data,
+				    enum hwmon_sensor_types type,
+				    u32 attr, int channel)
+{
+	const struct sfp *sfp = data;
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_min_alarm:
+		case hwmon_temp_max_alarm:
+		case hwmon_temp_lcrit_alarm:
+		case hwmon_temp_crit_alarm:
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+		case hwmon_temp_lcrit:
+		case hwmon_temp_crit:
+			if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_ALARMWARN))
+				return 0;
+			/* fall through */
+		case hwmon_temp_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_min_alarm:
+		case hwmon_in_max_alarm:
+		case hwmon_in_lcrit_alarm:
+		case hwmon_in_crit_alarm:
+		case hwmon_in_min:
+		case hwmon_in_max:
+		case hwmon_in_lcrit:
+		case hwmon_in_crit:
+			if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_ALARMWARN))
+				return 0;
+			/* fall through */
+		case hwmon_in_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_curr:
+		switch (attr) {
+		case hwmon_curr_min_alarm:
+		case hwmon_curr_max_alarm:
+		case hwmon_curr_lcrit_alarm:
+		case hwmon_curr_crit_alarm:
+		case hwmon_curr_min:
+		case hwmon_curr_max:
+		case hwmon_curr_lcrit:
+		case hwmon_curr_crit:
+			if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_ALARMWARN))
+				return 0;
+			/* fall through */
+		case hwmon_curr_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_power:
+		/* External calibration of receive power requires
+		 * floating point arithmetic. Doing that in the kernel
+		 * is not easy, so just skip it. If the module does
+		 * not require external calibration, we can however
+		 * show receiver power, since FP is then not needed.
+		 */
+		if (sfp->id.ext.diagmon & SFP_DIAGMON_EXT_CAL &&
+		    channel == 1)
+			return 0;
+		switch (attr) {
+		case hwmon_power_min_alarm:
+		case hwmon_power_max_alarm:
+		case hwmon_power_lcrit_alarm:
+		case hwmon_power_crit_alarm:
+		case hwmon_power_min:
+		case hwmon_power_max:
+		case hwmon_power_lcrit:
+		case hwmon_power_crit:
+			if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_ALARMWARN))
+				return 0;
+			/* fall through */
+		case hwmon_power_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+static int sfp_hwmon_read_sensor(struct sfp *sfp, int reg, long *value)
+{
+	__be16 val;
+	int err;
+
+	err = sfp_read(sfp, true, reg, &val, sizeof(val));
+	if (err < 0)
+		return err;
+
+	*value = be16_to_cpu(val);
+
+	return 0;
+}
+
+static void sfp_hwmon_to_rx_power(long *value)
+{
+	*value = DIV_ROUND_CLOSEST(*value, 10);
+}
+
+static void sfp_hwmon_calibrate(struct sfp *sfp, unsigned int slope, int offset,
+				long *value)
+{
+	if (sfp->id.ext.diagmon & SFP_DIAGMON_EXT_CAL)
+		*value = DIV_ROUND_CLOSEST(*value * slope, 256) + offset;
+}
+
+static void sfp_hwmon_calibrate_temp(struct sfp *sfp, long *value)
+{
+	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_t_slope),
+			    be16_to_cpu(sfp->diag.cal_t_offset), value);
+
+	if (*value >= 0x8000)
+		*value -= 0x10000;
+
+	*value = DIV_ROUND_CLOSEST(*value * 1000, 256);
+}
+
+static void sfp_hwmon_calibrate_vcc(struct sfp *sfp, long *value)
+{
+	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_v_slope),
+			    be16_to_cpu(sfp->diag.cal_v_offset), value);
+
+	*value = DIV_ROUND_CLOSEST(*value, 10);
+}
+
+static void sfp_hwmon_calibrate_bias(struct sfp *sfp, long *value)
+{
+	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_txi_slope),
+			    be16_to_cpu(sfp->diag.cal_txi_offset), value);
+
+	*value = DIV_ROUND_CLOSEST(*value, 500);
+}
+
+static void sfp_hwmon_calibrate_tx_power(struct sfp *sfp, long *value)
+{
+	sfp_hwmon_calibrate(sfp, be16_to_cpu(sfp->diag.cal_txpwr_slope),
+			    be16_to_cpu(sfp->diag.cal_txpwr_offset), value);
+
+	*value = DIV_ROUND_CLOSEST(*value, 10);
+}
+
+static int sfp_hwmon_read_temp(struct sfp *sfp, int reg, long *value)
+{
+	int err;
+
+	err = sfp_hwmon_read_sensor(sfp, reg, value);
+	if (err < 0)
+		return err;
+
+	sfp_hwmon_calibrate_temp(sfp, value);
+
+	return 0;
+}
+
+static int sfp_hwmon_read_vcc(struct sfp *sfp, int reg, long *value)
+{
+	int err;
+
+	err = sfp_hwmon_read_sensor(sfp, reg, value);
+	if (err < 0)
+		return err;
+
+	sfp_hwmon_calibrate_vcc(sfp, value);
+
+	return 0;
+}
+
+static int sfp_hwmon_read_bias(struct sfp *sfp, int reg, long *value)
+{
+	int err;
+
+	err = sfp_hwmon_read_sensor(sfp, reg, value);
+	if (err < 0)
+		return err;
+
+	sfp_hwmon_calibrate_bias(sfp, value);
+
+	return 0;
+}
+
+static int sfp_hwmon_read_tx_power(struct sfp *sfp, int reg, long *value)
+{
+	int err;
+
+	err = sfp_hwmon_read_sensor(sfp, reg, value);
+	if (err < 0)
+		return err;
+
+	sfp_hwmon_calibrate_tx_power(sfp, value);
+
+	return 0;
+}
+
+static int sfp_hwmon_read_rx_power(struct sfp *sfp, int reg, long *value)
+{
+	int err;
+
+	err = sfp_hwmon_read_sensor(sfp, reg, value);
+	if (err < 0)
+		return err;
+
+	sfp_hwmon_to_rx_power(value);
+
+	return 0;
+}
+
+static int sfp_hwmon_temp(struct sfp *sfp, u32 attr, long *value)
+{
+	u8 status;
+	int err;
+
+	switch (attr) {
+	case hwmon_temp_input:
+		return sfp_hwmon_read_temp(sfp, SFP_TEMP, value);
+
+	case hwmon_temp_lcrit:
+		*value = be16_to_cpu(sfp->diag.temp_low_alarm);
+		sfp_hwmon_calibrate_temp(sfp, value);
+		return 0;
+
+	case hwmon_temp_min:
+		*value = be16_to_cpu(sfp->diag.temp_low_warn);
+		sfp_hwmon_calibrate_temp(sfp, value);
+		return 0;
+	case hwmon_temp_max:
+		*value = be16_to_cpu(sfp->diag.temp_high_warn);
+		sfp_hwmon_calibrate_temp(sfp, value);
+		return 0;
+
+	case hwmon_temp_crit:
+		*value = be16_to_cpu(sfp->diag.temp_high_alarm);
+		sfp_hwmon_calibrate_temp(sfp, value);
+		return 0;
+
+	case hwmon_temp_lcrit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_TEMP_LOW);
+		return 0;
+
+	case hwmon_temp_min_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_TEMP_LOW);
+		return 0;
+
+	case hwmon_temp_max_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_TEMP_HIGH);
+		return 0;
+
+	case hwmon_temp_crit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_TEMP_HIGH);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int sfp_hwmon_vcc(struct sfp *sfp, u32 attr, long *value)
+{
+	u8 status;
+	int err;
+
+	switch (attr) {
+	case hwmon_in_input:
+		return sfp_hwmon_read_vcc(sfp, SFP_VCC, value);
+
+	case hwmon_in_lcrit:
+		*value = be16_to_cpu(sfp->diag.volt_low_alarm);
+		sfp_hwmon_calibrate_vcc(sfp, value);
+		return 0;
+
+	case hwmon_in_min:
+		*value = be16_to_cpu(sfp->diag.volt_low_warn);
+		sfp_hwmon_calibrate_vcc(sfp, value);
+		return 0;
+
+	case hwmon_in_max:
+		*value = be16_to_cpu(sfp->diag.volt_high_warn);
+		sfp_hwmon_calibrate_vcc(sfp, value);
+		return 0;
+
+	case hwmon_in_crit:
+		*value = be16_to_cpu(sfp->diag.volt_high_alarm);
+		sfp_hwmon_calibrate_vcc(sfp, value);
+		return 0;
+
+	case hwmon_in_lcrit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_VCC_LOW);
+		return 0;
+
+	case hwmon_in_min_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_VCC_LOW);
+		return 0;
+
+	case hwmon_in_max_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_VCC_HIGH);
+		return 0;
+
+	case hwmon_in_crit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_VCC_HIGH);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int sfp_hwmon_bias(struct sfp *sfp, u32 attr, long *value)
+{
+	u8 status;
+	int err;
+
+	switch (attr) {
+	case hwmon_curr_input:
+		return sfp_hwmon_read_bias(sfp, SFP_TX_BIAS, value);
+
+	case hwmon_curr_lcrit:
+		*value = be16_to_cpu(sfp->diag.bias_low_alarm);
+		sfp_hwmon_calibrate_bias(sfp, value);
+		return 0;
+
+	case hwmon_curr_min:
+		*value = be16_to_cpu(sfp->diag.bias_low_warn);
+		sfp_hwmon_calibrate_bias(sfp, value);
+		return 0;
+
+	case hwmon_curr_max:
+		*value = be16_to_cpu(sfp->diag.bias_high_warn);
+		sfp_hwmon_calibrate_bias(sfp, value);
+		return 0;
+
+	case hwmon_curr_crit:
+		*value = be16_to_cpu(sfp->diag.bias_high_alarm);
+		sfp_hwmon_calibrate_bias(sfp, value);
+		return 0;
+
+	case hwmon_curr_lcrit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_TX_BIAS_LOW);
+		return 0;
+
+	case hwmon_curr_min_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_TX_BIAS_LOW);
+		return 0;
+
+	case hwmon_curr_max_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_TX_BIAS_HIGH);
+		return 0;
+
+	case hwmon_curr_crit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_TX_BIAS_HIGH);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int sfp_hwmon_tx_power(struct sfp *sfp, u32 attr, long *value)
+{
+	u8 status;
+	int err;
+
+	switch (attr) {
+	case hwmon_power_input:
+		return sfp_hwmon_read_tx_power(sfp, SFP_TX_POWER, value);
+
+	case hwmon_power_lcrit:
+		*value = be16_to_cpu(sfp->diag.txpwr_low_alarm);
+		sfp_hwmon_calibrate_tx_power(sfp, value);
+		return 0;
+
+	case hwmon_power_min:
+		*value = be16_to_cpu(sfp->diag.txpwr_low_warn);
+		sfp_hwmon_calibrate_tx_power(sfp, value);
+		return 0;
+
+	case hwmon_power_max:
+		*value = be16_to_cpu(sfp->diag.txpwr_high_warn);
+		sfp_hwmon_calibrate_tx_power(sfp, value);
+		return 0;
+
+	case hwmon_power_crit:
+		*value = be16_to_cpu(sfp->diag.txpwr_high_alarm);
+		sfp_hwmon_calibrate_tx_power(sfp, value);
+		return 0;
+
+	case hwmon_power_lcrit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_TXPWR_LOW);
+		return 0;
+
+	case hwmon_power_min_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_TXPWR_LOW);
+		return 0;
+
+	case hwmon_power_max_alarm:
+		err = sfp_read(sfp, true, SFP_WARN0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN0_TXPWR_HIGH);
+		return 0;
+
+	case hwmon_power_crit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM0, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM0_TXPWR_HIGH);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int sfp_hwmon_rx_power(struct sfp *sfp, u32 attr, long *value)
+{
+	u8 status;
+	int err;
+
+	switch (attr) {
+	case hwmon_power_input:
+		return sfp_hwmon_read_rx_power(sfp, SFP_RX_POWER, value);
+
+	case hwmon_power_lcrit:
+		*value = be16_to_cpu(sfp->diag.rxpwr_low_alarm);
+		sfp_hwmon_to_rx_power(value);
+		return 0;
+
+	case hwmon_power_min:
+		*value = be16_to_cpu(sfp->diag.rxpwr_low_warn);
+		sfp_hwmon_to_rx_power(value);
+		return 0;
+
+	case hwmon_power_max:
+		*value = be16_to_cpu(sfp->diag.rxpwr_high_warn);
+		sfp_hwmon_to_rx_power(value);
+		return 0;
+
+	case hwmon_power_crit:
+		*value = be16_to_cpu(sfp->diag.rxpwr_high_alarm);
+		sfp_hwmon_to_rx_power(value);
+		return 0;
+
+	case hwmon_power_lcrit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM1, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM1_RXPWR_LOW);
+		return 0;
+
+	case hwmon_power_min_alarm:
+		err = sfp_read(sfp, true, SFP_WARN1, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN1_RXPWR_LOW);
+		return 0;
+
+	case hwmon_power_max_alarm:
+		err = sfp_read(sfp, true, SFP_WARN1, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_WARN1_RXPWR_HIGH);
+		return 0;
+
+	case hwmon_power_crit_alarm:
+		err = sfp_read(sfp, true, SFP_ALARM1, &status, sizeof(status));
+		if (err < 0)
+			return err;
+
+		*value = !!(status & SFP_ALARM1_RXPWR_HIGH);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int sfp_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+			  u32 attr, int channel, long *value)
+{
+	struct sfp *sfp = dev_get_drvdata(dev);
+
+	switch (type) {
+	case hwmon_temp:
+		return sfp_hwmon_temp(sfp, attr, value);
+	case hwmon_in:
+		return sfp_hwmon_vcc(sfp, attr, value);
+	case hwmon_curr:
+		return sfp_hwmon_bias(sfp, attr, value);
+	case hwmon_power:
+		switch (channel) {
+		case 0:
+			return sfp_hwmon_tx_power(sfp, attr, value);
+		case 1:
+			return sfp_hwmon_rx_power(sfp, attr, value);
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static const struct hwmon_ops sfp_hwmon_ops = {
+	.is_visible = sfp_hwmon_is_visible,
+	.read = sfp_hwmon_read,
+};
+
+static u32 sfp_hwmon_chip_config[] = {
+	HWMON_C_REGISTER_TZ,
+	0,
+};
+
+static const struct hwmon_channel_info sfp_hwmon_chip = {
+	.type = hwmon_chip,
+	.config = sfp_hwmon_chip_config,
+};
+
+static u32 sfp_hwmon_temp_config[] = {
+	HWMON_T_INPUT |
+	HWMON_T_MAX | HWMON_T_MIN |
+	HWMON_T_MAX_ALARM | HWMON_T_MIN_ALARM |
+	HWMON_T_CRIT | HWMON_T_LCRIT |
+	HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM,
+	0,
+};
+
+static const struct hwmon_channel_info sfp_hwmon_temp_channel_info = {
+	.type = hwmon_temp,
+	.config = sfp_hwmon_temp_config,
+};
+
+static u32 sfp_hwmon_vcc_config[] = {
+	HWMON_I_INPUT |
+	HWMON_I_MAX | HWMON_I_MIN |
+	HWMON_I_MAX_ALARM | HWMON_I_MIN_ALARM |
+	HWMON_I_CRIT | HWMON_I_LCRIT |
+	HWMON_I_CRIT_ALARM | HWMON_I_LCRIT_ALARM,
+	0,
+};
+
+static const struct hwmon_channel_info sfp_hwmon_vcc_channel_info = {
+	.type = hwmon_in,
+	.config = sfp_hwmon_vcc_config,
+};
+
+static u32 sfp_hwmon_bias_config[] = {
+	HWMON_C_INPUT |
+	HWMON_C_MAX | HWMON_C_MIN |
+	HWMON_C_MAX_ALARM | HWMON_C_MIN_ALARM |
+	HWMON_C_CRIT | HWMON_C_LCRIT |
+	HWMON_C_CRIT_ALARM | HWMON_C_LCRIT_ALARM,
+	0,
+};
+
+static const struct hwmon_channel_info sfp_hwmon_bias_channel_info = {
+	.type = hwmon_curr,
+	.config = sfp_hwmon_bias_config,
+};
+
+static u32 sfp_hwmon_power_config[] = {
+	/* Transmit power */
+	HWMON_P_INPUT |
+	HWMON_P_MAX | HWMON_P_MIN |
+	HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
+	HWMON_P_CRIT | HWMON_P_LCRIT |
+	HWMON_P_CRIT_ALARM | HWMON_P_LCRIT_ALARM,
+	/* Receive power */
+	HWMON_P_INPUT |
+	HWMON_P_MAX | HWMON_P_MIN |
+	HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
+	HWMON_P_CRIT | HWMON_P_LCRIT |
+	HWMON_P_CRIT_ALARM | HWMON_P_LCRIT_ALARM,
+	0,
+};
+
+static const struct hwmon_channel_info sfp_hwmon_power_channel_info = {
+	.type = hwmon_power,
+	.config = sfp_hwmon_power_config,
+};
+
+static const struct hwmon_channel_info *sfp_hwmon_info[] = {
+	&sfp_hwmon_chip,
+	&sfp_hwmon_vcc_channel_info,
+	&sfp_hwmon_temp_channel_info,
+	&sfp_hwmon_bias_channel_info,
+	&sfp_hwmon_power_channel_info,
+	NULL,
+};
+
+static const struct hwmon_chip_info sfp_hwmon_chip_info = {
+	.ops = &sfp_hwmon_ops,
+	.info = sfp_hwmon_info,
+};
+
+static void sfp_hwmon_probe(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, hwmon_probe.work);
+	int err, i;
+
+	err = sfp_read(sfp, true, 0, &sfp->diag, sizeof(sfp->diag));
+	if (err < 0) {
+		if (sfp->hwmon_tries--) {
+			mod_delayed_work(system_wq, &sfp->hwmon_probe,
+					 T_PROBE_RETRY_SLOW);
+		} else {
+			dev_warn(sfp->dev, "hwmon probe failed: %d\n", err);
+		}
+		return;
+	}
+
+	sfp->hwmon_name = kstrdup(dev_name(sfp->dev), GFP_KERNEL);
+	if (!sfp->hwmon_name) {
+		dev_err(sfp->dev, "out of memory for hwmon name\n");
+		return;
+	}
+
+	for (i = 0; sfp->hwmon_name[i]; i++)
+		if (hwmon_is_bad_char(sfp->hwmon_name[i]))
+			sfp->hwmon_name[i] = '_';
+
+	sfp->hwmon_dev = hwmon_device_register_with_info(sfp->dev,
+							 sfp->hwmon_name, sfp,
+							 &sfp_hwmon_chip_info,
+							 NULL);
+	if (IS_ERR(sfp->hwmon_dev))
+		dev_err(sfp->dev, "failed to register hwmon device: %ld\n",
+			PTR_ERR(sfp->hwmon_dev));
+}
+
+static int sfp_hwmon_insert(struct sfp *sfp)
+{
+	if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE)
+		return 0;
+
+	if (!(sfp->id.ext.diagmon & SFP_DIAGMON_DDM))
+		return 0;
+
+	if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE)
+		/* This driver in general does not support address
+		 * change.
+		 */
+		return 0;
+
+	mod_delayed_work(system_wq, &sfp->hwmon_probe, 1);
+	sfp->hwmon_tries = R_PROBE_RETRY_SLOW;
+
+	return 0;
+}
+
+static void sfp_hwmon_remove(struct sfp *sfp)
+{
+	cancel_delayed_work_sync(&sfp->hwmon_probe);
+	if (!IS_ERR_OR_NULL(sfp->hwmon_dev)) {
+		hwmon_device_unregister(sfp->hwmon_dev);
+		sfp->hwmon_dev = NULL;
+		kfree(sfp->hwmon_name);
+	}
+}
+
+static int sfp_hwmon_init(struct sfp *sfp)
+{
+	INIT_DELAYED_WORK(&sfp->hwmon_probe, sfp_hwmon_probe);
+
+	return 0;
+}
+
+static void sfp_hwmon_exit(struct sfp *sfp)
+{
+	cancel_delayed_work_sync(&sfp->hwmon_probe);
+}
+#else
+static int sfp_hwmon_insert(struct sfp *sfp)
+{
+	return 0;
+}
+
+static void sfp_hwmon_remove(struct sfp *sfp)
+{
+}
+
+static int sfp_hwmon_init(struct sfp *sfp)
+{
+	return 0;
+}
+
+static void sfp_hwmon_exit(struct sfp *sfp)
+{
+}
+#endif
+
+/* Helpers */
+static void sfp_module_tx_disable(struct sfp *sfp)
+{
+	dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+		sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
+	sfp->state |= SFP_F_TX_DISABLE;
+	sfp_set_state(sfp, sfp->state);
+}
+
+static void sfp_module_tx_enable(struct sfp *sfp)
+{
+	dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+		sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0);
+	sfp->state &= ~SFP_F_TX_DISABLE;
+	sfp_set_state(sfp, sfp->state);
+}
+
+static void sfp_module_tx_fault_reset(struct sfp *sfp)
+{
+	unsigned int state = sfp->state;
+
+	if (state & SFP_F_TX_DISABLE)
+		return;
+
+	sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
+
+	udelay(T_RESET_US);
+
+	sfp_set_state(sfp, state);
+}
+
+/* SFP state machine */
+static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
+{
+	if (timeout)
+		mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
+				 timeout);
+	else
+		cancel_delayed_work(&sfp->timeout);
+}
+
+static void sfp_sm_next(struct sfp *sfp, unsigned int state,
+			unsigned int timeout)
+{
+	sfp->sm_state = state;
+	sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_mod_next(struct sfp *sfp, unsigned int state,
+			    unsigned int timeout)
+{
+	sfp->sm_mod_state = state;
+	sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_phy_detach(struct sfp *sfp)
+{
+	sfp_remove_phy(sfp->sfp_bus);
+	phy_device_remove(sfp->mod_phy);
+	phy_device_free(sfp->mod_phy);
+	sfp->mod_phy = NULL;
+}
+
+static int sfp_sm_probe_phy(struct sfp *sfp, bool is_c45)
+{
+	struct phy_device *phy;
+	int err;
+
+	phy = get_phy_device(sfp->i2c_mii, SFP_PHY_ADDR, is_c45);
+	if (phy == ERR_PTR(-ENODEV))
+		return PTR_ERR(phy);
+	if (IS_ERR(phy)) {
+		dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
+		return PTR_ERR(phy);
+	}
+
+	err = phy_device_register(phy);
+	if (err) {
+		phy_device_free(phy);
+		dev_err(sfp->dev, "phy_device_register failed: %d\n", err);
+		return err;
+	}
+
+	err = sfp_add_phy(sfp->sfp_bus, phy);
+	if (err) {
+		phy_device_remove(phy);
+		phy_device_free(phy);
+		dev_err(sfp->dev, "sfp_add_phy failed: %d\n", err);
+		return err;
+	}
+
+	sfp->mod_phy = phy;
+
+	return 0;
+}
+
+static void sfp_sm_link_up(struct sfp *sfp)
+{
+	sfp_link_up(sfp->sfp_bus);
+	sfp_sm_next(sfp, SFP_S_LINK_UP, 0);
+}
+
+static void sfp_sm_link_down(struct sfp *sfp)
+{
+	sfp_link_down(sfp->sfp_bus);
+}
+
+static void sfp_sm_link_check_los(struct sfp *sfp)
+{
+	unsigned int los = sfp->state & SFP_F_LOS;
+
+	/* If neither SFP_OPTIONS_LOS_INVERTED nor SFP_OPTIONS_LOS_NORMAL
+	 * are set, we assume that no LOS signal is available.
+	 */
+	if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_LOS_INVERTED))
+		los ^= SFP_F_LOS;
+	else if (!(sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_LOS_NORMAL)))
+		los = 0;
+
+	if (los)
+		sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
+	else
+		sfp_sm_link_up(sfp);
+}
+
+static bool sfp_los_event_active(struct sfp *sfp, unsigned int event)
+{
+	return (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_LOS_INVERTED) &&
+		event == SFP_E_LOS_LOW) ||
+	       (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_LOS_NORMAL) &&
+		event == SFP_E_LOS_HIGH);
+}
+
+static bool sfp_los_event_inactive(struct sfp *sfp, unsigned int event)
+{
+	return (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_LOS_INVERTED) &&
+		event == SFP_E_LOS_HIGH) ||
+	       (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_LOS_NORMAL) &&
+		event == SFP_E_LOS_LOW);
+}
+
+static void sfp_sm_fault(struct sfp *sfp, unsigned int next_state, bool warn)
+{
+	if (sfp->sm_fault_retries && !--sfp->sm_fault_retries) {
+		dev_err(sfp->dev,
+			"module persistently indicates fault, disabling\n");
+		sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
+	} else {
+		if (warn)
+			dev_err(sfp->dev, "module transmit fault indicated\n");
+
+		sfp_sm_next(sfp, next_state, T_FAULT_RECOVER);
+	}
+}
+
+/* Probe a SFP for a PHY device if the module supports copper - the PHY
+ * normally sits at I2C bus address 0x56, and may either be a clause 22
+ * or clause 45 PHY.
+ *
+ * Clause 22 copper SFP modules normally operate in Cisco SGMII mode with
+ * negotiation enabled, but some may be in 1000base-X - which is for the
+ * PHY driver to determine.
+ *
+ * Clause 45 copper SFP+ modules (10G) appear to switch their interface
+ * mode according to the negotiated line speed.
+ */
+static int sfp_sm_probe_for_phy(struct sfp *sfp)
+{
+	int err = 0;
+
+	switch (sfp->id.base.extended_cc) {
+	case SFF8024_ECC_10GBASE_T_SFI:
+	case SFF8024_ECC_10GBASE_T_SR:
+	case SFF8024_ECC_5GBASE_T:
+	case SFF8024_ECC_2_5GBASE_T:
+		err = sfp_sm_probe_phy(sfp, true);
+		break;
+
+	default:
+		if (sfp->id.base.e1000_base_t)
+			err = sfp_sm_probe_phy(sfp, false);
+		break;
+	}
+	return err;
+}
+
+static int sfp_module_parse_power(struct sfp *sfp)
+{
+	u32 power_mW = 1000;
+
+	if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_POWER_DECL))
+		power_mW = 1500;
+	if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_HIGH_POWER_LEVEL))
+		power_mW = 2000;
+
+	if (power_mW > sfp->max_power_mW) {
+		/* Module power specification exceeds the allowed maximum. */
+		if (sfp->id.ext.sff8472_compliance ==
+			SFP_SFF8472_COMPLIANCE_NONE &&
+		    !(sfp->id.ext.diagmon & SFP_DIAGMON_DDM)) {
+			/* The module appears not to implement bus address
+			 * 0xa2, so assume that the module powers up in the
+			 * indicated mode.
+			 */
+			dev_err(sfp->dev,
+				"Host does not support %u.%uW modules\n",
+				power_mW / 1000, (power_mW / 100) % 10);
+			return -EINVAL;
+		} else {
+			dev_warn(sfp->dev,
+				 "Host does not support %u.%uW modules, module left in power mode 1\n",
+				 power_mW / 1000, (power_mW / 100) % 10);
+			return 0;
+		}
+	}
+
+	/* If the module requires a higher power mode, but also requires
+	 * an address change sequence, warn the user that the module may
+	 * not be functional.
+	 */
+	if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE && power_mW > 1000) {
+		dev_warn(sfp->dev,
+			 "Address Change Sequence not supported but module requies %u.%uW, module may not be functional\n",
+			 power_mW / 1000, (power_mW / 100) % 10);
+		return 0;
+	}
+
+	sfp->module_power_mW = power_mW;
+
+	return 0;
+}
+
+static int sfp_sm_mod_hpower(struct sfp *sfp, bool enable)
+{
+	u8 val;
+	int err;
+
+	err = sfp_read(sfp, true, SFP_EXT_STATUS, &val, sizeof(val));
+	if (err != sizeof(val)) {
+		dev_err(sfp->dev, "Failed to read EEPROM: %d\n", err);
+		return -EAGAIN;
+	}
+
+	/* DM7052 reports as a high power module, responds to reads (with
+	 * all bytes 0xff) at 0x51 but does not accept writes.  In any case,
+	 * if the bit is already set, we're already in high power mode.
+	 */
+	if (!!(val & BIT(0)) == enable)
+		return 0;
+
+	if (enable)
+		val |= BIT(0);
+	else
+		val &= ~BIT(0);
+
+	err = sfp_write(sfp, true, SFP_EXT_STATUS, &val, sizeof(val));
+	if (err != sizeof(val)) {
+		dev_err(sfp->dev, "Failed to write EEPROM: %d\n", err);
+		return -EAGAIN;
+	}
+
+	if (enable)
+		dev_info(sfp->dev, "Module switched to %u.%uW power level\n",
+			 sfp->module_power_mW / 1000,
+			 (sfp->module_power_mW / 100) % 10);
+
+	return 0;
+}
+
+static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+{
+	/* SFP module inserted - read I2C data */
+	struct sfp_eeprom_id id;
+	bool cotsworks;
+	u8 check;
+	int ret;
+
+	ret = sfp_read(sfp, false, 0, &id, sizeof(id));
+	if (ret < 0) {
+		if (report)
+			dev_err(sfp->dev, "failed to read EEPROM: %d\n", ret);
+		return -EAGAIN;
+	}
+
+	if (ret != sizeof(id)) {
+		dev_err(sfp->dev, "EEPROM short read: %d\n", ret);
+		return -EAGAIN;
+	}
+
+	/* Cotsworks do not seem to update the checksums when they
+	 * do the final programming with the final module part number,
+	 * serial number and date code.
+	 */
+	cotsworks = !memcmp(id.base.vendor_name, "COTSWORKS       ", 16);
+
+	/* Validate the checksum over the base structure */
+	check = sfp_check(&id.base, sizeof(id.base) - 1);
+	if (check != id.base.cc_base) {
+		if (cotsworks) {
+			dev_warn(sfp->dev,
+				 "EEPROM base structure checksum failure (0x%02x != 0x%02x)\n",
+				 check, id.base.cc_base);
+		} else {
+			dev_err(sfp->dev,
+				"EEPROM base structure checksum failure: 0x%02x != 0x%02x\n",
+				check, id.base.cc_base);
+			print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET,
+				       16, 1, &id, sizeof(id), true);
+			return -EINVAL;
+		}
+	}
+
+	check = sfp_check(&id.ext, sizeof(id.ext) - 1);
+	if (check != id.ext.cc_ext) {
+		if (cotsworks) {
+			dev_warn(sfp->dev,
+				 "EEPROM extended structure checksum failure (0x%02x != 0x%02x)\n",
+				 check, id.ext.cc_ext);
+		} else {
+			dev_err(sfp->dev,
+				"EEPROM extended structure checksum failure: 0x%02x != 0x%02x\n",
+				check, id.ext.cc_ext);
+			print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET,
+				       16, 1, &id, sizeof(id), true);
+			memset(&id.ext, 0, sizeof(id.ext));
+		}
+	}
+
+	sfp->id = id;
+
+	dev_info(sfp->dev, "module %.*s %.*s rev %.*s sn %.*s dc %.*s\n",
+		 (int)sizeof(id.base.vendor_name), id.base.vendor_name,
+		 (int)sizeof(id.base.vendor_pn), id.base.vendor_pn,
+		 (int)sizeof(id.base.vendor_rev), id.base.vendor_rev,
+		 (int)sizeof(id.ext.vendor_sn), id.ext.vendor_sn,
+		 (int)sizeof(id.ext.datecode), id.ext.datecode);
+
+	/* Check whether we support this module */
+	if (!sfp->type->module_supported(&id)) {
+		dev_err(sfp->dev,
+			"module is not supported - phys id 0x%02x 0x%02x\n",
+			sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
+		return -EINVAL;
+	}
+
+	/* If the module requires address swap mode, warn about it */
+	if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE)
+		dev_warn(sfp->dev,
+			 "module address swap to access page 0xA2 is not supported.\n");
+
+	/* Parse the module power requirement */
+	ret = sfp_module_parse_power(sfp);
+	if (ret < 0)
+		return ret;
+
+	if (!memcmp(id.base.vendor_name, "ALCATELLUCENT   ", 16) &&
+	    !memcmp(id.base.vendor_pn, "3FE46541AA      ", 16))
+		sfp->module_t_start_up = T_START_UP_BAD_GPON;
+	else
+		sfp->module_t_start_up = T_START_UP;
+
+	return 0;
+}
+
+static void sfp_sm_mod_remove(struct sfp *sfp)
+{
+	if (sfp->sm_mod_state > SFP_MOD_WAITDEV)
+		sfp_module_remove(sfp->sfp_bus);
+
+	sfp_hwmon_remove(sfp);
+
+	memset(&sfp->id, 0, sizeof(sfp->id));
+	sfp->module_power_mW = 0;
+
+	dev_info(sfp->dev, "module removed\n");
+}
+
+/* This state machine tracks the upstream's state */
+static void sfp_sm_device(struct sfp *sfp, unsigned int event)
+{
+	switch (sfp->sm_dev_state) {
+	default:
+		if (event == SFP_E_DEV_ATTACH)
+			sfp->sm_dev_state = SFP_DEV_DOWN;
+		break;
+
+	case SFP_DEV_DOWN:
+		if (event == SFP_E_DEV_DETACH)
+			sfp->sm_dev_state = SFP_DEV_DETACHED;
+		else if (event == SFP_E_DEV_UP)
+			sfp->sm_dev_state = SFP_DEV_UP;
+		break;
+
+	case SFP_DEV_UP:
+		if (event == SFP_E_DEV_DETACH)
+			sfp->sm_dev_state = SFP_DEV_DETACHED;
+		else if (event == SFP_E_DEV_DOWN)
+			sfp->sm_dev_state = SFP_DEV_DOWN;
+		break;
+	}
+}
+
+/* This state machine tracks the insert/remove state of the module, probes
+ * the on-board EEPROM, and sets up the power level.
+ */
+static void sfp_sm_module(struct sfp *sfp, unsigned int event)
+{
+	int err;
+
+	/* Handle remove event globally, it resets this state machine */
+	if (event == SFP_E_REMOVE) {
+		if (sfp->sm_mod_state > SFP_MOD_PROBE)
+			sfp_sm_mod_remove(sfp);
+		sfp_sm_mod_next(sfp, SFP_MOD_EMPTY, 0);
+		return;
+	}
+
+	/* Handle device detach globally */
+	if (sfp->sm_dev_state < SFP_DEV_DOWN &&
+	    sfp->sm_mod_state > SFP_MOD_WAITDEV) {
+		if (sfp->module_power_mW > 1000 &&
+		    sfp->sm_mod_state > SFP_MOD_HPOWER)
+			sfp_sm_mod_hpower(sfp, false);
+		sfp_sm_mod_next(sfp, SFP_MOD_WAITDEV, 0);
+		return;
+	}
+
+	switch (sfp->sm_mod_state) {
+	default:
+		if (event == SFP_E_INSERT) {
+			sfp_sm_mod_next(sfp, SFP_MOD_PROBE, T_SERIAL);
+			sfp->sm_mod_tries_init = R_PROBE_RETRY_INIT;
+			sfp->sm_mod_tries = R_PROBE_RETRY_SLOW;
+		}
+		break;
+
+	case SFP_MOD_PROBE:
+		/* Wait for T_PROBE_INIT to time out */
+		if (event != SFP_E_TIMEOUT)
+			break;
+
+		err = sfp_sm_mod_probe(sfp, sfp->sm_mod_tries == 1);
+		if (err == -EAGAIN) {
+			if (sfp->sm_mod_tries_init &&
+			   --sfp->sm_mod_tries_init) {
+				sfp_sm_set_timer(sfp, T_PROBE_RETRY_INIT);
+				break;
+			} else if (sfp->sm_mod_tries && --sfp->sm_mod_tries) {
+				if (sfp->sm_mod_tries == R_PROBE_RETRY_SLOW - 1)
+					dev_warn(sfp->dev,
+						 "please wait, module slow to respond\n");
+				sfp_sm_set_timer(sfp, T_PROBE_RETRY_SLOW);
+				break;
+			}
+		}
+		if (err < 0) {
+			sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+			break;
+		}
+
+		err = sfp_hwmon_insert(sfp);
+		if (err)
+			dev_warn(sfp->dev, "hwmon probe failed: %d\n", err);
+
+		sfp_sm_mod_next(sfp, SFP_MOD_WAITDEV, 0);
+		/* fall through */
+	case SFP_MOD_WAITDEV:
+		/* Ensure that the device is attached before proceeding */
+		if (sfp->sm_dev_state < SFP_DEV_DOWN)
+			break;
+
+		/* Report the module insertion to the upstream device */
+		err = sfp_module_insert(sfp->sfp_bus, &sfp->id);
+		if (err < 0) {
+			sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+			break;
+		}
+
+		/* If this is a power level 1 module, we are done */
+		if (sfp->module_power_mW <= 1000)
+			goto insert;
+
+		sfp_sm_mod_next(sfp, SFP_MOD_HPOWER, 0);
+		/* fall through */
+	case SFP_MOD_HPOWER:
+		/* Enable high power mode */
+		err = sfp_sm_mod_hpower(sfp, true);
+		if (err < 0) {
+			if (err != -EAGAIN) {
+				sfp_module_remove(sfp->sfp_bus);
+				sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+			} else {
+				sfp_sm_set_timer(sfp, T_PROBE_RETRY_INIT);
+			}
+			break;
+		}
+
+		sfp_sm_mod_next(sfp, SFP_MOD_WAITPWR, T_HPOWER_LEVEL);
+		break;
+
+	case SFP_MOD_WAITPWR:
+		/* Wait for T_HPOWER_LEVEL to time out */
+		if (event != SFP_E_TIMEOUT)
+			break;
+
+	insert:
+		sfp_sm_mod_next(sfp, SFP_MOD_PRESENT, 0);
+		break;
+
+	case SFP_MOD_PRESENT:
+	case SFP_MOD_ERROR:
+		break;
+	}
+}
+
+static void sfp_sm_main(struct sfp *sfp, unsigned int event)
+{
+	unsigned long timeout;
+	int ret;
+
+	/* Some events are global */
+	if (sfp->sm_state != SFP_S_DOWN &&
+	    (sfp->sm_mod_state != SFP_MOD_PRESENT ||
+	     sfp->sm_dev_state != SFP_DEV_UP)) {
+		if (sfp->sm_state == SFP_S_LINK_UP &&
+		    sfp->sm_dev_state == SFP_DEV_UP)
+			sfp_sm_link_down(sfp);
+		if (sfp->sm_state > SFP_S_INIT)
+			sfp_module_stop(sfp->sfp_bus);
+		if (sfp->mod_phy)
+			sfp_sm_phy_detach(sfp);
+		sfp_module_tx_disable(sfp);
+		sfp_soft_stop_poll(sfp);
+		sfp_sm_next(sfp, SFP_S_DOWN, 0);
+		return;
+	}
+
+	/* The main state machine */
+	switch (sfp->sm_state) {
+	case SFP_S_DOWN:
+		if (sfp->sm_mod_state != SFP_MOD_PRESENT ||
+		    sfp->sm_dev_state != SFP_DEV_UP)
+			break;
+
+		if (!(sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE))
+			sfp_soft_start_poll(sfp);
+
+		sfp_module_tx_enable(sfp);
+
+		/* Initialise the fault clearance retries */
+		sfp->sm_fault_retries = N_FAULT_INIT;
+
+		/* We need to check the TX_FAULT state, which is not defined
+		 * while TX_DISABLE is asserted. The earliest we want to do
+		 * anything (such as probe for a PHY) is 50ms.
+		 */
+		sfp_sm_next(sfp, SFP_S_WAIT, T_WAIT);
+		break;
+
+	case SFP_S_WAIT:
+		if (event != SFP_E_TIMEOUT)
+			break;
+
+		if (sfp->state & SFP_F_TX_FAULT) {
+			/* Wait up to t_init (SFF-8472) or t_start_up (SFF-8431)
+			 * from the TX_DISABLE deassertion for the module to
+			 * initialise, which is indicated by TX_FAULT
+			 * deasserting.
+			 */
+			timeout = sfp->module_t_start_up;
+			if (timeout > T_WAIT)
+				timeout -= T_WAIT;
+			else
+				timeout = 1;
+
+			sfp_sm_next(sfp, SFP_S_INIT, timeout);
+		} else {
+			/* TX_FAULT is not asserted, assume the module has
+			 * finished initialising.
+			 */
+			goto init_done;
+		}
+		break;
+
+	case SFP_S_INIT:
+		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+			/* TX_FAULT is still asserted after t_init or
+			 * or t_start_up, so assume there is a fault.
+			 */
+			sfp_sm_fault(sfp, SFP_S_INIT_TX_FAULT,
+				     sfp->sm_fault_retries == N_FAULT_INIT);
+		} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+	init_done:
+			sfp->sm_phy_retries = R_PHY_RETRY;
+			goto phy_probe;
+		}
+		break;
+
+	case SFP_S_INIT_PHY:
+		if (event != SFP_E_TIMEOUT)
+			break;
+	phy_probe:
+		/* TX_FAULT deasserted or we timed out with TX_FAULT
+		 * clear.  Probe for the PHY and check the LOS state.
+		 */
+		ret = sfp_sm_probe_for_phy(sfp);
+		if (ret == -ENODEV) {
+			if (--sfp->sm_phy_retries) {
+				sfp_sm_next(sfp, SFP_S_INIT_PHY, T_PHY_RETRY);
+				break;
+			} else {
+				dev_info(sfp->dev, "no PHY detected\n");
+			}
+		} else if (ret) {
+			sfp_sm_next(sfp, SFP_S_FAIL, 0);
+			break;
+		}
+		if (sfp_module_start(sfp->sfp_bus)) {
+			sfp_sm_next(sfp, SFP_S_FAIL, 0);
+			break;
+		}
+		sfp_sm_link_check_los(sfp);
+
+		/* Reset the fault retry count */
+		sfp->sm_fault_retries = N_FAULT;
+		break;
+
+	case SFP_S_INIT_TX_FAULT:
+		if (event == SFP_E_TIMEOUT) {
+			sfp_module_tx_fault_reset(sfp);
+			sfp_sm_next(sfp, SFP_S_INIT, sfp->module_t_start_up);
+		}
+		break;
+
+	case SFP_S_WAIT_LOS:
+		if (event == SFP_E_TX_FAULT)
+			sfp_sm_fault(sfp, SFP_S_TX_FAULT, true);
+		else if (sfp_los_event_inactive(sfp, event))
+			sfp_sm_link_up(sfp);
+		break;
+
+	case SFP_S_LINK_UP:
+		if (event == SFP_E_TX_FAULT) {
+			sfp_sm_link_down(sfp);
+			sfp_sm_fault(sfp, SFP_S_TX_FAULT, true);
+		} else if (sfp_los_event_active(sfp, event)) {
+			sfp_sm_link_down(sfp);
+			sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
+		}
+		break;
+
+	case SFP_S_TX_FAULT:
+		if (event == SFP_E_TIMEOUT) {
+			sfp_module_tx_fault_reset(sfp);
+			sfp_sm_next(sfp, SFP_S_REINIT, sfp->module_t_start_up);
+		}
+		break;
+
+	case SFP_S_REINIT:
+		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+			sfp_sm_fault(sfp, SFP_S_TX_FAULT, false);
+		} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+			dev_info(sfp->dev, "module transmit fault recovered\n");
+			sfp_sm_link_check_los(sfp);
+		}
+		break;
+
+	case SFP_S_TX_DISABLE:
+		break;
+	}
+}
+
+static void sfp_sm_event(struct sfp *sfp, unsigned int event)
+{
+	mutex_lock(&sfp->sm_mutex);
+
+	dev_dbg(sfp->dev, "SM: enter %s:%s:%s event %s\n",
+		mod_state_to_str(sfp->sm_mod_state),
+		dev_state_to_str(sfp->sm_dev_state),
+		sm_state_to_str(sfp->sm_state),
+		event_to_str(event));
+
+	sfp_sm_device(sfp, event);
+	sfp_sm_module(sfp, event);
+	sfp_sm_main(sfp, event);
+
+	dev_dbg(sfp->dev, "SM: exit %s:%s:%s\n",
+		mod_state_to_str(sfp->sm_mod_state),
+		dev_state_to_str(sfp->sm_dev_state),
+		sm_state_to_str(sfp->sm_state));
+
+	mutex_unlock(&sfp->sm_mutex);
+}
+
+static void sfp_attach(struct sfp *sfp)
+{
+	sfp_sm_event(sfp, SFP_E_DEV_ATTACH);
+}
+
+static void sfp_detach(struct sfp *sfp)
+{
+	sfp_sm_event(sfp, SFP_E_DEV_DETACH);
+}
+
+static void sfp_start(struct sfp *sfp)
+{
+	sfp_sm_event(sfp, SFP_E_DEV_UP);
+}
+
+static void sfp_stop(struct sfp *sfp)
+{
+	sfp_sm_event(sfp, SFP_E_DEV_DOWN);
+}
+
+static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo)
+{
+	/* locking... and check module is present */
+
+	if (sfp->id.ext.sff8472_compliance &&
+	    !(sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE)) {
+		modinfo->type = ETH_MODULE_SFF_8472;
+		modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+	} else {
+		modinfo->type = ETH_MODULE_SFF_8079;
+		modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
+	}
+	return 0;
+}
+
+static int sfp_module_eeprom(struct sfp *sfp, struct ethtool_eeprom *ee,
+			     u8 *data)
+{
+	unsigned int first, last, len;
+	int ret;
+
+	if (ee->len == 0)
+		return -EINVAL;
+
+	first = ee->offset;
+	last = ee->offset + ee->len;
+	if (first < ETH_MODULE_SFF_8079_LEN) {
+		len = min_t(unsigned int, last, ETH_MODULE_SFF_8079_LEN);
+		len -= first;
+
+		ret = sfp_read(sfp, false, first, data, len);
+		if (ret < 0)
+			return ret;
+
+		first += len;
+		data += len;
+	}
+	if (first < ETH_MODULE_SFF_8472_LEN && last > ETH_MODULE_SFF_8079_LEN) {
+		len = min_t(unsigned int, last, ETH_MODULE_SFF_8472_LEN);
+		len -= first;
+		first -= ETH_MODULE_SFF_8079_LEN;
+
+		ret = sfp_read(sfp, true, first, data, len);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static const struct sfp_socket_ops sfp_module_ops = {
+	.attach = sfp_attach,
+	.detach = sfp_detach,
+	.start = sfp_start,
+	.stop = sfp_stop,
+	.module_info = sfp_module_info,
+	.module_eeprom = sfp_module_eeprom,
+};
+
+static void sfp_timeout(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, timeout.work);
+
+	rtnl_lock();
+	sfp_sm_event(sfp, SFP_E_TIMEOUT);
+	rtnl_unlock();
+}
+
+static void sfp_check_state(struct sfp *sfp)
+{
+	unsigned int state, i, changed;
+
+	mutex_lock(&sfp->st_mutex);
+	state = sfp_get_state(sfp);
+	changed = state ^ sfp->state;
+	changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
+
+	for (i = 0; i < GPIO_MAX; i++)
+		if (changed & BIT(i))
+			dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i],
+				!!(sfp->state & BIT(i)), !!(state & BIT(i)));
+
+	state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
+	sfp->state = state;
+
+	rtnl_lock();
+	if (changed & SFP_F_PRESENT)
+		sfp_sm_event(sfp, state & SFP_F_PRESENT ?
+				SFP_E_INSERT : SFP_E_REMOVE);
+
+	if (changed & SFP_F_TX_FAULT)
+		sfp_sm_event(sfp, state & SFP_F_TX_FAULT ?
+				SFP_E_TX_FAULT : SFP_E_TX_CLEAR);
+
+	if (changed & SFP_F_LOS)
+		sfp_sm_event(sfp, state & SFP_F_LOS ?
+				SFP_E_LOS_HIGH : SFP_E_LOS_LOW);
+	rtnl_unlock();
+	mutex_unlock(&sfp->st_mutex);
+}
+
+static irqreturn_t sfp_irq(int irq, void *data)
+{
+	struct sfp *sfp = data;
+
+	sfp_check_state(sfp);
+
+	return IRQ_HANDLED;
+}
+
+static void sfp_poll(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, poll.work);
+
+	sfp_check_state(sfp);
+
+	if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) ||
+	    sfp->need_poll)
+		mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+}
+
+static struct sfp *sfp_alloc(struct device *dev)
+{
+	struct sfp *sfp;
+
+	sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
+	if (!sfp)
+		return ERR_PTR(-ENOMEM);
+
+	sfp->dev = dev;
+
+	mutex_init(&sfp->sm_mutex);
+	mutex_init(&sfp->st_mutex);
+	INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
+	INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
+
+	sfp_hwmon_init(sfp);
+
+	return sfp;
+}
+
+static void sfp_cleanup(void *data)
+{
+	struct sfp *sfp = data;
+
+	sfp_hwmon_exit(sfp);
+
+	cancel_delayed_work_sync(&sfp->poll);
+	cancel_delayed_work_sync(&sfp->timeout);
+	if (sfp->i2c_mii) {
+		mdiobus_unregister(sfp->i2c_mii);
+		mdiobus_free(sfp->i2c_mii);
+	}
+	if (sfp->i2c)
+		i2c_put_adapter(sfp->i2c);
+	kfree(sfp);
+}
+
+static int sfp_probe(struct platform_device *pdev)
+{
+	const struct sff_data *sff;
+	struct sfp *sfp;
+	int err, i;
+
+	sfp = sfp_alloc(&pdev->dev);
+	if (IS_ERR(sfp))
+		return PTR_ERR(sfp);
+
+	platform_set_drvdata(pdev, sfp);
+
+	err = devm_add_action(sfp->dev, sfp_cleanup, sfp);
+	if (err < 0)
+		return err;
+
+	sff = sfp->type = &sfp_data;
+
+	if (pdev->dev.of_node) {
+		struct device_node *node = pdev->dev.of_node;
+		const struct of_device_id *id;
+		struct i2c_adapter *i2c;
+		struct device_node *np;
+
+		id = of_match_node(sfp_of_match, node);
+		if (WARN_ON(!id))
+			return -EINVAL;
+
+		sff = sfp->type = id->data;
+
+		np = of_parse_phandle(node, "i2c-bus", 0);
+		if (!np) {
+			dev_err(sfp->dev, "missing 'i2c-bus' property\n");
+			return -ENODEV;
+		}
+
+		i2c = of_find_i2c_adapter_by_node(np);
+		of_node_put(np);
+		if (!i2c)
+			return -EPROBE_DEFER;
+
+		err = sfp_i2c_configure(sfp, i2c);
+		if (err < 0) {
+			i2c_put_adapter(i2c);
+			return err;
+		}
+	}
+
+	for (i = 0; i < GPIO_MAX; i++)
+		if (sff->gpios & BIT(i)) {
+			sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
+					   gpio_of_names[i], gpio_flags[i]);
+			if (IS_ERR(sfp->gpio[i]))
+				return PTR_ERR(sfp->gpio[i]);
+		}
+
+	sfp->get_state = sfp_gpio_get_state;
+	sfp->set_state = sfp_gpio_set_state;
+
+	/* Modules that have no detect signal are always present */
+	if (!(sfp->gpio[GPIO_MODDEF0]))
+		sfp->get_state = sff_gpio_get_state;
+
+	device_property_read_u32(&pdev->dev, "maximum-power-milliwatt",
+				 &sfp->max_power_mW);
+	if (!sfp->max_power_mW)
+		sfp->max_power_mW = 1000;
+
+	dev_info(sfp->dev, "Host maximum power %u.%uW\n",
+		 sfp->max_power_mW / 1000, (sfp->max_power_mW / 100) % 10);
+
+	/* Get the initial state, and always signal TX disable,
+	 * since the network interface will not be up.
+	 */
+	sfp->state = sfp_get_state(sfp) | SFP_F_TX_DISABLE;
+
+	if (sfp->gpio[GPIO_RATE_SELECT] &&
+	    gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
+		sfp->state |= SFP_F_RATE_SELECT;
+	sfp_set_state(sfp, sfp->state);
+	sfp_module_tx_disable(sfp);
+	if (sfp->state & SFP_F_PRESENT) {
+		rtnl_lock();
+		sfp_sm_event(sfp, SFP_E_INSERT);
+		rtnl_unlock();
+	}
+
+	for (i = 0; i < GPIO_MAX; i++) {
+		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+			continue;
+
+		sfp->gpio_irq[i] = gpiod_to_irq(sfp->gpio[i]);
+		if (!sfp->gpio_irq[i]) {
+			sfp->need_poll = true;
+			continue;
+		}
+
+		err = devm_request_threaded_irq(sfp->dev, sfp->gpio_irq[i],
+						NULL, sfp_irq,
+						IRQF_ONESHOT |
+						IRQF_TRIGGER_RISING |
+						IRQF_TRIGGER_FALLING,
+						dev_name(sfp->dev), sfp);
+		if (err) {
+			sfp->gpio_irq[i] = 0;
+			sfp->need_poll = true;
+		}
+	}
+
+	if (sfp->need_poll)
+		mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+
+	/* We could have an issue in cases no Tx disable pin is available or
+	 * wired as modules using a laser as their light source will continue to
+	 * be active when the fiber is removed. This could be a safety issue and
+	 * we should at least warn the user about that.
+	 */
+	if (!sfp->gpio[GPIO_TX_DISABLE])
+		dev_warn(sfp->dev,
+			 "No tx_disable pin: SFP modules will always be emitting.\n");
+
+	sfp->sfp_bus = sfp_register_socket(sfp->dev, sfp, &sfp_module_ops);
+	if (!sfp->sfp_bus)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int sfp_remove(struct platform_device *pdev)
+{
+	struct sfp *sfp = platform_get_drvdata(pdev);
+
+	sfp_unregister_socket(sfp->sfp_bus);
+
+	rtnl_lock();
+	sfp_sm_event(sfp, SFP_E_REMOVE);
+	rtnl_unlock();
+
+	return 0;
+}
+
+static void sfp_shutdown(struct platform_device *pdev)
+{
+	struct sfp *sfp = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < GPIO_MAX; i++) {
+		if (!sfp->gpio_irq[i])
+			continue;
+
+		devm_free_irq(sfp->dev, sfp->gpio_irq[i], sfp);
+	}
+
+	cancel_delayed_work_sync(&sfp->poll);
+	cancel_delayed_work_sync(&sfp->timeout);
+}
+
+static struct platform_driver sfp_driver = {
+	.probe = sfp_probe,
+	.remove = sfp_remove,
+	.shutdown = sfp_shutdown,
+	.driver = {
+		.name = "sfp",
+		.of_match_table = sfp_of_match,
+	},
+};
+
+static int sfp_init(void)
+{
+	poll_jiffies = msecs_to_jiffies(100);
+
+	return platform_driver_register(&sfp_driver);
+}
+module_init(sfp_init);
+
+static void sfp_exit(void)
+{
+	platform_driver_unregister(&sfp_driver);
+}
+module_exit(sfp_exit);
+
+MODULE_ALIAS("platform:sfp");
+MODULE_AUTHOR("Russell King");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/sfp.h b/src/kernel/linux/v4.19/drivers/net/phy/sfp.h
new file mode 100644
index 0000000..b83f705
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/sfp.h
@@ -0,0 +1,32 @@
+#ifndef SFP_H
+#define SFP_H
+
+#include <linux/ethtool.h>
+#include <linux/sfp.h>
+
+struct sfp;
+
+struct sfp_socket_ops {
+	void (*attach)(struct sfp *sfp);
+	void (*detach)(struct sfp *sfp);
+	void (*start)(struct sfp *sfp);
+	void (*stop)(struct sfp *sfp);
+	int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
+	int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
+			     u8 *data);
+};
+
+int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev);
+void sfp_remove_phy(struct sfp_bus *bus);
+void sfp_link_up(struct sfp_bus *bus);
+void sfp_link_down(struct sfp_bus *bus);
+int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
+void sfp_module_remove(struct sfp_bus *bus);
+int sfp_module_start(struct sfp_bus *bus);
+void sfp_module_stop(struct sfp_bus *bus);
+int sfp_link_configure(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
+struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
+				    const struct sfp_socket_ops *ops);
+void sfp_unregister_socket(struct sfp_bus *bus);
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/smsc.c b/src/kernel/linux/v4.19/drivers/net/phy/smsc.c
new file mode 100644
index 0000000..c328208
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/smsc.c
@@ -0,0 +1,376 @@
+/*
+ * drivers/net/phy/smsc.c
+ *
+ * Driver for SMSC PHYs
+ *
+ * Author: Herbert Valerio Riedel
+ *
+ * Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.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.
+ *
+ * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/smscphy.h>
+
+struct smsc_hw_stat {
+	const char *string;
+	u8 reg;
+	u8 bits;
+};
+
+static struct smsc_hw_stat smsc_hw_stats[] = {
+	{ "phy_symbol_errors", 26, 16},
+};
+
+struct smsc_phy_priv {
+	bool energy_enable;
+};
+
+static int smsc_phy_config_intr(struct phy_device *phydev)
+{
+	int rc = phy_write (phydev, MII_LAN83C185_IM,
+			((PHY_INTERRUPT_ENABLED == phydev->interrupts)
+			? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
+			: 0));
+
+	return rc < 0 ? rc : 0;
+}
+
+static int smsc_phy_ack_interrupt(struct phy_device *phydev)
+{
+	int rc = phy_read (phydev, MII_LAN83C185_ISF);
+
+	return rc < 0 ? rc : 0;
+}
+
+static int smsc_phy_config_init(struct phy_device *phydev)
+{
+	struct smsc_phy_priv *priv = phydev->priv;
+
+	int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
+
+	if (rc < 0)
+		return rc;
+
+	if (priv->energy_enable) {
+		/* Enable energy detect mode for this SMSC Transceivers */
+		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
+			       rc | MII_LAN83C185_EDPWRDOWN);
+		if (rc < 0)
+			return rc;
+	}
+
+	return smsc_phy_ack_interrupt(phydev);
+}
+
+static int smsc_phy_reset(struct phy_device *phydev)
+{
+	int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
+	if (rc < 0)
+		return rc;
+
+	/* If the SMSC PHY is in power down mode, then set it
+	 * in all capable mode before using it.
+	 */
+	if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
+		/* set "all capable" mode */
+		rc |= MII_LAN83C185_MODE_ALL;
+		phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
+	}
+
+	/* reset the phy */
+	return genphy_soft_reset(phydev);
+}
+
+static int lan911x_config_init(struct phy_device *phydev)
+{
+	return smsc_phy_ack_interrupt(phydev);
+}
+
+/*
+ * The LAN87xx suffers from rare absence of the ENERGYON-bit when Ethernet cable
+ * plugs in while LAN87xx is in Energy Detect Power-Down mode. This leads to
+ * unstable detection of plugging in Ethernet cable.
+ * This workaround disables Energy Detect Power-Down mode and waiting for
+ * response on link pulses to detect presence of plugged Ethernet cable.
+ * The Energy Detect Power-Down mode is enabled again in the end of procedure to
+ * save approximately 220 mW of power if cable is unplugged.
+ */
+static int lan87xx_read_status(struct phy_device *phydev)
+{
+	struct smsc_phy_priv *priv = phydev->priv;
+
+	int err = genphy_read_status(phydev);
+
+	if (!phydev->link && priv->energy_enable) {
+		int i;
+
+		/* Disable EDPD to wake up PHY */
+		int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
+		if (rc < 0)
+			return rc;
+
+		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
+			       rc & ~MII_LAN83C185_EDPWRDOWN);
+		if (rc < 0)
+			return rc;
+
+		/* Wait max 640 ms to detect energy */
+		for (i = 0; i < 64; i++) {
+			/* Sleep to allow link test pulses to be sent */
+			msleep(10);
+			rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
+			if (rc < 0)
+				return rc;
+			if (rc & MII_LAN83C185_ENERGYON)
+				break;
+		}
+
+		/* Re-enable EDPD */
+		rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
+		if (rc < 0)
+			return rc;
+
+		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
+			       rc | MII_LAN83C185_EDPWRDOWN);
+		if (rc < 0)
+			return rc;
+	}
+
+	return err;
+}
+
+static int smsc_get_sset_count(struct phy_device *phydev)
+{
+	return ARRAY_SIZE(smsc_hw_stats);
+}
+
+static void smsc_get_strings(struct phy_device *phydev, u8 *data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(smsc_hw_stats); i++) {
+		strncpy(data + i * ETH_GSTRING_LEN,
+		       smsc_hw_stats[i].string, ETH_GSTRING_LEN);
+	}
+}
+
+static u64 smsc_get_stat(struct phy_device *phydev, int i)
+{
+	struct smsc_hw_stat stat = smsc_hw_stats[i];
+	int val;
+	u64 ret;
+
+	val = phy_read(phydev, stat.reg);
+	if (val < 0)
+		ret = U64_MAX;
+	else
+		ret = val;
+
+	return ret;
+}
+
+static void smsc_get_stats(struct phy_device *phydev,
+			   struct ethtool_stats *stats, u64 *data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(smsc_hw_stats); i++)
+		data[i] = smsc_get_stat(phydev, i);
+}
+
+static int smsc_phy_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	struct smsc_phy_priv *priv;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->energy_enable = true;
+
+	if (of_property_read_bool(of_node, "smsc,disable-energy-detect"))
+		priv->energy_enable = false;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static struct phy_driver smsc_phy_driver[] = {
+{
+	.phy_id		= 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN83C185",
+
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+
+	.probe		= smsc_phy_probe,
+
+	/* basic functions */
+	.config_init	= smsc_phy_config_init,
+	.soft_reset	= smsc_phy_reset,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN8187",
+
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+
+	.probe		= smsc_phy_probe,
+
+	/* basic functions */
+	.config_init	= smsc_phy_config_init,
+	.soft_reset	= smsc_phy_reset,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	/* Statistics */
+	.get_sset_count = smsc_get_sset_count,
+	.get_strings	= smsc_get_strings,
+	.get_stats	= smsc_get_stats,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN8700",
+
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+
+	.probe		= smsc_phy_probe,
+
+	/* basic functions */
+	.read_status	= lan87xx_read_status,
+	.config_init	= smsc_phy_config_init,
+	.soft_reset	= smsc_phy_reset,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	/* Statistics */
+	.get_sset_count = smsc_get_sset_count,
+	.get_strings	= smsc_get_strings,
+	.get_stats	= smsc_get_stats,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN911x Internal PHY",
+
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+
+	.probe		= smsc_phy_probe,
+
+	/* basic functions */
+	.config_init	= lan911x_config_init,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN8710/LAN8720",
+
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT | PHY_RST_AFTER_CLK_EN,
+
+	.probe		= smsc_phy_probe,
+
+	/* basic functions */
+	.read_status	= lan87xx_read_status,
+	.config_init	= smsc_phy_config_init,
+	.soft_reset	= smsc_phy_reset,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	/* Statistics */
+	.get_sset_count = smsc_get_sset_count,
+	.get_strings	= smsc_get_strings,
+	.get_stats	= smsc_get_stats,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+}, {
+	.phy_id		= 0x0007c110,
+	.phy_id_mask	= 0xfffffff0,
+	.name		= "SMSC LAN8740",
+
+	.features	= PHY_BASIC_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+
+	.probe		= smsc_phy_probe,
+
+	/* basic functions */
+	.read_status	= lan87xx_read_status,
+	.config_init	= smsc_phy_config_init,
+	.soft_reset	= smsc_phy_reset,
+
+	/* IRQ related */
+	.ack_interrupt	= smsc_phy_ack_interrupt,
+	.config_intr	= smsc_phy_config_intr,
+
+	/* Statistics */
+	.get_sset_count = smsc_get_sset_count,
+	.get_strings	= smsc_get_strings,
+	.get_stats	= smsc_get_stats,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+} };
+
+module_phy_driver(smsc_phy_driver);
+
+MODULE_DESCRIPTION("SMSC PHY driver");
+MODULE_AUTHOR("Herbert Valerio Riedel");
+MODULE_LICENSE("GPL");
+
+static struct mdio_device_id __maybe_unused smsc_tbl[] = {
+	{ 0x0007c0a0, 0xfffffff0 },
+	{ 0x0007c0b0, 0xfffffff0 },
+	{ 0x0007c0c0, 0xfffffff0 },
+	{ 0x0007c0d0, 0xfffffff0 },
+	{ 0x0007c0f0, 0xfffffff0 },
+	{ 0x0007c110, 0xfffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, smsc_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/spi_ks8995.c b/src/kernel/linux/v4.19/drivers/net/phy/spi_ks8995.c
new file mode 100644
index 0000000..d8ea414
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/spi_ks8995.c
@@ -0,0 +1,552 @@
+/*
+ * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
+ *
+ * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
+ *
+ * This file was based on: drivers/spi/at25.c
+ *     Copyright (C) 2006 David Brownell
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+
+#include <linux/spi/spi.h>
+
+#define DRV_VERSION		"0.1.1"
+#define DRV_DESC		"Micrel KS8995 Ethernet switch SPI driver"
+
+/* ------------------------------------------------------------------------ */
+
+#define KS8995_REG_ID0		0x00    /* Chip ID0 */
+#define KS8995_REG_ID1		0x01    /* Chip ID1 */
+
+#define KS8995_REG_GC0		0x02    /* Global Control 0 */
+#define KS8995_REG_GC1		0x03    /* Global Control 1 */
+#define KS8995_REG_GC2		0x04    /* Global Control 2 */
+#define KS8995_REG_GC3		0x05    /* Global Control 3 */
+#define KS8995_REG_GC4		0x06    /* Global Control 4 */
+#define KS8995_REG_GC5		0x07    /* Global Control 5 */
+#define KS8995_REG_GC6		0x08    /* Global Control 6 */
+#define KS8995_REG_GC7		0x09    /* Global Control 7 */
+#define KS8995_REG_GC8		0x0a    /* Global Control 8 */
+#define KS8995_REG_GC9		0x0b    /* Global Control 9 */
+
+#define KS8995_REG_PC(p, r)	((0x10 * p) + r)	 /* Port Control */
+#define KS8995_REG_PS(p, r)	((0x10 * p) + r + 0xe)  /* Port Status */
+
+#define KS8995_REG_TPC0		0x60    /* TOS Priority Control 0 */
+#define KS8995_REG_TPC1		0x61    /* TOS Priority Control 1 */
+#define KS8995_REG_TPC2		0x62    /* TOS Priority Control 2 */
+#define KS8995_REG_TPC3		0x63    /* TOS Priority Control 3 */
+#define KS8995_REG_TPC4		0x64    /* TOS Priority Control 4 */
+#define KS8995_REG_TPC5		0x65    /* TOS Priority Control 5 */
+#define KS8995_REG_TPC6		0x66    /* TOS Priority Control 6 */
+#define KS8995_REG_TPC7		0x67    /* TOS Priority Control 7 */
+
+#define KS8995_REG_MAC0		0x68    /* MAC address 0 */
+#define KS8995_REG_MAC1		0x69    /* MAC address 1 */
+#define KS8995_REG_MAC2		0x6a    /* MAC address 2 */
+#define KS8995_REG_MAC3		0x6b    /* MAC address 3 */
+#define KS8995_REG_MAC4		0x6c    /* MAC address 4 */
+#define KS8995_REG_MAC5		0x6d    /* MAC address 5 */
+
+#define KS8995_REG_IAC0		0x6e    /* Indirect Access Control 0 */
+#define KS8995_REG_IAC1		0x6f    /* Indirect Access Control 0 */
+#define KS8995_REG_IAD7		0x70    /* Indirect Access Data 7 */
+#define KS8995_REG_IAD6		0x71    /* Indirect Access Data 6 */
+#define KS8995_REG_IAD5		0x72    /* Indirect Access Data 5 */
+#define KS8995_REG_IAD4		0x73    /* Indirect Access Data 4 */
+#define KS8995_REG_IAD3		0x74    /* Indirect Access Data 3 */
+#define KS8995_REG_IAD2		0x75    /* Indirect Access Data 2 */
+#define KS8995_REG_IAD1		0x76    /* Indirect Access Data 1 */
+#define KS8995_REG_IAD0		0x77    /* Indirect Access Data 0 */
+
+#define KSZ8864_REG_ID1		0xfe	/* Chip ID in bit 7 */
+
+#define KS8995_REGS_SIZE	0x80
+#define KSZ8864_REGS_SIZE	0x100
+#define KSZ8795_REGS_SIZE	0x100
+
+#define ID1_CHIPID_M		0xf
+#define ID1_CHIPID_S		4
+#define ID1_REVISION_M		0x7
+#define ID1_REVISION_S		1
+#define ID1_START_SW		1	/* start the switch */
+
+#define FAMILY_KS8995		0x95
+#define FAMILY_KSZ8795		0x87
+#define CHIPID_M		0
+#define KS8995_CHIP_ID		0x00
+#define KSZ8864_CHIP_ID		0x01
+#define KSZ8795_CHIP_ID		0x09
+
+#define KS8995_CMD_WRITE	0x02U
+#define KS8995_CMD_READ		0x03U
+
+#define KS8995_RESET_DELAY	10 /* usec */
+
+enum ks8995_chip_variant {
+	ks8995,
+	ksz8864,
+	ksz8795,
+	max_variant
+};
+
+struct ks8995_chip_params {
+	char *name;
+	int family_id;
+	int chip_id;
+	int regs_size;
+	int addr_width;
+	int addr_shift;
+};
+
+static const struct ks8995_chip_params ks8995_chip[] = {
+	[ks8995] = {
+		.name = "KS8995MA",
+		.family_id = FAMILY_KS8995,
+		.chip_id = KS8995_CHIP_ID,
+		.regs_size = KS8995_REGS_SIZE,
+		.addr_width = 8,
+		.addr_shift = 0,
+	},
+	[ksz8864] = {
+		.name = "KSZ8864RMN",
+		.family_id = FAMILY_KS8995,
+		.chip_id = KSZ8864_CHIP_ID,
+		.regs_size = KSZ8864_REGS_SIZE,
+		.addr_width = 8,
+		.addr_shift = 0,
+	},
+	[ksz8795] = {
+		.name = "KSZ8795CLX",
+		.family_id = FAMILY_KSZ8795,
+		.chip_id = KSZ8795_CHIP_ID,
+		.regs_size = KSZ8795_REGS_SIZE,
+		.addr_width = 12,
+		.addr_shift = 1,
+	},
+};
+
+struct ks8995_pdata {
+	int reset_gpio;
+	enum of_gpio_flags reset_gpio_flags;
+};
+
+struct ks8995_switch {
+	struct spi_device	*spi;
+	struct mutex		lock;
+	struct ks8995_pdata	*pdata;
+	struct bin_attribute	regs_attr;
+	const struct ks8995_chip_params	*chip;
+	int			revision_id;
+};
+
+static const struct spi_device_id ks8995_id[] = {
+	{"ks8995", ks8995},
+	{"ksz8864", ksz8864},
+	{"ksz8795", ksz8795},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ks8995_id);
+
+static const struct of_device_id ks8895_spi_of_match[] = {
+        { .compatible = "micrel,ks8995" },
+        { .compatible = "micrel,ksz8864" },
+        { .compatible = "micrel,ksz8795" },
+        { },
+ };
+MODULE_DEVICE_TABLE(of, ks8895_spi_of_match);
+
+static inline u8 get_chip_id(u8 val)
+{
+	return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
+}
+
+static inline u8 get_chip_rev(u8 val)
+{
+	return (val >> ID1_REVISION_S) & ID1_REVISION_M;
+}
+
+/* create_spi_cmd - create a chip specific SPI command header
+ * @ks: pointer to switch instance
+ * @cmd: SPI command for switch
+ * @address: register address for command
+ *
+ * Different chip families use different bit pattern to address the switches
+ * registers:
+ *
+ * KS8995: 8bit command + 8bit address
+ * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
+ */
+static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
+				    unsigned address)
+{
+	u16 result = cmd;
+
+	/* make room for address (incl. address shift) */
+	result <<= ks->chip->addr_width + ks->chip->addr_shift;
+	/* add address */
+	result |= address << ks->chip->addr_shift;
+	/* SPI protocol needs big endian */
+	return cpu_to_be16(result);
+}
+/* ------------------------------------------------------------------------ */
+static int ks8995_read(struct ks8995_switch *ks, char *buf,
+		 unsigned offset, size_t count)
+{
+	__be16 cmd;
+	struct spi_transfer t[2];
+	struct spi_message m;
+	int err;
+
+	cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
+	spi_message_init(&m);
+
+	memset(&t, 0, sizeof(t));
+
+	t[0].tx_buf = &cmd;
+	t[0].len = sizeof(cmd);
+	spi_message_add_tail(&t[0], &m);
+
+	t[1].rx_buf = buf;
+	t[1].len = count;
+	spi_message_add_tail(&t[1], &m);
+
+	mutex_lock(&ks->lock);
+	err = spi_sync(ks->spi, &m);
+	mutex_unlock(&ks->lock);
+
+	return err ? err : count;
+}
+
+static int ks8995_write(struct ks8995_switch *ks, char *buf,
+		 unsigned offset, size_t count)
+{
+	__be16 cmd;
+	struct spi_transfer t[2];
+	struct spi_message m;
+	int err;
+
+	cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
+	spi_message_init(&m);
+
+	memset(&t, 0, sizeof(t));
+
+	t[0].tx_buf = &cmd;
+	t[0].len = sizeof(cmd);
+	spi_message_add_tail(&t[0], &m);
+
+	t[1].tx_buf = buf;
+	t[1].len = count;
+	spi_message_add_tail(&t[1], &m);
+
+	mutex_lock(&ks->lock);
+	err = spi_sync(ks->spi, &m);
+	mutex_unlock(&ks->lock);
+
+	return err ? err : count;
+}
+
+static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
+{
+	return ks8995_read(ks, buf, addr, 1) != 1;
+}
+
+static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
+{
+	char buf = val;
+
+	return ks8995_write(ks, &buf, addr, 1) != 1;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int ks8995_stop(struct ks8995_switch *ks)
+{
+	return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
+}
+
+static int ks8995_start(struct ks8995_switch *ks)
+{
+	return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
+}
+
+static int ks8995_reset(struct ks8995_switch *ks)
+{
+	int err;
+
+	err = ks8995_stop(ks);
+	if (err)
+		return err;
+
+	udelay(KS8995_RESET_DELAY);
+
+	return ks8995_start(ks);
+}
+
+static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj,
+	struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+{
+	struct device *dev;
+	struct ks8995_switch *ks8995;
+
+	dev = container_of(kobj, struct device, kobj);
+	ks8995 = dev_get_drvdata(dev);
+
+	return ks8995_read(ks8995, buf, off, count);
+}
+
+static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj,
+	struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count)
+{
+	struct device *dev;
+	struct ks8995_switch *ks8995;
+
+	dev = container_of(kobj, struct device, kobj);
+	ks8995 = dev_get_drvdata(dev);
+
+	return ks8995_write(ks8995, buf, off, count);
+}
+
+/* ks8995_get_revision - get chip revision
+ * @ks: pointer to switch instance
+ *
+ * Verify chip family and id and get chip revision.
+ */
+static int ks8995_get_revision(struct ks8995_switch *ks)
+{
+	int err;
+	u8 id0, id1, ksz8864_id;
+
+	/* read family id */
+	err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
+	if (err) {
+		err = -EIO;
+		goto err_out;
+	}
+
+	/* verify family id */
+	if (id0 != ks->chip->family_id) {
+		dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
+			ks->chip->family_id, id0);
+		err = -ENODEV;
+		goto err_out;
+	}
+
+	switch (ks->chip->family_id) {
+	case FAMILY_KS8995:
+		/* try reading chip id at CHIP ID1 */
+		err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
+		if (err) {
+			err = -EIO;
+			goto err_out;
+		}
+
+		/* verify chip id */
+		if ((get_chip_id(id1) == CHIPID_M) &&
+		    (get_chip_id(id1) == ks->chip->chip_id)) {
+			/* KS8995MA */
+			ks->revision_id = get_chip_rev(id1);
+		} else if (get_chip_id(id1) != CHIPID_M) {
+			/* KSZ8864RMN */
+			err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
+			if (err) {
+				err = -EIO;
+				goto err_out;
+			}
+
+			if ((ksz8864_id & 0x80) &&
+			    (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
+				ks->revision_id = get_chip_rev(id1);
+			}
+
+		} else {
+			dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
+				id1);
+			err = -ENODEV;
+		}
+		break;
+	case FAMILY_KSZ8795:
+		/* try reading chip id at CHIP ID1 */
+		err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
+		if (err) {
+			err = -EIO;
+			goto err_out;
+		}
+
+		if (get_chip_id(id1) == ks->chip->chip_id) {
+			ks->revision_id = get_chip_rev(id1);
+		} else {
+			dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
+				id1);
+			err = -ENODEV;
+		}
+		break;
+	default:
+		dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
+		err = -ENODEV;
+		break;
+	}
+err_out:
+	return err;
+}
+
+/* ks8995_parse_dt - setup platform data from devicetree
+ * @ks: pointer to switch instance
+ *
+ * Parses supported DT properties and sets up platform data
+ * accordingly.
+ */
+static void ks8995_parse_dt(struct ks8995_switch *ks)
+{
+	struct device_node *np = ks->spi->dev.of_node;
+	struct ks8995_pdata *pdata = ks->pdata;
+
+	if (!np)
+		return;
+
+	pdata->reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0,
+		&pdata->reset_gpio_flags);
+}
+
+static const struct bin_attribute ks8995_registers_attr = {
+	.attr = {
+		.name   = "registers",
+		.mode   = 0600,
+	},
+	.size   = KS8995_REGS_SIZE,
+	.read   = ks8995_registers_read,
+	.write  = ks8995_registers_write,
+};
+
+/* ------------------------------------------------------------------------ */
+static int ks8995_probe(struct spi_device *spi)
+{
+	struct ks8995_switch *ks;
+	int err;
+	int variant = spi_get_device_id(spi)->driver_data;
+
+	if (variant >= max_variant) {
+		dev_err(&spi->dev, "bad chip variant %d\n", variant);
+		return -ENODEV;
+	}
+
+	ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
+	if (!ks)
+		return -ENOMEM;
+
+	mutex_init(&ks->lock);
+	ks->spi = spi;
+	ks->chip = &ks8995_chip[variant];
+
+	if (ks->spi->dev.of_node) {
+		ks->pdata = devm_kzalloc(&spi->dev, sizeof(*ks->pdata),
+					 GFP_KERNEL);
+		if (!ks->pdata)
+			return -ENOMEM;
+
+		ks->pdata->reset_gpio = -1;
+
+		ks8995_parse_dt(ks);
+	}
+
+	if (!ks->pdata)
+		ks->pdata = spi->dev.platform_data;
+
+	/* de-assert switch reset */
+	if (ks->pdata && gpio_is_valid(ks->pdata->reset_gpio)) {
+		unsigned long flags;
+
+		flags = (ks->pdata->reset_gpio_flags == OF_GPIO_ACTIVE_LOW ?
+			 GPIOF_ACTIVE_LOW : 0);
+
+		err = devm_gpio_request_one(&spi->dev,
+					    ks->pdata->reset_gpio,
+					    flags, "switch-reset");
+		if (err) {
+			dev_err(&spi->dev,
+				"failed to get reset-gpios: %d\n", err);
+			return -EIO;
+		}
+
+		gpiod_set_value(gpio_to_desc(ks->pdata->reset_gpio), 0);
+	}
+
+	spi_set_drvdata(spi, ks);
+
+	spi->mode = SPI_MODE_0;
+	spi->bits_per_word = 8;
+	err = spi_setup(spi);
+	if (err) {
+		dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
+		return err;
+	}
+
+	err = ks8995_get_revision(ks);
+	if (err)
+		return err;
+
+	memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr));
+	ks->regs_attr.size = ks->chip->regs_size;
+
+	err = ks8995_reset(ks);
+	if (err)
+		return err;
+
+	sysfs_attr_init(&ks->regs_attr.attr);
+	err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr);
+	if (err) {
+		dev_err(&spi->dev, "unable to create sysfs file, err=%d\n",
+				    err);
+		return err;
+	}
+
+	dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
+		 ks->chip->name, ks->chip->chip_id, ks->revision_id);
+
+	return 0;
+}
+
+static int ks8995_remove(struct spi_device *spi)
+{
+	struct ks8995_switch *ks = spi_get_drvdata(spi);
+
+	sysfs_remove_bin_file(&spi->dev.kobj, &ks->regs_attr);
+
+	/* assert reset */
+	if (ks->pdata && gpio_is_valid(ks->pdata->reset_gpio))
+		gpiod_set_value(gpio_to_desc(ks->pdata->reset_gpio), 1);
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+static struct spi_driver ks8995_driver = {
+	.driver = {
+		.name	    = "spi-ks8995",
+		.of_match_table = of_match_ptr(ks8895_spi_of_match),
+	},
+	.probe	  = ks8995_probe,
+	.remove	  = ks8995_remove,
+	.id_table = ks8995_id,
+};
+
+module_spi_driver(ks8995_driver);
+
+MODULE_DESCRIPTION(DRV_DESC);
+MODULE_VERSION(DRV_VERSION);
+MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/ste10Xp.c b/src/kernel/linux/v4.19/drivers/net/phy/ste10Xp.c
new file mode 100644
index 0000000..fbd548a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/ste10Xp.c
@@ -0,0 +1,121 @@
+/*
+ * drivers/net/phy/ste10Xp.c
+ *
+ * Driver for STMicroelectronics STe10Xp PHYs
+ *
+ * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ *
+ * Copyright (c) 2008 STMicroelectronics Limited
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+#define MII_XCIIS   	0x11	/* Configuration Info IRQ & Status Reg */
+#define MII_XIE     	0x12	/* Interrupt Enable Register */
+#define MII_XIE_DEFAULT_MASK 0x0070 /* ANE complete, Remote Fault, Link Down */
+
+#define STE101P_PHY_ID		0x00061c50
+#define STE100P_PHY_ID       	0x1c040011
+
+static int ste10Xp_config_init(struct phy_device *phydev)
+{
+	int value, err;
+
+	/* Software Reset PHY */
+	value = phy_read(phydev, MII_BMCR);
+	if (value < 0)
+		return value;
+
+	value |= BMCR_RESET;
+	err = phy_write(phydev, MII_BMCR, value);
+	if (err < 0)
+		return err;
+
+	do {
+		value = phy_read(phydev, MII_BMCR);
+	} while (value & BMCR_RESET);
+
+	return 0;
+}
+
+static int ste10Xp_config_intr(struct phy_device *phydev)
+{
+	int err, value;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* Enable all STe101P interrupts (PR12) */
+		err = phy_write(phydev, MII_XIE, MII_XIE_DEFAULT_MASK);
+		/* clear any pending interrupts */
+		if (err == 0) {
+			value = phy_read(phydev, MII_XCIIS);
+			if (value < 0)
+				err = value;
+		}
+	} else
+		err = phy_write(phydev, MII_XIE, 0);
+
+	return err;
+}
+
+static int ste10Xp_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, MII_XCIIS);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static struct phy_driver ste10xp_pdriver[] = {
+{
+	.phy_id = STE101P_PHY_ID,
+	.phy_id_mask = 0xfffffff0,
+	.name = "STe101p",
+	.features = PHY_BASIC_FEATURES | SUPPORTED_Pause,
+	.flags = PHY_HAS_INTERRUPT,
+	.config_init = ste10Xp_config_init,
+	.ack_interrupt = ste10Xp_ack_interrupt,
+	.config_intr = ste10Xp_config_intr,
+	.suspend = genphy_suspend,
+	.resume = genphy_resume,
+}, {
+	.phy_id = STE100P_PHY_ID,
+	.phy_id_mask = 0xffffffff,
+	.name = "STe100p",
+	.features = PHY_BASIC_FEATURES | SUPPORTED_Pause,
+	.flags = PHY_HAS_INTERRUPT,
+	.config_init = ste10Xp_config_init,
+	.ack_interrupt = ste10Xp_ack_interrupt,
+	.config_intr = ste10Xp_config_intr,
+	.suspend = genphy_suspend,
+	.resume = genphy_resume,
+} };
+
+module_phy_driver(ste10xp_pdriver);
+
+static struct mdio_device_id __maybe_unused ste10Xp_tbl[] = {
+	{ STE101P_PHY_ID, 0xfffffff0 },
+	{ STE100P_PHY_ID, 0xffffffff },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, ste10Xp_tbl);
+
+MODULE_DESCRIPTION("STMicroelectronics STe10Xp PHY driver");
+MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
+MODULE_LICENSE("GPL");
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/swconfig.c b/src/kernel/linux/v4.19/drivers/net/phy/swconfig.c
new file mode 100644
index 0000000..abf1a45
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/swconfig.c
@@ -0,0 +1,1280 @@
+/*
+ * 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 LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)
+		if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla,
+				port_policy))
+#else
+		if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla,
+				port_policy, NULL))
+#endif
+			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 LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)
+	if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy))
+#else
+	if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL))
+#endif
+		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,
+		.doit = swconfig_list_attrs,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_LIST_VLAN,
+		.doit = swconfig_list_attrs,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_LIST_PORT,
+		.doit = swconfig_list_attrs,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_GET_GLOBAL,
+		.doit = swconfig_get_attr,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_GET_VLAN,
+		.doit = swconfig_get_attr,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_GET_PORT,
+		.doit = swconfig_get_attr,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_SET_GLOBAL,
+		.flags = GENL_ADMIN_PERM,
+		.doit = swconfig_set_attr,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_SET_VLAN,
+		.flags = GENL_ADMIN_PERM,
+		.doit = swconfig_set_attr,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_SET_PORT,
+		.flags = GENL_ADMIN_PERM,
+		.doit = swconfig_set_attr,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+	},
+	{
+		.cmd = SWITCH_CMD_GET_SWITCH,
+		.dumpit = swconfig_dump_switches,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
+		.policy = switch_policy,
+#endif
+		.done = swconfig_done,
+	}
+};
+
+static struct genl_family switch_fam = {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
+	.id = GENL_ID_GENERATE,
+#endif
+	.name = "switch",
+	.hdrsize = 0,
+	.version = 1,
+	.maxattr = SWITCH_ATTR_MAX,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0)
+	.policy = switch_policy,
+#endif
+	.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);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
+	return genl_register_family_with_ops(&switch_fam, swconfig_ops);
+#else
+	return genl_register_family(&switch_fam);
+#endif
+}
+
+static void __exit
+swconfig_exit(void)
+{
+	genl_unregister_family(&switch_fam);
+}
+
+module_init(swconfig_init);
+module_exit(swconfig_exit);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/swconfig_leds.c b/src/kernel/linux/v4.19/drivers/net/phy/swconfig_leds.c
new file mode 100644
index 0000000..e982cb7
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/swconfig_leds.c
@@ -0,0 +1,567 @@
+/*
+ * 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;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0)
+static void
+swconfig_trig_activate_void(struct led_classdev *led_cdev)
+{
+	swconfig_trig_activate(led_cdev);
+}
+#endif
+
+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;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0)
+	sw_trig->trig.activate = swconfig_trig_activate_void;
+#else
+	sw_trig->trig.activate = swconfig_trig_activate;
+#endif
+	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 --git a/src/kernel/linux/v4.19/drivers/net/phy/swphy.c b/src/kernel/linux/v4.19/drivers/net/phy/swphy.c
new file mode 100644
index 0000000..34f58f2
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/swphy.c
@@ -0,0 +1,179 @@
+/*
+ * Software PHY emulation
+ *
+ * Code taken from fixed_phy.c by Russell King <rmk+kernel@arm.linux.org.uk>
+ *
+ * Author: Vitaly Bordug <vbordug@ru.mvista.com>
+ *         Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * Copyright (c) 2006-2007 MontaVista Software, Inc.
+ *
+ * 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.
+ */
+#include <linux/export.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+
+#include "swphy.h"
+
+#define MII_REGS_NUM 29
+
+struct swmii_regs {
+	u16 bmcr;
+	u16 bmsr;
+	u16 lpa;
+	u16 lpagb;
+};
+
+enum {
+	SWMII_SPEED_10 = 0,
+	SWMII_SPEED_100,
+	SWMII_SPEED_1000,
+	SWMII_DUPLEX_HALF = 0,
+	SWMII_DUPLEX_FULL,
+};
+
+/*
+ * These two tables get bitwise-anded together to produce the final result.
+ * This means the speed table must contain both duplex settings, and the
+ * duplex table must contain all speed settings.
+ */
+static const struct swmii_regs speed[] = {
+	[SWMII_SPEED_10] = {
+		.bmcr  = BMCR_FULLDPLX,
+		.lpa   = LPA_10FULL | LPA_10HALF,
+	},
+	[SWMII_SPEED_100] = {
+		.bmcr  = BMCR_FULLDPLX | BMCR_SPEED100,
+		.bmsr  = BMSR_100FULL | BMSR_100HALF,
+		.lpa   = LPA_100FULL | LPA_100HALF,
+	},
+	[SWMII_SPEED_1000] = {
+		.bmcr  = BMCR_FULLDPLX | BMCR_SPEED1000,
+		.bmsr  = BMSR_ESTATEN,
+		.lpagb = LPA_1000FULL | LPA_1000HALF,
+	},
+};
+
+static const struct swmii_regs duplex[] = {
+	[SWMII_DUPLEX_HALF] = {
+		.bmcr  = ~BMCR_FULLDPLX,
+		.bmsr  = BMSR_ESTATEN | BMSR_100HALF,
+		.lpa   = LPA_10HALF | LPA_100HALF,
+		.lpagb = LPA_1000HALF,
+	},
+	[SWMII_DUPLEX_FULL] = {
+		.bmcr  = ~0,
+		.bmsr  = BMSR_ESTATEN | BMSR_100FULL,
+		.lpa   = LPA_10FULL | LPA_100FULL,
+		.lpagb = LPA_1000FULL,
+	},
+};
+
+static int swphy_decode_speed(int speed)
+{
+	switch (speed) {
+	case 1000:
+		return SWMII_SPEED_1000;
+	case 100:
+		return SWMII_SPEED_100;
+	case 10:
+		return SWMII_SPEED_10;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * swphy_validate_state - validate the software phy status
+ * @state: software phy status
+ *
+ * This checks that we can represent the state stored in @state can be
+ * represented in the emulated MII registers.  Returns 0 if it can,
+ * otherwise returns -EINVAL.
+ */
+int swphy_validate_state(const struct fixed_phy_status *state)
+{
+	int err;
+
+	if (state->link) {
+		err = swphy_decode_speed(state->speed);
+		if (err < 0) {
+			pr_warn("swphy: unknown speed\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(swphy_validate_state);
+
+/**
+ * swphy_read_reg - return a MII register from the fixed phy state
+ * @reg: MII register
+ * @state: fixed phy status
+ *
+ * Return the MII @reg register generated from the fixed phy state @state.
+ */
+int swphy_read_reg(int reg, const struct fixed_phy_status *state)
+{
+	int speed_index, duplex_index;
+	u16 bmsr = BMSR_ANEGCAPABLE;
+	u16 bmcr = 0;
+	u16 lpagb = 0;
+	u16 lpa = 0;
+
+	if (reg > MII_REGS_NUM)
+		return -1;
+
+	speed_index = swphy_decode_speed(state->speed);
+	if (WARN_ON(speed_index < 0))
+		return 0;
+
+	duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
+
+	bmsr |= speed[speed_index].bmsr & duplex[duplex_index].bmsr;
+
+	if (state->link) {
+		bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
+
+		bmcr  |= speed[speed_index].bmcr  & duplex[duplex_index].bmcr;
+		lpa   |= speed[speed_index].lpa   & duplex[duplex_index].lpa;
+		lpagb |= speed[speed_index].lpagb & duplex[duplex_index].lpagb;
+
+		if (state->pause)
+			lpa |= LPA_PAUSE_CAP;
+
+		if (state->asym_pause)
+			lpa |= LPA_PAUSE_ASYM;
+	}
+
+	switch (reg) {
+	case MII_BMCR:
+		return bmcr;
+	case MII_BMSR:
+		return bmsr;
+	case MII_PHYSID1:
+	case MII_PHYSID2:
+		return 0;
+	case MII_LPA:
+		return lpa;
+	case MII_STAT1000:
+		return lpagb;
+
+	/*
+	 * We do not support emulating Clause 45 over Clause 22 register
+	 * reads.  Return an error instead of bogus data.
+	 */
+	case MII_MMD_CTRL:
+	case MII_MMD_DATA:
+		return -1;
+
+	default:
+		return 0xffff;
+	}
+}
+EXPORT_SYMBOL_GPL(swphy_read_reg);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/swphy.h b/src/kernel/linux/v4.19/drivers/net/phy/swphy.h
new file mode 100644
index 0000000..3668ab8
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/swphy.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef SWPHY_H
+#define SWPHY_H
+
+struct fixed_phy_status;
+
+int swphy_validate_state(const struct fixed_phy_status *state);
+int swphy_read_reg(int reg, const struct fixed_phy_status *state);
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/teranetics.c b/src/kernel/linux/v4.19/drivers/net/phy/teranetics.c
new file mode 100644
index 0000000..22f3bdd
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/teranetics.c
@@ -0,0 +1,99 @@
+/*
+ * Driver for Teranetics PHY
+ *
+ * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
+ *
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+
+MODULE_DESCRIPTION("Teranetics PHY driver");
+MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
+MODULE_LICENSE("GPL v2");
+
+#define PHY_ID_TN2020	0x00a19410
+#define MDIO_PHYXS_LNSTAT_SYNC0	0x0001
+#define MDIO_PHYXS_LNSTAT_SYNC1	0x0002
+#define MDIO_PHYXS_LNSTAT_SYNC2	0x0004
+#define MDIO_PHYXS_LNSTAT_SYNC3	0x0008
+#define MDIO_PHYXS_LNSTAT_ALIGN 0x1000
+
+#define MDIO_PHYXS_LANE_READY	(MDIO_PHYXS_LNSTAT_SYNC0 | \
+				MDIO_PHYXS_LNSTAT_SYNC1 | \
+				MDIO_PHYXS_LNSTAT_SYNC2 | \
+				MDIO_PHYXS_LNSTAT_SYNC3 | \
+				MDIO_PHYXS_LNSTAT_ALIGN)
+
+static int teranetics_aneg_done(struct phy_device *phydev)
+{
+	/* auto negotiation state can only be checked when using copper
+	 * port, if using fiber port, just lie it's done.
+	 */
+	if (!phy_read_mmd(phydev, MDIO_MMD_VEND1, 93))
+		return genphy_c45_aneg_done(phydev);
+
+	return 1;
+}
+
+static int teranetics_read_status(struct phy_device *phydev)
+{
+	int reg;
+
+	phydev->link = 1;
+
+	phydev->speed = SPEED_10000;
+	phydev->duplex = DUPLEX_FULL;
+
+	if (!phy_read_mmd(phydev, MDIO_MMD_VEND1, 93)) {
+		reg = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_LNSTAT);
+		if (reg < 0 ||
+		    !((reg & MDIO_PHYXS_LANE_READY) == MDIO_PHYXS_LANE_READY)) {
+			phydev->link = 0;
+			return 0;
+		}
+
+		reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+		if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS))
+			phydev->link = 0;
+	}
+
+	return 0;
+}
+
+static int teranetics_match_phy_device(struct phy_device *phydev)
+{
+	return phydev->c45_ids.device_ids[3] == PHY_ID_TN2020;
+}
+
+static struct phy_driver teranetics_driver[] = {
+{
+	.phy_id		= PHY_ID_TN2020,
+	.phy_id_mask	= 0xffffffff,
+	.name		= "Teranetics TN2020",
+	.soft_reset	= gen10g_no_soft_reset,
+	.aneg_done	= teranetics_aneg_done,
+	.config_init    = gen10g_config_init,
+	.config_aneg    = gen10g_config_aneg,
+	.read_status	= teranetics_read_status,
+	.match_phy_device = teranetics_match_phy_device,
+},
+};
+
+module_phy_driver(teranetics_driver);
+
+static struct mdio_device_id __maybe_unused teranetics_tbl[] = {
+	{ PHY_ID_TN2020, 0xffffffff },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, teranetics_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/uPD60620.c b/src/kernel/linux/v4.19/drivers/net/phy/uPD60620.c
new file mode 100644
index 0000000..55f48ee
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/uPD60620.c
@@ -0,0 +1,108 @@
+/*
+ * Driver for the Renesas PHY uPD60620.
+ *
+ * Copyright (C) 2015 Softing Industrial Automation GmbH
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define UPD60620_PHY_ID    0xb8242824
+
+/* Extended Registers and values */
+/* PHY Special Control/Status    */
+#define PHY_PHYSCR         0x1F      /* PHY.31 */
+#define PHY_PHYSCR_10MB    0x0004    /* PHY speed = 10mb */
+#define PHY_PHYSCR_100MB   0x0008    /* PHY speed = 100mb */
+#define PHY_PHYSCR_DUPLEX  0x0010    /* PHY Duplex */
+
+/* PHY Special Modes */
+#define PHY_SPM            0x12      /* PHY.18 */
+
+/* Init PHY */
+
+static int upd60620_config_init(struct phy_device *phydev)
+{
+	/* Enable support for passive HUBs (could be a strap option) */
+	/* PHYMODE: All speeds, HD in parallel detect */
+	return phy_write(phydev, PHY_SPM, 0x0180 | phydev->mdio.addr);
+}
+
+/* Get PHY status from common registers */
+
+static int upd60620_read_status(struct phy_device *phydev)
+{
+	int phy_state;
+
+	/* Read negotiated state */
+	phy_state = phy_read(phydev, MII_BMSR);
+	if (phy_state < 0)
+		return phy_state;
+
+	phydev->link = 0;
+	phydev->lp_advertising = 0;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+
+	if (phy_state & (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)) {
+		phy_state = phy_read(phydev, PHY_PHYSCR);
+		if (phy_state < 0)
+			return phy_state;
+
+		if (phy_state & (PHY_PHYSCR_10MB | PHY_PHYSCR_100MB)) {
+			phydev->link = 1;
+			phydev->speed = SPEED_10;
+			phydev->duplex = DUPLEX_HALF;
+
+			if (phy_state & PHY_PHYSCR_100MB)
+				phydev->speed = SPEED_100;
+			if (phy_state & PHY_PHYSCR_DUPLEX)
+				phydev->duplex = DUPLEX_FULL;
+
+			phy_state = phy_read(phydev, MII_LPA);
+			if (phy_state < 0)
+				return phy_state;
+
+			phydev->lp_advertising
+				= mii_lpa_to_ethtool_lpa_t(phy_state);
+
+			if (phydev->duplex == DUPLEX_FULL) {
+				if (phy_state & LPA_PAUSE_CAP)
+					phydev->pause = 1;
+				if (phy_state & LPA_PAUSE_ASYM)
+					phydev->asym_pause = 1;
+			}
+		}
+	}
+	return 0;
+}
+
+MODULE_DESCRIPTION("Renesas uPD60620 PHY driver");
+MODULE_AUTHOR("Bernd Edlinger <bernd.edlinger@hotmail.de>");
+MODULE_LICENSE("GPL");
+
+static struct phy_driver upd60620_driver[1] = { {
+	.phy_id         = UPD60620_PHY_ID,
+	.phy_id_mask    = 0xfffffffe,
+	.name           = "Renesas uPD60620",
+	.features       = PHY_BASIC_FEATURES,
+	.flags          = 0,
+	.config_init    = upd60620_config_init,
+	.read_status    = upd60620_read_status,
+} };
+
+module_phy_driver(upd60620_driver);
+
+static struct mdio_device_id __maybe_unused upd60620_tbl[] = {
+	{ UPD60620_PHY_ID, 0xfffffffe },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, upd60620_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/vitesse.c b/src/kernel/linux/v4.19/drivers/net/phy/vitesse.c
new file mode 100644
index 0000000..fbf9ad4
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/vitesse.c
@@ -0,0 +1,542 @@
+/*
+ * Driver for Vitesse PHYs
+ *
+ * Author: Kriston Carson
+ *
+ * Copyright (c) 2005, 2009, 2011 Freescale Semiconductor, Inc.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+/* Vitesse Extended Page Magic Register(s) */
+#define MII_VSC82X4_EXT_PAGE_16E	0x10
+#define MII_VSC82X4_EXT_PAGE_17E	0x11
+#define MII_VSC82X4_EXT_PAGE_18E	0x12
+
+/* Vitesse Extended Control Register 1 */
+#define MII_VSC8244_EXT_CON1           0x17
+#define MII_VSC8244_EXTCON1_INIT       0x0000
+#define MII_VSC8244_EXTCON1_TX_SKEW_MASK	0x0c00
+#define MII_VSC8244_EXTCON1_RX_SKEW_MASK	0x0300
+#define MII_VSC8244_EXTCON1_TX_SKEW	0x0800
+#define MII_VSC8244_EXTCON1_RX_SKEW	0x0200
+
+/* Vitesse Interrupt Mask Register */
+#define MII_VSC8244_IMASK		0x19
+#define MII_VSC8244_IMASK_IEN		0x8000
+#define MII_VSC8244_IMASK_SPEED		0x4000
+#define MII_VSC8244_IMASK_LINK		0x2000
+#define MII_VSC8244_IMASK_DUPLEX	0x1000
+#define MII_VSC8244_IMASK_MASK		0xf000
+
+#define MII_VSC8221_IMASK_MASK		0xa000
+
+/* Vitesse Interrupt Status Register */
+#define MII_VSC8244_ISTAT		0x1a
+#define MII_VSC8244_ISTAT_STATUS	0x8000
+#define MII_VSC8244_ISTAT_SPEED		0x4000
+#define MII_VSC8244_ISTAT_LINK		0x2000
+#define MII_VSC8244_ISTAT_DUPLEX	0x1000
+
+/* Vitesse Auxiliary Control/Status Register */
+#define MII_VSC8244_AUX_CONSTAT		0x1c
+#define MII_VSC8244_AUXCONSTAT_INIT	0x0000
+#define MII_VSC8244_AUXCONSTAT_DUPLEX	0x0020
+#define MII_VSC8244_AUXCONSTAT_SPEED	0x0018
+#define MII_VSC8244_AUXCONSTAT_GBIT	0x0010
+#define MII_VSC8244_AUXCONSTAT_100	0x0008
+
+#define MII_VSC8221_AUXCONSTAT_INIT	0x0004 /* need to set this bit? */
+#define MII_VSC8221_AUXCONSTAT_RESERVED	0x0004
+
+/* Vitesse Extended Page Access Register */
+#define MII_VSC82X4_EXT_PAGE_ACCESS	0x1f
+
+/* Vitesse VSC8601 Extended PHY Control Register 1 */
+#define MII_VSC8601_EPHY_CTL		0x17
+#define MII_VSC8601_EPHY_CTL_RGMII_SKEW	(1 << 8)
+
+#define PHY_ID_VSC8234			0x000fc620
+#define PHY_ID_VSC8244			0x000fc6c0
+#define PHY_ID_VSC8514			0x00070670
+#define PHY_ID_VSC8572			0x000704d0
+#define PHY_ID_VSC8574			0x000704a0
+#define PHY_ID_VSC8601			0x00070420
+#define PHY_ID_VSC7385			0x00070450
+#define PHY_ID_VSC7388			0x00070480
+#define PHY_ID_VSC7395			0x00070550
+#define PHY_ID_VSC7398			0x00070580
+#define PHY_ID_VSC8662			0x00070660
+#define PHY_ID_VSC8221			0x000fc550
+#define PHY_ID_VSC8211			0x000fc4b0
+
+MODULE_DESCRIPTION("Vitesse PHY driver");
+MODULE_AUTHOR("Kriston Carson");
+MODULE_LICENSE("GPL");
+
+static int vsc824x_add_skew(struct phy_device *phydev)
+{
+	int err;
+	int extcon;
+
+	extcon = phy_read(phydev, MII_VSC8244_EXT_CON1);
+
+	if (extcon < 0)
+		return extcon;
+
+	extcon &= ~(MII_VSC8244_EXTCON1_TX_SKEW_MASK |
+			MII_VSC8244_EXTCON1_RX_SKEW_MASK);
+
+	extcon |= (MII_VSC8244_EXTCON1_TX_SKEW |
+			MII_VSC8244_EXTCON1_RX_SKEW);
+
+	err = phy_write(phydev, MII_VSC8244_EXT_CON1, extcon);
+
+	return err;
+}
+
+static int vsc824x_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT,
+			MII_VSC8244_AUXCONSTAT_INIT);
+	if (err < 0)
+		return err;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+		err = vsc824x_add_skew(phydev);
+
+	return err;
+}
+
+#define VSC73XX_EXT_PAGE_ACCESS 0x1f
+
+static int vsc73xx_read_page(struct phy_device *phydev)
+{
+	return __phy_read(phydev, VSC73XX_EXT_PAGE_ACCESS);
+}
+
+static int vsc73xx_write_page(struct phy_device *phydev, int page)
+{
+	return __phy_write(phydev, VSC73XX_EXT_PAGE_ACCESS, page);
+}
+
+static void vsc73xx_config_init(struct phy_device *phydev)
+{
+	/* Receiver init */
+	phy_write(phydev, 0x1f, 0x2a30);
+	phy_modify(phydev, 0x0c, 0x0300, 0x0200);
+	phy_write(phydev, 0x1f, 0x0000);
+
+	/* Config LEDs 0x61 */
+	phy_modify(phydev, MII_TPISTATUS, 0xff00, 0x0061);
+}
+
+static int vsc738x_config_init(struct phy_device *phydev)
+{
+	u16 rev;
+	/* This magic sequence appear in the application note
+	 * "VSC7385/7388 PHY Configuration".
+	 *
+	 * Maybe one day we will get to know what it all means.
+	 */
+	phy_write(phydev, 0x1f, 0x2a30);
+	phy_modify(phydev, 0x08, 0x0200, 0x0200);
+	phy_write(phydev, 0x1f, 0x52b5);
+	phy_write(phydev, 0x10, 0xb68a);
+	phy_modify(phydev, 0x12, 0xff07, 0x0003);
+	phy_modify(phydev, 0x11, 0x00ff, 0x00a2);
+	phy_write(phydev, 0x10, 0x968a);
+	phy_write(phydev, 0x1f, 0x2a30);
+	phy_modify(phydev, 0x08, 0x0200, 0x0000);
+	phy_write(phydev, 0x1f, 0x0000);
+
+	/* Read revision */
+	rev = phy_read(phydev, MII_PHYSID2);
+	rev &= 0x0f;
+
+	/* Special quirk for revision 0 */
+	if (rev == 0) {
+		phy_write(phydev, 0x1f, 0x2a30);
+		phy_modify(phydev, 0x08, 0x0200, 0x0200);
+		phy_write(phydev, 0x1f, 0x52b5);
+		phy_write(phydev, 0x12, 0x0000);
+		phy_write(phydev, 0x11, 0x0689);
+		phy_write(phydev, 0x10, 0x8f92);
+		phy_write(phydev, 0x1f, 0x52b5);
+		phy_write(phydev, 0x12, 0x0000);
+		phy_write(phydev, 0x11, 0x0e35);
+		phy_write(phydev, 0x10, 0x9786);
+		phy_write(phydev, 0x1f, 0x2a30);
+		phy_modify(phydev, 0x08, 0x0200, 0x0000);
+		phy_write(phydev, 0x17, 0xff80);
+		phy_write(phydev, 0x17, 0x0000);
+	}
+
+	phy_write(phydev, 0x1f, 0x0000);
+	phy_write(phydev, 0x12, 0x0048);
+
+	if (rev == 0) {
+		phy_write(phydev, 0x1f, 0x2a30);
+		phy_write(phydev, 0x14, 0x6600);
+		phy_write(phydev, 0x1f, 0x0000);
+		phy_write(phydev, 0x18, 0xa24e);
+	} else {
+		phy_write(phydev, 0x1f, 0x2a30);
+		phy_modify(phydev, 0x16, 0x0fc0, 0x0240);
+		phy_modify(phydev, 0x14, 0x6000, 0x4000);
+		/* bits 14-15 in extended register 0x14 controls DACG amplitude
+		 * 6 = -8%, 2 is hardware default
+		 */
+		phy_write(phydev, 0x1f, 0x0001);
+		phy_modify(phydev, 0x14, 0xe000, 0x6000);
+		phy_write(phydev, 0x1f, 0x0000);
+	}
+
+	vsc73xx_config_init(phydev);
+
+	return genphy_config_init(phydev);
+}
+
+static int vsc739x_config_init(struct phy_device *phydev)
+{
+	/* This magic sequence appears in the VSC7395 SparX-G5e application
+	 * note "VSC7395/VSC7398 PHY Configuration"
+	 *
+	 * Maybe one day we will get to know what it all means.
+	 */
+	phy_write(phydev, 0x1f, 0x2a30);
+	phy_modify(phydev, 0x08, 0x0200, 0x0200);
+	phy_write(phydev, 0x1f, 0x52b5);
+	phy_write(phydev, 0x10, 0xb68a);
+	phy_modify(phydev, 0x12, 0xff07, 0x0003);
+	phy_modify(phydev, 0x11, 0x00ff, 0x00a2);
+	phy_write(phydev, 0x10, 0x968a);
+	phy_write(phydev, 0x1f, 0x2a30);
+	phy_modify(phydev, 0x08, 0x0200, 0x0000);
+	phy_write(phydev, 0x1f, 0x0000);
+
+	phy_write(phydev, 0x1f, 0x0000);
+	phy_write(phydev, 0x12, 0x0048);
+	phy_write(phydev, 0x1f, 0x2a30);
+	phy_modify(phydev, 0x16, 0x0fc0, 0x0240);
+	phy_modify(phydev, 0x14, 0x6000, 0x4000);
+	phy_write(phydev, 0x1f, 0x0001);
+	phy_modify(phydev, 0x14, 0xe000, 0x6000);
+	phy_write(phydev, 0x1f, 0x0000);
+
+	vsc73xx_config_init(phydev);
+
+	return genphy_config_init(phydev);
+}
+
+static int vsc73xx_config_aneg(struct phy_device *phydev)
+{
+	/* The VSC73xx switches does not like to be instructed to
+	 * do autonegotiation in any way, it prefers that you just go
+	 * with the power-on/reset defaults. Writing some registers will
+	 * just make autonegotiation permanently fail.
+	 */
+	return 0;
+}
+
+/* This adds a skew for both TX and RX clocks, so the skew should only be
+ * applied to "rgmii-id" interfaces. It may not work as expected
+ * on "rgmii-txid", "rgmii-rxid" or "rgmii" interfaces. */
+static int vsc8601_add_skew(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_read(phydev, MII_VSC8601_EPHY_CTL);
+	if (ret < 0)
+		return ret;
+
+	ret |= MII_VSC8601_EPHY_CTL_RGMII_SKEW;
+	return phy_write(phydev, MII_VSC8601_EPHY_CTL, ret);
+}
+
+static int vsc8601_config_init(struct phy_device *phydev)
+{
+	int ret = 0;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+		ret = vsc8601_add_skew(phydev);
+
+	if (ret < 0)
+		return ret;
+
+	return genphy_config_init(phydev);
+}
+
+static int vsc824x_ack_interrupt(struct phy_device *phydev)
+{
+	int err = 0;
+
+	/* Don't bother to ACK the interrupts if interrupts
+	 * are disabled.  The 824x cannot clear the interrupts
+	 * if they are disabled.
+	 */
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_read(phydev, MII_VSC8244_ISTAT);
+
+	return (err < 0) ? err : 0;
+}
+
+static int vsc82xx_config_intr(struct phy_device *phydev)
+{
+	int err;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+		err = phy_write(phydev, MII_VSC8244_IMASK,
+			(phydev->drv->phy_id == PHY_ID_VSC8234 ||
+			 phydev->drv->phy_id == PHY_ID_VSC8244 ||
+			 phydev->drv->phy_id == PHY_ID_VSC8514 ||
+			 phydev->drv->phy_id == PHY_ID_VSC8572 ||
+			 phydev->drv->phy_id == PHY_ID_VSC8574 ||
+			 phydev->drv->phy_id == PHY_ID_VSC8601) ?
+				MII_VSC8244_IMASK_MASK :
+				MII_VSC8221_IMASK_MASK);
+	else {
+		/* The Vitesse PHY cannot clear the interrupt
+		 * once it has disabled them, so we clear them first
+		 */
+		err = phy_read(phydev, MII_VSC8244_ISTAT);
+
+		if (err < 0)
+			return err;
+
+		err = phy_write(phydev, MII_VSC8244_IMASK, 0);
+	}
+
+	return err;
+}
+
+static int vsc8221_config_init(struct phy_device *phydev)
+{
+	int err;
+
+	err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT,
+			MII_VSC8221_AUXCONSTAT_INIT);
+	return err;
+
+	/* Perhaps we should set EXT_CON1 based on the interface?
+	 * Options are 802.3Z SerDes or SGMII
+	 */
+}
+
+/* vsc82x4_config_autocross_enable - Enable auto MDI/MDI-X for forced links
+ * @phydev: target phy_device struct
+ *
+ * Enable auto MDI/MDI-X when in 10/100 forced link speeds by writing
+ * special values in the VSC8234/VSC8244 extended reserved registers
+ */
+static int vsc82x4_config_autocross_enable(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->autoneg == AUTONEG_ENABLE || phydev->speed > SPEED_100)
+		return 0;
+
+	/* map extended registers set 0x10 - 0x1e */
+	ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x52b5);
+	if (ret >= 0)
+		ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_18E, 0x0012);
+	if (ret >= 0)
+		ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_17E, 0x2803);
+	if (ret >= 0)
+		ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_16E, 0x87fa);
+	/* map standard registers set 0x10 - 0x1e */
+	if (ret >= 0)
+		ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x0000);
+	else
+		phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x0000);
+
+	return ret;
+}
+
+/* vsc82x4_config_aneg - restart auto-negotiation or write BMCR
+ * @phydev: target phy_device struct
+ *
+ * Description: If auto-negotiation is enabled, we configure the
+ *   advertising, and then restart auto-negotiation.  If it is not
+ *   enabled, then we write the BMCR and also start the auto
+ *   MDI/MDI-X feature
+ */
+static int vsc82x4_config_aneg(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Enable auto MDI/MDI-X when in 10/100 forced link speeds by
+	 * writing special values in the VSC8234 extended reserved registers
+	 */
+	if (phydev->autoneg != AUTONEG_ENABLE && phydev->speed <= SPEED_100) {
+		ret = genphy_setup_forced(phydev);
+
+		if (ret < 0) /* error */
+			return ret;
+
+		return vsc82x4_config_autocross_enable(phydev);
+	}
+
+	return genphy_config_aneg(phydev);
+}
+
+/* Vitesse 82xx */
+static struct phy_driver vsc82xx_driver[] = {
+{
+	.phy_id         = PHY_ID_VSC8234,
+	.name           = "Vitesse VSC8234",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = &vsc824x_config_init,
+	.config_aneg    = &vsc82x4_config_aneg,
+	.ack_interrupt  = &vsc824x_ack_interrupt,
+	.config_intr    = &vsc82xx_config_intr,
+}, {
+	.phy_id		= PHY_ID_VSC8244,
+	.name		= "Vitesse VSC8244",
+	.phy_id_mask	= 0x000fffc0,
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= &vsc824x_config_init,
+	.config_aneg	= &vsc82x4_config_aneg,
+	.ack_interrupt	= &vsc824x_ack_interrupt,
+	.config_intr	= &vsc82xx_config_intr,
+}, {
+	.phy_id		= PHY_ID_VSC8514,
+	.name		= "Vitesse VSC8514",
+	.phy_id_mask	= 0x000ffff0,
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= &vsc824x_config_init,
+	.config_aneg	= &vsc82x4_config_aneg,
+	.ack_interrupt	= &vsc824x_ack_interrupt,
+	.config_intr	= &vsc82xx_config_intr,
+}, {
+	.phy_id         = PHY_ID_VSC8572,
+	.name           = "Vitesse VSC8572",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = &vsc824x_config_init,
+	.config_aneg    = &vsc82x4_config_aneg,
+	.ack_interrupt  = &vsc824x_ack_interrupt,
+	.config_intr    = &vsc82xx_config_intr,
+}, {
+	.phy_id         = PHY_ID_VSC8574,
+	.name           = "Vitesse VSC8574",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = &vsc824x_config_init,
+	.config_aneg    = &vsc82x4_config_aneg,
+	.ack_interrupt  = &vsc824x_ack_interrupt,
+	.config_intr    = &vsc82xx_config_intr,
+}, {
+	.phy_id         = PHY_ID_VSC8601,
+	.name           = "Vitesse VSC8601",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = &vsc8601_config_init,
+	.ack_interrupt  = &vsc824x_ack_interrupt,
+	.config_intr    = &vsc82xx_config_intr,
+}, {
+	.phy_id         = PHY_ID_VSC7385,
+	.name           = "Vitesse VSC7385",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.config_init    = vsc738x_config_init,
+	.config_aneg    = vsc73xx_config_aneg,
+	.read_page      = vsc73xx_read_page,
+	.write_page     = vsc73xx_write_page,
+}, {
+	.phy_id         = PHY_ID_VSC7388,
+	.name           = "Vitesse VSC7388",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.config_init    = vsc738x_config_init,
+	.config_aneg    = vsc73xx_config_aneg,
+	.read_page      = vsc73xx_read_page,
+	.write_page     = vsc73xx_write_page,
+}, {
+	.phy_id         = PHY_ID_VSC7395,
+	.name           = "Vitesse VSC7395",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.config_init    = vsc739x_config_init,
+	.config_aneg    = vsc73xx_config_aneg,
+	.read_page      = vsc73xx_read_page,
+	.write_page     = vsc73xx_write_page,
+}, {
+	.phy_id         = PHY_ID_VSC7398,
+	.name           = "Vitesse VSC7398",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.config_init    = vsc739x_config_init,
+	.config_aneg    = vsc73xx_config_aneg,
+	.read_page      = vsc73xx_read_page,
+	.write_page     = vsc73xx_write_page,
+}, {
+	.phy_id         = PHY_ID_VSC8662,
+	.name           = "Vitesse VSC8662",
+	.phy_id_mask    = 0x000ffff0,
+	.features       = PHY_GBIT_FEATURES,
+	.flags          = PHY_HAS_INTERRUPT,
+	.config_init    = &vsc824x_config_init,
+	.config_aneg    = &vsc82x4_config_aneg,
+	.ack_interrupt  = &vsc824x_ack_interrupt,
+	.config_intr    = &vsc82xx_config_intr,
+}, {
+	/* Vitesse 8221 */
+	.phy_id		= PHY_ID_VSC8221,
+	.phy_id_mask	= 0x000ffff0,
+	.name		= "Vitesse VSC8221",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= &vsc8221_config_init,
+	.ack_interrupt	= &vsc824x_ack_interrupt,
+	.config_intr	= &vsc82xx_config_intr,
+}, {
+	/* Vitesse 8211 */
+	.phy_id		= PHY_ID_VSC8211,
+	.phy_id_mask	= 0x000ffff0,
+	.name		= "Vitesse VSC8211",
+	.features	= PHY_GBIT_FEATURES,
+	.flags		= PHY_HAS_INTERRUPT,
+	.config_init	= &vsc8221_config_init,
+	.ack_interrupt	= &vsc824x_ack_interrupt,
+	.config_intr	= &vsc82xx_config_intr,
+} };
+
+module_phy_driver(vsc82xx_driver);
+
+static struct mdio_device_id __maybe_unused vitesse_tbl[] = {
+	{ PHY_ID_VSC8234, 0x000ffff0 },
+	{ PHY_ID_VSC8244, 0x000fffc0 },
+	{ PHY_ID_VSC8514, 0x000ffff0 },
+	{ PHY_ID_VSC8572, 0x000ffff0 },
+	{ PHY_ID_VSC8574, 0x000ffff0 },
+	{ PHY_ID_VSC7385, 0x000ffff0 },
+	{ PHY_ID_VSC7388, 0x000ffff0 },
+	{ PHY_ID_VSC7395, 0x000ffff0 },
+	{ PHY_ID_VSC7398, 0x000ffff0 },
+	{ PHY_ID_VSC8662, 0x000ffff0 },
+	{ PHY_ID_VSC8221, 0x000ffff0 },
+	{ PHY_ID_VSC8211, 0x000ffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, vitesse_tbl);
diff --git a/src/kernel/linux/v4.19/drivers/net/phy/xilinx_gmii2rgmii.c b/src/kernel/linux/v4.19/drivers/net/phy/xilinx_gmii2rgmii.c
new file mode 100644
index 0000000..bd6084e
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/net/phy/xilinx_gmii2rgmii.c
@@ -0,0 +1,125 @@
+/* Xilinx GMII2RGMII Converter driver
+ *
+ * Copyright (C) 2016 Xilinx, Inc.
+ * Copyright (C) 2016 Andrew Lunn <andrew@lunn.ch>
+ *
+ * Author: Andrew Lunn <andrew@lunn.ch>
+ * Author: Kedareswara rao Appana <appanad@xilinx.com>
+ *
+ * Description:
+ * This driver is developed for Xilinx GMII2RGMII Converter
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/of_mdio.h>
+
+#define XILINX_GMII2RGMII_REG		0x10
+#define XILINX_GMII2RGMII_SPEED_MASK	(BMCR_SPEED1000 | BMCR_SPEED100)
+
+struct gmii2rgmii {
+	struct phy_device *phy_dev;
+	struct phy_driver *phy_drv;
+	struct phy_driver conv_phy_drv;
+	struct mdio_device *mdio;
+};
+
+static int xgmiitorgmii_read_status(struct phy_device *phydev)
+{
+	struct gmii2rgmii *priv = phydev->priv;
+	struct mii_bus *bus = priv->mdio->bus;
+	int addr = priv->mdio->addr;
+	u16 val = 0;
+	int err;
+
+	if (priv->phy_drv->read_status)
+		err = priv->phy_drv->read_status(phydev);
+	else
+		err = genphy_read_status(phydev);
+	if (err < 0)
+		return err;
+
+	val = mdiobus_read(bus, addr, XILINX_GMII2RGMII_REG);
+	val &= ~XILINX_GMII2RGMII_SPEED_MASK;
+
+	if (phydev->speed == SPEED_1000)
+		val |= BMCR_SPEED1000;
+	else if (phydev->speed == SPEED_100)
+		val |= BMCR_SPEED100;
+	else
+		val |= BMCR_SPEED10;
+
+	mdiobus_write(bus, addr, XILINX_GMII2RGMII_REG, val);
+
+	return 0;
+}
+
+static int xgmiitorgmii_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct device_node *np = dev->of_node, *phy_node;
+	struct gmii2rgmii *priv;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phy_node = of_parse_phandle(np, "phy-handle", 0);
+	if (!phy_node) {
+		dev_err(dev, "Couldn't parse phy-handle\n");
+		return -ENODEV;
+	}
+
+	priv->phy_dev = of_phy_find_device(phy_node);
+	of_node_put(phy_node);
+	if (!priv->phy_dev) {
+		dev_info(dev, "Couldn't find phydev\n");
+		return -EPROBE_DEFER;
+	}
+
+	if (!priv->phy_dev->drv) {
+		dev_info(dev, "Attached phy not ready\n");
+		return -EPROBE_DEFER;
+	}
+
+	priv->mdio = mdiodev;
+	priv->phy_drv = priv->phy_dev->drv;
+	memcpy(&priv->conv_phy_drv, priv->phy_dev->drv,
+	       sizeof(struct phy_driver));
+	priv->conv_phy_drv.read_status = xgmiitorgmii_read_status;
+	priv->phy_dev->priv = priv;
+	priv->phy_dev->drv = &priv->conv_phy_drv;
+
+	return 0;
+}
+
+static const struct of_device_id xgmiitorgmii_of_match[] = {
+	{ .compatible = "xlnx,gmii-to-rgmii-1.0" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, xgmiitorgmii_of_match);
+
+static struct mdio_driver xgmiitorgmii_driver = {
+	.probe	= xgmiitorgmii_probe,
+	.mdiodrv.driver = {
+		.name = "xgmiitorgmii",
+		.of_match_table = xgmiitorgmii_of_match,
+	},
+};
+
+mdio_module_driver(xgmiitorgmii_driver);
+
+MODULE_DESCRIPTION("Xilinx GMII2RGMII converter driver");
+MODULE_LICENSE("GPL");