summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/net/phy/Kconfig444
-rw-r--r--drivers/net/phy/Makefile87
-rw-r--r--drivers/net/phy/amd.c82
-rw-r--r--drivers/net/phy/aquantia.c204
-rw-r--r--drivers/net/phy/at803x.c428
-rw-r--r--drivers/net/phy/ax88796b.c63
-rw-r--r--drivers/net/phy/bcm-cygnus.c155
-rw-r--r--drivers/net/phy/bcm-phy-lib.c389
-rw-r--r--drivers/net/phy/bcm-phy-lib.h57
-rw-r--r--drivers/net/phy/bcm63xx.c94
-rw-r--r--drivers/net/phy/bcm7xxx.c791
-rw-r--r--drivers/net/phy/bcm87xx.c220
-rw-r--r--drivers/net/phy/broadcom.c790
-rw-r--r--drivers/net/phy/cicada.c134
-rw-r--r--drivers/net/phy/cortina.c110
-rw-r--r--drivers/net/phy/davicom.c198
-rw-r--r--drivers/net/phy/dp83640.c1569
-rw-r--r--drivers/net/phy/dp83640_reg.h268
-rw-r--r--drivers/net/phy/dp83822.c342
-rw-r--r--drivers/net/phy/dp83848.c137
-rw-r--r--drivers/net/phy/dp83867.c401
-rw-r--r--drivers/net/phy/dp83tc811.c371
-rw-r--r--drivers/net/phy/et1011c.c107
-rw-r--r--drivers/net/phy/fixed_phy.c311
-rw-r--r--drivers/net/phy/icplus.c253
-rw-r--r--drivers/net/phy/intel-xway.c384
-rw-r--r--drivers/net/phy/lxt.c300
-rw-r--r--drivers/net/phy/marvell.c2402
-rw-r--r--drivers/net/phy/marvell10g.c580
-rw-r--r--drivers/net/phy/mdio-bcm-iproc.c231
-rw-r--r--drivers/net/phy/mdio-bcm-unimac.c372
-rw-r--r--drivers/net/phy/mdio-bitbang.c235
-rw-r--r--drivers/net/phy/mdio-boardinfo.c83
-rw-r--r--drivers/net/phy/mdio-boardinfo.h23
-rw-r--r--drivers/net/phy/mdio-cavium.c153
-rw-r--r--drivers/net/phy/mdio-cavium.h119
-rw-r--r--drivers/net/phy/mdio-gpio.c213
-rw-r--r--drivers/net/phy/mdio-hisi-femac.c166
-rw-r--r--drivers/net/phy/mdio-i2c.c109
-rw-r--r--drivers/net/phy/mdio-i2c.h19
-rw-r--r--drivers/net/phy/mdio-moxart.c193
-rw-r--r--drivers/net/phy/mdio-mscc-miim.c193
-rw-r--r--drivers/net/phy/mdio-mux-bcm-iproc.c342
-rw-r--r--drivers/net/phy/mdio-mux-gpio.c109
-rw-r--r--drivers/net/phy/mdio-mux-mmioreg.c207
-rw-r--r--drivers/net/phy/mdio-mux.c225
-rw-r--r--drivers/net/phy/mdio-octeon.c123
-rw-r--r--drivers/net/phy/mdio-sun4i.c182
-rw-r--r--drivers/net/phy/mdio-thunder.c153
-rw-r--r--drivers/net/phy/mdio-xgene.c480
-rw-r--r--drivers/net/phy/mdio-xgene.h142
-rw-r--r--drivers/net/phy/mdio_bus.c762
-rw-r--r--drivers/net/phy/mdio_device.c223
-rw-r--r--drivers/net/phy/meson-gxl.c265
-rw-r--r--drivers/net/phy/micrel.c1059
-rw-r--r--drivers/net/phy/microchip.c377
-rw-r--r--drivers/net/phy/microchip_t1.c74
-rw-r--r--drivers/net/phy/mscc.c761
-rw-r--r--drivers/net/phy/national.c157
-rw-r--r--drivers/net/phy/phy-c45.c343
-rw-r--r--drivers/net/phy/phy-core.c529
-rw-r--r--drivers/net/phy/phy.c1371
-rw-r--r--drivers/net/phy/phy_device.c2045
-rw-r--r--drivers/net/phy/phy_led_triggers.c177
-rw-r--r--drivers/net/phy/phylink.c1754
-rw-r--r--drivers/net/phy/qsemi.c132
-rw-r--r--drivers/net/phy/realtek.c320
-rw-r--r--drivers/net/phy/rockchip.c232
-rw-r--r--drivers/net/phy/sfp-bus.c700
-rw-r--r--drivers/net/phy/sfp.c1957
-rw-r--r--drivers/net/phy/sfp.h30
-rw-r--r--drivers/net/phy/smsc.c376
-rw-r--r--drivers/net/phy/spi_ks8995.c552
-rw-r--r--drivers/net/phy/ste10Xp.c121
-rw-r--r--drivers/net/phy/swphy.c179
-rw-r--r--drivers/net/phy/swphy.h10
-rw-r--r--drivers/net/phy/teranetics.c99
-rw-r--r--drivers/net/phy/uPD60620.c108
-rw-r--r--drivers/net/phy/vitesse.c542
-rw-r--r--drivers/net/phy/xilinx_gmii2rgmii.c125
80 files changed, 31123 insertions, 0 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
new file mode 100644
index 000000000..2386871e1
--- /dev/null
+++ b/drivers/net/phy/Kconfig
@@ -0,0 +1,444 @@
+#
+# 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
+ select MDIO_DEVRES
+ 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 "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 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 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 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 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.
+
+endif # PHYLIB
+
+config MICREL_KS8995MA
+ tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch"
+ depends on SPI
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
new file mode 100644
index 000000000..f21cda9d8
--- /dev/null
+++ b/drivers/net/phy/Makefile
@@ -0,0 +1,87 @@
+# 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_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_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_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_QSEMI_PHY) += qsemi.o
+obj-$(CONFIG_REALTEK_PHY) += 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
diff --git a/drivers/net/phy/amd.c b/drivers/net/phy/amd.c
new file mode 100644
index 000000000..6fe5dc920
--- /dev/null
+++ b/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/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c
new file mode 100644
index 000000000..319edc9c8
--- /dev/null
+++ b/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/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c
new file mode 100644
index 000000000..411cf1072
--- /dev/null
+++ b/drivers/net/phy/at803x.c
@@ -0,0 +1,428 @@
+/*
+ * 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_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 ATH8035_PHY_ID 0x004dd072
+#define AT803X_PHY_ID_MASK 0xffffffef
+
+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;
+
+ 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 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,
+} };
+
+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 },
+ { ATH8035_PHY_ID, AT803X_PHY_ID_MASK },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, atheros_tbl);
diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
new file mode 100644
index 000000000..8ebe7f548
--- /dev/null
+++ b/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/drivers/net/phy/bcm-cygnus.c b/drivers/net/phy/bcm-cygnus.c
new file mode 100644
index 000000000..e757b09f1
--- /dev/null
+++ b/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/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c
new file mode 100644
index 000000000..7e5892597
--- /dev/null
+++ b/drivers/net/phy/bcm-phy-lib.c
@@ -0,0 +1,389 @@
+/*
+ * 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, mask = 0;
+
+ /* 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 (phydev->supported & SUPPORTED_1000baseT_Full)
+ mask |= MDIO_EEE_1000T;
+ if (phydev->supported & SUPPORTED_100baseT_Full)
+ mask |= MDIO_EEE_100TX;
+
+ if (enable)
+ val |= mask;
+ else
+ val &= ~mask;
+
+ 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/drivers/net/phy/bcm-phy-lib.h b/drivers/net/phy/bcm-phy-lib.h
new file mode 100644
index 000000000..81cceaa41
--- /dev/null
+++ b/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/drivers/net/phy/bcm63xx.c b/drivers/net/phy/bcm63xx.c
new file mode 100644
index 000000000..cf1461374
--- /dev/null
+++ b/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/drivers/net/phy/bcm7xxx.c b/drivers/net/phy/bcm7xxx.c
new file mode 100644
index 000000000..35dc4ca69
--- /dev/null
+++ b/drivers/net/phy/bcm7xxx.c
@@ -0,0 +1,791 @@
+/*
+ * 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_PCS_CTRL 0x0
+#define MII_BCM7XXX_SHD_3_PCS_STATUS 0x1
+#define MII_BCM7XXX_SHD_3_EEE_CAP 0x2
+#define MII_BCM7XXX_SHD_3_AN_EEE_ADV 0x3
+#define MII_BCM7XXX_SHD_3_EEE_LP 0x4
+#define MII_BCM7XXX_SHD_3_EEE_WK_ERR 0x5
+#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);
+}
+
+#define MII_BCM7XXX_REG_INVALID 0xff
+
+static u8 bcm7xxx_28nm_ephy_regnum_to_shd(u16 regnum)
+{
+ switch (regnum) {
+ case MDIO_CTRL1:
+ return MII_BCM7XXX_SHD_3_PCS_CTRL;
+ case MDIO_STAT1:
+ return MII_BCM7XXX_SHD_3_PCS_STATUS;
+ case MDIO_PCS_EEE_ABLE:
+ return MII_BCM7XXX_SHD_3_EEE_CAP;
+ case MDIO_AN_EEE_ADV:
+ return MII_BCM7XXX_SHD_3_AN_EEE_ADV;
+ case MDIO_AN_EEE_LPABLE:
+ return MII_BCM7XXX_SHD_3_EEE_LP;
+ case MDIO_PCS_EEE_WK_ERR:
+ return MII_BCM7XXX_SHD_3_EEE_WK_ERR;
+ default:
+ return MII_BCM7XXX_REG_INVALID;
+ }
+}
+
+static bool bcm7xxx_28nm_ephy_dev_valid(int devnum)
+{
+ return devnum == MDIO_MMD_AN || devnum == MDIO_MMD_PCS;
+}
+
+static int bcm7xxx_28nm_ephy_read_mmd(struct phy_device *phydev,
+ int devnum, u16 regnum)
+{
+ u8 shd = bcm7xxx_28nm_ephy_regnum_to_shd(regnum);
+ int ret;
+
+ if (!bcm7xxx_28nm_ephy_dev_valid(devnum) ||
+ shd == MII_BCM7XXX_REG_INVALID)
+ return -EOPNOTSUPP;
+
+ /* set shadow mode 2 */
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+ MII_BCM7XXX_SHD_MODE_2, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Access the desired shadow register address */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL, shd);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ ret = phy_read(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT);
+
+reset_shadow_mode:
+ /* reset shadow mode 2 */
+ phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0,
+ MII_BCM7XXX_SHD_MODE_2);
+ return ret;
+}
+
+static int bcm7xxx_28nm_ephy_write_mmd(struct phy_device *phydev,
+ int devnum, u16 regnum, u16 val)
+{
+ u8 shd = bcm7xxx_28nm_ephy_regnum_to_shd(regnum);
+ int ret;
+
+ if (!bcm7xxx_28nm_ephy_dev_valid(devnum) ||
+ shd == MII_BCM7XXX_REG_INVALID)
+ return -EOPNOTSUPP;
+
+ /* set shadow mode 2 */
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+ MII_BCM7XXX_SHD_MODE_2, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Access the desired shadow register address */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL, shd);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ /* Write the desired value in the shadow register */
+ phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT, val);
+
+reset_shadow_mode:
+ /* reset shadow mode 2 */
+ return phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0,
+ MII_BCM7XXX_SHD_MODE_2);
+}
+
+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, \
+ .read_mmd = bcm7xxx_28nm_ephy_read_mmd, \
+ .write_mmd = bcm7xxx_28nm_ephy_write_mmd, \
+}
+
+#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/drivers/net/phy/bcm87xx.c b/drivers/net/phy/bcm87xx.c
new file mode 100644
index 000000000..f7ebdcff5
--- /dev/null
+++ b/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/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
new file mode 100644
index 000000000..94622d119
--- /dev/null
+++ b/drivers/net/phy/broadcom.c
@@ -0,0 +1,790 @@
+/*
+ * 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/delay.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;
+
+ /* The datasheet indicates the PHY needs up to 1us to complete a reset,
+ * build some slack here.
+ */
+ usleep_range(1000, 2000);
+
+ /* The PHY requires 65 MDC clock cycles to complete a write operation
+ * and turnaround the line properly.
+ *
+ * We ignore -EIO here as the MDIO controller (e.g.: mdio-bcm-unimac)
+ * may flag the lack of turn-around as a read failure. This is
+ * particularly true with this combination since the MDIO controller
+ * only used 64 MDC cycles. This is not a critical failure in this
+ * specific case and it has no functional impact otherwise, so we let
+ * that one go through. If there is a genuine bus error, the next read
+ * of MII_BRCM_FET_INTREG will error out.
+ */
+ err = phy_read(phydev, MII_BMCR);
+ if (err < 0 && err != -EIO)
+ 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/drivers/net/phy/cicada.c b/drivers/net/phy/cicada.c
new file mode 100644
index 000000000..c05af00bf
--- /dev/null
+++ b/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/drivers/net/phy/cortina.c b/drivers/net/phy/cortina.c
new file mode 100644
index 000000000..8022cd317
--- /dev/null
+++ b/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/drivers/net/phy/davicom.c b/drivers/net/phy/davicom.c
new file mode 100644
index 000000000..5ee99b3b4
--- /dev/null
+++ b/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/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
new file mode 100644
index 000000000..dd0a658c8
--- /dev/null
+++ b/drivers/net/phy/dp83640.c
@@ -0,0 +1,1569 @@
+/*
+ * 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(&clock->list, &phyter_clocks);
+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;
+ cfg.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
+ 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;
+ cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
+ 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;
+ cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+ 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;
+ cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+ 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/drivers/net/phy/dp83640_reg.h b/drivers/net/phy/dp83640_reg.h
new file mode 100644
index 000000000..daae7fa58
--- /dev/null
+++ b/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/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c
new file mode 100644
index 000000000..9e2ed98f7
--- /dev/null
+++ b/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_MISR2, 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/drivers/net/phy/dp83848.c b/drivers/net/phy/dp83848.c
new file mode 100644
index 000000000..6e8e42361
--- /dev/null
+++ b/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/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
new file mode 100644
index 000000000..879096d3f
--- /dev/null
+++ b/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/drivers/net/phy/dp83tc811.c b/drivers/net/phy/dp83tc811.c
new file mode 100644
index 000000000..78cad134a
--- /dev/null
+++ b/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/drivers/net/phy/et1011c.c b/drivers/net/phy/et1011c.c
new file mode 100644
index 000000000..a9a4edfa2
--- /dev/null
+++ b/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/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
new file mode 100644
index 000000000..598201645
--- /dev/null
+++ b/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);
+ /* Check the GPIO for change in 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/drivers/net/phy/icplus.c b/drivers/net/phy/icplus.c
new file mode 100644
index 000000000..791587a49
--- /dev/null
+++ b/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/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c
new file mode 100644
index 000000000..7d936fb61
--- /dev/null
+++ b/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/drivers/net/phy/lxt.c b/drivers/net/phy/lxt.c
new file mode 100644
index 000000000..c14b254b2
--- /dev/null
+++ b/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/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
new file mode 100644
index 000000000..418b28b42
--- /dev/null
+++ b/drivers/net/phy/marvell.c
@@ -0,0 +1,2402 @@
+/*
+ * 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 = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+ if (err < 0)
+ return err;
+
+ err = genphy_config_aneg(phydev);
+ if (err < 0)
+ return err;
+
+ return genphy_soft_reset(phydev);
+}
+
+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;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ err = m88e1121_config_aneg_rgmii_delays(phydev);
+ 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_88E6341_FAMILY,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E6341 Family",
+ .features = PHY_GBIT_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .probe = m88e1510_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,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E6390_FAMILY,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E6390 Family",
+ .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_88E6341_FAMILY, MARVELL_PHY_ID_MASK },
+ { MARVELL_PHY_ID_88E6390_FAMILY, MARVELL_PHY_ID_MASK },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, marvell_tbl);
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
new file mode 100644
index 000000000..456a1f882
--- /dev/null
+++ b/drivers/net/phy/marvell10g.c
@@ -0,0 +1,580 @@
+/*
+ * 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>
+
+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_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 0;
+}
+
+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/drivers/net/phy/mdio-bcm-iproc.c b/drivers/net/phy/mdio-bcm-iproc.c
new file mode 100644
index 000000000..51ce3ea17
--- /dev/null
+++ b/drivers/net/phy/mdio-bcm-iproc.c
@@ -0,0 +1,231 @@
+/*
+ * 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+int iproc_mdio_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iproc_mdio_priv *priv = platform_get_drvdata(pdev);
+
+ /* restore the mii clock configuration */
+ iproc_mdio_config_clk(priv->base);
+
+ return 0;
+}
+
+static const struct dev_pm_ops iproc_mdio_pm_ops = {
+ .resume = iproc_mdio_resume
+};
+#endif /* CONFIG_PM_SLEEP */
+
+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,
+#ifdef CONFIG_PM_SLEEP
+ .pm = &iproc_mdio_pm_ops,
+#endif
+ },
+ .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/drivers/net/phy/mdio-bcm-unimac.c b/drivers/net/phy/mdio-bcm-unimac.c
new file mode 100644
index 000000000..df75efa96
--- /dev/null
+++ b/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/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c
new file mode 100644
index 000000000..15352f987
--- /dev/null
+++ b/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/drivers/net/phy/mdio-boardinfo.c b/drivers/net/phy/mdio-boardinfo.c
new file mode 100644
index 000000000..863496fa5
--- /dev/null
+++ b/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/drivers/net/phy/mdio-boardinfo.h b/drivers/net/phy/mdio-boardinfo.h
new file mode 100644
index 000000000..773bb5139
--- /dev/null
+++ b/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/drivers/net/phy/mdio-cavium.c b/drivers/net/phy/mdio-cavium.c
new file mode 100644
index 000000000..6df2fa755
--- /dev/null
+++ b/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/drivers/net/phy/mdio-cavium.h b/drivers/net/phy/mdio-cavium.h
new file mode 100644
index 000000000..4bccd45d2
--- /dev/null
+++ b/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/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c
new file mode 100644
index 000000000..0fbcedcdf
--- /dev/null
+++ b/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/drivers/net/phy/mdio-hisi-femac.c b/drivers/net/phy/mdio-hisi-femac.c
new file mode 100644
index 000000000..b03fedd6c
--- /dev/null
+++ b/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/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c
new file mode 100644
index 000000000..6d24fd13c
--- /dev/null
+++ b/drivers/net/phy/mdio-i2c.c
@@ -0,0 +1,109 @@
+/*
+ * 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 data[2], dev_addr = reg;
+ int bus_addr, ret;
+
+ if (!i2c_mii_valid_phy_id(phy_id))
+ return 0xffff;
+
+ bus_addr = i2c_mii_phy_addr(phy_id);
+ 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 = 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[3];
+
+ if (!i2c_mii_valid_phy_id(phy_id))
+ return 0;
+
+ data[0] = reg;
+ data[1] = val >> 8;
+ data[2] = val;
+
+ msg.addr = i2c_mii_phy_addr(phy_id);
+ msg.flags = 0;
+ msg.len = 3;
+ 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/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h
new file mode 100644
index 000000000..889ab57d7
--- /dev/null
+++ b/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/drivers/net/phy/mdio-moxart.c b/drivers/net/phy/mdio-moxart.c
new file mode 100644
index 000000000..5bb56d126
--- /dev/null
+++ b/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/drivers/net/phy/mdio-mscc-miim.c b/drivers/net/phy/mdio-mscc-miim.c
new file mode 100644
index 000000000..badbc99be
--- /dev/null
+++ b/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/drivers/net/phy/mdio-mux-bcm-iproc.c b/drivers/net/phy/mdio-mux-bcm-iproc.c
new file mode 100644
index 000000000..575e0bd76
--- /dev/null
+++ b/drivers/net/phy/mdio-mux-bcm-iproc.c
@@ -0,0 +1,342 @@
+/*
+ * 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);
+ int rc;
+
+ rc = clk_prepare_enable(md->core_clk);
+ if (rc) {
+ dev_err(md->dev, "failed to enable core clk\n");
+ return rc;
+ }
+ 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/drivers/net/phy/mdio-mux-gpio.c b/drivers/net/phy/mdio-mux-gpio.c
new file mode 100644
index 000000000..bc90764a8
--- /dev/null
+++ b/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/drivers/net/phy/mdio-mux-mmioreg.c b/drivers/net/phy/mdio-mux-mmioreg.c
new file mode 100644
index 000000000..70f611553
--- /dev/null
+++ b/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/drivers/net/phy/mdio-mux.c b/drivers/net/phy/mdio-mux.c
new file mode 100644
index 000000000..c16f875ed
--- /dev/null
+++ b/drivers/net/phy/mdio-mux.c
@@ -0,0 +1,225 @@
+/*
+ * 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;
+
+static void mdio_mux_uninit_children(struct mdio_mux_parent_bus *pb)
+{
+ struct mdio_mux_child_bus *cb = pb->children;
+
+ while (cb) {
+ mdiobus_unregister(cb->mii_bus);
+ mdiobus_free(cb->mii_bus);
+ cb = cb->next;
+ }
+}
+
+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;
+ goto err_loop;
+ }
+ cb->bus_number = v;
+ cb->parent = pb;
+
+ cb->mii_bus = mdiobus_alloc();
+ if (!cb->mii_bus) {
+ ret_val = -ENOMEM;
+ goto err_loop;
+ }
+ 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) {
+ mdiobus_free(cb->mii_bus);
+ if (r == -EPROBE_DEFER) {
+ ret_val = r;
+ goto err_loop;
+ }
+ devm_kfree(dev, cb);
+ dev_err(dev,
+ "Error: Failed to register MDIO bus for child %pOF\n",
+ child_bus_node);
+ } 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_loop:
+ mdio_mux_uninit_children(pb);
+ of_node_put(child_bus_node);
+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;
+
+ mdio_mux_uninit_children(pb);
+ 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/drivers/net/phy/mdio-octeon.c b/drivers/net/phy/mdio-octeon.c
new file mode 100644
index 000000000..1da104150
--- /dev/null
+++ b/drivers/net/phy/mdio-octeon.c
@@ -0,0 +1,123 @@
+/*
+ * 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:
+ 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);
+ 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/drivers/net/phy/mdio-sun4i.c b/drivers/net/phy/mdio-sun4i.c
new file mode 100644
index 000000000..6425ce04d
--- /dev/null
+++ b/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/drivers/net/phy/mdio-thunder.c b/drivers/net/phy/mdio-thunder.c
new file mode 100644
index 000000000..c0c922eff
--- /dev/null
+++ b/drivers/net/phy/mdio-thunder.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/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);
+ 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/drivers/net/phy/mdio-xgene.c b/drivers/net/phy/mdio-xgene.c
new file mode 100644
index 000000000..07c604820
--- /dev/null
+++ b/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/drivers/net/phy/mdio-xgene.h b/drivers/net/phy/mdio-xgene.h
new file mode 100644
index 000000000..3c85f3e30
--- /dev/null
+++ b/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/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
new file mode 100644
index 000000000..efdac68da
--- /dev/null
+++ b/drivers/net/phy/mdio_bus.c
@@ -0,0 +1,762 @@
+/* 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 (IS_ERR(gpiod)) {
+ if (PTR_ERR(gpiod) == -ENOENT || PTR_ERR(gpiod) == -ENOSYS)
+ gpiod = NULL;
+ else
+ 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);
+
+ /* We need to set state to MDIOBUS_UNREGISTERED to correctly release
+ * the device in mdiobus_free()
+ *
+ * State will be updated later in this function in case of success
+ */
+ bus->state = MDIOBUS_UNREGISTERED;
+
+ 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;
+ dev_dbg(&bus->dev, "probed\n");
+ 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;
+
+ if (WARN_ON_ONCE(bus->state != MDIOBUS_REGISTERED))
+ return;
+ 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;
+}
+
+#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/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c
new file mode 100644
index 000000000..922f0f897
--- /dev/null
+++ b/drivers/net/phy/mdio_device.c
@@ -0,0 +1,223 @@
+/* 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;
+}
+
+static void mdio_shutdown(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->shutdown)
+ mdiodrv->shutdown(mdiodev);
+}
+
+/**
+ * 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;
+ mdiodrv->driver.shutdown = mdio_shutdown;
+
+ 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/drivers/net/phy/meson-gxl.c b/drivers/net/phy/meson-gxl.c
new file mode 100644
index 000000000..7ceebbc4b
--- /dev/null
+++ b/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/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c
new file mode 100644
index 000000000..1d00a5638
--- /dev/null
+++ b/drivers/net/phy/micrel.c
@@ -0,0 +1,1059 @@
+/*
+ * 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>
+
+/* Operation Mode Strap Override */
+#define MII_KSZPHY_OMSO 0x16
+#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->type && 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 && type->has_broadcast_disable)
+ kszphy_broadcast_disable(phydev);
+
+ if (type && 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 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 && 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;
+
+ if (type)
+ 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 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,
+ /* No suspend/resume callbacks because of errata DS80000700A,
+ * receiver error following software power down.
+ */
+}, {
+ .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,
+ .soft_reset = genphy_soft_reset,
+ .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_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_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/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c
new file mode 100644
index 000000000..2d6793786
--- /dev/null
+++ b/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/drivers/net/phy/microchip_t1.c b/drivers/net/phy/microchip_t1.c
new file mode 100644
index 000000000..b1917dd19
--- /dev/null
+++ b/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/drivers/net/phy/mscc.c b/drivers/net/phy/mscc.c
new file mode 100644
index 000000000..36647b70b
--- /dev/null
+++ b/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/drivers/net/phy/national.c b/drivers/net/phy/national.c
new file mode 100644
index 000000000..bf4070ef6
--- /dev/null
+++ b/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/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
new file mode 100644
index 000000000..0ba360758
--- /dev/null
+++ b/drivers/net/phy/phy-c45.c
@@ -0,0 +1,343 @@
+/*
+ * 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_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/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c
new file mode 100644
index 000000000..c7da4cbb1
--- /dev/null
+++ b/drivers/net/phy/phy-core.c
@@ -0,0 +1,529 @@
+/*
+ * 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;
+
+ mutex_lock(&bus->mdio_lock);
+ 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);
+ mutex_unlock(&bus->mdio_lock);
+ }
+ return val;
+}
+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;
+
+ mutex_lock(&bus->mdio_lock);
+ 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);
+ mutex_unlock(&bus->mdio_lock);
+
+ ret = 0;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(phy_write_mmd);
+
+/**
+ * __phy_modify() - 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
+ */
+int __phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+ int ret;
+
+ ret = __phy_read(phydev, regnum);
+ if (ret < 0)
+ return ret;
+
+ ret = __phy_write(phydev, regnum, (ret & ~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);
+
+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/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
new file mode 100644
index 000000000..dd4bf4265
--- /dev/null
+++ b/drivers/net/phy/phy.c
@@ -0,0 +1,1371 @@
+/* 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/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;
+
+ if (autoneg == AUTONEG_DISABLE) {
+ phydev->speed = speed;
+ phydev->duplex = duplex;
+ }
+
+ phydev->advertising = advertising;
+
+ if (autoneg == AUTONEG_ENABLE)
+ phydev->advertising |= ADVERTISED_Autoneg;
+ else
+ phydev->advertising &= ~ADVERTISED_Autoneg;
+
+ 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;
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ mii_data->phy_id = phydev->mdio.addr;
+ /* fall through */
+
+ case SIOCGMIIREG:
+ mii_data->val_out = mdiobus_read(phydev->mdio.bus,
+ mii_data->phy_id,
+ mii_data->reg_num);
+ return 0;
+
+ case SIOCSMIIREG:
+ if (mii_data->phy_id == phydev->mdio.addr) {
+ switch (mii_data->reg_num) {
+ 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, mii_data->phy_id,
+ mii_data->reg_num, val);
+
+ if (mii_data->phy_id == phydev->mdio.addr &&
+ mii_data->reg_num == 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);
+
+ 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);
+
+ 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.
+ */
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ 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/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
new file mode 100644
index 000000000..a03d0627e
--- /dev/null
+++ b/drivers/net/phy/phy_device.c
@@ -0,0 +1,2045 @@
+/* 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/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)
+ goto out;
+
+ 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;
+
+out:
+ return !phydev->suspended;
+}
+
+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;
+
+ phydev->suspended_by_mdio_bus = 1;
+
+ return phy_suspend(phydev);
+}
+
+static int mdio_bus_phy_resume(struct device *dev)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ int ret;
+
+ if (!phydev->suspended_by_mdio_bus)
+ goto no_resume;
+
+ phydev->suspended_by_mdio_bus = 0;
+
+ 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.devices_in_package & (1 << i)))
+ 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) {
+ /* returning -ENODEV doesn't stop bus scanning */
+ return (phy_reg == -EIO || phy_reg == -ENODEV) ? -ENODEV : -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 = {0};
+ u32 phy_id = 0;
+ int r;
+
+ 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);
+ else
+ ret = genphy_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_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;
+
+ /* 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->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);
+
+ if (phydev->mdio.dev.driver)
+ 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);
+
+ /* Assert the reset signal */
+ phy_device_reset(phydev, 1);
+
+ /*
+ * 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);
+}
+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;
+
+ /* 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 = 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 = 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_set_bits(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);
+
+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);
+
+ 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/drivers/net/phy/phy_led_triggers.c b/drivers/net/phy/phy_led_triggers.c
new file mode 100644
index 000000000..7278eca70
--- /dev/null
+++ b/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/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
new file mode 100644
index 000000000..e0e175c69
--- /dev/null
+++ b/drivers/net/phy/phylink.c
@@ -0,0 +1,1754 @@
+/*
+ * 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 link_an_mode; /* MLO_AN_xxx */
+ 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;
+ 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;
+};
+
+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->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->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->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->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->link_an_mode, 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->link_an_mode) {
+ case MLO_AN_PHY:
+ link_state = pl->phy_state;
+ phylink_resolve_flow(pl, &link_state);
+ phylink_mac_config(pl, &link_state);
+ break;
+
+ case MLO_AN_FIXED:
+ phylink_get_fixed_state(pl, &link_state);
+ phylink_mac_config(pl, &link_state);
+ break;
+
+ case MLO_AN_INBAND:
+ phylink_get_mac_state(pl, &link_state);
+ if (pl->phydev) {
+ bool changed = false;
+
+ link_state.link = link_state.link &&
+ pl->phy_state.link;
+
+ if (pl->phy_state.interface !=
+ link_state.interface) {
+ link_state.interface = pl->phy_state.interface;
+ changed = true;
+ }
+
+ /* Propagate the flow control from the PHY
+ * to the MAC. Also propagate the interface
+ * if changed.
+ */
+ if (pl->phy_state.link || changed) {
+ 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->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->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 fwnode_reference_args ref;
+ int ret;
+
+ if (!fwnode)
+ return 0;
+
+ ret = fwnode_property_get_reference_args(fwnode, "sfp", NULL,
+ 0, 0, &ref);
+ if (ret < 0) {
+ if (ret == -ENOENT)
+ return 0;
+
+ netdev_err(pl->netdev, "unable to parse \"sfp\" node: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (!fwnode_device_is_available(ref.fwnode)) {
+ fwnode_handle_put(ref.fwnode);
+ return 0;
+ }
+
+ pl->sfp_bus = sfp_register_upstream(ref.fwnode, pl->netdev, pl,
+ &sfp_phylink_ops);
+ if (!pl->sfp_bus)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * 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->link_an_mode == MLO_AN_FIXED) {
+ ret = phylink_parse_fixedlink(pl, fwnode);
+ if (ret < 0) {
+ kfree(pl);
+ return ERR_PTR(ret);
+ }
+ }
+
+ 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)
+{
+ if (pl->sfp_bus)
+ sfp_unregister_upstream(pl->sfp_bus);
+ if (!IS_ERR_OR_NULL(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)
+{
+ struct phylink_link_state config;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+ u32 advertising;
+ int ret;
+
+ 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 = pl->link_config.interface;
+
+ /*
+ * 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.
+ */
+ if (phylink_test(supported, Pause))
+ phylink_set(config.advertising, Pause);
+ if (phylink_test(supported, Asym_Pause))
+ phylink_set(config.advertising, Asym_Pause);
+
+ 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;
+ 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_connect_phy(struct phylink *pl, struct phy_device *phy,
+ phy_interface_t interface)
+{
+ int ret;
+
+ if (WARN_ON(pl->link_an_mode == MLO_AN_FIXED ||
+ (pl->link_an_mode == MLO_AN_INBAND &&
+ phy_interface_mode_is_8023z(interface))))
+ return -EINVAL;
+
+ if (pl->phydev)
+ return -EBUSY;
+
+ ret = phy_attach_direct(pl->netdev, phy, 0, interface);
+ if (ret)
+ return ret;
+
+ ret = phylink_bringup_phy(pl, phy);
+ if (ret)
+ phy_detach(phy);
+
+ return ret;
+}
+
+/**
+ * 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)
+{
+ /* 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;
+ }
+
+ return __phylink_connect_phy(pl, phy, pl->link_interface);
+}
+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->link_an_mode == MLO_AN_FIXED ||
+ (pl->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->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);
+ 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->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);
+
+/**
+ * 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->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->link_an_mode == MLO_AN_FIXED && !IS_ERR(pl->link_gpio))
+ mod_timer(&pl->link_poll, jiffies + HZ);
+ if (pl->sfp_bus)
+ sfp_upstream_start(pl->sfp_bus);
+ if (pl->phydev)
+ phy_start(pl->phydev);
+}
+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->phydev)
+ phy_stop(pl->phydev);
+ if (pl->sfp_bus)
+ sfp_upstream_stop(pl->sfp_bus);
+ if (pl->link_an_mode == MLO_AN_FIXED && !IS_ERR(pl->link_gpio))
+ del_timer_sync(&pl->link_poll);
+
+ 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->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)
+{
+ 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;
+
+ config = pl->link_config;
+
+ /* Mask out unsupported advertisements */
+ linkmode_and(config.advertising, kset->link_modes.advertising,
+ pl->supported);
+
+ /* 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,
+ pl->supported,
+ __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->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->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, pl->supported, &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 (!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->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 (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) {
+ switch (pl->link_an_mode) {
+ case MLO_AN_PHY:
+ /* Silently mark the carrier down, and then trigger a resolve */
+ netif_carrier_off(pl->netdev);
+ phylink_run_resolve(pl);
+ break;
+
+ 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(struct net_device *ndev, unsigned int reg,
+ struct phylink_link_state *state, bool aneg)
+{
+ 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;
+ if (!aneg)
+ val &= ~BMSR_ANEGCAPABLE;
+ }
+ 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->link_an_mode) {
+ case MLO_AN_FIXED:
+ if (phy_id == 0) {
+ phylink_get_fixed_state(pl, &state);
+ val = phylink_mii_emul_read(pl->netdev, reg, &state,
+ true);
+ }
+ 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(pl->netdev, reg, &state,
+ true);
+ }
+ break;
+ }
+
+ return val & 0xffff;
+}
+
+static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
+ unsigned int reg, unsigned int val)
+{
+ switch (pl->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 int phylink_sfp_module_insert(void *upstream,
+ const struct sfp_eeprom_id *id)
+{
+ struct phylink *pl = upstream;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
+ struct phylink_link_state config;
+ phy_interface_t iface;
+ int ret = 0;
+ bool changed;
+ u8 port;
+
+ ASSERT_RTNL();
+
+ sfp_parse_support(pl->sfp_bus, id, support);
+ port = sfp_parse_port(pl->sfp_bus, id, support);
+
+ memset(&config, 0, sizeof(config));
+ linkmode_copy(config.advertising, support);
+ 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, id, 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;
+ ret = phylink_validate(pl, support, &config);
+ if (ret) {
+ netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n",
+ phylink_an_mode_str(MLO_AN_INBAND),
+ 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(MLO_AN_INBAND),
+ 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->link_an_mode != MLO_AN_INBAND ||
+ pl->link_config.interface != config.interface) {
+ pl->link_config.interface = config.interface;
+ pl->link_an_mode = MLO_AN_INBAND;
+
+ changed = true;
+
+ netdev_info(pl->netdev, "switched to %s/%s link mode\n",
+ phylink_an_mode_str(MLO_AN_INBAND),
+ phy_modes(config.interface));
+ }
+
+ pl->link_port = port;
+
+ if (changed && !test_bit(PHYLINK_DISABLE_STOPPED,
+ &pl->phylink_disable_state))
+ phylink_mac_config(pl, &pl->link_config);
+
+ return ret;
+}
+
+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);
+}
+
+static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+{
+ struct phylink *pl = upstream;
+
+ return __phylink_connect_phy(upstream, phy, pl->link_config.interface);
+}
+
+static void phylink_sfp_disconnect_phy(void *upstream)
+{
+ phylink_disconnect_phy(upstream);
+}
+
+static const struct sfp_upstream_ops sfp_phylink_ops = {
+ .module_insert = phylink_sfp_module_insert,
+ .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/drivers/net/phy/qsemi.c b/drivers/net/phy/qsemi.c
new file mode 100644
index 000000000..889a4dce1
--- /dev/null
+++ b/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/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
new file mode 100644
index 000000000..271e8adc3
--- /dev/null
+++ b/drivers/net/phy/realtek.c
@@ -0,0 +1,320 @@
+/*
+ * 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;
+
+ 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/drivers/net/phy/rockchip.c b/drivers/net/phy/rockchip.c
new file mode 100644
index 000000000..f1da70b9b
--- /dev/null
+++ b/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/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c
new file mode 100644
index 000000000..1fe7783c2
--- /dev/null
+++ b/drivers/net/phy/sfp-bus.c
@@ -0,0 +1,700 @@
+#include <linux/export.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/phylink.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 net_device *netdev;
+ 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 SFP_CONNECTOR_SC:
+ case SFP_CONNECTOR_FIBERJACK:
+ case SFP_CONNECTOR_LC:
+ case SFP_CONNECTOR_MT_RJ:
+ case SFP_CONNECTOR_MU:
+ case SFP_CONNECTOR_OPTICAL_PIGTAIL:
+ port = PORT_FIBRE;
+ break;
+
+ case SFP_CONNECTOR_RJ45:
+ port = PORT_TP;
+ break;
+
+ case SFP_CONNECTOR_COPPER_PIGTAIL:
+ port = PORT_DA;
+ break;
+
+ case SFP_CONNECTOR_UNSPEC:
+ if (id->base.e1000_base_t) {
+ port = PORT_TP;
+ break;
+ }
+ /* fallthrough */
+ case SFP_CONNECTOR_SG: /* guess */
+ case SFP_CONNECTOR_MPO_1X12:
+ case SFP_CONNECTOR_MPO_2X16:
+ case SFP_CONNECTOR_HSSDC_II:
+ case SFP_CONNECTOR_NOSEPARATE:
+ case SFP_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_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 0x00: /* Unspecified */
+ break;
+ case 0x02: /* 100Gbase-SR4 or 25Gbase-SR */
+ phylink_set(modes, 100000baseSR4_Full);
+ phylink_set(modes, 25000baseSR_Full);
+ break;
+ case 0x03: /* 100Gbase-LR4 or 25Gbase-LR */
+ case 0x04: /* 100Gbase-ER4 or 25Gbase-ER */
+ phylink_set(modes, 100000baseLR4_ER4_Full);
+ break;
+ case 0x0b: /* 100Gbase-CR4 or 25Gbase-CR CA-L */
+ case 0x0c: /* 25Gbase-CR CA-S */
+ case 0x0d: /* 25Gbase-CR CA-N */
+ phylink_set(modes, 100000baseCR4_Full);
+ phylink_set(modes, 25000baseCR_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 == SFP_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
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ * @link_modes: ethtool link modes mask
+ *
+ * Derive the phy_interface_t mode for the information found in the
+ * module's identifying EEPROM and the link modes mask. There is no
+ * standard or defined way to derive this information, so we decide
+ * based upon the link mode mask.
+ */
+phy_interface_t sfp_select_interface(struct sfp_bus *bus,
+ const struct sfp_eeprom_id *id,
+ 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))
+ return PHY_INTERFACE_MODE_10GKR;
+
+ if (phylink_test(link_modes, 2500baseX_Full))
+ return PHY_INTERFACE_MODE_2500BASEX;
+
+ if (id->base.e1000_base_t ||
+ id->base.e100_base_lx ||
+ id->base.e100_base_fx)
+ 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);
+}
+
+static void sfp_bus_put(struct sfp_bus *bus)
+{
+ kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex);
+}
+
+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->socket_ops->attach(bus->sfp);
+ if (bus->started)
+ bus->socket_ops->start(bus->sfp);
+ bus->netdev->sfp_bus = bus;
+ bus->registered = true;
+ return 0;
+}
+
+static void sfp_unregister_bus(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = bus->upstream_ops;
+
+ bus->netdev->sfp_bus = NULL;
+ if (bus->registered) {
+ 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;
+ bus->netdev = NULL;
+}
+
+/**
+ * sfp_register_upstream() - Register the neighbouring device
+ * @fwnode: firmware node for the SFP bus
+ * @ndev: network device associated with the interface
+ * @upstream: the upstream private data
+ * @ops: the upstream's &struct sfp_upstream_ops
+ *
+ * Register the upstream device (eg, PHY) with the SFP bus. MAC drivers
+ * should use phylink, which will call this function for them. Returns
+ * a pointer to the allocated &struct sfp_bus.
+ *
+ * On error, returns %NULL.
+ */
+struct sfp_bus *sfp_register_upstream(struct fwnode_handle *fwnode,
+ struct net_device *ndev, void *upstream,
+ const struct sfp_upstream_ops *ops)
+{
+ struct sfp_bus *bus = sfp_bus_get(fwnode);
+ int ret = 0;
+
+ if (bus) {
+ rtnl_lock();
+ bus->upstream_ops = ops;
+ bus->upstream = upstream;
+ bus->netdev = ndev;
+
+ if (bus->sfp) {
+ ret = sfp_register_bus(bus);
+ if (ret)
+ sfp_upstream_clear(bus);
+ }
+ rtnl_unlock();
+ }
+
+ if (ret) {
+ sfp_bus_put(bus);
+ bus = NULL;
+ }
+
+ return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_register_upstream);
+
+/**
+ * sfp_unregister_upstream() - Unregister sfp bus
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Unregister a previously registered upstream connection for the SFP
+ * module. @bus is returned from sfp_register_upstream().
+ */
+void sfp_unregister_upstream(struct sfp_bus *bus)
+{
+ rtnl_lock();
+ if (bus->sfp)
+ sfp_unregister_bus(bus);
+ sfp_upstream_clear(bus);
+ rtnl_unlock();
+
+ sfp_bus_put(bus);
+}
+EXPORT_SYMBOL_GPL(sfp_unregister_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);
+
+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->netdev) {
+ 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->netdev)
+ sfp_unregister_bus(bus);
+ sfp_socket_clear(bus);
+ rtnl_unlock();
+
+ sfp_bus_put(bus);
+}
+EXPORT_SYMBOL_GPL(sfp_unregister_socket);
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
new file mode 100644
index 000000000..71bafc8f5
--- /dev/null
+++ b/drivers/net/phy/sfp.c
@@ -0,0 +1,1957 @@
+#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_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_PROBE,
+ SFP_MOD_HPOWER,
+ SFP_MOD_PRESENT,
+ SFP_MOD_ERROR,
+
+ SFP_DEV_DOWN = 0,
+ SFP_DEV_UP,
+
+ SFP_S_DOWN = 0,
+ SFP_S_INIT,
+ 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_PROBE] = "probe",
+ [SFP_MOD_HPOWER] = "hpower",
+ [SFP_MOD_PRESENT] = "present",
+ [SFP_MOD_ERROR] = "error",
+};
+
+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_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_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_INIT] = "init",
+ [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] = "tx_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,
+};
+
+#define T_INIT_JIFFIES msecs_to_jiffies(300)
+#define T_RESET_US 10
+#define T_FAULT_RECOVER msecs_to_jiffies(1000)
+
+/* 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 SFP MSA specifies 300ms as t_init (the time taken for TX_FAULT to
+ * be deasserted) but makes no mention of the earliest time before we can
+ * access the I2C EEPROM. However, Avago modules require 300ms.
+ */
+#define T_PROBE_INIT msecs_to_jiffies(300)
+#define T_HPOWER_LEVEL msecs_to_jiffies(300)
+#define T_PROBE_RETRY msecs_to_jiffies(100)
+
+/* 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
+
+/* Give this long for the PHY to reset. */
+#define T_PHY_RESET_MS 50
+
+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];
+
+ bool attached;
+ struct mutex st_mutex; /* Protects state */
+ 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_dev_state;
+ unsigned short sm_state;
+ unsigned int sm_retries;
+
+ struct sfp_eeprom_id id;
+#if IS_ENABLED(CONFIG_HWMON)
+ struct sfp_diag diag;
+ struct device *hwmon_dev;
+ char *hwmon_name;
+#endif
+
+};
+
+static bool sff_module_supported(const struct sfp_eeprom_id *id)
+{
+ return id->base.phys_id == SFP_PHYS_ID_SFF &&
+ 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 == SFP_PHYS_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 unsigned int sfp_get_state(struct sfp *sfp)
+{
+ return sfp->get_state(sfp);
+}
+
+static void sfp_set_state(struct sfp *sfp, unsigned int state)
+{
+ sfp->set_state(sfp, state);
+}
+
+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_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 int sfp_hwmon_insert(struct sfp *sfp)
+{
+ int err, i;
+
+ 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;
+
+ err = sfp_read(sfp, true, 0, &sfp->diag, sizeof(sfp->diag));
+ if (err < 0)
+ return err;
+
+ sfp->hwmon_name = kstrdup(dev_name(sfp->dev), GFP_KERNEL);
+ if (!sfp->hwmon_name)
+ return -ENODEV;
+
+ 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);
+
+ return PTR_ERR_OR_ZERO(sfp->hwmon_dev);
+}
+
+static void sfp_hwmon_remove(struct sfp *sfp)
+{
+ if (!IS_ERR_OR_NULL(sfp->hwmon_dev)) {
+ hwmon_device_unregister(sfp->hwmon_dev);
+ sfp->hwmon_dev = NULL;
+ kfree(sfp->hwmon_name);
+ }
+}
+#else
+static int sfp_hwmon_insert(struct sfp *sfp)
+{
+ return 0;
+}
+
+static void sfp_hwmon_remove(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_ins_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)
+{
+ phy_stop(sfp->mod_phy);
+ sfp_remove_phy(sfp->sfp_bus);
+ phy_device_remove(sfp->mod_phy);
+ phy_device_free(sfp->mod_phy);
+ sfp->mod_phy = NULL;
+}
+
+static void sfp_sm_probe_phy(struct sfp *sfp)
+{
+ struct phy_device *phy;
+ int err;
+
+ msleep(T_PHY_RESET_MS);
+
+ phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
+ if (phy == ERR_PTR(-ENODEV)) {
+ dev_info(sfp->dev, "no PHY detected\n");
+ return;
+ }
+ if (IS_ERR(phy)) {
+ dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
+ return;
+ }
+
+ 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;
+ }
+
+ sfp->mod_phy = phy;
+ phy_start(phy);
+}
+
+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, bool warn)
+{
+ if (sfp->sm_retries && !--sfp->sm_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, SFP_S_TX_FAULT, T_FAULT_RECOVER);
+ }
+}
+
+static void sfp_sm_mod_init(struct sfp *sfp)
+{
+ sfp_module_tx_enable(sfp);
+
+ /* Wait t_init before indicating that the link is up, provided the
+ * current state indicates no TX_FAULT. If TX_FAULT clears before
+ * this time, that's fine too.
+ */
+ sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
+ sfp->sm_retries = 5;
+
+ /* Setting the serdes link mode is guesswork: there's no
+ * field in the EEPROM which indicates what mode should
+ * be used.
+ *
+ * If it's a gigabit-only fiber module, it probably does
+ * not have a PHY, so switch to 802.3z negotiation mode.
+ * Otherwise, switch to SGMII mode (which is required to
+ * support non-gigabit speeds) and probe for a PHY.
+ */
+ if (sfp->id.base.e1000_base_t ||
+ sfp->id.base.e100_base_lx ||
+ sfp->id.base.e100_base_fx)
+ sfp_sm_probe_phy(sfp);
+}
+
+static int sfp_sm_mod_hpower(struct sfp *sfp)
+{
+ u32 power;
+ u8 val;
+ int err;
+
+ power = 1000;
+ if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_POWER_DECL))
+ power = 1500;
+ if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_HIGH_POWER_LEVEL))
+ power = 2000;
+
+ if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE &&
+ (sfp->id.ext.diagmon & (SFP_DIAGMON_DDM | SFP_DIAGMON_ADDRMODE)) !=
+ SFP_DIAGMON_DDM) {
+ /* The module appears not to implement bus address 0xa2,
+ * or requires an address change sequence, so assume that
+ * the module powers up in the indicated power mode.
+ */
+ if (power > sfp->max_power_mW) {
+ dev_err(sfp->dev,
+ "Host does not support %u.%uW modules\n",
+ power / 1000, (power / 100) % 10);
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ if (power > sfp->max_power_mW) {
+ dev_warn(sfp->dev,
+ "Host does not support %u.%uW modules, module left in power mode 1\n",
+ power / 1000, (power / 100) % 10);
+ return 0;
+ }
+
+ if (power <= 1000)
+ return 0;
+
+ 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);
+ err = -EAGAIN;
+ goto err;
+ }
+
+ 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);
+ err = -EAGAIN;
+ goto err;
+ }
+
+ dev_info(sfp->dev, "Module switched to %u.%uW power level\n",
+ power / 1000, (power / 100) % 10);
+ return T_HPOWER_LEVEL;
+
+err:
+ return err;
+}
+
+static int sfp_sm_mod_probe(struct sfp *sfp)
+{
+ /* 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) {
+ 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(&sfp->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");
+
+ ret = sfp_hwmon_insert(sfp);
+ if (ret < 0)
+ return ret;
+
+ ret = sfp_module_insert(sfp->sfp_bus, &sfp->id);
+ if (ret < 0)
+ return ret;
+
+ return sfp_sm_mod_hpower(sfp);
+}
+
+static void sfp_sm_mod_remove(struct sfp *sfp)
+{
+ sfp_module_remove(sfp->sfp_bus);
+
+ sfp_hwmon_remove(sfp);
+
+ if (sfp->mod_phy)
+ sfp_sm_phy_detach(sfp);
+
+ sfp_module_tx_disable(sfp);
+
+ memset(&sfp->id, 0, sizeof(sfp->id));
+
+ dev_info(sfp->dev, "module removed\n");
+}
+
+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));
+
+ /* This state machine tracks the insert/remove state of
+ * the module, and handles probing the on-board EEPROM.
+ */
+ switch (sfp->sm_mod_state) {
+ default:
+ if (event == SFP_E_INSERT && sfp->attached) {
+ sfp_module_tx_disable(sfp);
+ sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT);
+ }
+ break;
+
+ case SFP_MOD_PROBE:
+ if (event == SFP_E_REMOVE) {
+ sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
+ } else if (event == SFP_E_TIMEOUT) {
+ int val = sfp_sm_mod_probe(sfp);
+
+ if (val == 0)
+ sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
+ else if (val > 0)
+ sfp_sm_ins_next(sfp, SFP_MOD_HPOWER, val);
+ else if (val != -EAGAIN)
+ sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0);
+ else
+ sfp_sm_set_timer(sfp, T_PROBE_RETRY);
+ }
+ break;
+
+ case SFP_MOD_HPOWER:
+ if (event == SFP_E_TIMEOUT) {
+ sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
+ break;
+ }
+ /* fallthrough */
+ case SFP_MOD_PRESENT:
+ case SFP_MOD_ERROR:
+ if (event == SFP_E_REMOVE) {
+ sfp_sm_mod_remove(sfp);
+ sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
+ }
+ break;
+ }
+
+ /* This state machine tracks the netdev up/down state */
+ switch (sfp->sm_dev_state) {
+ default:
+ if (event == SFP_E_DEV_UP)
+ sfp->sm_dev_state = SFP_DEV_UP;
+ break;
+
+ case SFP_DEV_UP:
+ if (event == SFP_E_DEV_DOWN) {
+ /* If the module has a PHY, avoid raising TX disable
+ * as this resets the PHY. Otherwise, raise it to
+ * turn the laser off.
+ */
+ if (!sfp->mod_phy)
+ sfp_module_tx_disable(sfp);
+ sfp->sm_dev_state = SFP_DEV_DOWN;
+ }
+ break;
+ }
+
+ /* 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->mod_phy)
+ sfp_sm_phy_detach(sfp);
+ sfp_sm_next(sfp, SFP_S_DOWN, 0);
+ mutex_unlock(&sfp->sm_mutex);
+ 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)
+ sfp_sm_mod_init(sfp);
+ break;
+
+ case SFP_S_INIT:
+ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT)
+ sfp_sm_fault(sfp, true);
+ else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
+ sfp_sm_link_check_los(sfp);
+ break;
+
+ case SFP_S_WAIT_LOS:
+ if (event == SFP_E_TX_FAULT)
+ sfp_sm_fault(sfp, 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, 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, T_INIT_JIFFIES);
+ }
+ break;
+
+ case SFP_S_REINIT:
+ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+ sfp_sm_fault(sfp, 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;
+ }
+
+ 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->attached = true;
+ if (sfp->state & SFP_F_PRESENT)
+ sfp_sm_event(sfp, SFP_E_INSERT);
+}
+
+static void sfp_detach(struct sfp *sfp)
+{
+ sfp->attached = false;
+ sfp_sm_event(sfp, SFP_E_REMOVE);
+}
+
+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);
+ 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);
+
+ return sfp;
+}
+
+static void sfp_cleanup(void *data)
+{
+ struct sfp *sfp = data;
+
+ 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;
+ bool poll = false;
+ int irq, 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);
+
+ for (i = 0; i < GPIO_MAX; i++) {
+ if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+ continue;
+
+ irq = gpiod_to_irq(sfp->gpio[i]);
+ if (irq < 0) {
+ irq = 0;
+ poll = true;
+ continue;
+ }
+
+ err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ dev_name(sfp->dev), sfp);
+ if (err)
+ poll = true;
+ }
+
+ if (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);
+
+ return 0;
+}
+
+static struct platform_driver sfp_driver = {
+ .probe = sfp_probe,
+ .remove = sfp_remove,
+ .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/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
new file mode 100644
index 000000000..64f54b0bb
--- /dev/null
+++ b/drivers/net/phy/sfp.h
@@ -0,0 +1,30 @@
+#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_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/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c
new file mode 100644
index 000000000..c32820838
--- /dev/null
+++ b/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/drivers/net/phy/spi_ks8995.c b/drivers/net/phy/spi_ks8995.c
new file mode 100644
index 000000000..d8ea4147d
--- /dev/null
+++ b/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/drivers/net/phy/ste10Xp.c b/drivers/net/phy/ste10Xp.c
new file mode 100644
index 000000000..fbd548a1a
--- /dev/null
+++ b/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/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c
new file mode 100644
index 000000000..34f58f234
--- /dev/null
+++ b/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/drivers/net/phy/swphy.h b/drivers/net/phy/swphy.h
new file mode 100644
index 000000000..3668ab8c9
--- /dev/null
+++ b/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/drivers/net/phy/teranetics.c b/drivers/net/phy/teranetics.c
new file mode 100644
index 000000000..22f3bdd82
--- /dev/null
+++ b/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/drivers/net/phy/uPD60620.c b/drivers/net/phy/uPD60620.c
new file mode 100644
index 000000000..55f48ee35
--- /dev/null
+++ b/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/drivers/net/phy/vitesse.c b/drivers/net/phy/vitesse.c
new file mode 100644
index 000000000..fbf9ad429
--- /dev/null
+++ b/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/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
new file mode 100644
index 000000000..bd6084e31
--- /dev/null
+++ b/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");