summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy')
-rw-r--r--drivers/net/phy/Kconfig416
-rw-r--r--drivers/net/phy/Makefile97
-rw-r--r--drivers/net/phy/adin.c998
-rw-r--r--drivers/net/phy/adin1100.c297
-rw-r--r--drivers/net/phy/amd.c107
-rw-r--r--drivers/net/phy/aquantia.h16
-rw-r--r--drivers/net/phy/aquantia_hwmon.c250
-rw-r--r--drivers/net/phy/aquantia_main.c882
-rw-r--r--drivers/net/phy/at803x.c2248
-rw-r--r--drivers/net/phy/ax88796b.c131
-rw-r--r--drivers/net/phy/bcm-cygnus.c292
-rw-r--r--drivers/net/phy/bcm-phy-lib.c1071
-rw-r--r--drivers/net/phy/bcm-phy-lib.h124
-rw-r--r--drivers/net/phy/bcm-phy-ptp.c944
-rw-r--r--drivers/net/phy/bcm54140.c886
-rw-r--r--drivers/net/phy/bcm63xx.c102
-rw-r--r--drivers/net/phy/bcm7xxx.c962
-rw-r--r--drivers/net/phy/bcm84881.c250
-rw-r--r--drivers/net/phy/bcm87xx.c225
-rw-r--r--drivers/net/phy/broadcom.c1307
-rw-r--r--drivers/net/phy/cicada.c154
-rw-r--r--drivers/net/phy/cortina.c99
-rw-r--r--drivers/net/phy/davicom.c220
-rw-r--r--drivers/net/phy/dp83640.c1556
-rw-r--r--drivers/net/phy/dp83640_reg.h268
-rw-r--r--drivers/net/phy/dp83822.c653
-rw-r--r--drivers/net/phy/dp83848.c166
-rw-r--r--drivers/net/phy/dp83867.c1063
-rw-r--r--drivers/net/phy/dp83869.c926
-rw-r--r--drivers/net/phy/dp83tc811.c414
-rw-r--r--drivers/net/phy/dp83td510.c245
-rw-r--r--drivers/net/phy/et1011c.c102
-rw-r--r--drivers/net/phy/fixed_phy.c391
-rw-r--r--drivers/net/phy/icplus.c634
-rw-r--r--drivers/net/phy/intel-xway.c475
-rw-r--r--drivers/net/phy/linkmode.c95
-rw-r--r--drivers/net/phy/lxt.c358
-rw-r--r--drivers/net/phy/marvell-88q2xxx.c263
-rw-r--r--drivers/net/phy/marvell-88x2222.c623
-rw-r--r--drivers/net/phy/marvell.c3764
-rw-r--r--drivers/net/phy/marvell10g.c1431
-rw-r--r--drivers/net/phy/mdio-boardinfo.c80
-rw-r--r--drivers/net/phy/mdio-boardinfo.h23
-rw-r--r--drivers/net/phy/mdio-open-alliance.h46
-rw-r--r--drivers/net/phy/mdio_bus.c1422
-rw-r--r--drivers/net/phy/mdio_device.c228
-rw-r--r--drivers/net/phy/mdio_devres.c134
-rw-r--r--drivers/net/phy/mediatek-ge-soc.c1531
-rw-r--r--drivers/net/phy/mediatek-ge.c114
-rw-r--r--drivers/net/phy/meson-gxl.c238
-rw-r--r--drivers/net/phy/micrel.c4926
-rw-r--r--drivers/net/phy/microchip.c416
-rw-r--r--drivers/net/phy/microchip_t1.c912
-rw-r--r--drivers/net/phy/microchip_t1s.c306
-rw-r--r--drivers/net/phy/mii_timestamper.c135
-rw-r--r--drivers/net/phy/motorcomm.c2327
-rw-r--r--drivers/net/phy/mscc/Makefile15
-rw-r--r--drivers/net/phy/mscc/mscc.h495
-rw-r--r--drivers/net/phy/mscc/mscc_fc_buffer.h64
-rw-r--r--drivers/net/phy/mscc/mscc_mac.h159
-rw-r--r--drivers/net/phy/mscc/mscc_macsec.c1011
-rw-r--r--drivers/net/phy/mscc/mscc_macsec.h323
-rw-r--r--drivers/net/phy/mscc/mscc_main.c2715
-rw-r--r--drivers/net/phy/mscc/mscc_ptp.c1589
-rw-r--r--drivers/net/phy/mscc/mscc_ptp.h472
-rw-r--r--drivers/net/phy/mscc/mscc_serdes.c650
-rw-r--r--drivers/net/phy/mscc/mscc_serdes.h31
-rw-r--r--drivers/net/phy/mxl-gpy.c1043
-rw-r--r--drivers/net/phy/national.c181
-rw-r--r--drivers/net/phy/ncn26000.c171
-rw-r--r--drivers/net/phy/nxp-c45-tja11xx.c1978
-rw-r--r--drivers/net/phy/nxp-cbtx.c227
-rw-r--r--drivers/net/phy/nxp-tja11xx.c904
-rw-r--r--drivers/net/phy/phy-c45.c1517
-rw-r--r--drivers/net/phy/phy-core.c1013
-rw-r--r--drivers/net/phy/phy.c1725
-rw-r--r--drivers/net/phy/phy_device.c3576
-rw-r--r--drivers/net/phy/phy_led_triggers.c152
-rw-r--r--drivers/net/phy/phylink.c3744
-rw-r--r--drivers/net/phy/qsemi.c163
-rw-r--r--drivers/net/phy/realtek.c1094
-rw-r--r--drivers/net/phy/rockchip.c200
-rw-r--r--drivers/net/phy/sfp-bus.c861
-rw-r--r--drivers/net/phy/sfp.c3140
-rw-r--r--drivers/net/phy/sfp.h44
-rw-r--r--drivers/net/phy/smsc.c853
-rw-r--r--drivers/net/phy/spi_ks8995.c506
-rw-r--r--drivers/net/phy/ste10Xp.c137
-rw-r--r--drivers/net/phy/stubs.c10
-rw-r--r--drivers/net/phy/swphy.c175
-rw-r--r--drivers/net/phy/swphy.h10
-rw-r--r--drivers/net/phy/teranetics.c95
-rw-r--r--drivers/net/phy/uPD60620.c98
-rw-r--r--drivers/net/phy/vitesse.c522
-rw-r--r--drivers/net/phy/xilinx_gmii2rgmii.c141
95 files changed, 70147 insertions, 0 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
new file mode 100644
index 000000000..107880d13
--- /dev/null
+++ b/drivers/net/phy/Kconfig
@@ -0,0 +1,416 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# PHY Layer Configuration
+#
+
+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
+ select MDIO_DEVRES
+ 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.
+
+config PHYLIB_LEDS
+ def_bool OF
+ depends on LEDS_CLASS=y || LEDS_CLASS=PHYLIB
+ help
+ When LED class support is enabled, phylib can automatically
+ probe LED setting from device tree.
+
+config FIXED_PHY
+ tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
+ 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 SFP
+ tristate "SFP cage support"
+ depends on I2C && PHYLINK
+ depends on HWMON || HWMON=n
+ select MDIO_I2C
+
+comment "MII PHY device drivers"
+
+config AMD_PHY
+ tristate "AMD PHYs"
+ help
+ Currently supports the am79c874
+
+config MESON_GXL_PHY
+ tristate "Amlogic Meson GXL Internal PHY"
+ depends on ARCH_MESON || COMPILE_TEST
+ select SMSC_PHY
+ help
+ Currently has a driver for the Amlogic Meson GXL Internal PHY
+
+config ADIN_PHY
+ tristate "Analog Devices Industrial Ethernet PHYs"
+ help
+ Adds support for the Analog Devices Industrial Ethernet PHYs.
+ Currently supports the:
+ - ADIN1200 - Robust,Industrial, Low Power 10/100 Ethernet PHY
+ - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
+ Ethernet PHY
+
+config ADIN1100_PHY
+ tristate "Analog Devices Industrial Ethernet T1L PHYs"
+ help
+ Adds support for the Analog Devices Industrial T1L Ethernet PHYs.
+ Currently supports the:
+ - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
+
+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 BROADCOM_PHY
+ tristate "Broadcom 54XX PHYs"
+ select BCM_NET_PHYLIB
+ select BCM_NET_PHYPTP if NETWORK_PHY_TIMESTAMPING
+ depends on PTP_1588_CLOCK_OPTIONAL
+ help
+ Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
+ BCM5481, BCM54810 and BCM5482 PHYs.
+
+config BCM54140_PHY
+ tristate "Broadcom BCM54140 PHY"
+ depends on HWMON || HWMON=n
+ select BCM_NET_PHYLIB
+ help
+ Support the Broadcom BCM54140 Quad SGMII/QSGMII PHY.
+
+ This driver also supports the hardware monitoring of this PHY and
+ exposes voltage and temperature sensors.
+
+config BCM63XX_PHY
+ tristate "Broadcom 63xx SOCs internal PHY"
+ depends on BCM63XX || COMPILE_TEST
+ 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 BCM84881_PHY
+ tristate "Broadcom BCM84881 PHY"
+ help
+ Support the Broadcom BCM84881 PHY.
+
+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/Omega SoC internal PHY"
+ depends on ARCH_BCM_IPROC || 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 and Omega 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 BCM_NET_PHYPTP
+ tristate
+
+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 ICPLUS_PHY
+ tristate "ICPlus PHYs"
+ help
+ Currently supports the IP175C and IP1001 PHYs.
+
+config LXT_PHY
+ tristate "Intel LXT PHYs"
+ help
+ Currently supports the lxt970, lxt971
+
+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 MARVELL_PHY
+ tristate "Marvell Alaska PHYs"
+ help
+ Currently has a driver for the 88E1XXX
+
+config MARVELL_10G_PHY
+ tristate "Marvell Alaska 10Gbit PHYs"
+ help
+ Support for the Marvell Alaska MV88X3310 and compatible PHYs.
+
+config MARVELL_88Q2XXX_PHY
+ tristate "Marvell 88Q2XXX PHY"
+ help
+ Support for the Marvell 88Q2XXX 100/1000BASE-T1 Automotive Ethernet
+ PHYs.
+
+config MARVELL_88X2222_PHY
+ tristate "Marvell 88X2222 PHY"
+ help
+ Support for the Marvell 88X2222 Dual-port Multi-speed Ethernet
+ Transceiver.
+
+config MAXLINEAR_GPHY
+ tristate "Maxlinear Ethernet PHYs"
+ select POLYNOMIAL if HWMON
+ depends on HWMON || HWMON=n
+ help
+ Support for the Maxlinear GPY115, GPY211, GPY212, GPY215,
+ GPY241, GPY245 PHYs.
+
+config MEDIATEK_GE_PHY
+ tristate "MediaTek Gigabit Ethernet PHYs"
+ help
+ Supports the MediaTek Gigabit Ethernet PHYs.
+
+config MEDIATEK_GE_SOC_PHY
+ tristate "MediaTek SoC Ethernet PHYs"
+ depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
+ depends on NVMEM_MTK_EFUSE
+ help
+ Supports MediaTek SoC built-in Gigabit Ethernet PHYs.
+
+ Include support for built-in Ethernet PHYs which are present in
+ the MT7981 and MT7988 SoCs. These PHYs need calibration data
+ present in the SoCs efuse and will dynamically calibrate VCM
+ (common-mode voltage) during startup.
+
+config MICREL_PHY
+ tristate "Micrel PHYs"
+ depends on PTP_1588_CLOCK_OPTIONAL
+ help
+ Supports the KSZ9021, VSC8201, KS8001 PHYs.
+
+config MICROCHIP_T1S_PHY
+ tristate "Microchip 10BASE-T1S Ethernet PHYs"
+ help
+ Currently supports the LAN8670/1/2 Rev.B1 and LAN8650/1 Rev.B0 Internal
+ 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"
+ depends on MACSEC || MACSEC=n
+ depends on PTP_1588_CLOCK_OPTIONAL || !NETWORK_PHY_TIMESTAMPING
+ select CRYPTO_LIB_AES if MACSEC
+ help
+ Currently supports VSC8514, VSC8530, VSC8531, VSC8540 and VSC8541 PHYs
+
+config MOTORCOMM_PHY
+ tristate "Motorcomm PHYs"
+ help
+ Enables support for Motorcomm network PHYs.
+ Currently supports YT85xx Gigabit Ethernet PHYs.
+
+config NATIONAL_PHY
+ tristate "National Semiconductor PHYs"
+ help
+ Currently supports the DP83865 PHY.
+
+config NXP_CBTX_PHY
+ tristate "NXP 100BASE-TX PHYs"
+ help
+ Support the 100BASE-TX PHY integrated on the SJA1110 automotive
+ switch family.
+
+config NXP_C45_TJA11XX_PHY
+ tristate "NXP C45 TJA11XX PHYs"
+ depends on PTP_1588_CLOCK_OPTIONAL
+ help
+ Enable support for NXP C45 TJA11XX PHYs.
+ Currently supports the TJA1103 and TJA1120 PHYs.
+
+config NXP_TJA11XX_PHY
+ tristate "NXP TJA11xx PHYs support"
+ depends on HWMON
+ help
+ Currently supports the NXP TJA1100 and TJA1101 PHY.
+
+config NCN26000_PHY
+ tristate "Onsemi 10BASE-T1S Ethernet PHY"
+ help
+ Adds support for the onsemi 10BASE-T1S Ethernet PHY.
+ Currently supports the NCN26000 10BASE-T1S Industrial PHY
+ with MII interface.
+
+config AT803X_PHY
+ tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
+ depends on REGULATOR
+ help
+ Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+ QCA8337(Internal qca8k PHY) model
+
+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 "Renesas PHYs"
+ help
+ Supports the Renesas PHYs uPD60620 and uPD60620A.
+
+config ROCKCHIP_PHY
+ tristate "Rockchip Ethernet PHYs"
+ help
+ Currently supports the integrated Ethernet PHY.
+
+config SMSC_PHY
+ tristate "SMSC PHYs"
+ select CRC16
+ 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 DP83822_PHY
+ tristate "Texas Instruments DP83822/825/826 PHYs"
+ help
+ Supports the DP83822, DP83825I, DP83825CM, DP83825CS, DP83825S,
+ DP83826C and DP83826NC PHYs.
+
+config DP83TC811_PHY
+ tristate "Texas Instruments DP83TC811 PHY"
+ help
+ Supports the DP83TC811 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 DP83869_PHY
+ tristate "Texas Instruments DP83869 Gigabit PHY"
+ help
+ Currently supports the DP83869 PHY. This PHY supports copper and
+ fiber connections.
+
+config DP83TD510_PHY
+ tristate "Texas Instruments DP83TD510 Ethernet 10Base-T1L PHY"
+ help
+ Support for the DP83TD510 Ethernet 10Base-T1L PHY. This PHY supports
+ a 10M single pair Ethernet connection for up to 1000 meter cable.
+
+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..c945ed9bd
--- /dev/null
+++ b/drivers/net/phy/Makefile
@@ -0,0 +1,97 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for Linux PHY drivers
+
+libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \
+ linkmode.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)
+# the stubs are built-in whenever PHYLIB is built-in or module
+obj-y += stubs.o
+else
+obj-$(CONFIG_MDIO_DEVICE) += mdio-bus.o
+endif
+obj-$(CONFIG_MDIO_DEVRES) += mdio_devres.o
+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_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
+
+obj-$(CONFIG_SFP) += sfp.o
+sfp-obj-$(CONFIG_SFP) += sfp-bus.o
+obj-y += $(sfp-obj-y) $(sfp-obj-m)
+
+obj-$(CONFIG_ADIN_PHY) += adin.o
+obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
+obj-$(CONFIG_AMD_PHY) += amd.o
+aquantia-objs += aquantia_main.o
+ifdef CONFIG_HWMON
+aquantia-objs += aquantia_hwmon.o
+endif
+obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
+obj-$(CONFIG_AT803X_PHY) += at803x.o
+obj-$(CONFIG_AX88796B_PHY) += ax88796b.o
+obj-$(CONFIG_BCM54140_PHY) += bcm54140.o
+obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o
+obj-$(CONFIG_BCM7XXX_PHY) += bcm7xxx.o
+obj-$(CONFIG_BCM84881_PHY) += bcm84881.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_BCM_NET_PHYPTP) += bcm-phy-ptp.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_DP83848_PHY) += dp83848.o
+obj-$(CONFIG_DP83867_PHY) += dp83867.o
+obj-$(CONFIG_DP83869_PHY) += dp83869.o
+obj-$(CONFIG_DP83TC811_PHY) += dp83tc811.o
+obj-$(CONFIG_DP83TD510_PHY) += dp83td510.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_10G_PHY) += marvell10g.o
+obj-$(CONFIG_MARVELL_PHY) += marvell.o
+obj-$(CONFIG_MARVELL_88Q2XXX_PHY) += marvell-88q2xxx.o
+obj-$(CONFIG_MARVELL_88X2222_PHY) += marvell-88x2222.o
+obj-$(CONFIG_MAXLINEAR_GPHY) += mxl-gpy.o
+obj-$(CONFIG_MEDIATEK_GE_PHY) += mediatek-ge.o
+obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mediatek-ge-soc.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_MICROCHIP_T1S_PHY) += microchip_t1s.o
+obj-$(CONFIG_MICROSEMI_PHY) += mscc/
+obj-$(CONFIG_MOTORCOMM_PHY) += motorcomm.o
+obj-$(CONFIG_NATIONAL_PHY) += national.o
+obj-$(CONFIG_NCN26000_PHY) += ncn26000.o
+obj-$(CONFIG_NXP_C45_TJA11XX_PHY) += nxp-c45-tja11xx.o
+obj-$(CONFIG_NXP_CBTX_PHY) += nxp-cbtx.o
+obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.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/adin.c b/drivers/net/phy/adin.c
new file mode 100644
index 000000000..134637584
--- /dev/null
+++ b/drivers/net/phy/adin.c
@@ -0,0 +1,998 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Analog Devices Industrial Ethernet PHYs
+ *
+ * Copyright 2019 Analog Devices Inc.
+ */
+#include <linux/kernel.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+
+#define PHY_ID_ADIN1200 0x0283bc20
+#define PHY_ID_ADIN1300 0x0283bc30
+
+#define ADIN1300_MII_EXT_REG_PTR 0x0010
+#define ADIN1300_MII_EXT_REG_DATA 0x0011
+
+#define ADIN1300_PHY_CTRL1 0x0012
+#define ADIN1300_AUTO_MDI_EN BIT(10)
+#define ADIN1300_MAN_MDIX_EN BIT(9)
+#define ADIN1300_DIAG_CLK_EN BIT(2)
+
+#define ADIN1300_RX_ERR_CNT 0x0014
+
+#define ADIN1300_PHY_CTRL_STATUS2 0x0015
+#define ADIN1300_NRG_PD_EN BIT(3)
+#define ADIN1300_NRG_PD_TX_EN BIT(2)
+#define ADIN1300_NRG_PD_STATUS BIT(1)
+
+#define ADIN1300_PHY_CTRL2 0x0016
+#define ADIN1300_DOWNSPEED_AN_100_EN BIT(11)
+#define ADIN1300_DOWNSPEED_AN_10_EN BIT(10)
+#define ADIN1300_GROUP_MDIO_EN BIT(6)
+#define ADIN1300_DOWNSPEEDS_EN \
+ (ADIN1300_DOWNSPEED_AN_100_EN | ADIN1300_DOWNSPEED_AN_10_EN)
+
+#define ADIN1300_PHY_CTRL3 0x0017
+#define ADIN1300_LINKING_EN BIT(13)
+#define ADIN1300_DOWNSPEED_RETRIES_MSK GENMASK(12, 10)
+
+#define ADIN1300_INT_MASK_REG 0x0018
+#define ADIN1300_INT_MDIO_SYNC_EN BIT(9)
+#define ADIN1300_INT_ANEG_STAT_CHNG_EN BIT(8)
+#define ADIN1300_INT_ANEG_PAGE_RX_EN BIT(6)
+#define ADIN1300_INT_IDLE_ERR_CNT_EN BIT(5)
+#define ADIN1300_INT_MAC_FIFO_OU_EN BIT(4)
+#define ADIN1300_INT_RX_STAT_CHNG_EN BIT(3)
+#define ADIN1300_INT_LINK_STAT_CHNG_EN BIT(2)
+#define ADIN1300_INT_SPEED_CHNG_EN BIT(1)
+#define ADIN1300_INT_HW_IRQ_EN BIT(0)
+#define ADIN1300_INT_MASK_EN \
+ (ADIN1300_INT_LINK_STAT_CHNG_EN | ADIN1300_INT_HW_IRQ_EN)
+#define ADIN1300_INT_STATUS_REG 0x0019
+
+#define ADIN1300_PHY_STATUS1 0x001a
+#define ADIN1300_PAIR_01_SWAP BIT(11)
+
+/* EEE register addresses, accessible via Clause 22 access using
+ * ADIN1300_MII_EXT_REG_PTR & ADIN1300_MII_EXT_REG_DATA.
+ * The bit-fields are the same as specified by IEEE for EEE.
+ */
+#define ADIN1300_EEE_CAP_REG 0x8000
+#define ADIN1300_EEE_ADV_REG 0x8001
+#define ADIN1300_EEE_LPABLE_REG 0x8002
+#define ADIN1300_CLOCK_STOP_REG 0x9400
+#define ADIN1300_LPI_WAKE_ERR_CNT_REG 0xa000
+
+#define ADIN1300_CDIAG_RUN 0xba1b
+#define ADIN1300_CDIAG_RUN_EN BIT(0)
+
+/*
+ * The XSIM3/2/1 and XSHRT3/2/1 are actually relative.
+ * For CDIAG_DTLD_RSLTS(0) it's ADIN1300_CDIAG_RSLT_XSIM3/2/1
+ * For CDIAG_DTLD_RSLTS(1) it's ADIN1300_CDIAG_RSLT_XSIM3/2/0
+ * For CDIAG_DTLD_RSLTS(2) it's ADIN1300_CDIAG_RSLT_XSIM3/1/0
+ * For CDIAG_DTLD_RSLTS(3) it's ADIN1300_CDIAG_RSLT_XSIM2/1/0
+ */
+#define ADIN1300_CDIAG_DTLD_RSLTS(x) (0xba1d + (x))
+#define ADIN1300_CDIAG_RSLT_BUSY BIT(10)
+#define ADIN1300_CDIAG_RSLT_XSIM3 BIT(9)
+#define ADIN1300_CDIAG_RSLT_XSIM2 BIT(8)
+#define ADIN1300_CDIAG_RSLT_XSIM1 BIT(7)
+#define ADIN1300_CDIAG_RSLT_SIM BIT(6)
+#define ADIN1300_CDIAG_RSLT_XSHRT3 BIT(5)
+#define ADIN1300_CDIAG_RSLT_XSHRT2 BIT(4)
+#define ADIN1300_CDIAG_RSLT_XSHRT1 BIT(3)
+#define ADIN1300_CDIAG_RSLT_SHRT BIT(2)
+#define ADIN1300_CDIAG_RSLT_OPEN BIT(1)
+#define ADIN1300_CDIAG_RSLT_GOOD BIT(0)
+
+#define ADIN1300_CDIAG_FLT_DIST(x) (0xba21 + (x))
+
+#define ADIN1300_GE_SOFT_RESET_REG 0xff0c
+#define ADIN1300_GE_SOFT_RESET BIT(0)
+
+#define ADIN1300_GE_CLK_CFG_REG 0xff1f
+#define ADIN1300_GE_CLK_CFG_MASK GENMASK(5, 0)
+#define ADIN1300_GE_CLK_CFG_RCVR_125 BIT(5)
+#define ADIN1300_GE_CLK_CFG_FREE_125 BIT(4)
+#define ADIN1300_GE_CLK_CFG_REF_EN BIT(3)
+#define ADIN1300_GE_CLK_CFG_HRT_RCVR BIT(2)
+#define ADIN1300_GE_CLK_CFG_HRT_FREE BIT(1)
+#define ADIN1300_GE_CLK_CFG_25 BIT(0)
+
+#define ADIN1300_GE_RGMII_CFG_REG 0xff23
+#define ADIN1300_GE_RGMII_RX_MSK GENMASK(8, 6)
+#define ADIN1300_GE_RGMII_RX_SEL(x) \
+ FIELD_PREP(ADIN1300_GE_RGMII_RX_MSK, x)
+#define ADIN1300_GE_RGMII_GTX_MSK GENMASK(5, 3)
+#define ADIN1300_GE_RGMII_GTX_SEL(x) \
+ FIELD_PREP(ADIN1300_GE_RGMII_GTX_MSK, x)
+#define ADIN1300_GE_RGMII_RXID_EN BIT(2)
+#define ADIN1300_GE_RGMII_TXID_EN BIT(1)
+#define ADIN1300_GE_RGMII_EN BIT(0)
+
+/* RGMII internal delay settings for rx and tx for ADIN1300 */
+#define ADIN1300_RGMII_1_60_NS 0x0001
+#define ADIN1300_RGMII_1_80_NS 0x0002
+#define ADIN1300_RGMII_2_00_NS 0x0000
+#define ADIN1300_RGMII_2_20_NS 0x0006
+#define ADIN1300_RGMII_2_40_NS 0x0007
+
+#define ADIN1300_GE_RMII_CFG_REG 0xff24
+#define ADIN1300_GE_RMII_FIFO_DEPTH_MSK GENMASK(6, 4)
+#define ADIN1300_GE_RMII_FIFO_DEPTH_SEL(x) \
+ FIELD_PREP(ADIN1300_GE_RMII_FIFO_DEPTH_MSK, x)
+#define ADIN1300_GE_RMII_EN BIT(0)
+
+/* RMII fifo depth values */
+#define ADIN1300_RMII_4_BITS 0x0000
+#define ADIN1300_RMII_8_BITS 0x0001
+#define ADIN1300_RMII_12_BITS 0x0002
+#define ADIN1300_RMII_16_BITS 0x0003
+#define ADIN1300_RMII_20_BITS 0x0004
+#define ADIN1300_RMII_24_BITS 0x0005
+
+/**
+ * struct adin_cfg_reg_map - map a config value to aregister value
+ * @cfg: value in device configuration
+ * @reg: value in the register
+ */
+struct adin_cfg_reg_map {
+ int cfg;
+ int reg;
+};
+
+static const struct adin_cfg_reg_map adin_rgmii_delays[] = {
+ { 1600, ADIN1300_RGMII_1_60_NS },
+ { 1800, ADIN1300_RGMII_1_80_NS },
+ { 2000, ADIN1300_RGMII_2_00_NS },
+ { 2200, ADIN1300_RGMII_2_20_NS },
+ { 2400, ADIN1300_RGMII_2_40_NS },
+ { },
+};
+
+static const struct adin_cfg_reg_map adin_rmii_fifo_depths[] = {
+ { 4, ADIN1300_RMII_4_BITS },
+ { 8, ADIN1300_RMII_8_BITS },
+ { 12, ADIN1300_RMII_12_BITS },
+ { 16, ADIN1300_RMII_16_BITS },
+ { 20, ADIN1300_RMII_20_BITS },
+ { 24, ADIN1300_RMII_24_BITS },
+ { },
+};
+
+/**
+ * struct adin_clause45_mmd_map - map to convert Clause 45 regs to Clause 22
+ * @devad: device address used in Clause 45 access
+ * @cl45_regnum: register address defined by Clause 45
+ * @adin_regnum: equivalent register address accessible via Clause 22
+ */
+struct adin_clause45_mmd_map {
+ int devad;
+ u16 cl45_regnum;
+ u16 adin_regnum;
+};
+
+static const struct adin_clause45_mmd_map adin_clause45_mmd_map[] = {
+ { MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE, ADIN1300_EEE_CAP_REG },
+ { MDIO_MMD_AN, MDIO_AN_EEE_LPABLE, ADIN1300_EEE_LPABLE_REG },
+ { MDIO_MMD_AN, MDIO_AN_EEE_ADV, ADIN1300_EEE_ADV_REG },
+ { MDIO_MMD_PCS, MDIO_CTRL1, ADIN1300_CLOCK_STOP_REG },
+ { MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR, ADIN1300_LPI_WAKE_ERR_CNT_REG },
+};
+
+struct adin_hw_stat {
+ const char *string;
+ u16 reg1;
+ u16 reg2;
+};
+
+static const struct adin_hw_stat adin_hw_stats[] = {
+ { "total_frames_checked_count", 0x940A, 0x940B }, /* hi + lo */
+ { "length_error_frames_count", 0x940C },
+ { "alignment_error_frames_count", 0x940D },
+ { "symbol_error_count", 0x940E },
+ { "oversized_frames_count", 0x940F },
+ { "undersized_frames_count", 0x9410 },
+ { "odd_nibble_frames_count", 0x9411 },
+ { "odd_preamble_packet_count", 0x9412 },
+ { "dribble_bits_frames_count", 0x9413 },
+ { "false_carrier_events_count", 0x9414 },
+};
+
+/**
+ * struct adin_priv - ADIN PHY driver private data
+ * @stats: statistic counters for the PHY
+ */
+struct adin_priv {
+ u64 stats[ARRAY_SIZE(adin_hw_stats)];
+};
+
+static int adin_lookup_reg_value(const struct adin_cfg_reg_map *tbl, int cfg)
+{
+ size_t i;
+
+ for (i = 0; tbl[i].cfg; i++) {
+ if (tbl[i].cfg == cfg)
+ return tbl[i].reg;
+ }
+
+ return -EINVAL;
+}
+
+static u32 adin_get_reg_value(struct phy_device *phydev,
+ const char *prop_name,
+ const struct adin_cfg_reg_map *tbl,
+ u32 dflt)
+{
+ struct device *dev = &phydev->mdio.dev;
+ u32 val;
+ int rc;
+
+ if (device_property_read_u32(dev, prop_name, &val))
+ return dflt;
+
+ rc = adin_lookup_reg_value(tbl, val);
+ if (rc < 0) {
+ phydev_warn(phydev,
+ "Unsupported value %u for %s using default (%u)\n",
+ val, prop_name, dflt);
+ return dflt;
+ }
+
+ return rc;
+}
+
+static int adin_config_rgmii_mode(struct phy_device *phydev)
+{
+ u32 val;
+ int reg;
+
+ if (!phy_interface_is_rgmii(phydev))
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_GE_RGMII_CFG_REG,
+ ADIN1300_GE_RGMII_EN);
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_GE_RGMII_CFG_REG);
+ if (reg < 0)
+ return reg;
+
+ reg |= ADIN1300_GE_RGMII_EN;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+ reg |= ADIN1300_GE_RGMII_RXID_EN;
+
+ val = adin_get_reg_value(phydev, "adi,rx-internal-delay-ps",
+ adin_rgmii_delays,
+ ADIN1300_RGMII_2_00_NS);
+ reg &= ~ADIN1300_GE_RGMII_RX_MSK;
+ reg |= ADIN1300_GE_RGMII_RX_SEL(val);
+ } else {
+ reg &= ~ADIN1300_GE_RGMII_RXID_EN;
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+ reg |= ADIN1300_GE_RGMII_TXID_EN;
+
+ val = adin_get_reg_value(phydev, "adi,tx-internal-delay-ps",
+ adin_rgmii_delays,
+ ADIN1300_RGMII_2_00_NS);
+ reg &= ~ADIN1300_GE_RGMII_GTX_MSK;
+ reg |= ADIN1300_GE_RGMII_GTX_SEL(val);
+ } else {
+ reg &= ~ADIN1300_GE_RGMII_TXID_EN;
+ }
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_GE_RGMII_CFG_REG, reg);
+}
+
+static int adin_config_rmii_mode(struct phy_device *phydev)
+{
+ u32 val;
+ int reg;
+
+ if (phydev->interface != PHY_INTERFACE_MODE_RMII)
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_GE_RMII_CFG_REG,
+ ADIN1300_GE_RMII_EN);
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_GE_RMII_CFG_REG);
+ if (reg < 0)
+ return reg;
+
+ reg |= ADIN1300_GE_RMII_EN;
+
+ val = adin_get_reg_value(phydev, "adi,fifo-depth-bits",
+ adin_rmii_fifo_depths,
+ ADIN1300_RMII_8_BITS);
+
+ reg &= ~ADIN1300_GE_RMII_FIFO_DEPTH_MSK;
+ reg |= ADIN1300_GE_RMII_FIFO_DEPTH_SEL(val);
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_GE_RMII_CFG_REG, reg);
+}
+
+static int adin_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val, cnt, enable;
+
+ val = phy_read(phydev, ADIN1300_PHY_CTRL2);
+ if (val < 0)
+ return val;
+
+ cnt = phy_read(phydev, ADIN1300_PHY_CTRL3);
+ if (cnt < 0)
+ return cnt;
+
+ enable = FIELD_GET(ADIN1300_DOWNSPEEDS_EN, val);
+ cnt = FIELD_GET(ADIN1300_DOWNSPEED_RETRIES_MSK, cnt);
+
+ *data = (enable && cnt) ? cnt : DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int adin_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ u16 val;
+ int rc;
+
+ if (cnt == DOWNSHIFT_DEV_DISABLE)
+ return phy_clear_bits(phydev, ADIN1300_PHY_CTRL2,
+ ADIN1300_DOWNSPEEDS_EN);
+
+ if (cnt > 7)
+ return -E2BIG;
+
+ val = FIELD_PREP(ADIN1300_DOWNSPEED_RETRIES_MSK, cnt);
+
+ rc = phy_modify(phydev, ADIN1300_PHY_CTRL3,
+ ADIN1300_DOWNSPEED_RETRIES_MSK,
+ val);
+ if (rc < 0)
+ return rc;
+
+ return phy_set_bits(phydev, ADIN1300_PHY_CTRL2,
+ ADIN1300_DOWNSPEEDS_EN);
+}
+
+static int adin_get_edpd(struct phy_device *phydev, u16 *tx_interval)
+{
+ int val;
+
+ val = phy_read(phydev, ADIN1300_PHY_CTRL_STATUS2);
+ if (val < 0)
+ return val;
+
+ if (ADIN1300_NRG_PD_EN & val) {
+ if (val & ADIN1300_NRG_PD_TX_EN)
+ /* default is 1 second */
+ *tx_interval = ETHTOOL_PHY_EDPD_DFLT_TX_MSECS;
+ else
+ *tx_interval = ETHTOOL_PHY_EDPD_NO_TX;
+ } else {
+ *tx_interval = ETHTOOL_PHY_EDPD_DISABLE;
+ }
+
+ return 0;
+}
+
+static int adin_set_edpd(struct phy_device *phydev, u16 tx_interval)
+{
+ u16 val;
+
+ if (tx_interval == ETHTOOL_PHY_EDPD_DISABLE)
+ return phy_clear_bits(phydev, ADIN1300_PHY_CTRL_STATUS2,
+ (ADIN1300_NRG_PD_EN | ADIN1300_NRG_PD_TX_EN));
+
+ val = ADIN1300_NRG_PD_EN;
+
+ switch (tx_interval) {
+ case 1000: /* 1 second */
+ fallthrough;
+ case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
+ val |= ADIN1300_NRG_PD_TX_EN;
+ fallthrough;
+ case ETHTOOL_PHY_EDPD_NO_TX:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return phy_modify(phydev, ADIN1300_PHY_CTRL_STATUS2,
+ (ADIN1300_NRG_PD_EN | ADIN1300_NRG_PD_TX_EN),
+ val);
+}
+
+static int adin_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return adin_get_downshift(phydev, data);
+ case ETHTOOL_PHY_EDPD:
+ return adin_get_edpd(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int adin_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return adin_set_downshift(phydev, *(const u8 *)data);
+ case ETHTOOL_PHY_EDPD:
+ return adin_set_edpd(phydev, *(const u16 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int adin_config_clk_out(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ const char *val = NULL;
+ u8 sel = 0;
+
+ device_property_read_string(dev, "adi,phy-output-clock", &val);
+ if (!val) {
+ /* property not present, do not enable GP_CLK pin */
+ } else if (strcmp(val, "25mhz-reference") == 0) {
+ sel |= ADIN1300_GE_CLK_CFG_25;
+ } else if (strcmp(val, "125mhz-free-running") == 0) {
+ sel |= ADIN1300_GE_CLK_CFG_FREE_125;
+ } else if (strcmp(val, "adaptive-free-running") == 0) {
+ sel |= ADIN1300_GE_CLK_CFG_HRT_FREE;
+ } else {
+ phydev_err(phydev, "invalid adi,phy-output-clock\n");
+ return -EINVAL;
+ }
+
+ if (device_property_read_bool(dev, "adi,phy-output-reference-clock"))
+ sel |= ADIN1300_GE_CLK_CFG_REF_EN;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_GE_CLK_CFG_REG,
+ ADIN1300_GE_CLK_CFG_MASK, sel);
+}
+
+static int adin_config_init(struct phy_device *phydev)
+{
+ int rc;
+
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ rc = adin_config_rgmii_mode(phydev);
+ if (rc < 0)
+ return rc;
+
+ rc = adin_config_rmii_mode(phydev);
+ if (rc < 0)
+ return rc;
+
+ rc = adin_set_downshift(phydev, 4);
+ if (rc < 0)
+ return rc;
+
+ rc = adin_set_edpd(phydev, ETHTOOL_PHY_EDPD_DFLT_TX_MSECS);
+ if (rc < 0)
+ return rc;
+
+ rc = adin_config_clk_out(phydev);
+ if (rc < 0)
+ return rc;
+
+ phydev_dbg(phydev, "PHY is using mode '%s'\n",
+ phy_modes(phydev->interface));
+
+ return 0;
+}
+
+static int adin_phy_ack_intr(struct phy_device *phydev)
+{
+ /* Clear pending interrupts */
+ int rc = phy_read(phydev, ADIN1300_INT_STATUS_REG);
+
+ return rc < 0 ? rc : 0;
+}
+
+static int adin_phy_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = adin_phy_ack_intr(phydev);
+ if (err)
+ return err;
+
+ err = phy_set_bits(phydev, ADIN1300_INT_MASK_REG,
+ ADIN1300_INT_MASK_EN);
+ } else {
+ err = phy_clear_bits(phydev, ADIN1300_INT_MASK_REG,
+ ADIN1300_INT_MASK_EN);
+ if (err)
+ return err;
+
+ err = adin_phy_ack_intr(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t adin_phy_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, ADIN1300_INT_STATUS_REG);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & ADIN1300_INT_LINK_STAT_CHNG_EN))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int adin_cl45_to_adin_reg(struct phy_device *phydev, int devad,
+ u16 cl45_regnum)
+{
+ const struct adin_clause45_mmd_map *m;
+ int i;
+
+ if (devad == MDIO_MMD_VEND1)
+ return cl45_regnum;
+
+ for (i = 0; i < ARRAY_SIZE(adin_clause45_mmd_map); i++) {
+ m = &adin_clause45_mmd_map[i];
+ if (m->devad == devad && m->cl45_regnum == cl45_regnum)
+ return m->adin_regnum;
+ }
+
+ phydev_err(phydev,
+ "No translation available for devad: %d reg: %04x\n",
+ devad, cl45_regnum);
+
+ return -EINVAL;
+}
+
+static int adin_read_mmd(struct phy_device *phydev, int devad, u16 regnum)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ int phy_addr = phydev->mdio.addr;
+ int adin_regnum;
+ int err;
+
+ adin_regnum = adin_cl45_to_adin_reg(phydev, devad, regnum);
+ if (adin_regnum < 0)
+ return adin_regnum;
+
+ err = __mdiobus_write(bus, phy_addr, ADIN1300_MII_EXT_REG_PTR,
+ adin_regnum);
+ if (err)
+ return err;
+
+ return __mdiobus_read(bus, phy_addr, ADIN1300_MII_EXT_REG_DATA);
+}
+
+static int adin_write_mmd(struct phy_device *phydev, int devad, u16 regnum,
+ u16 val)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ int phy_addr = phydev->mdio.addr;
+ int adin_regnum;
+ int err;
+
+ adin_regnum = adin_cl45_to_adin_reg(phydev, devad, regnum);
+ if (adin_regnum < 0)
+ return adin_regnum;
+
+ err = __mdiobus_write(bus, phy_addr, ADIN1300_MII_EXT_REG_PTR,
+ adin_regnum);
+ if (err)
+ return err;
+
+ return __mdiobus_write(bus, phy_addr, ADIN1300_MII_EXT_REG_DATA, val);
+}
+
+static int adin_config_mdix(struct phy_device *phydev)
+{
+ bool auto_en, mdix_en;
+ int reg;
+
+ mdix_en = false;
+ auto_en = false;
+ switch (phydev->mdix_ctrl) {
+ case ETH_TP_MDI:
+ break;
+ case ETH_TP_MDI_X:
+ mdix_en = true;
+ break;
+ case ETH_TP_MDI_AUTO:
+ auto_en = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ reg = phy_read(phydev, ADIN1300_PHY_CTRL1);
+ if (reg < 0)
+ return reg;
+
+ if (mdix_en)
+ reg |= ADIN1300_MAN_MDIX_EN;
+ else
+ reg &= ~ADIN1300_MAN_MDIX_EN;
+
+ if (auto_en)
+ reg |= ADIN1300_AUTO_MDI_EN;
+ else
+ reg &= ~ADIN1300_AUTO_MDI_EN;
+
+ return phy_write(phydev, ADIN1300_PHY_CTRL1, reg);
+}
+
+static int adin_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_clear_bits(phydev, ADIN1300_PHY_CTRL1, ADIN1300_DIAG_CLK_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_set_bits(phydev, ADIN1300_PHY_CTRL3, ADIN1300_LINKING_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = adin_config_mdix(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_config_aneg(phydev);
+}
+
+static int adin_mdix_update(struct phy_device *phydev)
+{
+ bool auto_en, mdix_en;
+ bool swapped;
+ int reg;
+
+ reg = phy_read(phydev, ADIN1300_PHY_CTRL1);
+ if (reg < 0)
+ return reg;
+
+ auto_en = !!(reg & ADIN1300_AUTO_MDI_EN);
+ mdix_en = !!(reg & ADIN1300_MAN_MDIX_EN);
+
+ /* If MDI/MDIX is forced, just read it from the control reg */
+ if (!auto_en) {
+ if (mdix_en)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+ return 0;
+ }
+
+ /**
+ * Otherwise, we need to deduce it from the PHY status2 reg.
+ * When Auto-MDI is enabled, the ADIN1300_MAN_MDIX_EN bit implies
+ * a preference for MDIX when it is set.
+ */
+ reg = phy_read(phydev, ADIN1300_PHY_STATUS1);
+ if (reg < 0)
+ return reg;
+
+ swapped = !!(reg & ADIN1300_PAIR_01_SWAP);
+
+ if (mdix_en != swapped)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+
+ return 0;
+}
+
+static int adin_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = adin_mdix_update(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_read_status(phydev);
+}
+
+static int adin_soft_reset(struct phy_device *phydev)
+{
+ int rc;
+
+ /* The reset bit is self-clearing, set it and wait */
+ rc = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_GE_SOFT_RESET_REG,
+ ADIN1300_GE_SOFT_RESET);
+ if (rc < 0)
+ return rc;
+
+ msleep(20);
+
+ /* If we get a read error something may be wrong */
+ rc = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_GE_SOFT_RESET_REG);
+
+ return rc < 0 ? rc : 0;
+}
+
+static int adin_get_sset_count(struct phy_device *phydev)
+{
+ return ARRAY_SIZE(adin_hw_stats);
+}
+
+static void adin_get_strings(struct phy_device *phydev, u8 *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adin_hw_stats); i++) {
+ strscpy(&data[i * ETH_GSTRING_LEN],
+ adin_hw_stats[i].string, ETH_GSTRING_LEN);
+ }
+}
+
+static int adin_read_mmd_stat_regs(struct phy_device *phydev,
+ const struct adin_hw_stat *stat,
+ u32 *val)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, stat->reg1);
+ if (ret < 0)
+ return ret;
+
+ *val = (ret & 0xffff);
+
+ if (stat->reg2 == 0)
+ return 0;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, stat->reg2);
+ if (ret < 0)
+ return ret;
+
+ *val <<= 16;
+ *val |= (ret & 0xffff);
+
+ return 0;
+}
+
+static u64 adin_get_stat(struct phy_device *phydev, int i)
+{
+ const struct adin_hw_stat *stat = &adin_hw_stats[i];
+ struct adin_priv *priv = phydev->priv;
+ u32 val;
+ int ret;
+
+ if (stat->reg1 > 0x1f) {
+ ret = adin_read_mmd_stat_regs(phydev, stat, &val);
+ if (ret < 0)
+ return (u64)(~0);
+ } else {
+ ret = phy_read(phydev, stat->reg1);
+ if (ret < 0)
+ return (u64)(~0);
+ val = (ret & 0xffff);
+ }
+
+ priv->stats[i] += val;
+
+ return priv->stats[i];
+}
+
+static void adin_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ int i, rc;
+
+ /* latch copies of all the frame-checker counters */
+ rc = phy_read(phydev, ADIN1300_RX_ERR_CNT);
+ if (rc < 0)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(adin_hw_stats); i++)
+ data[i] = adin_get_stat(phydev, i);
+}
+
+static int adin_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct adin_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
+static int adin_cable_test_start(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_clear_bits(phydev, ADIN1300_PHY_CTRL3, ADIN1300_LINKING_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_clear_bits(phydev, ADIN1300_PHY_CTRL1, ADIN1300_DIAG_CLK_EN);
+ if (ret < 0)
+ return ret;
+
+ /* wait a bit for the clock to stabilize */
+ msleep(50);
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_CDIAG_RUN,
+ ADIN1300_CDIAG_RUN_EN);
+}
+
+static int adin_cable_test_report_trans(int result)
+{
+ int mask;
+
+ if (result & ADIN1300_CDIAG_RSLT_GOOD)
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ if (result & ADIN1300_CDIAG_RSLT_OPEN)
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+
+ /* short with other pairs */
+ mask = ADIN1300_CDIAG_RSLT_XSHRT3 |
+ ADIN1300_CDIAG_RSLT_XSHRT2 |
+ ADIN1300_CDIAG_RSLT_XSHRT1;
+ if (result & mask)
+ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+
+ if (result & ADIN1300_CDIAG_RSLT_SHRT)
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+}
+
+static int adin_cable_test_report_pair(struct phy_device *phydev,
+ unsigned int pair)
+{
+ int fault_rslt;
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_CDIAG_DTLD_RSLTS(pair));
+ if (ret < 0)
+ return ret;
+
+ fault_rslt = adin_cable_test_report_trans(ret);
+
+ ret = ethnl_cable_test_result(phydev, pair, fault_rslt);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_CDIAG_FLT_DIST(pair));
+ if (ret < 0)
+ return ret;
+
+ switch (fault_rslt) {
+ case ETHTOOL_A_CABLE_RESULT_CODE_OPEN:
+ case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT:
+ case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT:
+ return ethnl_cable_test_fault_length(phydev, pair, ret * 100);
+ default:
+ return 0;
+ }
+}
+
+static int adin_cable_test_report(struct phy_device *phydev)
+{
+ unsigned int pair;
+ int ret;
+
+ for (pair = ETHTOOL_A_CABLE_PAIR_A; pair <= ETHTOOL_A_CABLE_PAIR_D; pair++) {
+ ret = adin_cable_test_report_pair(phydev, pair);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adin_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ int ret;
+
+ *finished = false;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_CDIAG_RUN);
+ if (ret < 0)
+ return ret;
+
+ if (ret & ADIN1300_CDIAG_RUN_EN)
+ return 0;
+
+ *finished = true;
+
+ return adin_cable_test_report(phydev);
+}
+
+static struct phy_driver adin_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1200),
+ .name = "ADIN1200",
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = adin_probe,
+ .config_init = adin_config_init,
+ .soft_reset = adin_soft_reset,
+ .config_aneg = adin_config_aneg,
+ .read_status = adin_read_status,
+ .get_tunable = adin_get_tunable,
+ .set_tunable = adin_set_tunable,
+ .config_intr = adin_phy_config_intr,
+ .handle_interrupt = adin_phy_handle_interrupt,
+ .get_sset_count = adin_get_sset_count,
+ .get_strings = adin_get_strings,
+ .get_stats = adin_get_stats,
+ .resume = genphy_resume,
+ .suspend = genphy_suspend,
+ .read_mmd = adin_read_mmd,
+ .write_mmd = adin_write_mmd,
+ .cable_test_start = adin_cable_test_start,
+ .cable_test_get_status = adin_cable_test_get_status,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1300),
+ .name = "ADIN1300",
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = adin_probe,
+ .config_init = adin_config_init,
+ .soft_reset = adin_soft_reset,
+ .config_aneg = adin_config_aneg,
+ .read_status = adin_read_status,
+ .get_tunable = adin_get_tunable,
+ .set_tunable = adin_set_tunable,
+ .config_intr = adin_phy_config_intr,
+ .handle_interrupt = adin_phy_handle_interrupt,
+ .get_sset_count = adin_get_sset_count,
+ .get_strings = adin_get_strings,
+ .get_stats = adin_get_stats,
+ .resume = genphy_resume,
+ .suspend = genphy_suspend,
+ .read_mmd = adin_read_mmd,
+ .write_mmd = adin_write_mmd,
+ .cable_test_start = adin_cable_test_start,
+ .cable_test_get_status = adin_cable_test_get_status,
+ },
+};
+
+module_phy_driver(adin_driver);
+
+static struct mdio_device_id __maybe_unused adin_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1200) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1300) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, adin_tbl);
+MODULE_DESCRIPTION("Analog Devices Industrial Ethernet PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
new file mode 100644
index 000000000..7619d6185
--- /dev/null
+++ b/drivers/net/phy/adin1100.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * Driver for Analog Devices Industrial Ethernet T1L PHYs
+ *
+ * Copyright 2020 Analog Devices Inc.
+ */
+#include <linux/kernel.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+
+#define PHY_ID_ADIN1100 0x0283bc81
+#define PHY_ID_ADIN1110 0x0283bc91
+#define PHY_ID_ADIN2111 0x0283bca1
+
+#define ADIN_FORCED_MODE 0x8000
+#define ADIN_FORCED_MODE_EN BIT(0)
+
+#define ADIN_CRSM_SFT_RST 0x8810
+#define ADIN_CRSM_SFT_RST_EN BIT(0)
+
+#define ADIN_CRSM_SFT_PD_CNTRL 0x8812
+#define ADIN_CRSM_SFT_PD_CNTRL_EN BIT(0)
+
+#define ADIN_AN_PHY_INST_STATUS 0x8030
+#define ADIN_IS_CFG_SLV BIT(2)
+#define ADIN_IS_CFG_MST BIT(3)
+
+#define ADIN_CRSM_STAT 0x8818
+#define ADIN_CRSM_SFT_PD_RDY BIT(1)
+#define ADIN_CRSM_SYS_RDY BIT(0)
+
+#define ADIN_MSE_VAL 0x830B
+
+#define ADIN_SQI_MAX 7
+
+struct adin_mse_sqi_range {
+ u16 start;
+ u16 end;
+};
+
+static const struct adin_mse_sqi_range adin_mse_sqi_map[] = {
+ { 0x0A74, 0xFFFF },
+ { 0x084E, 0x0A74 },
+ { 0x0698, 0x084E },
+ { 0x053D, 0x0698 },
+ { 0x0429, 0x053D },
+ { 0x034E, 0x0429 },
+ { 0x02A0, 0x034E },
+ { 0x0000, 0x02A0 },
+};
+
+/**
+ * struct adin_priv - ADIN PHY driver private data
+ * @tx_level_2v4_able: set if the PHY supports 2.4V TX levels (10BASE-T1L)
+ * @tx_level_2v4: set if the PHY requests 2.4V TX levels (10BASE-T1L)
+ * @tx_level_prop_present: set if the TX level is specified in DT
+ */
+struct adin_priv {
+ unsigned int tx_level_2v4_able:1;
+ unsigned int tx_level_2v4:1;
+ unsigned int tx_level_prop_present:1;
+};
+
+static int adin_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_read_status(phydev);
+ if (ret)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, ADIN_AN_PHY_INST_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & ADIN_IS_CFG_SLV)
+ phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+
+ if (ret & ADIN_IS_CFG_MST)
+ phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+
+ return 0;
+}
+
+static int adin_config_aneg(struct phy_device *phydev)
+{
+ struct adin_priv *priv = phydev->priv;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE) {
+ ret = genphy_c45_pma_setup_forced(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (priv->tx_level_prop_present && priv->tx_level_2v4)
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_B10L_PMA_CTRL,
+ MDIO_PMA_10T1L_CTRL_2V4_EN);
+ else
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_B10L_PMA_CTRL,
+ MDIO_PMA_10T1L_CTRL_2V4_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Force PHY to use above configurations */
+ return phy_set_bits_mmd(phydev, MDIO_MMD_AN, ADIN_FORCED_MODE, ADIN_FORCED_MODE_EN);
+ }
+
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_AN, ADIN_FORCED_MODE, ADIN_FORCED_MODE_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Request increased transmit level from LP. */
+ if (priv->tx_level_prop_present && priv->tx_level_2v4) {
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H,
+ MDIO_AN_T1_ADV_H_10L_TX_HI |
+ MDIO_AN_T1_ADV_H_10L_TX_HI_REQ);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Disable 2.4 Vpp transmit level. */
+ if ((priv->tx_level_prop_present && !priv->tx_level_2v4) || !priv->tx_level_2v4_able) {
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H,
+ MDIO_AN_T1_ADV_H_10L_TX_HI |
+ MDIO_AN_T1_ADV_H_10L_TX_HI_REQ);
+ if (ret < 0)
+ return ret;
+ }
+
+ return genphy_c45_config_aneg(phydev);
+}
+
+static int adin_set_powerdown_mode(struct phy_device *phydev, bool en)
+{
+ int ret;
+ int val;
+
+ val = en ? ADIN_CRSM_SFT_PD_CNTRL_EN : 0;
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN_CRSM_SFT_PD_CNTRL, val);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
+ (ret & ADIN_CRSM_SFT_PD_RDY) == val,
+ 1000, 30000, true);
+}
+
+static int adin_suspend(struct phy_device *phydev)
+{
+ return adin_set_powerdown_mode(phydev, true);
+}
+
+static int adin_resume(struct phy_device *phydev)
+{
+ return adin_set_powerdown_mode(phydev, false);
+}
+
+static int adin_set_loopback(struct phy_device *phydev, bool enable)
+{
+ if (enable)
+ return phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL,
+ BMCR_LOOPBACK);
+
+ /* PCS loopback (according to 10BASE-T1L spec) */
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL,
+ BMCR_LOOPBACK);
+}
+
+static int adin_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN_CRSM_SFT_RST, ADIN_CRSM_SFT_RST_EN);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
+ (ret & ADIN_CRSM_SYS_RDY),
+ 10000, 30000, true);
+}
+
+static int adin_get_features(struct phy_device *phydev)
+{
+ struct adin_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int ret;
+ u8 val;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10T1L_STAT);
+ if (ret < 0)
+ return ret;
+
+ /* This depends on the voltage level from the power source */
+ priv->tx_level_2v4_able = !!(ret & MDIO_PMA_10T1L_STAT_2V4_ABLE);
+
+ phydev_dbg(phydev, "PHY supports 2.4V TX level: %s\n",
+ priv->tx_level_2v4_able ? "yes" : "no");
+
+ priv->tx_level_prop_present = device_property_present(dev, "phy-10base-t1l-2.4vpp");
+ if (priv->tx_level_prop_present) {
+ ret = device_property_read_u8(dev, "phy-10base-t1l-2.4vpp", &val);
+ if (ret < 0)
+ return ret;
+
+ priv->tx_level_2v4 = val;
+ if (!priv->tx_level_2v4 && priv->tx_level_2v4_able)
+ phydev_info(phydev,
+ "PHY supports 2.4V TX level, but disabled via config\n");
+ }
+
+ linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array),
+ phydev->supported);
+
+ return genphy_c45_pma_read_abilities(phydev);
+}
+
+static int adin_get_sqi(struct phy_device *phydev)
+{
+ u16 mse_val;
+ int sqi;
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT1);
+ if (ret < 0)
+ return ret;
+ else if (!(ret & MDIO_STAT1_LSTATUS))
+ return 0;
+
+ ret = phy_read_mmd(phydev, MDIO_STAT1, ADIN_MSE_VAL);
+ if (ret < 0)
+ return ret;
+
+ mse_val = 0xFFFF & ret;
+ for (sqi = 0; sqi < ARRAY_SIZE(adin_mse_sqi_map); sqi++) {
+ if (mse_val >= adin_mse_sqi_map[sqi].start && mse_val <= adin_mse_sqi_map[sqi].end)
+ return sqi;
+ }
+
+ return -EINVAL;
+}
+
+static int adin_get_sqi_max(struct phy_device *phydev)
+{
+ return ADIN_SQI_MAX;
+}
+
+static int adin_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct adin_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
+static struct phy_driver adin_driver[] = {
+ {
+ .phy_id = PHY_ID_ADIN1100,
+ .phy_id_mask = 0xffffffcf,
+ .name = "ADIN1100",
+ .get_features = adin_get_features,
+ .soft_reset = adin_soft_reset,
+ .probe = adin_probe,
+ .config_aneg = adin_config_aneg,
+ .read_status = adin_read_status,
+ .set_loopback = adin_set_loopback,
+ .suspend = adin_suspend,
+ .resume = adin_resume,
+ .get_sqi = adin_get_sqi,
+ .get_sqi_max = adin_get_sqi_max,
+ },
+};
+
+module_phy_driver(adin_driver);
+
+static struct mdio_device_id __maybe_unused adin_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1110) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_ADIN2111) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, adin_tbl);
+MODULE_DESCRIPTION("Analog Devices Industrial Ethernet T1L PHY driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/amd.c b/drivers/net/phy/amd.c
new file mode 100644
index 000000000..001bb6d8b
--- /dev/null
+++ b/drivers/net/phy/amd.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for AMD am79c PHYs
+ *
+ * Author: Heiko Schocher <hs@denx.de>
+ *
+ * Copyright (c) 2011 DENX Software Engineering GmbH
+ */
+#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)
+
+#define MII_AM79C_IR_LINK_DOWN BIT(2)
+#define MII_AM79C_IR_ANEG_DONE BIT(0)
+#define MII_AM79C_IR_IMASK_STAT (MII_AM79C_IR_LINK_DOWN | MII_AM79C_IR_ANEG_DONE)
+
+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 = am79c_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_AM79C_IR, MII_AM79C_IR_IMASK_INIT);
+ } else {
+ err = phy_write(phydev, MII_AM79C_IR, 0);
+ if (err)
+ return err;
+
+ err = am79c_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t am79c_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_AM79C_IR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_AM79C_IR_IMASK_STAT))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static struct phy_driver am79c_driver[] = { {
+ .phy_id = PHY_ID_AM79C874,
+ .name = "AM79C874",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_init = am79c_config_init,
+ .config_intr = am79c_config_intr,
+ .handle_interrupt = am79c_handle_interrupt,
+} };
+
+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.h b/drivers/net/phy/aquantia.h
new file mode 100644
index 000000000..c684b65c6
--- /dev/null
+++ b/drivers/net/phy/aquantia.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* HWMON driver for Aquantia PHY
+ *
+ * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
+ * Author: Andrew Lunn <andrew@lunn.ch>
+ * Author: Heiner Kallweit <hkallweit1@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/phy.h>
+
+#if IS_REACHABLE(CONFIG_HWMON)
+int aqr_hwmon_probe(struct phy_device *phydev);
+#else
+static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
+#endif
diff --git a/drivers/net/phy/aquantia_hwmon.c b/drivers/net/phy/aquantia_hwmon.c
new file mode 100644
index 000000000..0da451e46
--- /dev/null
+++ b/drivers/net/phy/aquantia_hwmon.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+/* HWMON driver for Aquantia PHY
+ *
+ * Author: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
+ * Author: Andrew Lunn <andrew@lunn.ch>
+ * Author: Heiner Kallweit <hkallweit1@gmail.com>
+ */
+
+#include <linux/phy.h>
+#include <linux/device.h>
+#include <linux/ctype.h>
+#include <linux/hwmon.h>
+
+#include "aquantia.h"
+
+/* Vendor specific 1, MDIO_MMD_VEND2 */
+#define VEND1_THERMAL_PROV_HIGH_TEMP_FAIL 0xc421
+#define VEND1_THERMAL_PROV_LOW_TEMP_FAIL 0xc422
+#define VEND1_THERMAL_PROV_HIGH_TEMP_WARN 0xc423
+#define VEND1_THERMAL_PROV_LOW_TEMP_WARN 0xc424
+#define VEND1_THERMAL_STAT1 0xc820
+#define VEND1_THERMAL_STAT2 0xc821
+#define VEND1_THERMAL_STAT2_VALID BIT(0)
+#define VEND1_GENERAL_STAT1 0xc830
+#define VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL BIT(14)
+#define VEND1_GENERAL_STAT1_LOW_TEMP_FAIL BIT(13)
+#define VEND1_GENERAL_STAT1_HIGH_TEMP_WARN BIT(12)
+#define VEND1_GENERAL_STAT1_LOW_TEMP_WARN BIT(11)
+
+#if IS_REACHABLE(CONFIG_HWMON)
+
+static umode_t aqr_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_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_lcrit_alarm:
+ case hwmon_temp_crit_alarm:
+ return 0444;
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_lcrit:
+ case hwmon_temp_crit:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int aqr_hwmon_get(struct phy_device *phydev, int reg, long *value)
+{
+ int temp = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
+
+ if (temp < 0)
+ return temp;
+
+ /* 16 bit value is 2's complement with LSB = 1/256th degree Celsius */
+ *value = (s16)temp * 1000 / 256;
+
+ return 0;
+}
+
+static int aqr_hwmon_set(struct phy_device *phydev, int reg, long value)
+{
+ int temp;
+
+ if (value >= 128000 || value < -128000)
+ return -ERANGE;
+
+ temp = value * 256 / 1000;
+
+ /* temp is in s16 range and we're interested in lower 16 bits only */
+ return phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, (u16)temp);
+}
+
+static int aqr_hwmon_test_bit(struct phy_device *phydev, int reg, int bit)
+{
+ int val = phy_read_mmd(phydev, MDIO_MMD_VEND1, reg);
+
+ if (val < 0)
+ return val;
+
+ return !!(val & bit);
+}
+
+static int aqr_hwmon_status1(struct phy_device *phydev, int bit, long *value)
+{
+ int val = aqr_hwmon_test_bit(phydev, VEND1_GENERAL_STAT1, bit);
+
+ if (val < 0)
+ return val;
+
+ *value = val;
+
+ return 0;
+}
+
+static int aqr_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 reg;
+
+ if (type != hwmon_temp)
+ return -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = aqr_hwmon_test_bit(phydev, VEND1_THERMAL_STAT2,
+ VEND1_THERMAL_STAT2_VALID);
+ if (reg < 0)
+ return reg;
+ if (!reg)
+ return -EBUSY;
+
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_STAT1, value);
+
+ case hwmon_temp_lcrit:
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
+ value);
+ case hwmon_temp_min:
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
+ value);
+ case hwmon_temp_max:
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
+ value);
+ case hwmon_temp_crit:
+ return aqr_hwmon_get(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
+ value);
+ case hwmon_temp_lcrit_alarm:
+ return aqr_hwmon_status1(phydev,
+ VEND1_GENERAL_STAT1_LOW_TEMP_FAIL,
+ value);
+ case hwmon_temp_min_alarm:
+ return aqr_hwmon_status1(phydev,
+ VEND1_GENERAL_STAT1_LOW_TEMP_WARN,
+ value);
+ case hwmon_temp_max_alarm:
+ return aqr_hwmon_status1(phydev,
+ VEND1_GENERAL_STAT1_HIGH_TEMP_WARN,
+ value);
+ case hwmon_temp_crit_alarm:
+ return aqr_hwmon_status1(phydev,
+ VEND1_GENERAL_STAT1_HIGH_TEMP_FAIL,
+ value);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int aqr_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long value)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+
+ if (type != hwmon_temp)
+ return -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_temp_lcrit:
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_FAIL,
+ value);
+ case hwmon_temp_min:
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_LOW_TEMP_WARN,
+ value);
+ case hwmon_temp_max:
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_WARN,
+ value);
+ case hwmon_temp_crit:
+ return aqr_hwmon_set(phydev, VEND1_THERMAL_PROV_HIGH_TEMP_FAIL,
+ value);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_ops aqr_hwmon_ops = {
+ .is_visible = aqr_hwmon_is_visible,
+ .read = aqr_hwmon_read,
+ .write = aqr_hwmon_write,
+};
+
+static u32 aqr_hwmon_chip_config[] = {
+ HWMON_C_REGISTER_TZ,
+ 0,
+};
+
+static const struct hwmon_channel_info aqr_hwmon_chip = {
+ .type = hwmon_chip,
+ .config = aqr_hwmon_chip_config,
+};
+
+static u32 aqr_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 aqr_hwmon_temp = {
+ .type = hwmon_temp,
+ .config = aqr_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info * const aqr_hwmon_info[] = {
+ &aqr_hwmon_chip,
+ &aqr_hwmon_temp,
+ NULL,
+};
+
+static const struct hwmon_chip_info aqr_hwmon_chip_info = {
+ .ops = &aqr_hwmon_ops,
+ .info = aqr_hwmon_info,
+};
+
+int aqr_hwmon_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct device *hwmon_dev;
+ char *hwmon_name;
+ int i, j;
+
+ hwmon_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
+ if (!hwmon_name)
+ return -ENOMEM;
+
+ for (i = j = 0; hwmon_name[i]; i++) {
+ if (isalnum(hwmon_name[i])) {
+ if (i != j)
+ hwmon_name[j] = hwmon_name[i];
+ j++;
+ }
+ }
+ hwmon_name[j] = '\0';
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
+ phydev, &aqr_hwmon_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+#endif
diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c
new file mode 100644
index 000000000..334a6904c
--- /dev/null
+++ b/drivers/net/phy/aquantia_main.c
@@ -0,0 +1,882 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Aquantia PHY
+ *
+ * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
+ *
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/bitfield.h>
+#include <linux/phy.h>
+
+#include "aquantia.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_AQCS109 0x03a1b5c2
+#define PHY_ID_AQR405 0x03a1b4b0
+#define PHY_ID_AQR112 0x03a1b662
+#define PHY_ID_AQR412 0x03a1b712
+#define PHY_ID_AQR113C 0x31c31c12
+
+#define MDIO_PHYXS_VEND_IF_STATUS 0xe812
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK GENMASK(7, 3)
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR 0
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX 1
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI 2
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII 3
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI 4
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII 6
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI 7
+#define MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII 10
+
+#define MDIO_AN_VEND_PROV 0xc400
+#define MDIO_AN_VEND_PROV_1000BASET_FULL BIT(15)
+#define MDIO_AN_VEND_PROV_1000BASET_HALF BIT(14)
+#define MDIO_AN_VEND_PROV_5000BASET_FULL BIT(11)
+#define MDIO_AN_VEND_PROV_2500BASET_FULL BIT(10)
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN BIT(4)
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK GENMASK(3, 0)
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
+
+#define MDIO_AN_TX_VEND_STATUS1 0xc800
+#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK GENMASK(3, 1)
+#define MDIO_AN_TX_VEND_STATUS1_10BASET 0
+#define MDIO_AN_TX_VEND_STATUS1_100BASETX 1
+#define MDIO_AN_TX_VEND_STATUS1_1000BASET 2
+#define MDIO_AN_TX_VEND_STATUS1_10GBASET 3
+#define MDIO_AN_TX_VEND_STATUS1_2500BASET 4
+#define MDIO_AN_TX_VEND_STATUS1_5000BASET 5
+#define MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX BIT(0)
+
+#define MDIO_AN_TX_VEND_INT_STATUS1 0xcc00
+#define MDIO_AN_TX_VEND_INT_STATUS1_DOWNSHIFT BIT(1)
+
+#define MDIO_AN_TX_VEND_INT_STATUS2 0xcc01
+#define MDIO_AN_TX_VEND_INT_STATUS2_MASK BIT(0)
+
+#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
+#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
+
+#define MDIO_AN_RX_LP_STAT1 0xe820
+#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
+#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
+#define MDIO_AN_RX_LP_STAT1_SHORT_REACH BIT(13)
+#define MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT BIT(12)
+#define MDIO_AN_RX_LP_STAT1_AQ_PHY BIT(2)
+
+#define MDIO_AN_RX_LP_STAT4 0xe823
+#define MDIO_AN_RX_LP_STAT4_FW_MAJOR GENMASK(15, 8)
+#define MDIO_AN_RX_LP_STAT4_FW_MINOR GENMASK(7, 0)
+
+#define MDIO_AN_RX_VEND_STAT3 0xe832
+#define MDIO_AN_RX_VEND_STAT3_AFR BIT(0)
+
+/* MDIO_MMD_C22EXT */
+#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
+#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
+#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
+#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
+#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
+#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
+#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
+#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
+#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
+#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
+
+/* Vendor specific 1, MDIO_MMD_VEND1 */
+#define VEND1_GLOBAL_FW_ID 0x0020
+#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
+#define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0)
+
+#define VEND1_GLOBAL_GEN_STAT2 0xc831
+#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15)
+
+/* The following registers all have similar layouts; first the registers... */
+#define VEND1_GLOBAL_CFG_10M 0x0310
+#define VEND1_GLOBAL_CFG_100M 0x031b
+#define VEND1_GLOBAL_CFG_1G 0x031c
+#define VEND1_GLOBAL_CFG_2_5G 0x031d
+#define VEND1_GLOBAL_CFG_5G 0x031e
+#define VEND1_GLOBAL_CFG_10G 0x031f
+/* ...and now the fields */
+#define VEND1_GLOBAL_CFG_RATE_ADAPT GENMASK(8, 7)
+#define VEND1_GLOBAL_CFG_RATE_ADAPT_NONE 0
+#define VEND1_GLOBAL_CFG_RATE_ADAPT_USX 1
+#define VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE 2
+
+#define VEND1_GLOBAL_RSVD_STAT1 0xc885
+#define VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID GENMASK(7, 4)
+#define VEND1_GLOBAL_RSVD_STAT1_PROV_ID GENMASK(3, 0)
+
+#define VEND1_GLOBAL_RSVD_STAT9 0xc88d
+#define VEND1_GLOBAL_RSVD_STAT9_MODE GENMASK(7, 0)
+#define VEND1_GLOBAL_RSVD_STAT9_1000BT2 0x23
+
+#define VEND1_GLOBAL_INT_STD_STATUS 0xfc00
+#define VEND1_GLOBAL_INT_VEND_STATUS 0xfc01
+
+#define VEND1_GLOBAL_INT_STD_MASK 0xff00
+#define VEND1_GLOBAL_INT_STD_MASK_PMA1 BIT(15)
+#define VEND1_GLOBAL_INT_STD_MASK_PMA2 BIT(14)
+#define VEND1_GLOBAL_INT_STD_MASK_PCS1 BIT(13)
+#define VEND1_GLOBAL_INT_STD_MASK_PCS2 BIT(12)
+#define VEND1_GLOBAL_INT_STD_MASK_PCS3 BIT(11)
+#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS1 BIT(10)
+#define VEND1_GLOBAL_INT_STD_MASK_PHY_XS2 BIT(9)
+#define VEND1_GLOBAL_INT_STD_MASK_AN1 BIT(8)
+#define VEND1_GLOBAL_INT_STD_MASK_AN2 BIT(7)
+#define VEND1_GLOBAL_INT_STD_MASK_GBE BIT(6)
+#define VEND1_GLOBAL_INT_STD_MASK_ALL BIT(0)
+
+#define VEND1_GLOBAL_INT_VEND_MASK 0xff01
+#define VEND1_GLOBAL_INT_VEND_MASK_PMA BIT(15)
+#define VEND1_GLOBAL_INT_VEND_MASK_PCS BIT(14)
+#define VEND1_GLOBAL_INT_VEND_MASK_PHY_XS BIT(13)
+#define VEND1_GLOBAL_INT_VEND_MASK_AN BIT(12)
+#define VEND1_GLOBAL_INT_VEND_MASK_GBE BIT(11)
+#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL1 BIT(2)
+#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
+#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
+
+/* Sleep and timeout for checking if the Processor-Intensive
+ * MDIO operation is finished
+ */
+#define AQR107_OP_IN_PROG_SLEEP 1000
+#define AQR107_OP_IN_PROG_TIMEOUT 100000
+
+struct aqr107_hw_stat {
+ const char *name;
+ int reg;
+ int size;
+};
+
+#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
+static const struct aqr107_hw_stat aqr107_hw_stats[] = {
+ SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
+ SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
+ SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
+ SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
+ SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
+ SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
+ SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
+ SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
+ SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
+ SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
+};
+#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
+
+struct aqr107_priv {
+ u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
+};
+
+static int aqr107_get_sset_count(struct phy_device *phydev)
+{
+ return AQR107_SGMII_STAT_SZ;
+}
+
+static void aqr107_get_strings(struct phy_device *phydev, u8 *data)
+{
+ int i;
+
+ for (i = 0; i < AQR107_SGMII_STAT_SZ; i++)
+ strscpy(data + i * ETH_GSTRING_LEN, aqr107_hw_stats[i].name,
+ ETH_GSTRING_LEN);
+}
+
+static u64 aqr107_get_stat(struct phy_device *phydev, int index)
+{
+ const struct aqr107_hw_stat *stat = aqr107_hw_stats + index;
+ int len_l = min(stat->size, 16);
+ int len_h = stat->size - len_l;
+ u64 ret;
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg);
+ if (val < 0)
+ return U64_MAX;
+
+ ret = val & GENMASK(len_l - 1, 0);
+ if (len_h) {
+ val = phy_read_mmd(phydev, MDIO_MMD_C22EXT, stat->reg + 1);
+ if (val < 0)
+ return U64_MAX;
+
+ ret += (val & GENMASK(len_h - 1, 0)) << 16;
+ }
+
+ return ret;
+}
+
+static void aqr107_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct aqr107_priv *priv = phydev->priv;
+ u64 val;
+ int i;
+
+ for (i = 0; i < AQR107_SGMII_STAT_SZ; i++) {
+ val = aqr107_get_stat(phydev, i);
+ if (val == U64_MAX)
+ phydev_err(phydev, "Reading HW Statistics failed for %s\n",
+ aqr107_hw_stats[i].name);
+ else
+ priv->sgmii_stats[i] += val;
+
+ data[i] = priv->sgmii_stats[i];
+ }
+}
+
+static int aqr_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ u16 reg;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return genphy_c45_pma_setup_forced(phydev);
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ /* Clause 45 has no standardized support for 1000BaseT, therefore
+ * use vendor registers for this mode.
+ */
+ reg = 0;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->advertising))
+ reg |= MDIO_AN_VEND_PROV_1000BASET_FULL;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->advertising))
+ reg |= MDIO_AN_VEND_PROV_1000BASET_HALF;
+
+ /* Handle the case when the 2.5G and 5G speeds are not advertised */
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->advertising))
+ reg |= MDIO_AN_VEND_PROV_2500BASET_FULL;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ phydev->advertising))
+ reg |= MDIO_AN_VEND_PROV_5000BASET_FULL;
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+ MDIO_AN_VEND_PROV_1000BASET_HALF |
+ MDIO_AN_VEND_PROV_1000BASET_FULL |
+ MDIO_AN_VEND_PROV_2500BASET_FULL |
+ MDIO_AN_VEND_PROV_5000BASET_FULL, reg);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int aqr_config_intr(struct phy_device *phydev)
+{
+ bool en = phydev->interrupts == PHY_INTERRUPT_ENABLED;
+ int err;
+
+ if (en) {
+ /* Clear any pending interrupts before enabling them */
+ err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
+ if (err < 0)
+ return err;
+ }
+
+ err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_MASK2,
+ en ? MDIO_AN_TX_VEND_INT_MASK2_LINK : 0);
+ if (err < 0)
+ return err;
+
+ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_STD_MASK,
+ en ? VEND1_GLOBAL_INT_STD_MASK_ALL : 0);
+ if (err < 0)
+ return err;
+
+ err = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_INT_VEND_MASK,
+ en ? VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 |
+ VEND1_GLOBAL_INT_VEND_MASK_AN : 0);
+ if (err < 0)
+ return err;
+
+ if (!en) {
+ /* Clear any pending interrupts after we have disabled them */
+ err = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_INT_STATUS2);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static irqreturn_t aqr_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read_mmd(phydev, MDIO_MMD_AN,
+ MDIO_AN_TX_VEND_INT_STATUS2);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MDIO_AN_TX_VEND_INT_STATUS2_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int aqr_read_status(struct phy_device *phydev)
+{
+ int val;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->lp_advertising,
+ val & MDIO_AN_RX_LP_STAT1_1000BASET_FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->lp_advertising,
+ val & MDIO_AN_RX_LP_STAT1_1000BASET_HALF);
+ }
+
+ return genphy_c45_read_status(phydev);
+}
+
+static int aqr107_read_rate(struct phy_device *phydev)
+{
+ u32 config_reg;
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_TX_VEND_STATUS1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_TX_VEND_STATUS1_FULL_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ switch (FIELD_GET(MDIO_AN_TX_VEND_STATUS1_RATE_MASK, val)) {
+ case MDIO_AN_TX_VEND_STATUS1_10BASET:
+ phydev->speed = SPEED_10;
+ config_reg = VEND1_GLOBAL_CFG_10M;
+ break;
+ case MDIO_AN_TX_VEND_STATUS1_100BASETX:
+ phydev->speed = SPEED_100;
+ config_reg = VEND1_GLOBAL_CFG_100M;
+ break;
+ case MDIO_AN_TX_VEND_STATUS1_1000BASET:
+ phydev->speed = SPEED_1000;
+ config_reg = VEND1_GLOBAL_CFG_1G;
+ break;
+ case MDIO_AN_TX_VEND_STATUS1_2500BASET:
+ phydev->speed = SPEED_2500;
+ config_reg = VEND1_GLOBAL_CFG_2_5G;
+ break;
+ case MDIO_AN_TX_VEND_STATUS1_5000BASET:
+ phydev->speed = SPEED_5000;
+ config_reg = VEND1_GLOBAL_CFG_5G;
+ break;
+ case MDIO_AN_TX_VEND_STATUS1_10GBASET:
+ phydev->speed = SPEED_10000;
+ config_reg = VEND1_GLOBAL_CFG_10G;
+ break;
+ default:
+ phydev->speed = SPEED_UNKNOWN;
+ return 0;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, config_reg);
+ if (val < 0)
+ return val;
+
+ if (FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val) ==
+ VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE)
+ phydev->rate_matching = RATE_MATCH_PAUSE;
+ else
+ phydev->rate_matching = RATE_MATCH_NONE;
+
+ return 0;
+}
+
+static int aqr107_read_status(struct phy_device *phydev)
+{
+ int val, ret;
+
+ ret = aqr_read_status(phydev);
+ if (ret)
+ return ret;
+
+ if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE)
+ return 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_VEND_IF_STATUS);
+ if (val < 0)
+ return val;
+
+ switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) {
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR:
+ phydev->interface = PHY_INTERFACE_MODE_10GKR;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX:
+ phydev->interface = PHY_INTERFACE_MODE_1000BASEKX;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI:
+ phydev->interface = PHY_INTERFACE_MODE_10GBASER;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII:
+ phydev->interface = PHY_INTERFACE_MODE_USXGMII;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI:
+ phydev->interface = PHY_INTERFACE_MODE_XAUI;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII:
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI:
+ phydev->interface = PHY_INTERFACE_MODE_RXAUI;
+ break;
+ case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII:
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ break;
+ default:
+ phydev->interface = PHY_INTERFACE_MODE_NA;
+ break;
+ }
+
+ /* Read possibly downshifted rate from vendor register */
+ return aqr107_read_rate(phydev);
+}
+
+static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val, cnt, enable;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
+ if (val < 0)
+ return val;
+
+ enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
+ cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+
+ *data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ int val = 0;
+
+ if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
+ return -E2BIG;
+
+ if (cnt != DOWNSHIFT_DEV_DISABLE) {
+ val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
+ val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
+ }
+
+ return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+ MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
+ MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+}
+
+static int aqr107_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return aqr107_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int aqr107_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return aqr107_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* If we configure settings whilst firmware is still initializing the chip,
+ * then these settings may be overwritten. Therefore make sure chip
+ * initialization has completed. Use presence of the firmware ID as
+ * indicator for initialization having completed.
+ * The chip also provides a "reset completed" bit, but it's cleared after
+ * read. Therefore function would time out if called again.
+ */
+static int aqr107_wait_reset_complete(struct phy_device *phydev)
+{
+ int val;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ VEND1_GLOBAL_FW_ID, val, val != 0,
+ 20000, 2000000, false);
+}
+
+static void aqr107_chip_info(struct phy_device *phydev)
+{
+ u8 fw_major, fw_minor, build_id, prov_id;
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID);
+ if (val < 0)
+ return;
+
+ fw_major = FIELD_GET(VEND1_GLOBAL_FW_ID_MAJOR, val);
+ fw_minor = FIELD_GET(VEND1_GLOBAL_FW_ID_MINOR, val);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT1);
+ if (val < 0)
+ return;
+
+ build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val);
+ prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val);
+
+ phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u\n",
+ fw_major, fw_minor, build_id, prov_id);
+}
+
+static int aqr107_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Check that the PHY interface type is compatible */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_1000BASEKX &&
+ phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
+ phydev->interface != PHY_INTERFACE_MODE_XGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_USXGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_10GKR &&
+ phydev->interface != PHY_INTERFACE_MODE_10GBASER &&
+ phydev->interface != PHY_INTERFACE_MODE_XAUI &&
+ phydev->interface != PHY_INTERFACE_MODE_RXAUI)
+ return -ENODEV;
+
+ WARN(phydev->interface == PHY_INTERFACE_MODE_XGMII,
+ "Your devicetree is out of date, please update it. The AQR107 family doesn't support XGMII, maybe you mean USXGMII.\n");
+
+ ret = aqr107_wait_reset_complete(phydev);
+ if (!ret)
+ aqr107_chip_info(phydev);
+
+ return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+}
+
+static int aqcs109_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Check that the PHY interface type is compatible */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
+ return -ENODEV;
+
+ ret = aqr107_wait_reset_complete(phydev);
+ if (!ret)
+ aqr107_chip_info(phydev);
+
+ /* AQCS109 belongs to a chip family partially supporting 10G and 5G.
+ * PMA speed ability bits are the same for all members of the family,
+ * AQCS109 however supports speeds up to 2.5G only.
+ */
+ phy_set_max_speed(phydev, SPEED_2500);
+
+ return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+}
+
+static void aqr107_link_change_notify(struct phy_device *phydev)
+{
+ u8 fw_major, fw_minor;
+ bool downshift, short_reach, afr;
+ int mode, val;
+
+ if (phydev->state != PHY_RUNNING || phydev->autoneg == AUTONEG_DISABLE)
+ return;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT1);
+ /* call failed or link partner is no Aquantia PHY */
+ if (val < 0 || !(val & MDIO_AN_RX_LP_STAT1_AQ_PHY))
+ return;
+
+ short_reach = val & MDIO_AN_RX_LP_STAT1_SHORT_REACH;
+ downshift = val & MDIO_AN_RX_LP_STAT1_AQRATE_DOWNSHIFT;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_LP_STAT4);
+ if (val < 0)
+ return;
+
+ fw_major = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MAJOR, val);
+ fw_minor = FIELD_GET(MDIO_AN_RX_LP_STAT4_FW_MINOR, val);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RX_VEND_STAT3);
+ if (val < 0)
+ return;
+
+ afr = val & MDIO_AN_RX_VEND_STAT3_AFR;
+
+ phydev_dbg(phydev, "Link partner is Aquantia PHY, FW %u.%u%s%s%s\n",
+ fw_major, fw_minor,
+ short_reach ? ", short reach mode" : "",
+ downshift ? ", fast-retrain downshift advertised" : "",
+ afr ? ", fast reframe advertised" : "");
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT9);
+ if (val < 0)
+ return;
+
+ mode = FIELD_GET(VEND1_GLOBAL_RSVD_STAT9_MODE, val);
+ if (mode == VEND1_GLOBAL_RSVD_STAT9_1000BT2)
+ phydev_info(phydev, "Aquantia 1000Base-T2 mode active\n");
+}
+
+static int aqr107_wait_processor_intensive_op(struct phy_device *phydev)
+{
+ int val, err;
+
+ /* The datasheet notes to wait at least 1ms after issuing a
+ * processor intensive operation before checking.
+ * We cannot use the 'sleep_before_read' parameter of read_poll_timeout
+ * because that just determines the maximum time slept, not the minimum.
+ */
+ usleep_range(1000, 5000);
+
+ err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ VEND1_GLOBAL_GEN_STAT2, val,
+ !(val & VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG),
+ AQR107_OP_IN_PROG_SLEEP,
+ AQR107_OP_IN_PROG_TIMEOUT, false);
+ if (err) {
+ phydev_err(phydev, "timeout: processor-intensive MDIO operation\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int aqr107_get_rate_matching(struct phy_device *phydev,
+ phy_interface_t iface)
+{
+ if (iface == PHY_INTERFACE_MODE_10GBASER ||
+ iface == PHY_INTERFACE_MODE_2500BASEX ||
+ iface == PHY_INTERFACE_MODE_NA)
+ return RATE_MATCH_PAUSE;
+ return RATE_MATCH_NONE;
+}
+
+static int aqr107_suspend(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
+ MDIO_CTRL1_LPOWER);
+ if (err)
+ return err;
+
+ return aqr107_wait_processor_intensive_op(phydev);
+}
+
+static int aqr107_resume(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
+ MDIO_CTRL1_LPOWER);
+ if (err)
+ return err;
+
+ return aqr107_wait_processor_intensive_op(phydev);
+}
+
+static int aqr107_probe(struct phy_device *phydev)
+{
+ phydev->priv = devm_kzalloc(&phydev->mdio.dev,
+ sizeof(struct aqr107_priv), GFP_KERNEL);
+ if (!phydev->priv)
+ return -ENOMEM;
+
+ return aqr_hwmon_probe(phydev);
+}
+
+static struct phy_driver aqr_driver[] = {
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
+ .name = "Aquantia AQ1202",
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr_read_status,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQ2104),
+ .name = "Aquantia AQ2104",
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr_read_status,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR105),
+ .name = "Aquantia AQR105",
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr_read_status,
+ .suspend = aqr107_suspend,
+ .resume = aqr107_resume,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR106),
+ .name = "Aquantia AQR106",
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr_read_status,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
+ .name = "Aquantia AQR107",
+ .probe = aqr107_probe,
+ .get_rate_matching = aqr107_get_rate_matching,
+ .config_init = aqr107_config_init,
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr107_read_status,
+ .get_tunable = aqr107_get_tunable,
+ .set_tunable = aqr107_set_tunable,
+ .suspend = aqr107_suspend,
+ .resume = aqr107_resume,
+ .get_sset_count = aqr107_get_sset_count,
+ .get_strings = aqr107_get_strings,
+ .get_stats = aqr107_get_stats,
+ .link_change_notify = aqr107_link_change_notify,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQCS109),
+ .name = "Aquantia AQCS109",
+ .probe = aqr107_probe,
+ .get_rate_matching = aqr107_get_rate_matching,
+ .config_init = aqcs109_config_init,
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr107_read_status,
+ .get_tunable = aqr107_get_tunable,
+ .set_tunable = aqr107_set_tunable,
+ .suspend = aqr107_suspend,
+ .resume = aqr107_resume,
+ .get_sset_count = aqr107_get_sset_count,
+ .get_strings = aqr107_get_strings,
+ .get_stats = aqr107_get_stats,
+ .link_change_notify = aqr107_link_change_notify,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR405),
+ .name = "Aquantia AQR405",
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr_read_status,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR112),
+ .name = "Aquantia AQR112",
+ .probe = aqr107_probe,
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .get_tunable = aqr107_get_tunable,
+ .set_tunable = aqr107_set_tunable,
+ .suspend = aqr107_suspend,
+ .resume = aqr107_resume,
+ .read_status = aqr107_read_status,
+ .get_rate_matching = aqr107_get_rate_matching,
+ .get_sset_count = aqr107_get_sset_count,
+ .get_strings = aqr107_get_strings,
+ .get_stats = aqr107_get_stats,
+ .link_change_notify = aqr107_link_change_notify,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR412),
+ .name = "Aquantia AQR412",
+ .probe = aqr107_probe,
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .get_tunable = aqr107_get_tunable,
+ .set_tunable = aqr107_set_tunable,
+ .suspend = aqr107_suspend,
+ .resume = aqr107_resume,
+ .read_status = aqr107_read_status,
+ .get_rate_matching = aqr107_get_rate_matching,
+ .get_sset_count = aqr107_get_sset_count,
+ .get_strings = aqr107_get_strings,
+ .get_stats = aqr107_get_stats,
+ .link_change_notify = aqr107_link_change_notify,
+},
+{
+ PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
+ .name = "Aquantia AQR113C",
+ .probe = aqr107_probe,
+ .get_rate_matching = aqr107_get_rate_matching,
+ .config_init = aqr107_config_init,
+ .config_aneg = aqr_config_aneg,
+ .config_intr = aqr_config_intr,
+ .handle_interrupt = aqr_handle_interrupt,
+ .read_status = aqr107_read_status,
+ .get_tunable = aqr107_get_tunable,
+ .set_tunable = aqr107_set_tunable,
+ .suspend = aqr107_suspend,
+ .resume = aqr107_resume,
+ .get_sset_count = aqr107_get_sset_count,
+ .get_strings = aqr107_get_strings,
+ .get_stats = aqr107_get_stats,
+ .link_change_notify = aqr107_link_change_notify,
+},
+};
+
+module_phy_driver(aqr_driver);
+
+static struct mdio_device_id __maybe_unused aqr_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQ1202) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQ2104) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR105) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR106) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR107) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQCS109) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR405) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR112) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR412) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, aqr_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..37fb033e1
--- /dev/null
+++ b/drivers/net/phy/at803x.c
@@ -0,0 +1,2248 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/at803x.c
+ *
+ * Driver for Qualcomm Atheros AR803x PHY
+ *
+ * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
+ */
+
+#include <linux/phy.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/bitfield.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of.h>
+#include <linux/phylink.h>
+#include <linux/sfp.h>
+#include <dt-bindings/net/qca-ar803x.h>
+
+#define AT803X_SPECIFIC_FUNCTION_CONTROL 0x10
+#define AT803X_SFC_ASSERT_CRS BIT(11)
+#define AT803X_SFC_FORCE_LINK BIT(10)
+#define AT803X_SFC_MDI_CROSSOVER_MODE_M GENMASK(6, 5)
+#define AT803X_SFC_AUTOMATIC_CROSSOVER 0x3
+#define AT803X_SFC_MANUAL_MDIX 0x1
+#define AT803X_SFC_MANUAL_MDI 0x0
+#define AT803X_SFC_SQE_TEST BIT(2)
+#define AT803X_SFC_POLARITY_REVERSAL BIT(1)
+#define AT803X_SFC_DISABLE_JABBER BIT(0)
+
+#define AT803X_SPECIFIC_STATUS 0x11
+#define AT803X_SS_SPEED_MASK GENMASK(15, 14)
+#define AT803X_SS_SPEED_1000 2
+#define AT803X_SS_SPEED_100 1
+#define AT803X_SS_SPEED_10 0
+#define AT803X_SS_DUPLEX BIT(13)
+#define AT803X_SS_SPEED_DUPLEX_RESOLVED BIT(11)
+#define AT803X_SS_MDIX BIT(6)
+
+#define QCA808X_SS_SPEED_MASK GENMASK(9, 7)
+#define QCA808X_SS_SPEED_2500 4
+
+#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_LINK_FAIL_BX BIT(8)
+#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX BIT(7)
+#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_SMART_SPEED_ENABLE BIT(5)
+#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK GENMASK(4, 2)
+#define AT803X_SMART_SPEED_BYPASS_TIMER BIT(1)
+#define AT803X_CDT 0x16
+#define AT803X_CDT_MDI_PAIR_MASK GENMASK(9, 8)
+#define AT803X_CDT_ENABLE_TEST BIT(0)
+#define AT803X_CDT_STATUS 0x1c
+#define AT803X_CDT_STATUS_STAT_NORMAL 0
+#define AT803X_CDT_STATUS_STAT_SHORT 1
+#define AT803X_CDT_STATUS_STAT_OPEN 2
+#define AT803X_CDT_STATUS_STAT_FAIL 3
+#define AT803X_CDT_STATUS_STAT_MASK GENMASK(9, 8)
+#define AT803X_CDT_STATUS_DELTA_TIME_MASK GENMASK(7, 0)
+#define AT803X_LED_CONTROL 0x18
+
+#define AT803X_PHY_MMD3_WOL_CTRL 0x8012
+#define AT803X_WOL_EN BIT(5)
+#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_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_BASET_RGMII 0x00
+#define AT803X_MODE_CFG_BASET_SGMII 0x01
+#define AT803X_MODE_CFG_BX1000_RGMII_50OHM 0x02
+#define AT803X_MODE_CFG_BX1000_RGMII_75OHM 0x03
+#define AT803X_MODE_CFG_BX1000_CONV_50OHM 0x04
+#define AT803X_MODE_CFG_BX1000_CONV_75OHM 0x05
+#define AT803X_MODE_CFG_FX100_RGMII_50OHM 0x06
+#define AT803X_MODE_CFG_FX100_CONV_50OHM 0x07
+#define AT803X_MODE_CFG_RGMII_AUTO_MDET 0x0B
+#define AT803X_MODE_CFG_FX100_RGMII_75OHM 0x0E
+#define AT803X_MODE_CFG_FX100_CONV_75OHM 0x0F
+
+#define AT803X_PSSR 0x11 /*PHY-Specific Status Register*/
+#define AT803X_PSSR_MR_AN_COMPLETE 0x0200
+
+#define AT803X_DEBUG_ANALOG_TEST_CTRL 0x00
+#define QCA8327_DEBUG_MANU_CTRL_EN BIT(2)
+#define QCA8337_DEBUG_MANU_CTRL_EN GENMASK(3, 2)
+#define AT803X_DEBUG_RX_CLK_DLY_EN BIT(15)
+
+#define AT803X_DEBUG_SYSTEM_CTRL_MODE 0x05
+#define AT803X_DEBUG_TX_CLK_DLY_EN BIT(8)
+
+#define AT803X_DEBUG_REG_HIB_CTRL 0x0b
+#define AT803X_DEBUG_HIB_CTRL_SEL_RST_80U BIT(10)
+#define AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE BIT(13)
+#define AT803X_DEBUG_HIB_CTRL_PS_HIB_EN BIT(15)
+
+#define AT803X_DEBUG_REG_3C 0x3C
+
+#define AT803X_DEBUG_REG_GREEN 0x3D
+#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6)
+
+#define AT803X_DEBUG_REG_1F 0x1F
+#define AT803X_DEBUG_PLL_ON BIT(2)
+#define AT803X_DEBUG_RGMII_1V8 BIT(3)
+
+#define MDIO_AZ_DEBUG 0x800D
+
+/* AT803x supports either the XTAL input pad, an internal PLL or the
+ * DSP as clock reference for the clock output pad. The XTAL reference
+ * is only used for 25 MHz output, all other frequencies need the PLL.
+ * The DSP as a clock reference is used in synchronous ethernet
+ * applications.
+ *
+ * By default the PLL is only enabled if there is a link. Otherwise
+ * the PHY will go into low power state and disabled the PLL. You can
+ * set the PLL_ON bit (see debug register 0x1f) to keep the PLL always
+ * enabled.
+ */
+#define AT803X_MMD7_CLK25M 0x8016
+#define AT803X_CLK_OUT_MASK GENMASK(4, 2)
+#define AT803X_CLK_OUT_25MHZ_XTAL 0
+#define AT803X_CLK_OUT_25MHZ_DSP 1
+#define AT803X_CLK_OUT_50MHZ_PLL 2
+#define AT803X_CLK_OUT_50MHZ_DSP 3
+#define AT803X_CLK_OUT_62_5MHZ_PLL 4
+#define AT803X_CLK_OUT_62_5MHZ_DSP 5
+#define AT803X_CLK_OUT_125MHZ_PLL 6
+#define AT803X_CLK_OUT_125MHZ_DSP 7
+
+/* The AR8035 has another mask which is compatible with the AR8031/AR8033 mask
+ * but doesn't support choosing between XTAL/PLL and DSP.
+ */
+#define AT8035_CLK_OUT_MASK GENMASK(4, 3)
+
+#define AT803X_CLK_OUT_STRENGTH_MASK GENMASK(8, 7)
+#define AT803X_CLK_OUT_STRENGTH_FULL 0
+#define AT803X_CLK_OUT_STRENGTH_HALF 1
+#define AT803X_CLK_OUT_STRENGTH_QUARTER 2
+
+#define AT803X_DEFAULT_DOWNSHIFT 5
+#define AT803X_MIN_DOWNSHIFT 2
+#define AT803X_MAX_DOWNSHIFT 9
+
+#define AT803X_MMD3_SMARTEEE_CTL1 0x805b
+#define AT803X_MMD3_SMARTEEE_CTL2 0x805c
+#define AT803X_MMD3_SMARTEEE_CTL3 0x805d
+#define AT803X_MMD3_SMARTEEE_CTL3_LPI_EN BIT(8)
+
+#define ATH9331_PHY_ID 0x004dd041
+#define ATH8030_PHY_ID 0x004dd076
+#define ATH8031_PHY_ID 0x004dd074
+#define ATH8032_PHY_ID 0x004dd023
+#define ATH8035_PHY_ID 0x004dd072
+#define AT8030_PHY_ID_MASK 0xffffffef
+
+#define QCA8081_PHY_ID 0x004dd101
+
+#define QCA8327_A_PHY_ID 0x004dd033
+#define QCA8327_B_PHY_ID 0x004dd034
+#define QCA8337_PHY_ID 0x004dd036
+#define QCA9561_PHY_ID 0x004dd042
+#define QCA8K_PHY_ID_MASK 0xffffffff
+
+#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
+
+#define AT803X_PAGE_FIBER 0
+#define AT803X_PAGE_COPPER 1
+
+/* don't turn off internal PLL */
+#define AT803X_KEEP_PLL_ENABLED BIT(0)
+#define AT803X_DISABLE_SMARTEEE BIT(1)
+
+/* disable hibernation mode */
+#define AT803X_DISABLE_HIBERNATION_MODE BIT(2)
+
+/* ADC threshold */
+#define QCA808X_PHY_DEBUG_ADC_THRESHOLD 0x2c80
+#define QCA808X_ADC_THRESHOLD_MASK GENMASK(7, 0)
+#define QCA808X_ADC_THRESHOLD_80MV 0
+#define QCA808X_ADC_THRESHOLD_100MV 0xf0
+#define QCA808X_ADC_THRESHOLD_200MV 0x0f
+#define QCA808X_ADC_THRESHOLD_300MV 0xff
+
+/* CLD control */
+#define QCA808X_PHY_MMD3_ADDR_CLD_CTRL7 0x8007
+#define QCA808X_8023AZ_AFE_CTRL_MASK GENMASK(8, 4)
+#define QCA808X_8023AZ_AFE_EN 0x90
+
+/* AZ control */
+#define QCA808X_PHY_MMD3_AZ_TRAINING_CTRL 0x8008
+#define QCA808X_MMD3_AZ_TRAINING_VAL 0x1c32
+
+#define QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB 0x8014
+#define QCA808X_MSE_THRESHOLD_20DB_VALUE 0x529
+
+#define QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB 0x800E
+#define QCA808X_MSE_THRESHOLD_17DB_VALUE 0x341
+
+#define QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB 0x801E
+#define QCA808X_MSE_THRESHOLD_27DB_VALUE 0x419
+
+#define QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB 0x8020
+#define QCA808X_MSE_THRESHOLD_28DB_VALUE 0x341
+
+#define QCA808X_PHY_MMD7_TOP_OPTION1 0x901c
+#define QCA808X_TOP_OPTION1_DATA 0x0
+
+#define QCA808X_PHY_MMD3_DEBUG_1 0xa100
+#define QCA808X_MMD3_DEBUG_1_VALUE 0x9203
+#define QCA808X_PHY_MMD3_DEBUG_2 0xa101
+#define QCA808X_MMD3_DEBUG_2_VALUE 0x48ad
+#define QCA808X_PHY_MMD3_DEBUG_3 0xa103
+#define QCA808X_MMD3_DEBUG_3_VALUE 0x1698
+#define QCA808X_PHY_MMD3_DEBUG_4 0xa105
+#define QCA808X_MMD3_DEBUG_4_VALUE 0x8001
+#define QCA808X_PHY_MMD3_DEBUG_5 0xa106
+#define QCA808X_MMD3_DEBUG_5_VALUE 0x1111
+#define QCA808X_PHY_MMD3_DEBUG_6 0xa011
+#define QCA808X_MMD3_DEBUG_6_VALUE 0x5f85
+
+/* master/slave seed config */
+#define QCA808X_PHY_DEBUG_LOCAL_SEED 9
+#define QCA808X_MASTER_SLAVE_SEED_ENABLE BIT(1)
+#define QCA808X_MASTER_SLAVE_SEED_CFG GENMASK(12, 2)
+#define QCA808X_MASTER_SLAVE_SEED_RANGE 0x32
+
+/* Hibernation yields lower power consumpiton in contrast with normal operation mode.
+ * when the copper cable is unplugged, the PHY enters into hibernation mode in about 10s.
+ */
+#define QCA808X_DBG_AN_TEST 0xb
+#define QCA808X_HIBERNATION_EN BIT(15)
+
+#define QCA808X_CDT_ENABLE_TEST BIT(15)
+#define QCA808X_CDT_INTER_CHECK_DIS BIT(13)
+#define QCA808X_CDT_LENGTH_UNIT BIT(10)
+
+#define QCA808X_MMD3_CDT_STATUS 0x8064
+#define QCA808X_MMD3_CDT_DIAG_PAIR_A 0x8065
+#define QCA808X_MMD3_CDT_DIAG_PAIR_B 0x8066
+#define QCA808X_MMD3_CDT_DIAG_PAIR_C 0x8067
+#define QCA808X_MMD3_CDT_DIAG_PAIR_D 0x8068
+#define QCA808X_CDT_DIAG_LENGTH GENMASK(7, 0)
+
+#define QCA808X_CDT_CODE_PAIR_A GENMASK(15, 12)
+#define QCA808X_CDT_CODE_PAIR_B GENMASK(11, 8)
+#define QCA808X_CDT_CODE_PAIR_C GENMASK(7, 4)
+#define QCA808X_CDT_CODE_PAIR_D GENMASK(3, 0)
+#define QCA808X_CDT_STATUS_STAT_FAIL 0
+#define QCA808X_CDT_STATUS_STAT_NORMAL 1
+#define QCA808X_CDT_STATUS_STAT_OPEN 2
+#define QCA808X_CDT_STATUS_STAT_SHORT 3
+
+/* QCA808X 1G chip type */
+#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
+
+#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
+#define QCA8081_PHY_FIFO_RSTN BIT(11)
+
+MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
+MODULE_AUTHOR("Matus Ujhelyi");
+MODULE_LICENSE("GPL");
+
+enum stat_access_type {
+ PHY,
+ MMD
+};
+
+struct at803x_hw_stat {
+ const char *string;
+ u8 reg;
+ u32 mask;
+ enum stat_access_type access_type;
+};
+
+static struct at803x_hw_stat at803x_hw_stats[] = {
+ { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
+ { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
+ { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
+};
+
+struct at803x_priv {
+ int flags;
+ u16 clk_25m_reg;
+ u16 clk_25m_mask;
+ u8 smarteee_lpi_tw_1g;
+ u8 smarteee_lpi_tw_100m;
+ bool is_fiber;
+ bool is_1000basex;
+ struct regulator_dev *vddio_rdev;
+ struct regulator_dev *vddh_rdev;
+ u64 stats[ARRAY_SIZE(at803x_hw_stats)];
+};
+
+struct at803x_context {
+ u16 bmcr;
+ u16 advertise;
+ u16 control1000;
+ u16 int_enable;
+ u16 smart_speed;
+ u16 led_control;
+};
+
+static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
+{
+ int ret;
+
+ ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
+ if (ret < 0)
+ return ret;
+
+ return phy_write(phydev, AT803X_DEBUG_DATA, data);
+}
+
+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 int at803x_write_page(struct phy_device *phydev, int page)
+{
+ int mask;
+ int set;
+
+ if (page == AT803X_PAGE_COPPER) {
+ set = AT803X_BT_BX_REG_SEL;
+ mask = 0;
+ } else {
+ set = 0;
+ mask = AT803X_BT_BX_REG_SEL;
+ }
+
+ return __phy_modify(phydev, AT803X_REG_CHIP_CONFIG, mask, set);
+}
+
+static int at803x_read_page(struct phy_device *phydev)
+{
+ int ccr = __phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+
+ if (ccr < 0)
+ return ccr;
+
+ if (ccr & AT803X_BT_BX_REG_SEL)
+ return AT803X_PAGE_COPPER;
+
+ return AT803X_PAGE_FIBER;
+}
+
+static int at803x_enable_rx_delay(struct phy_device *phydev)
+{
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0,
+ AT803X_DEBUG_RX_CLK_DLY_EN);
+}
+
+static int at803x_enable_tx_delay(struct phy_device *phydev)
+{
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0,
+ AT803X_DEBUG_TX_CLK_DLY_EN);
+}
+
+static int at803x_disable_rx_delay(struct phy_device *phydev)
+{
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+ AT803X_DEBUG_RX_CLK_DLY_EN, 0);
+}
+
+static int at803x_disable_tx_delay(struct phy_device *phydev)
+{
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE,
+ AT803X_DEBUG_TX_CLK_DLY_EN, 0);
+}
+
+/* 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)
+{
+ int ret, irq_enabled;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ struct net_device *ndev = phydev->attached_dev;
+ const u8 *mac;
+ unsigned int i;
+ static const unsigned int 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;
+
+ mac = (const u8 *) ndev->dev_addr;
+
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
+
+ for (i = 0; i < 3; i++)
+ phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],
+ mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
+
+ /* Enable WOL function for 1588 */
+ if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ AT803X_PHY_MMD3_WOL_CTRL,
+ 0, AT803X_WOL_EN);
+ if (ret)
+ return ret;
+ }
+ /* Enable WOL interrupt */
+ ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);
+ if (ret)
+ return ret;
+ } else {
+ /* Disable WoL function for 1588 */
+ if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ AT803X_PHY_MMD3_WOL_CTRL,
+ AT803X_WOL_EN, 0);
+ if (ret)
+ return ret;
+ }
+ /* Disable WOL interrupt */
+ ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Clear WOL status */
+ ret = phy_read(phydev, AT803X_INTR_STATUS);
+ if (ret < 0)
+ return ret;
+
+ /* Check if there are other interrupts except for WOL triggered when PHY is
+ * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can
+ * be passed up to the interrupt PIN.
+ */
+ irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+ if (irq_enabled < 0)
+ return irq_enabled;
+
+ irq_enabled &= ~AT803X_INTR_ENABLE_WOL;
+ if (ret & irq_enabled && !phy_polling_mode(phydev))
+ phy_trigger_machine(phydev);
+
+ return 0;
+}
+
+static void at803x_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int value;
+
+ wol->supported = WAKE_MAGIC;
+ wol->wolopts = 0;
+
+ value = phy_read(phydev, AT803X_INTR_ENABLE);
+ if (value < 0)
+ return;
+
+ if (value & AT803X_INTR_ENABLE_WOL)
+ wol->wolopts |= WAKE_MAGIC;
+}
+
+static int at803x_get_sset_count(struct phy_device *phydev)
+{
+ return ARRAY_SIZE(at803x_hw_stats);
+}
+
+static void at803x_get_strings(struct phy_device *phydev, u8 *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++) {
+ strscpy(data + i * ETH_GSTRING_LEN,
+ at803x_hw_stats[i].string, ETH_GSTRING_LEN);
+ }
+}
+
+static u64 at803x_get_stat(struct phy_device *phydev, int i)
+{
+ struct at803x_hw_stat stat = at803x_hw_stats[i];
+ struct at803x_priv *priv = phydev->priv;
+ int val;
+ u64 ret;
+
+ if (stat.access_type == MMD)
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
+ else
+ val = phy_read(phydev, stat.reg);
+
+ if (val < 0) {
+ ret = U64_MAX;
+ } else {
+ val = val & stat.mask;
+ priv->stats[i] += val;
+ ret = priv->stats[i];
+ }
+
+ return ret;
+}
+
+static void at803x_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++)
+ data[i] = at803x_get_stat(phydev, i);
+}
+
+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_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct phy_device *phydev = rdev_get_drvdata(rdev);
+
+ if (selector)
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+ 0, AT803X_DEBUG_RGMII_1V8);
+ else
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+ AT803X_DEBUG_RGMII_1V8, 0);
+}
+
+static int at803x_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev)
+{
+ struct phy_device *phydev = rdev_get_drvdata(rdev);
+ int val;
+
+ val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F);
+ if (val < 0)
+ return val;
+
+ return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0;
+}
+
+static const struct regulator_ops vddio_regulator_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .set_voltage_sel = at803x_rgmii_reg_set_voltage_sel,
+ .get_voltage_sel = at803x_rgmii_reg_get_voltage_sel,
+};
+
+static const unsigned int vddio_voltage_table[] = {
+ 1500000,
+ 1800000,
+};
+
+static const struct regulator_desc vddio_desc = {
+ .name = "vddio",
+ .of_match = of_match_ptr("vddio-regulator"),
+ .n_voltages = ARRAY_SIZE(vddio_voltage_table),
+ .volt_table = vddio_voltage_table,
+ .ops = &vddio_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static const struct regulator_ops vddh_regulator_ops = {
+};
+
+static const struct regulator_desc vddh_desc = {
+ .name = "vddh",
+ .of_match = of_match_ptr("vddh-regulator"),
+ .n_voltages = 1,
+ .fixed_uV = 2500000,
+ .ops = &vddh_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static int at8031_register_regulators(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ struct regulator_config config = { };
+
+ config.dev = dev;
+ config.driver_data = phydev;
+
+ priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config);
+ if (IS_ERR(priv->vddio_rdev)) {
+ phydev_err(phydev, "failed to register VDDIO regulator\n");
+ return PTR_ERR(priv->vddio_rdev);
+ }
+
+ priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config);
+ if (IS_ERR(priv->vddh_rdev)) {
+ phydev_err(phydev, "failed to register VDDH regulator\n");
+ return PTR_ERR(priv->vddh_rdev);
+ }
+
+ return 0;
+}
+
+static int at803x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+ struct phy_device *phydev = upstream;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
+ phy_interface_t iface;
+
+ linkmode_zero(phy_support);
+ phylink_set(phy_support, 1000baseX_Full);
+ phylink_set(phy_support, 1000baseT_Full);
+ phylink_set(phy_support, Autoneg);
+ phylink_set(phy_support, Pause);
+ phylink_set(phy_support, Asym_Pause);
+
+ linkmode_zero(sfp_support);
+ sfp_parse_support(phydev->sfp_bus, id, sfp_support, interfaces);
+ /* Some modules support 10G modes as well as others we support.
+ * Mask out non-supported modes so the correct interface is picked.
+ */
+ linkmode_and(sfp_support, phy_support, sfp_support);
+
+ if (linkmode_empty(sfp_support)) {
+ dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+ return -EINVAL;
+ }
+
+ iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
+
+ /* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
+ * interface for use with SFP modules.
+ * However, some copper modules detected as having a preferred SGMII
+ * interface do default to and function in 1000Base-X mode, so just
+ * print a warning and allow such modules, as they may have some chance
+ * of working.
+ */
+ if (iface == PHY_INTERFACE_MODE_SGMII)
+ dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
+ else if (iface != PHY_INTERFACE_MODE_1000BASEX)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct sfp_upstream_ops at803x_sfp_ops = {
+ .attach = phy_sfp_attach,
+ .detach = phy_sfp_detach,
+ .module_insert = at803x_sfp_insert,
+};
+
+static int at803x_parse_dt(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ struct at803x_priv *priv = phydev->priv;
+ u32 freq, strength, tw;
+ unsigned int sel;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_OF_MDIO))
+ return 0;
+
+ if (of_property_read_bool(node, "qca,disable-smarteee"))
+ priv->flags |= AT803X_DISABLE_SMARTEEE;
+
+ if (of_property_read_bool(node, "qca,disable-hibernation-mode"))
+ priv->flags |= AT803X_DISABLE_HIBERNATION_MODE;
+
+ if (!of_property_read_u32(node, "qca,smarteee-tw-us-1g", &tw)) {
+ if (!tw || tw > 255) {
+ phydev_err(phydev, "invalid qca,smarteee-tw-us-1g\n");
+ return -EINVAL;
+ }
+ priv->smarteee_lpi_tw_1g = tw;
+ }
+
+ if (!of_property_read_u32(node, "qca,smarteee-tw-us-100m", &tw)) {
+ if (!tw || tw > 255) {
+ phydev_err(phydev, "invalid qca,smarteee-tw-us-100m\n");
+ return -EINVAL;
+ }
+ priv->smarteee_lpi_tw_100m = tw;
+ }
+
+ ret = of_property_read_u32(node, "qca,clk-out-frequency", &freq);
+ if (!ret) {
+ switch (freq) {
+ case 25000000:
+ sel = AT803X_CLK_OUT_25MHZ_XTAL;
+ break;
+ case 50000000:
+ sel = AT803X_CLK_OUT_50MHZ_PLL;
+ break;
+ case 62500000:
+ sel = AT803X_CLK_OUT_62_5MHZ_PLL;
+ break;
+ case 125000000:
+ sel = AT803X_CLK_OUT_125MHZ_PLL;
+ break;
+ default:
+ phydev_err(phydev, "invalid qca,clk-out-frequency\n");
+ return -EINVAL;
+ }
+
+ priv->clk_25m_reg |= FIELD_PREP(AT803X_CLK_OUT_MASK, sel);
+ priv->clk_25m_mask |= AT803X_CLK_OUT_MASK;
+
+ /* Fixup for the AR8030/AR8035. This chip has another mask and
+ * doesn't support the DSP reference. Eg. the lowest bit of the
+ * mask. The upper two bits select the same frequencies. Mask
+ * the lowest bit here.
+ *
+ * Warning:
+ * There was no datasheet for the AR8030 available so this is
+ * just a guess. But the AR8035 is listed as pin compatible
+ * to the AR8030 so there might be a good chance it works on
+ * the AR8030 too.
+ */
+ if (phydev->drv->phy_id == ATH8030_PHY_ID ||
+ phydev->drv->phy_id == ATH8035_PHY_ID) {
+ priv->clk_25m_reg &= AT8035_CLK_OUT_MASK;
+ priv->clk_25m_mask &= AT8035_CLK_OUT_MASK;
+ }
+ }
+
+ ret = of_property_read_u32(node, "qca,clk-out-strength", &strength);
+ if (!ret) {
+ priv->clk_25m_mask |= AT803X_CLK_OUT_STRENGTH_MASK;
+ switch (strength) {
+ case AR803X_STRENGTH_FULL:
+ priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_FULL;
+ break;
+ case AR803X_STRENGTH_HALF:
+ priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_HALF;
+ break;
+ case AR803X_STRENGTH_QUARTER:
+ priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_QUARTER;
+ break;
+ default:
+ phydev_err(phydev, "invalid qca,clk-out-strength\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping
+ * options.
+ */
+ if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+ if (of_property_read_bool(node, "qca,keep-pll-enabled"))
+ priv->flags |= AT803X_KEEP_PLL_ENABLED;
+
+ ret = at8031_register_regulators(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_regulator_get_enable_optional(&phydev->mdio.dev,
+ "vddio");
+ if (ret) {
+ phydev_err(phydev, "failed to get VDDIO regulator\n");
+ return ret;
+ }
+
+ /* Only AR8031/8033 support 1000Base-X for SFP modules */
+ ret = phy_sfp_probe(phydev, &at803x_sfp_ops);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int at803x_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct at803x_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ ret = at803x_parse_dt(phydev);
+ if (ret)
+ return ret;
+
+ if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+ int ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+ int mode_cfg;
+
+ if (ccr < 0)
+ return ccr;
+ mode_cfg = ccr & AT803X_MODE_CFG_MASK;
+
+ switch (mode_cfg) {
+ case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
+ case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
+ priv->is_1000basex = true;
+ fallthrough;
+ case AT803X_MODE_CFG_FX100_RGMII_50OHM:
+ case AT803X_MODE_CFG_FX100_RGMII_75OHM:
+ priv->is_fiber = true;
+ break;
+ }
+
+ /* Disable WoL in 1588 register which is enabled
+ * by default
+ */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ AT803X_PHY_MMD3_WOL_CTRL,
+ AT803X_WOL_EN, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int at803x_get_features(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+ int err;
+
+ err = genphy_read_abilities(phydev);
+ if (err)
+ return err;
+
+ if (phydev->drv->phy_id != ATH8031_PHY_ID)
+ return 0;
+
+ /* AR8031/AR8033 have different status registers
+ * for copper and fiber operation. However, the
+ * extended status register is the same for both
+ * operation modes.
+ *
+ * As a result of that, ESTATUS_1000_XFULL is set
+ * to 1 even when operating in copper TP mode.
+ *
+ * Remove this mode from the supported link modes
+ * when not operating in 1000BaseX mode.
+ */
+ if (!priv->is_1000basex)
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->supported);
+
+ return 0;
+}
+
+static int at803x_smarteee_config(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+ u16 mask = 0, val = 0;
+ int ret;
+
+ if (priv->flags & AT803X_DISABLE_SMARTEEE)
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ AT803X_MMD3_SMARTEEE_CTL3,
+ AT803X_MMD3_SMARTEEE_CTL3_LPI_EN, 0);
+
+ if (priv->smarteee_lpi_tw_1g) {
+ mask |= 0xff00;
+ val |= priv->smarteee_lpi_tw_1g << 8;
+ }
+ if (priv->smarteee_lpi_tw_100m) {
+ mask |= 0x00ff;
+ val |= priv->smarteee_lpi_tw_100m;
+ }
+ if (!mask)
+ return 0;
+
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL1,
+ mask, val);
+ if (ret)
+ return ret;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS, AT803X_MMD3_SMARTEEE_CTL3,
+ AT803X_MMD3_SMARTEEE_CTL3_LPI_EN,
+ AT803X_MMD3_SMARTEEE_CTL3_LPI_EN);
+}
+
+static int at803x_clk_out_config(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+
+ if (!priv->clk_25m_mask)
+ return 0;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M,
+ priv->clk_25m_mask, priv->clk_25m_reg);
+}
+
+static int at8031_pll_config(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+
+ /* The default after hardware reset is PLL OFF. After a soft reset, the
+ * values are retained.
+ */
+ if (priv->flags & AT803X_KEEP_PLL_ENABLED)
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+ 0, AT803X_DEBUG_PLL_ON);
+ else
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F,
+ AT803X_DEBUG_PLL_ON, 0);
+}
+
+static int at803x_hibernation_mode_config(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+
+ /* The default after hardware reset is hibernation mode enabled. After
+ * software reset, the value is retained.
+ */
+ if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE))
+ return 0;
+
+ return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+ AT803X_DEBUG_HIB_CTRL_PS_HIB_EN, 0);
+}
+
+static int at803x_config_init(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+ int ret;
+
+ if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+ /* Some bootloaders leave the fiber page selected.
+ * Switch to the appropriate page (fiber or copper), as otherwise we
+ * read the PHY capabilities from the wrong page.
+ */
+ phy_lock_mdio_bus(phydev);
+ ret = at803x_write_page(phydev,
+ priv->is_fiber ? AT803X_PAGE_FIBER :
+ AT803X_PAGE_COPPER);
+ phy_unlock_mdio_bus(phydev);
+ if (ret)
+ return ret;
+
+ ret = at8031_pll_config(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* The RX and TX delay default is:
+ * after HW reset: RX delay enabled and TX delay disabled
+ * after SW reset: RX delay enabled, while TX delay retains the
+ * value before reset.
+ */
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+ ret = at803x_enable_rx_delay(phydev);
+ else
+ ret = at803x_disable_rx_delay(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+ ret = at803x_enable_tx_delay(phydev);
+ else
+ ret = at803x_disable_tx_delay(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = at803x_smarteee_config(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = at803x_clk_out_config(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = at803x_hibernation_mode_config(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* Ar803x extended next page bit is enabled by default. Cisco
+ * multigig switches read this bit and attempt to negotiate 10Gbps
+ * rates even if the next page bit is disabled. This is incorrect
+ * behaviour but we still need to accommodate it. XNP is only needed
+ * for 10Gbps support, so disable XNP.
+ */
+ return phy_modify(phydev, MII_ADVERTISE, MDIO_AN_CTRL1_XNP, 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)
+{
+ struct at803x_priv *priv = phydev->priv;
+ int err;
+ int value;
+
+ value = phy_read(phydev, AT803X_INTR_ENABLE);
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* Clear any pending interrupts */
+ err = at803x_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ 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;
+ if (priv->is_fiber) {
+ value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
+ value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
+ }
+
+ err = phy_write(phydev, AT803X_INTR_ENABLE, value);
+ } else {
+ err = phy_write(phydev, AT803X_INTR_ENABLE, 0);
+ if (err)
+ return err;
+
+ /* Clear any pending interrupts */
+ err = at803x_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, int_enabled;
+
+ irq_status = phy_read(phydev, AT803X_INTR_STATUS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ /* Read the current enabled interrupts */
+ int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);
+ if (int_enabled < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ /* See if this was one of our enabled interrupts */
+ if (!(irq_status & int_enabled))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static void at803x_link_change_notify(struct phy_device *phydev)
+{
+ /*
+ * 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 && phydev->mdio.reset_gpio) {
+ 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__);
+ }
+}
+
+static int at803x_read_specific_status(struct phy_device *phydev)
+{
+ int ss;
+
+ /* Read the AT8035 PHY-Specific Status register, which indicates the
+ * speed and duplex that the PHY is actually using, irrespective of
+ * whether we are in autoneg mode or not.
+ */
+ ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
+ if (ss < 0)
+ return ss;
+
+ if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
+ int sfc, speed;
+
+ sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);
+ if (sfc < 0)
+ return sfc;
+
+ /* qca8081 takes the different bits for speed value from at803x */
+ if (phydev->drv->phy_id == QCA8081_PHY_ID)
+ speed = FIELD_GET(QCA808X_SS_SPEED_MASK, ss);
+ else
+ speed = FIELD_GET(AT803X_SS_SPEED_MASK, ss);
+
+ switch (speed) {
+ case AT803X_SS_SPEED_10:
+ phydev->speed = SPEED_10;
+ break;
+ case AT803X_SS_SPEED_100:
+ phydev->speed = SPEED_100;
+ break;
+ case AT803X_SS_SPEED_1000:
+ phydev->speed = SPEED_1000;
+ break;
+ case QCA808X_SS_SPEED_2500:
+ phydev->speed = SPEED_2500;
+ break;
+ }
+ if (ss & AT803X_SS_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ if (ss & AT803X_SS_MDIX)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+
+ switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {
+ case AT803X_SFC_MANUAL_MDI:
+ phydev->mdix_ctrl = ETH_TP_MDI;
+ break;
+ case AT803X_SFC_MANUAL_MDIX:
+ phydev->mdix_ctrl = ETH_TP_MDI_X;
+ break;
+ case AT803X_SFC_AUTOMATIC_CROSSOVER:
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int at803x_read_status(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+ int err, old_link = phydev->link;
+
+ if (priv->is_1000basex)
+ return genphy_c37_read_status(phydev);
+
+ /* Update the link, but return if there was an error */
+ err = genphy_update_link(phydev);
+ if (err)
+ return err;
+
+ /* why bother the PHY if nothing can have changed */
+ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+ return 0;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ err = genphy_read_lpa(phydev);
+ if (err < 0)
+ return err;
+
+ err = at803x_read_specific_status(phydev);
+ if (err < 0)
+ return err;
+
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
+ phy_resolve_aneg_pause(phydev);
+
+ return 0;
+}
+
+static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
+{
+ u16 val;
+
+ switch (ctrl) {
+ case ETH_TP_MDI:
+ val = AT803X_SFC_MANUAL_MDI;
+ break;
+ case ETH_TP_MDI_X:
+ val = AT803X_SFC_MANUAL_MDIX;
+ break;
+ case ETH_TP_MDI_AUTO:
+ val = AT803X_SFC_AUTOMATIC_CROSSOVER;
+ break;
+ default:
+ return 0;
+ }
+
+ return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,
+ AT803X_SFC_MDI_CROSSOVER_MODE_M,
+ FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));
+}
+
+static int at803x_config_aneg(struct phy_device *phydev)
+{
+ struct at803x_priv *priv = phydev->priv;
+ int ret;
+
+ ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
+ if (ret < 0)
+ return ret;
+
+ /* Changes of the midx bits are disruptive to the normal operation;
+ * therefore any changes to these registers must be followed by a
+ * software reset to take effect.
+ */
+ if (ret == 1) {
+ ret = genphy_soft_reset(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (priv->is_1000basex)
+ return genphy_c37_config_aneg(phydev);
+
+ /* Do not restart auto-negotiation by setting ret to 0 defautly,
+ * when calling __genphy_config_aneg later.
+ */
+ ret = 0;
+
+ if (phydev->drv->phy_id == QCA8081_PHY_ID) {
+ int phy_ctrl = 0;
+
+ /* The reg MII_BMCR also needs to be configured for force mode, the
+ * genphy_config_aneg is also needed.
+ */
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ genphy_c45_pma_setup_forced(phydev);
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising))
+ phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+ MDIO_AN_10GBT_CTRL_ADV2_5G, phy_ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ return __genphy_config_aneg(phydev, ret);
+}
+
+static int at803x_get_downshift(struct phy_device *phydev, u8 *d)
+{
+ int val;
+
+ val = phy_read(phydev, AT803X_SMART_SPEED);
+ if (val < 0)
+ return val;
+
+ if (val & AT803X_SMART_SPEED_ENABLE)
+ *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;
+ else
+ *d = DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ u16 mask, set;
+ int ret;
+
+ switch (cnt) {
+ case DOWNSHIFT_DEV_DEFAULT_COUNT:
+ cnt = AT803X_DEFAULT_DOWNSHIFT;
+ fallthrough;
+ case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:
+ set = AT803X_SMART_SPEED_ENABLE |
+ AT803X_SMART_SPEED_BYPASS_TIMER |
+ FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);
+ mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;
+ break;
+ case DOWNSHIFT_DEV_DISABLE:
+ set = 0;
+ mask = AT803X_SMART_SPEED_ENABLE |
+ AT803X_SMART_SPEED_BYPASS_TIMER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);
+
+ /* After changing the smart speed settings, we need to perform a
+ * software reset, use phy_init_hw() to make sure we set the
+ * reapply any values which might got lost during software reset.
+ */
+ if (ret == 1)
+ ret = phy_init_hw(phydev);
+
+ return ret;
+}
+
+static int at803x_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return at803x_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int at803x_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return at803x_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int at803x_cable_test_result_trans(u16 status)
+{
+ switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+ case AT803X_CDT_STATUS_STAT_NORMAL:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case AT803X_CDT_STATUS_STAT_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ case AT803X_CDT_STATUS_STAT_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case AT803X_CDT_STATUS_STAT_FAIL:
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static bool at803x_cdt_test_failed(u16 status)
+{
+ return FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status) ==
+ AT803X_CDT_STATUS_STAT_FAIL;
+}
+
+static bool at803x_cdt_fault_length_valid(u16 status)
+{
+ switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
+ case AT803X_CDT_STATUS_STAT_OPEN:
+ case AT803X_CDT_STATUS_STAT_SHORT:
+ return true;
+ }
+ return false;
+}
+
+static int at803x_cdt_fault_length(u16 status)
+{
+ int dt;
+
+ /* According to the datasheet the distance to the fault is
+ * DELTA_TIME * 0.824 meters.
+ *
+ * The author suspect the correct formula is:
+ *
+ * fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2
+ *
+ * where c is the speed of light, VF is the velocity factor of
+ * the twisted pair cable, 125MHz the counter frequency and
+ * we need to divide by 2 because the hardware will measure the
+ * round trip time to the fault and back to the PHY.
+ *
+ * With a VF of 0.69 we get the factor 0.824 mentioned in the
+ * datasheet.
+ */
+ dt = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, status);
+
+ return (dt * 824) / 10;
+}
+
+static int at803x_cdt_start(struct phy_device *phydev, int pair)
+{
+ u16 cdt;
+
+ /* qca8081 takes the different bit 15 to enable CDT test */
+ if (phydev->drv->phy_id == QCA8081_PHY_ID)
+ cdt = QCA808X_CDT_ENABLE_TEST |
+ QCA808X_CDT_LENGTH_UNIT |
+ QCA808X_CDT_INTER_CHECK_DIS;
+ else
+ cdt = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) |
+ AT803X_CDT_ENABLE_TEST;
+
+ return phy_write(phydev, AT803X_CDT, cdt);
+}
+
+static int at803x_cdt_wait_for_completion(struct phy_device *phydev)
+{
+ int val, ret;
+ u16 cdt_en;
+
+ if (phydev->drv->phy_id == QCA8081_PHY_ID)
+ cdt_en = QCA808X_CDT_ENABLE_TEST;
+ else
+ cdt_en = AT803X_CDT_ENABLE_TEST;
+
+ /* One test run takes about 25ms */
+ ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,
+ !(val & cdt_en),
+ 30000, 100000, true);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int at803x_cable_test_one_pair(struct phy_device *phydev, int pair)
+{
+ static const int ethtool_pair[] = {
+ ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_PAIR_B,
+ ETHTOOL_A_CABLE_PAIR_C,
+ ETHTOOL_A_CABLE_PAIR_D,
+ };
+ int ret, val;
+
+ ret = at803x_cdt_start(phydev, pair);
+ if (ret)
+ return ret;
+
+ ret = at803x_cdt_wait_for_completion(phydev);
+ if (ret)
+ return ret;
+
+ val = phy_read(phydev, AT803X_CDT_STATUS);
+ if (val < 0)
+ return val;
+
+ if (at803x_cdt_test_failed(val))
+ return 0;
+
+ ethnl_cable_test_result(phydev, ethtool_pair[pair],
+ at803x_cable_test_result_trans(val));
+
+ if (at803x_cdt_fault_length_valid(val))
+ ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
+ at803x_cdt_fault_length(val));
+
+ return 1;
+}
+
+static int at803x_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ unsigned long pair_mask;
+ int retries = 20;
+ int pair, ret;
+
+ if (phydev->phy_id == ATH9331_PHY_ID ||
+ phydev->phy_id == ATH8032_PHY_ID ||
+ phydev->phy_id == QCA9561_PHY_ID)
+ pair_mask = 0x3;
+ else
+ pair_mask = 0xf;
+
+ *finished = false;
+
+ /* According to the datasheet the CDT can be performed when
+ * there is no link partner or when the link partner is
+ * auto-negotiating. Starting the test will restart the AN
+ * automatically. It seems that doing this repeatedly we will
+ * get a slot where our link partner won't disturb our
+ * measurement.
+ */
+ while (pair_mask && retries--) {
+ for_each_set_bit(pair, &pair_mask, 4) {
+ ret = at803x_cable_test_one_pair(phydev, pair);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ clear_bit(pair, &pair_mask);
+ }
+ if (pair_mask)
+ msleep(250);
+ }
+
+ *finished = true;
+
+ return 0;
+}
+
+static int at803x_cable_test_start(struct phy_device *phydev)
+{
+ /* Enable auto-negotiation, but advertise no capabilities, no link
+ * will be established. A restart of the auto-negotiation is not
+ * required, because the cable test will automatically break the link.
+ */
+ phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+ phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+ if (phydev->phy_id != ATH9331_PHY_ID &&
+ phydev->phy_id != ATH8032_PHY_ID &&
+ phydev->phy_id != QCA9561_PHY_ID)
+ phy_write(phydev, MII_CTRL1000, 0);
+
+ /* we do all the (time consuming) work later */
+ return 0;
+}
+
+static int qca83xx_config_init(struct phy_device *phydev)
+{
+ u8 switch_revision;
+
+ switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
+
+ switch (switch_revision) {
+ case 1:
+ /* For 100M waveform */
+ at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea);
+ /* Turn on Gigabit clock */
+ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0);
+ break;
+
+ case 2:
+ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+ fallthrough;
+ case 4:
+ phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
+ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860);
+ at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46);
+ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
+ break;
+ }
+
+ /* QCA8327 require DAC amplitude adjustment for 100m set to +6%.
+ * Disable on init and enable only with 100m speed following
+ * qca original source code.
+ */
+ if (phydev->drv->phy_id == QCA8327_A_PHY_ID ||
+ phydev->drv->phy_id == QCA8327_B_PHY_ID)
+ at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+ QCA8327_DEBUG_MANU_CTRL_EN, 0);
+
+ /* Following original QCA sourcecode set port to prefer master */
+ phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER);
+
+ return 0;
+}
+
+static void qca83xx_link_change_notify(struct phy_device *phydev)
+{
+ /* QCA8337 doesn't require DAC Amplitude adjustement */
+ if (phydev->drv->phy_id == QCA8337_PHY_ID)
+ return;
+
+ /* Set DAC Amplitude adjustment to +6% for 100m on link running */
+ if (phydev->state == PHY_RUNNING) {
+ if (phydev->speed == SPEED_100)
+ at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+ QCA8327_DEBUG_MANU_CTRL_EN,
+ QCA8327_DEBUG_MANU_CTRL_EN);
+ } else {
+ /* Reset DAC Amplitude adjustment */
+ at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL,
+ QCA8327_DEBUG_MANU_CTRL_EN, 0);
+ }
+}
+
+static int qca83xx_resume(struct phy_device *phydev)
+{
+ int ret, val;
+
+ /* Skip reset if not suspended */
+ if (!phydev->suspended)
+ return 0;
+
+ /* Reinit the port, reset values set by suspend */
+ qca83xx_config_init(phydev);
+
+ /* Reset the port on port resume */
+ phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);
+
+ /* On resume from suspend the switch execute a reset and
+ * restart auto-negotiation. Wait for reset to complete.
+ */
+ ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+ 50000, 600000, true);
+ if (ret)
+ return ret;
+
+ msleep(1);
+
+ return 0;
+}
+
+static int qca83xx_suspend(struct phy_device *phydev)
+{
+ u16 mask = 0;
+
+ /* Only QCA8337 support actual suspend.
+ * QCA8327 cause port unreliability when phy suspend
+ * is set.
+ */
+ if (phydev->drv->phy_id == QCA8337_PHY_ID) {
+ genphy_suspend(phydev);
+ } else {
+ mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX);
+ phy_modify(phydev, MII_BMCR, mask, 0);
+ }
+
+ at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN,
+ AT803X_DEBUG_GATE_CLK_IN1000, 0);
+
+ at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL,
+ AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE |
+ AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0);
+
+ return 0;
+}
+
+static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Enable fast retrain */
+ ret = genphy_c45_fast_retrain(phydev, true);
+ if (ret)
+ return ret;
+
+ phy_write_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_TOP_OPTION1,
+ QCA808X_TOP_OPTION1_DATA);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_20DB,
+ QCA808X_MSE_THRESHOLD_20DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_17DB,
+ QCA808X_MSE_THRESHOLD_17DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_27DB,
+ QCA808X_MSE_THRESHOLD_27DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, QCA808X_PHY_MMD1_MSE_THRESHOLD_28DB,
+ QCA808X_MSE_THRESHOLD_28DB_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_1,
+ QCA808X_MMD3_DEBUG_1_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_4,
+ QCA808X_MMD3_DEBUG_4_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_5,
+ QCA808X_MMD3_DEBUG_5_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_3,
+ QCA808X_MMD3_DEBUG_3_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_6,
+ QCA808X_MMD3_DEBUG_6_VALUE);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_DEBUG_2,
+ QCA808X_MMD3_DEBUG_2_VALUE);
+
+ return 0;
+}
+
+static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+{
+ u16 seed_value;
+
+ if (!enable)
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+ QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+
+ seed_value = get_random_u32_below(QCA808X_MASTER_SLAVE_SEED_RANGE);
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+ QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
+ FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
+ QCA808X_MASTER_SLAVE_SEED_ENABLE);
+}
+
+static bool qca808x_is_prefer_master(struct phy_device *phydev)
+{
+ return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
+ (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+}
+
+static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+{
+ return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+}
+
+static int qca808x_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Active adc&vga on 802.3az for the link 1000M and 100M */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, QCA808X_PHY_MMD3_ADDR_CLD_CTRL7,
+ QCA808X_8023AZ_AFE_CTRL_MASK, QCA808X_8023AZ_AFE_EN);
+ if (ret)
+ return ret;
+
+ /* Adjust the threshold on 802.3az for the link 1000M */
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ QCA808X_PHY_MMD3_AZ_TRAINING_CTRL, QCA808X_MMD3_AZ_TRAINING_VAL);
+ if (ret)
+ return ret;
+
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+ /* Config the fast retrain for the link 2500M */
+ ret = qca808x_phy_fast_retrain_config(phydev);
+ if (ret)
+ return ret;
+
+ ret = genphy_read_master_slave(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (!qca808x_is_prefer_master(phydev)) {
+ /* Enable seed and configure lower ramdom seed to make phy
+ * linked as slave mode.
+ */
+ ret = qca808x_phy_ms_seed_enable(phydev, true);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /* Configure adc threshold as 100mv for the link 10M */
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
+ QCA808X_ADC_THRESHOLD_MASK, QCA808X_ADC_THRESHOLD_100MV);
+}
+
+static int qca808x_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+ if (ret < 0)
+ return ret;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising,
+ ret & MDIO_AN_10GBT_STAT_LP2_5G);
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ ret = at803x_read_specific_status(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->link) {
+ if (phydev->speed == SPEED_2500)
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ else
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ } else {
+ /* generate seed as a lower random value to make PHY linked as SLAVE easily,
+ * except for master/slave configuration fault detected or the master mode
+ * preferred.
+ *
+ * the reason for not putting this code into the function link_change_notify is
+ * the corner case where the link partner is also the qca8081 PHY and the seed
+ * value is configured as the same value, the link can't be up and no link change
+ * occurs.
+ */
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+ qca808x_is_prefer_master(phydev)) {
+ qca808x_phy_ms_seed_enable(phydev, false);
+ } else {
+ qca808x_phy_ms_seed_enable(phydev, true);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int qca808x_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_soft_reset(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev))
+ ret = qca808x_phy_ms_seed_enable(phydev, true);
+
+ return ret;
+}
+
+static bool qca808x_cdt_fault_length_valid(int cdt_code)
+{
+ switch (cdt_code) {
+ case QCA808X_CDT_STATUS_STAT_SHORT:
+ case QCA808X_CDT_STATUS_STAT_OPEN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int qca808x_cable_test_result_trans(int cdt_code)
+{
+ switch (cdt_code) {
+ case QCA808X_CDT_STATUS_STAT_NORMAL:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case QCA808X_CDT_STATUS_STAT_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ case QCA808X_CDT_STATUS_STAT_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case QCA808X_CDT_STATUS_STAT_FAIL:
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair)
+{
+ int val;
+ u32 cdt_length_reg = 0;
+
+ switch (pair) {
+ case ETHTOOL_A_CABLE_PAIR_A:
+ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;
+ break;
+ case ETHTOOL_A_CABLE_PAIR_B:
+ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;
+ break;
+ case ETHTOOL_A_CABLE_PAIR_C:
+ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;
+ break;
+ case ETHTOOL_A_CABLE_PAIR_D:
+ cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);
+ if (val < 0)
+ return val;
+
+ return (FIELD_GET(QCA808X_CDT_DIAG_LENGTH, val) * 824) / 10;
+}
+
+static int qca808x_cable_test_start(struct phy_device *phydev)
+{
+ int ret;
+
+ /* perform CDT with the following configs:
+ * 1. disable hibernation.
+ * 2. force PHY working in MDI mode.
+ * 3. for PHY working in 1000BaseT.
+ * 4. configure the threshold.
+ */
+
+ ret = at803x_debug_reg_mask(phydev, QCA808X_DBG_AN_TEST, QCA808X_HIBERNATION_EN, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = at803x_config_mdix(phydev, ETH_TP_MDI);
+ if (ret < 0)
+ return ret;
+
+ /* Force 1000base-T needs to configure PMA/PMD and MII_BMCR */
+ phydev->duplex = DUPLEX_FULL;
+ phydev->speed = SPEED_1000;
+ ret = genphy_c45_pma_setup_forced(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = genphy_setup_forced(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* configure the thresholds for open, short, pair ok test */
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8074, 0xc040);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8076, 0xc040);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8077, 0xa060);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8078, 0xc050);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807a, 0xc060);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, 0x807e, 0xb060);
+
+ return 0;
+}
+
+static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)
+{
+ int ret, val;
+ int pair_a, pair_b, pair_c, pair_d;
+
+ *finished = false;
+
+ ret = at803x_cdt_start(phydev, 0);
+ if (ret)
+ return ret;
+
+ ret = at803x_cdt_wait_for_completion(phydev);
+ if (ret)
+ return ret;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);
+ if (val < 0)
+ return val;
+
+ pair_a = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, val);
+ pair_b = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, val);
+ pair_c = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, val);
+ pair_d = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, val);
+
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ qca808x_cable_test_result_trans(pair_a));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+ qca808x_cable_test_result_trans(pair_b));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+ qca808x_cable_test_result_trans(pair_c));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+ qca808x_cable_test_result_trans(pair_d));
+
+ if (qca808x_cdt_fault_length_valid(pair_a))
+ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A));
+ if (qca808x_cdt_fault_length_valid(pair_b))
+ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B,
+ qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_B));
+ if (qca808x_cdt_fault_length_valid(pair_c))
+ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C,
+ qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_C));
+ if (qca808x_cdt_fault_length_valid(pair_d))
+ ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D,
+ qca808x_cdt_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_D));
+
+ *finished = true;
+
+ return 0;
+}
+
+static int qca808x_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* The autoneg ability is not existed in bit3 of MMD7.1,
+ * but it is supported by qca808x PHY, so we add it here
+ * manually.
+ */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+
+ /* As for the qca8081 1G version chip, the 2500baseT ability is also
+ * existed in the bit0 of MMD1.21, we need to remove it manually if
+ * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
+ */
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+ if (ret < 0)
+ return ret;
+
+ if (QCA808X_PHY_CHIP_TYPE_1G & ret)
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+
+ return 0;
+}
+
+static void qca808x_link_change_notify(struct phy_device *phydev)
+{
+ /* Assert interface sgmii fifo on link down, deassert it on link up,
+ * the interface device address is always phy address added by 1.
+ */
+ mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+ MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+ QCA8081_PHY_FIFO_RSTN, phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+}
+
+static struct phy_driver at803x_driver[] = {
+{
+ /* Qualcomm Atheros AR8035 */
+ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID),
+ .name = "Qualcomm Atheros AR8035",
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = at803x_probe,
+ .config_aneg = at803x_config_aneg,
+ .config_init = at803x_config_init,
+ .soft_reset = genphy_soft_reset,
+ .set_wol = at803x_set_wol,
+ .get_wol = at803x_get_wol,
+ .suspend = at803x_suspend,
+ .resume = at803x_resume,
+ /* PHY_GBIT_FEATURES */
+ .read_status = at803x_read_status,
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
+ .set_tunable = at803x_set_tunable,
+ .cable_test_start = at803x_cable_test_start,
+ .cable_test_get_status = at803x_cable_test_get_status,
+}, {
+ /* Qualcomm Atheros AR8030 */
+ .phy_id = ATH8030_PHY_ID,
+ .name = "Qualcomm Atheros AR8030",
+ .phy_id_mask = AT8030_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,
+ /* PHY_BASIC_FEATURES */
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+}, {
+ /* Qualcomm Atheros AR8031/AR8033 */
+ PHY_ID_MATCH_EXACT(ATH8031_PHY_ID),
+ .name = "Qualcomm Atheros AR8031/AR8033",
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = at803x_probe,
+ .config_init = at803x_config_init,
+ .config_aneg = at803x_config_aneg,
+ .soft_reset = genphy_soft_reset,
+ .set_wol = at803x_set_wol,
+ .get_wol = at803x_get_wol,
+ .suspend = at803x_suspend,
+ .resume = at803x_resume,
+ .read_page = at803x_read_page,
+ .write_page = at803x_write_page,
+ .get_features = at803x_get_features,
+ .read_status = at803x_read_status,
+ .config_intr = &at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
+ .set_tunable = at803x_set_tunable,
+ .cable_test_start = at803x_cable_test_start,
+ .cable_test_get_status = at803x_cable_test_get_status,
+}, {
+ /* Qualcomm Atheros AR8032 */
+ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),
+ .name = "Qualcomm Atheros AR8032",
+ .probe = at803x_probe,
+ .flags = PHY_POLL_CABLE_TEST,
+ .config_init = at803x_config_init,
+ .link_change_notify = at803x_link_change_notify,
+ .suspend = at803x_suspend,
+ .resume = at803x_resume,
+ /* PHY_BASIC_FEATURES */
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = at803x_cable_test_start,
+ .cable_test_get_status = at803x_cable_test_get_status,
+}, {
+ /* ATHEROS AR9331 */
+ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID),
+ .name = "Qualcomm Atheros AR9331 built-in PHY",
+ .probe = at803x_probe,
+ .suspend = at803x_suspend,
+ .resume = at803x_resume,
+ .flags = PHY_POLL_CABLE_TEST,
+ /* PHY_BASIC_FEATURES */
+ .config_intr = &at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = at803x_cable_test_start,
+ .cable_test_get_status = at803x_cable_test_get_status,
+ .read_status = at803x_read_status,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
+}, {
+ /* Qualcomm Atheros QCA9561 */
+ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
+ .name = "Qualcomm Atheros QCA9561 built-in PHY",
+ .probe = at803x_probe,
+ .suspend = at803x_suspend,
+ .resume = at803x_resume,
+ .flags = PHY_POLL_CABLE_TEST,
+ /* PHY_BASIC_FEATURES */
+ .config_intr = &at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = at803x_cable_test_start,
+ .cable_test_get_status = at803x_cable_test_get_status,
+ .read_status = at803x_read_status,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
+}, {
+ /* QCA8337 */
+ .phy_id = QCA8337_PHY_ID,
+ .phy_id_mask = QCA8K_PHY_ID_MASK,
+ .name = "Qualcomm Atheros 8337 internal PHY",
+ /* PHY_GBIT_FEATURES */
+ .link_change_notify = qca83xx_link_change_notify,
+ .probe = at803x_probe,
+ .flags = PHY_IS_INTERNAL,
+ .config_init = qca83xx_config_init,
+ .soft_reset = genphy_soft_reset,
+ .get_sset_count = at803x_get_sset_count,
+ .get_strings = at803x_get_strings,
+ .get_stats = at803x_get_stats,
+ .suspend = qca83xx_suspend,
+ .resume = qca83xx_resume,
+}, {
+ /* QCA8327-A from switch QCA8327-AL1A */
+ .phy_id = QCA8327_A_PHY_ID,
+ .phy_id_mask = QCA8K_PHY_ID_MASK,
+ .name = "Qualcomm Atheros 8327-A internal PHY",
+ /* PHY_GBIT_FEATURES */
+ .link_change_notify = qca83xx_link_change_notify,
+ .probe = at803x_probe,
+ .flags = PHY_IS_INTERNAL,
+ .config_init = qca83xx_config_init,
+ .soft_reset = genphy_soft_reset,
+ .get_sset_count = at803x_get_sset_count,
+ .get_strings = at803x_get_strings,
+ .get_stats = at803x_get_stats,
+ .suspend = qca83xx_suspend,
+ .resume = qca83xx_resume,
+}, {
+ /* QCA8327-B from switch QCA8327-BL1A */
+ .phy_id = QCA8327_B_PHY_ID,
+ .phy_id_mask = QCA8K_PHY_ID_MASK,
+ .name = "Qualcomm Atheros 8327-B internal PHY",
+ /* PHY_GBIT_FEATURES */
+ .link_change_notify = qca83xx_link_change_notify,
+ .probe = at803x_probe,
+ .flags = PHY_IS_INTERNAL,
+ .config_init = qca83xx_config_init,
+ .soft_reset = genphy_soft_reset,
+ .get_sset_count = at803x_get_sset_count,
+ .get_strings = at803x_get_strings,
+ .get_stats = at803x_get_stats,
+ .suspend = qca83xx_suspend,
+ .resume = qca83xx_resume,
+}, {
+ /* Qualcomm QCA8081 */
+ PHY_ID_MATCH_EXACT(QCA8081_PHY_ID),
+ .name = "Qualcomm QCA8081",
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = at803x_probe,
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .get_tunable = at803x_get_tunable,
+ .set_tunable = at803x_set_tunable,
+ .set_wol = at803x_set_wol,
+ .get_wol = at803x_get_wol,
+ .get_features = qca808x_get_features,
+ .config_aneg = at803x_config_aneg,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_status = qca808x_read_status,
+ .config_init = qca808x_config_init,
+ .soft_reset = qca808x_soft_reset,
+ .cable_test_start = qca808x_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
+ .link_change_notify = qca808x_link_change_notify,
+}, };
+
+module_phy_driver(at803x_driver);
+
+static struct mdio_device_id __maybe_unused atheros_tbl[] = {
+ { ATH8030_PHY_ID, AT8030_PHY_ID_MASK },
+ { PHY_ID_MATCH_EXACT(ATH8031_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) },
+ { }
+};
+
+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..0f1e617a2
--- /dev/null
+++ b/drivers/net/phy/ax88796b.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Driver for Asix PHYs
+ *
+ * Author: Michael Schmitz <schmitzmic@gmail.com>
+ */
+#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_AX88772A 0x003b1861
+#define PHY_ID_ASIX_AX88772C 0x003b1881
+#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);
+}
+
+/* AX88772A is not working properly with some old switches (NETGEAR EN 108TP):
+ * after autoneg is done and the link status is reported as active, the MII_LPA
+ * register is 0. This issue is not reproducible on AX88772C.
+ */
+static int asix_ax88772a_read_status(struct phy_device *phydev)
+{
+ int ret, val;
+
+ ret = genphy_update_link(phydev);
+ if (ret)
+ return ret;
+
+ if (!phydev->link)
+ return 0;
+
+ /* If MII_LPA is 0, phy_resolve_aneg_linkmode() will fail to resolve
+ * linkmode so use MII_BMCR as default values.
+ */
+ val = phy_read(phydev, MII_BMCR);
+ if (val < 0)
+ return val;
+
+ if (val & BMCR_SPEED100)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_10;
+
+ if (val & BMCR_FULLDPLX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ ret = genphy_read_lpa(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
+ phy_resolve_aneg_linkmode(phydev);
+
+ return 0;
+}
+
+static void asix_ax88772a_link_change_notify(struct phy_device *phydev)
+{
+ /* Reset PHY, otherwise MII_LPA will provide outdated information.
+ * This issue is reproducible only with some link partner PHYs
+ */
+ if (phydev->state == PHY_NOLINK) {
+ phy_init_hw(phydev);
+ phy_start_aneg(phydev);
+ }
+}
+
+static struct phy_driver asix_driver[] = {
+{
+ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A),
+ .name = "Asix Electronics AX88772A",
+ .flags = PHY_IS_INTERNAL,
+ .read_status = asix_ax88772a_read_status,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .soft_reset = asix_soft_reset,
+ .link_change_notify = asix_ax88772a_link_change_notify,
+}, {
+ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C),
+ .name = "Asix Electronics AX88772C",
+ .flags = PHY_IS_INTERNAL,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .soft_reset = asix_soft_reset,
+}, {
+ .phy_id = PHY_ID_ASIX_AX88796B,
+ .name = "Asix Electronics AX88796B",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .soft_reset = asix_soft_reset,
+} };
+
+module_phy_driver(asix_driver);
+
+static struct mdio_device_id __maybe_unused asix_tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C) },
+ { 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..da8f7cb41
--- /dev/null
+++ b/drivers/net/phy/bcm-cygnus.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ */
+
+/* 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>
+
+struct bcm_omega_phy_priv {
+ u64 *stats;
+};
+
+/* 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 int bcm_omega_config_init(struct phy_device *phydev)
+{
+ u8 count, rev;
+ int ret = 0;
+
+ rev = phydev->phy_id & ~phydev->drv->phy_id_mask;
+
+ pr_info_once("%s: %s PHY revision: 0x%02x\n",
+ phydev_name(phydev), phydev->drv->name, rev);
+
+ /* 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 0x00:
+ ret = bcm_phy_28nm_a0b0_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 bcm_omega_resume(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Re-apply workarounds coming out suspend/resume */
+ ret = bcm_omega_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 bcm_omega_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 bcm_omega_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 bcm_omega_get_phy_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct bcm_omega_phy_priv *priv = phydev->priv;
+
+ bcm_phy_get_stats(phydev, priv->stats, stats, data);
+}
+
+static int bcm_omega_probe(struct phy_device *phydev)
+{
+ struct bcm_omega_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 struct phy_driver bcm_cygnus_phy_driver[] = {
+{
+ .phy_id = PHY_ID_BCM_CYGNUS,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom Cygnus PHY",
+ /* PHY_GBIT_FEATURES */
+ .config_init = bcm_cygnus_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = bcm_cygnus_resume,
+}, {
+ .phy_id = PHY_ID_BCM_OMEGA,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom Omega Combo GPHY",
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_IS_INTERNAL,
+ .config_init = bcm_omega_config_init,
+ .suspend = genphy_suspend,
+ .resume = bcm_omega_resume,
+ .get_tunable = bcm_omega_get_tunable,
+ .set_tunable = bcm_omega_set_tunable,
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm_omega_get_phy_stats,
+ .probe = bcm_omega_probe,
+}
+};
+
+static struct mdio_device_id __maybe_unused bcm_cygnus_phy_tbl[] = {
+ { PHY_ID_BCM_CYGNUS, 0xfffffff0, },
+ { PHY_ID_BCM_OMEGA, 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..876f28fd8
--- /dev/null
+++ b/drivers/net/phy/bcm-phy-lib.c
@@ -0,0 +1,1071 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2017 Broadcom
+ */
+
+#include "bcm-phy-lib.h"
+#include <linux/bitfield.h>
+#include <linux/brcmphy.h>
+#include <linux/etherdevice.h>
+#include <linux/export.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/netdevice.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_write_exp(struct phy_device *phydev, u16 reg, u16 val)
+{
+ int rc;
+
+ phy_lock_mdio_bus(phydev);
+ rc = __bcm_phy_write_exp(phydev, reg, val);
+ phy_unlock_mdio_bus(phydev);
+
+ return rc;
+}
+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 bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
+{
+ int rc;
+
+ phy_lock_mdio_bus(phydev);
+ rc = __bcm_phy_read_exp(phydev, reg);
+ phy_unlock_mdio_bus(phydev);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_read_exp);
+
+int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
+{
+ int new, ret;
+
+ ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
+ if (ret < 0)
+ return ret;
+
+ new = (ret & ~mask) | set;
+ if (new == ret)
+ return 0;
+
+ return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new);
+}
+EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp);
+
+int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __bcm_phy_modify_exp(phydev, reg, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_modify_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, err;
+
+ reg = phy_read(phydev, MII_BCM54XX_ECR);
+ if (reg < 0)
+ return reg;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = bcm_phy_ack_intr(phydev);
+ if (err)
+ return err;
+
+ reg &= ~MII_BCM54XX_ECR_IM;
+ err = phy_write(phydev, MII_BCM54XX_ECR, reg);
+ } else {
+ reg |= MII_BCM54XX_ECR_IM;
+ err = phy_write(phydev, MII_BCM54XX_ECR, reg);
+ if (err)
+ return err;
+
+ err = bcm_phy_ack_intr(phydev);
+ }
+ return err;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_config_intr);
+
+irqreturn_t bcm_phy_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, irq_mask;
+
+ irq_status = phy_read(phydev, MII_BCM54XX_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ /* If a bit from the Interrupt Mask register is set, the corresponding
+ * bit from the Interrupt Status register is masked. So read the IMR
+ * and then flip the bits to get the list of possible interrupt
+ * sources.
+ */
+ irq_mask = phy_read(phydev, MII_BCM54XX_IMR);
+ if (irq_mask < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ irq_mask = ~irq_mask;
+
+ if (!(irq_status & irq_mask))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_handle_interrupt);
+
+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_read_rdb(struct phy_device *phydev, u16 rdb)
+{
+ int val;
+
+ val = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
+ if (val < 0)
+ return val;
+
+ return __phy_read(phydev, MII_BCM54XX_RDB_DATA);
+}
+EXPORT_SYMBOL_GPL(__bcm_phy_read_rdb);
+
+int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __bcm_phy_read_rdb(phydev, rdb);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_read_rdb);
+
+int __bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
+{
+ int ret;
+
+ ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
+ if (ret < 0)
+ return ret;
+
+ return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val);
+}
+EXPORT_SYMBOL_GPL(__bcm_phy_write_rdb);
+
+int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __bcm_phy_write_rdb(phydev, rdb, val);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_write_rdb);
+
+int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
+{
+ int new, ret;
+
+ ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
+ if (ret < 0)
+ return ret;
+
+ ret = __phy_read(phydev, MII_BCM54XX_RDB_DATA);
+ if (ret < 0)
+ return ret;
+
+ new = (ret & ~mask) | set;
+ if (new == ret)
+ return 0;
+
+ return __phy_write(phydev, MII_BCM54XX_RDB_DATA, new);
+}
+EXPORT_SYMBOL_GPL(__bcm_phy_modify_rdb);
+
+int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __bcm_phy_modify_rdb(phydev, rdb, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_modify_rdb);
+
+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 (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported))
+ mask |= MDIO_EEE_1000T;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ phydev->supported))
+ 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;
+ int devad;
+ u16 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", -1, MII_BRCM_CORE_BASE12, 0, 16 },
+ { "phy_serdes_ber_errors", -1, MII_BRCM_CORE_BASE13, 8, 8 },
+ { "phy_false_carrier_sense_errors", -1, MII_BRCM_CORE_BASE13, 0, 8 },
+ { "phy_local_rcvr_nok", -1, MII_BRCM_CORE_BASE14, 8, 8 },
+ { "phy_remote_rcv_nok", -1, MII_BRCM_CORE_BASE14, 0, 8 },
+ { "phy_lpi_count", MDIO_MMD_AN, BRCM_CL45VEN_EEE_LPI_CNT, 0, 16 },
+};
+
+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++)
+ strscpy(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;
+
+ if (stat.devad < 0)
+ val = phy_read(phydev, stat.reg);
+ else
+ val = phy_read_mmd(phydev, stat.devad, 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);
+
+void bcm_phy_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);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_r_rc_cal_reset);
+
+int bcm_phy_28nm_a0b0_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);
+
+ bcm_phy_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;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_28nm_a0b0_afe_config_init);
+
+int bcm_phy_enable_jumbo(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL);
+ if (ret < 0)
+ return ret;
+
+ /* Enable extended length packet reception */
+ ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
+ ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN);
+ if (ret < 0)
+ return ret;
+
+ /* Enable the elastic FIFO for raising the transmission limit from
+ * 4.5KB to 10KB, at the expense of an additional 16 ns in propagation
+ * latency.
+ */
+ return phy_set_bits(phydev, MII_BCM54XX_ECR, MII_BCM54XX_ECR_FIFOE);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);
+
+static int __bcm_phy_enable_rdb_access(struct phy_device *phydev)
+{
+ return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0);
+}
+
+static int __bcm_phy_enable_legacy_access(struct phy_device *phydev)
+{
+ return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087,
+ BCM54XX_ACCESS_MODE_LEGACY_EN);
+}
+
+static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb)
+{
+ u16 mask, set;
+ int ret;
+
+ /* Auto-negotiation must be enabled for cable diagnostics to work, but
+ * don't advertise any capabilities.
+ */
+ phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+ phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+ phy_write(phydev, MII_CTRL1000, 0);
+
+ phy_lock_mdio_bus(phydev);
+ if (is_rdb) {
+ ret = __bcm_phy_enable_legacy_access(phydev);
+ if (ret)
+ goto out;
+ }
+
+ mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK;
+ set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
+ FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK,
+ BCM54XX_ECD_CTRL_UNIT_CM);
+
+ ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set);
+
+out:
+ /* re-enable the RDB access even if there was an error */
+ if (is_rdb)
+ ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
+
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+static int bcm_phy_cable_test_report_trans(int result)
+{
+ switch (result) {
+ case BCM54XX_ECD_FAULT_TYPE_OK:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case BCM54XX_ECD_FAULT_TYPE_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+ case BCM54XX_ECD_FAULT_TYPE_INVALID:
+ case BCM54XX_ECD_FAULT_TYPE_BUSY:
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static bool bcm_phy_distance_valid(int result)
+{
+ switch (result) {
+ case BCM54XX_ECD_FAULT_TYPE_OPEN:
+ case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
+ case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
+ return true;
+ }
+ return false;
+}
+
+static int bcm_phy_report_length(struct phy_device *phydev, int pair)
+{
+ int val;
+
+ val = __bcm_phy_read_exp(phydev,
+ BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair);
+ if (val < 0)
+ return val;
+
+ if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID)
+ return 0;
+
+ ethnl_cable_test_fault_length(phydev, pair, val);
+
+ return 0;
+}
+
+static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
+ bool *finished, bool is_rdb)
+{
+ int pair_a, pair_b, pair_c, pair_d, ret;
+
+ *finished = false;
+
+ phy_lock_mdio_bus(phydev);
+
+ if (is_rdb) {
+ ret = __bcm_phy_enable_legacy_access(phydev);
+ if (ret)
+ goto out;
+ }
+
+ ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL);
+ if (ret < 0)
+ goto out;
+
+ if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE);
+ if (ret < 0)
+ goto out;
+
+ pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret);
+ pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret);
+ pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret);
+ pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret);
+
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ bcm_phy_cable_test_report_trans(pair_a));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+ bcm_phy_cable_test_report_trans(pair_b));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+ bcm_phy_cable_test_report_trans(pair_c));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+ bcm_phy_cable_test_report_trans(pair_d));
+
+ if (bcm_phy_distance_valid(pair_a))
+ bcm_phy_report_length(phydev, 0);
+ if (bcm_phy_distance_valid(pair_b))
+ bcm_phy_report_length(phydev, 1);
+ if (bcm_phy_distance_valid(pair_c))
+ bcm_phy_report_length(phydev, 2);
+ if (bcm_phy_distance_valid(pair_d))
+ bcm_phy_report_length(phydev, 3);
+
+ ret = 0;
+ *finished = true;
+out:
+ /* re-enable the RDB access even if there was an error */
+ if (is_rdb)
+ ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
+
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+int bcm_phy_cable_test_start(struct phy_device *phydev)
+{
+ return _bcm_phy_cable_test_start(phydev, false);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start);
+
+int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished)
+{
+ return _bcm_phy_cable_test_get_status(phydev, finished, false);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status);
+
+/* We assume that all PHYs which support RDB access can be switched to legacy
+ * mode. If, in the future, this is not true anymore, we have to re-implement
+ * this with RDB access.
+ */
+int bcm_phy_cable_test_start_rdb(struct phy_device *phydev)
+{
+ return _bcm_phy_cable_test_start(phydev, true);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb);
+
+int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
+ bool *finished)
+{
+ return _bcm_phy_cable_test_get_status(phydev, finished, true);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);
+
+#define BCM54XX_WOL_SUPPORTED_MASK (WAKE_UCAST | \
+ WAKE_MCAST | \
+ WAKE_BCAST | \
+ WAKE_MAGIC | \
+ WAKE_MAGICSECURE)
+
+int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ u8 da[ETH_ALEN], mask[ETH_ALEN];
+ unsigned int i;
+ u16 ctl;
+ int ret;
+
+ /* Allow a MAC driver to play through its own Wake-on-LAN
+ * implementation
+ */
+ if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK)
+ return -EOPNOTSUPP;
+
+ /* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's
+ * ethtool only supports 6, for now.
+ */
+ BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN);
+
+ /* Clear previous interrupts */
+ ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
+ if (ret < 0)
+ return ret;
+
+ ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
+ if (ret < 0)
+ return ret;
+
+ ctl = ret;
+
+ if (!wol->wolopts) {
+ if (phy_interrupt_is_valid(phydev))
+ disable_irq_wake(phydev->irq);
+
+ /* Leave all interrupts disabled */
+ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK,
+ BCM54XX_WOL_ALL_INTRS);
+ if (ret < 0)
+ return ret;
+
+ /* Disable the global Wake-on-LAN enable bit */
+ ctl &= ~BCM54XX_WOL_EN;
+
+ return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
+ }
+
+ /* Clear the previously configured mode and mask mode for Wake-on-LAN */
+ ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT);
+ ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT);
+ ctl &= ~BCM54XX_WOL_DIR_PKT_EN;
+ ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT);
+
+ /* When using WAKE_MAGIC, we program the magic pattern filter to match
+ * the device's MAC address and we accept any MAC DA in the Ethernet
+ * frame.
+ *
+ * When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the
+ * following:
+ * - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match
+ * - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care
+ * - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match
+ *
+ * Note that the Broadcast MAC DA is inherently going to match the
+ * multicast pattern being matched.
+ */
+ memset(mask, 0, sizeof(mask));
+
+ if (wol->wolopts & WAKE_MCAST) {
+ memset(da, 0, sizeof(da));
+ memset(mask, 0xff, sizeof(mask));
+ da[0] = 0x01;
+ mask[0] = ~da[0];
+ } else {
+ if (wol->wolopts & WAKE_UCAST) {
+ ether_addr_copy(da, ndev->dev_addr);
+ } else if (wol->wolopts & WAKE_BCAST) {
+ eth_broadcast_addr(da);
+ } else if (wol->wolopts & WAKE_MAGICSECURE) {
+ ether_addr_copy(da, wol->sopass);
+ } else if (wol->wolopts & WAKE_MAGIC) {
+ memset(da, 0, sizeof(da));
+ memset(mask, 0xff, sizeof(mask));
+ }
+ }
+
+ for (i = 0; i < ETH_ALEN / 2; i++) {
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) {
+ ret = bcm_phy_write_exp(phydev,
+ BCM54XX_WOL_MPD_DATA1(2 - i),
+ ndev->dev_addr[i * 2] << 8 |
+ ndev->dev_addr[i * 2 + 1]);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i),
+ da[i * 2] << 8 | da[i * 2 + 1]);
+ if (ret < 0)
+ return ret;
+
+ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i),
+ mask[i * 2] << 8 | mask[i * 2 + 1]);
+ if (ret)
+ return ret;
+ }
+
+ if (wol->wolopts & WAKE_MAGICSECURE) {
+ ctl |= BCM54XX_WOL_SECKEY_OPT_6B <<
+ BCM54XX_WOL_SECKEY_OPT_SHIFT;
+ ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT;
+ ctl |= BCM54XX_WOL_MASK_MODE_DA_FF <<
+ BCM54XX_WOL_MASK_MODE_SHIFT;
+ } else {
+ if (wol->wolopts & WAKE_MAGIC)
+ ctl |= BCM54XX_WOL_MODE_SINGLE_MPD;
+ else
+ ctl |= BCM54XX_WOL_DIR_PKT_EN;
+ ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY <<
+ BCM54XX_WOL_MASK_MODE_SHIFT;
+ }
+
+ /* Globally enable Wake-on-LAN */
+ ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK;
+
+ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
+ if (ret < 0)
+ return ret;
+
+ /* Enable WOL interrupt on LED4 */
+ ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL);
+ if (ret < 0)
+ return ret;
+
+ ret |= BCM54XX_LED4_SEL_INTR;
+ ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret);
+ if (ret < 0)
+ return ret;
+
+ /* Enable all Wake-on-LAN interrupt sources */
+ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0);
+ if (ret < 0)
+ return ret;
+
+ if (phy_interrupt_is_valid(phydev))
+ enable_irq_wake(phydev->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_set_wol);
+
+void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ u8 da[ETH_ALEN];
+ unsigned int i;
+ int ret;
+ u16 ctl;
+
+ wol->supported = BCM54XX_WOL_SUPPORTED_MASK;
+ wol->wolopts = 0;
+
+ ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
+ if (ret < 0)
+ return;
+
+ ctl = ret;
+
+ if (!(ctl & BCM54XX_WOL_EN))
+ return;
+
+ for (i = 0; i < sizeof(da) / 2; i++) {
+ ret = bcm_phy_read_exp(phydev,
+ BCM54XX_WOL_MPD_DATA2(2 - i));
+ if (ret < 0)
+ return;
+
+ da[i * 2] = ret >> 8;
+ da[i * 2 + 1] = ret & 0xff;
+ }
+
+ if (ctl & BCM54XX_WOL_DIR_PKT_EN) {
+ if (is_broadcast_ether_addr(da))
+ wol->wolopts |= WAKE_BCAST;
+ else if (is_multicast_ether_addr(da))
+ wol->wolopts |= WAKE_MCAST;
+ else if (ether_addr_equal(da, ndev->dev_addr))
+ wol->wolopts |= WAKE_UCAST;
+ } else {
+ ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK;
+ switch (ctl) {
+ case BCM54XX_WOL_MODE_SINGLE_MPD:
+ wol->wolopts |= WAKE_MAGIC;
+ break;
+ case BCM54XX_WOL_MODE_SINGLE_MPDSEC:
+ wol->wolopts |= WAKE_MAGICSECURE;
+ memcpy(wol->sopass, da, sizeof(da));
+ break;
+ default:
+ break;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(bcm_phy_get_wol);
+
+irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id)
+{
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(bcm_phy_wol_isr);
+
+int bcm_phy_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ u8 led_num;
+ int ret;
+ u16 reg;
+
+ if (index >= 4)
+ return -EINVAL;
+
+ /* Two LEDS per register */
+ led_num = index % 2;
+ reg = index >= 2 ? BCM54XX_SHD_LEDS2 : BCM54XX_SHD_LEDS1;
+
+ ret = bcm_phy_read_shadow(phydev, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~(BCM_LED_SRC_MASK << BCM54XX_SHD_LEDS_SHIFT(led_num));
+ if (value == LED_OFF)
+ ret |= BCM_LED_SRC_OFF << BCM54XX_SHD_LEDS_SHIFT(led_num);
+ else
+ ret |= BCM_LED_SRC_ON << BCM54XX_SHD_LEDS_SHIFT(led_num);
+ return bcm_phy_write_shadow(phydev, reg, ret);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_led_brightness_set);
+
+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..b52189e45
--- /dev/null
+++ b/drivers/net/phy/bcm-phy-lib.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ */
+
+#ifndef _LINUX_BCM_PHY_LIB_H
+#define _LINUX_BCM_PHY_LIB_H
+
+#include <linux/brcmphy.h>
+#include <linux/phy.h>
+#include <linux/interrupt.h>
+
+struct ethtool_wolinfo;
+
+/* 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)
+
+
+int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val);
+int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg);
+int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set);
+int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val);
+int bcm_phy_read_exp(struct phy_device *phydev, u16 reg);
+int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set);
+
+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);
+}
+
+static inline int bcm_phy_read_exp_sel(struct phy_device *phydev, u16 reg)
+{
+ return bcm_phy_read_exp(phydev, reg | MII_BCM54XX_EXP_SEL_ER);
+}
+
+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_write_rdb(struct phy_device *phydev, u16 rdb, u16 val);
+int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val);
+int __bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb);
+int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb);
+int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask,
+ u16 set);
+int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask,
+ u16 set);
+
+int bcm_phy_ack_intr(struct phy_device *phydev);
+int bcm_phy_config_intr(struct phy_device *phydev);
+irqreturn_t bcm_phy_handle_interrupt(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);
+void bcm_phy_r_rc_cal_reset(struct phy_device *phydev);
+int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev);
+int bcm_phy_enable_jumbo(struct phy_device *phydev);
+
+int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
+ bool *finished);
+int bcm_phy_cable_test_start_rdb(struct phy_device *phydev);
+int bcm_phy_cable_test_start(struct phy_device *phydev);
+int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished);
+
+#if IS_ENABLED(CONFIG_BCM_NET_PHYPTP)
+struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev);
+void bcm_ptp_config_init(struct phy_device *phydev);
+void bcm_ptp_stop(struct bcm_ptp_private *priv);
+#else
+static inline struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev)
+{
+ return NULL;
+}
+
+static inline void bcm_ptp_config_init(struct phy_device *phydev)
+{
+}
+
+static inline void bcm_ptp_stop(struct bcm_ptp_private *priv)
+{
+}
+#endif
+
+int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol);
+void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol);
+irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id);
+
+int bcm_phy_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value);
+
+#endif /* _LINUX_BCM_PHY_LIB_H */
diff --git a/drivers/net/phy/bcm-phy-ptp.c b/drivers/net/phy/bcm-phy-ptp.c
new file mode 100644
index 000000000..ef00d6163
--- /dev/null
+++ b/drivers/net/phy/bcm-phy-ptp.c
@@ -0,0 +1,944 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Meta Platforms Inc.
+ * Copyright (C) 2022 Jonathan Lemon <jonathan.lemon@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+
+#include "bcm-phy-lib.h"
+
+/* IEEE 1588 Expansion registers */
+#define SLICE_CTRL 0x0810
+#define SLICE_TX_EN BIT(0)
+#define SLICE_RX_EN BIT(8)
+#define TX_EVENT_MODE 0x0811
+#define MODE_TX_UPDATE_CF BIT(0)
+#define MODE_TX_REPLACE_TS_CF BIT(1)
+#define MODE_TX_REPLACE_TS GENMASK(1, 0)
+#define RX_EVENT_MODE 0x0819
+#define MODE_RX_UPDATE_CF BIT(0)
+#define MODE_RX_INSERT_TS_48 BIT(1)
+#define MODE_RX_INSERT_TS_64 GENMASK(1, 0)
+
+#define MODE_EVT_SHIFT_SYNC 0
+#define MODE_EVT_SHIFT_DELAY_REQ 2
+#define MODE_EVT_SHIFT_PDELAY_REQ 4
+#define MODE_EVT_SHIFT_PDELAY_RESP 6
+
+#define MODE_SEL_SHIFT_PORT 0
+#define MODE_SEL_SHIFT_CPU 8
+
+#define RX_MODE_SEL(sel, evt, act) \
+ (((MODE_RX_##act) << (MODE_EVT_SHIFT_##evt)) << (MODE_SEL_SHIFT_##sel))
+
+#define TX_MODE_SEL(sel, evt, act) \
+ (((MODE_TX_##act) << (MODE_EVT_SHIFT_##evt)) << (MODE_SEL_SHIFT_##sel))
+
+/* needs global TS capture first */
+#define TX_TS_CAPTURE 0x0821
+#define TX_TS_CAP_EN BIT(0)
+#define RX_TS_CAPTURE 0x0822
+#define RX_TS_CAP_EN BIT(0)
+
+#define TIME_CODE_0 0x0854
+#define TIME_CODE_1 0x0855
+#define TIME_CODE_2 0x0856
+#define TIME_CODE_3 0x0857
+#define TIME_CODE_4 0x0858
+
+#define DPLL_SELECT 0x085b
+#define DPLL_HB_MODE2 BIT(6)
+
+#define SHADOW_CTRL 0x085c
+#define SHADOW_LOAD 0x085d
+#define TIME_CODE_LOAD BIT(10)
+#define SYNC_OUT_LOAD BIT(9)
+#define NCO_TIME_LOAD BIT(7)
+#define FREQ_LOAD BIT(6)
+#define INTR_MASK 0x085e
+#define INTR_STATUS 0x085f
+#define INTC_FSYNC BIT(0)
+#define INTC_SOP BIT(1)
+
+#define NCO_FREQ_LSB 0x0873
+#define NCO_FREQ_MSB 0x0874
+
+#define NCO_TIME_0 0x0875
+#define NCO_TIME_1 0x0876
+#define NCO_TIME_2_CTRL 0x0877
+#define FREQ_MDIO_SEL BIT(14)
+
+#define SYNC_OUT_0 0x0878
+#define SYNC_OUT_1 0x0879
+#define SYNC_OUT_2 0x087a
+
+#define SYNC_IN_DIVIDER 0x087b
+
+#define SYNOUT_TS_0 0x087c
+#define SYNOUT_TS_1 0x087d
+#define SYNOUT_TS_2 0x087e
+
+#define NSE_CTRL 0x087f
+#define NSE_GMODE_EN GENMASK(15, 14)
+#define NSE_CAPTURE_EN BIT(13)
+#define NSE_INIT BIT(12)
+#define NSE_CPU_FRAMESYNC BIT(5)
+#define NSE_SYNC1_FRAMESYNC BIT(3)
+#define NSE_FRAMESYNC_MASK GENMASK(5, 2)
+#define NSE_PEROUT_EN BIT(1)
+#define NSE_ONESHOT_EN BIT(0)
+#define NSE_SYNC_OUT_MASK GENMASK(1, 0)
+
+#define TS_READ_CTRL 0x0885
+#define TS_READ_START BIT(0)
+#define TS_READ_END BIT(1)
+
+#define HB_REG_0 0x0886
+#define HB_REG_1 0x0887
+#define HB_REG_2 0x0888
+#define HB_REG_3 0x08ec
+#define HB_REG_4 0x08ed
+#define HB_STAT_CTRL 0x088e
+#define HB_READ_START BIT(10)
+#define HB_READ_END BIT(11)
+#define HB_READ_MASK GENMASK(11, 10)
+
+#define TS_REG_0 0x0889
+#define TS_REG_1 0x088a
+#define TS_REG_2 0x088b
+#define TS_REG_3 0x08c4
+
+#define TS_INFO_0 0x088c
+#define TS_INFO_1 0x088d
+
+#define TIMECODE_CTRL 0x08c3
+#define TX_TIMECODE_SEL GENMASK(7, 0)
+#define RX_TIMECODE_SEL GENMASK(15, 8)
+
+#define TIME_SYNC 0x0ff5
+#define TIME_SYNC_EN BIT(0)
+
+struct bcm_ptp_private {
+ struct phy_device *phydev;
+ struct mii_timestamper mii_ts;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_info;
+ struct ptp_pin_desc pin;
+ struct mutex mutex;
+ struct sk_buff_head tx_queue;
+ int tx_type;
+ bool hwts_rx;
+ u16 nse_ctrl;
+ bool pin_active;
+ struct delayed_work pin_work;
+};
+
+struct bcm_ptp_skb_cb {
+ unsigned long timeout;
+ u16 seq_id;
+ u8 msgtype;
+ bool discard;
+};
+
+struct bcm_ptp_capture {
+ ktime_t hwtstamp;
+ u16 seq_id;
+ u8 msgtype;
+ bool tx_dir;
+};
+
+#define BCM_SKB_CB(skb) ((struct bcm_ptp_skb_cb *)(skb)->cb)
+#define SKB_TS_TIMEOUT 10 /* jiffies */
+
+#define BCM_MAX_PULSE_8NS ((1U << 9) - 1)
+#define BCM_MAX_PERIOD_8NS ((1U << 30) - 1)
+
+#define BRCM_PHY_MODEL(phydev) \
+ ((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask)
+
+static struct bcm_ptp_private *mii2priv(struct mii_timestamper *mii_ts)
+{
+ return container_of(mii_ts, struct bcm_ptp_private, mii_ts);
+}
+
+static struct bcm_ptp_private *ptp2priv(struct ptp_clock_info *info)
+{
+ return container_of(info, struct bcm_ptp_private, ptp_info);
+}
+
+static void bcm_ptp_get_framesync_ts(struct phy_device *phydev,
+ struct timespec64 *ts)
+{
+ u16 hb[4];
+
+ bcm_phy_write_exp(phydev, HB_STAT_CTRL, HB_READ_START);
+
+ hb[0] = bcm_phy_read_exp(phydev, HB_REG_0);
+ hb[1] = bcm_phy_read_exp(phydev, HB_REG_1);
+ hb[2] = bcm_phy_read_exp(phydev, HB_REG_2);
+ hb[3] = bcm_phy_read_exp(phydev, HB_REG_3);
+
+ bcm_phy_write_exp(phydev, HB_STAT_CTRL, HB_READ_END);
+ bcm_phy_write_exp(phydev, HB_STAT_CTRL, 0);
+
+ ts->tv_sec = (hb[3] << 16) | hb[2];
+ ts->tv_nsec = (hb[1] << 16) | hb[0];
+}
+
+static u16 bcm_ptp_framesync_disable(struct phy_device *phydev, u16 orig_ctrl)
+{
+ u16 ctrl = orig_ctrl & ~(NSE_FRAMESYNC_MASK | NSE_CAPTURE_EN);
+
+ bcm_phy_write_exp(phydev, NSE_CTRL, ctrl);
+
+ return ctrl;
+}
+
+static void bcm_ptp_framesync_restore(struct phy_device *phydev, u16 orig_ctrl)
+{
+ if (orig_ctrl & NSE_FRAMESYNC_MASK)
+ bcm_phy_write_exp(phydev, NSE_CTRL, orig_ctrl);
+}
+
+static void bcm_ptp_framesync(struct phy_device *phydev, u16 ctrl)
+{
+ /* trigger framesync - must have 0->1 transition. */
+ bcm_phy_write_exp(phydev, NSE_CTRL, ctrl | NSE_CPU_FRAMESYNC);
+}
+
+static int bcm_ptp_framesync_ts(struct phy_device *phydev,
+ struct ptp_system_timestamp *sts,
+ struct timespec64 *ts,
+ u16 orig_ctrl)
+{
+ u16 ctrl, reg;
+ int i;
+
+ ctrl = bcm_ptp_framesync_disable(phydev, orig_ctrl);
+
+ ptp_read_system_prets(sts);
+
+ /* trigger framesync + capture */
+ bcm_ptp_framesync(phydev, ctrl | NSE_CAPTURE_EN);
+
+ ptp_read_system_postts(sts);
+
+ /* poll for FSYNC interrupt from TS capture */
+ for (i = 0; i < 10; i++) {
+ reg = bcm_phy_read_exp(phydev, INTR_STATUS);
+ if (reg & INTC_FSYNC) {
+ bcm_ptp_get_framesync_ts(phydev, ts);
+ break;
+ }
+ }
+
+ bcm_ptp_framesync_restore(phydev, orig_ctrl);
+
+ return reg & INTC_FSYNC ? 0 : -ETIMEDOUT;
+}
+
+static int bcm_ptp_gettimex(struct ptp_clock_info *info,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct bcm_ptp_private *priv = ptp2priv(info);
+ int err;
+
+ mutex_lock(&priv->mutex);
+ err = bcm_ptp_framesync_ts(priv->phydev, sts, ts, priv->nse_ctrl);
+ mutex_unlock(&priv->mutex);
+
+ return err;
+}
+
+static int bcm_ptp_settime_locked(struct bcm_ptp_private *priv,
+ const struct timespec64 *ts)
+{
+ struct phy_device *phydev = priv->phydev;
+ u16 ctrl;
+ u64 ns;
+
+ ctrl = bcm_ptp_framesync_disable(phydev, priv->nse_ctrl);
+
+ /* set up time code */
+ bcm_phy_write_exp(phydev, TIME_CODE_0, ts->tv_nsec);
+ bcm_phy_write_exp(phydev, TIME_CODE_1, ts->tv_nsec >> 16);
+ bcm_phy_write_exp(phydev, TIME_CODE_2, ts->tv_sec);
+ bcm_phy_write_exp(phydev, TIME_CODE_3, ts->tv_sec >> 16);
+ bcm_phy_write_exp(phydev, TIME_CODE_4, ts->tv_sec >> 32);
+
+ /* set NCO counter to match */
+ ns = timespec64_to_ns(ts);
+ bcm_phy_write_exp(phydev, NCO_TIME_0, ns >> 4);
+ bcm_phy_write_exp(phydev, NCO_TIME_1, ns >> 20);
+ bcm_phy_write_exp(phydev, NCO_TIME_2_CTRL, (ns >> 36) & 0xfff);
+
+ /* set up load on next frame sync (auto-clears due to NSE_INIT) */
+ bcm_phy_write_exp(phydev, SHADOW_LOAD, TIME_CODE_LOAD | NCO_TIME_LOAD);
+
+ /* must have NSE_INIT in order to write time code */
+ bcm_ptp_framesync(phydev, ctrl | NSE_INIT);
+
+ bcm_ptp_framesync_restore(phydev, priv->nse_ctrl);
+
+ return 0;
+}
+
+static int bcm_ptp_settime(struct ptp_clock_info *info,
+ const struct timespec64 *ts)
+{
+ struct bcm_ptp_private *priv = ptp2priv(info);
+ int err;
+
+ mutex_lock(&priv->mutex);
+ err = bcm_ptp_settime_locked(priv, ts);
+ mutex_unlock(&priv->mutex);
+
+ return err;
+}
+
+static int bcm_ptp_adjtime_locked(struct bcm_ptp_private *priv,
+ s64 delta_ns)
+{
+ struct timespec64 ts;
+ int err;
+ s64 ns;
+
+ err = bcm_ptp_framesync_ts(priv->phydev, NULL, &ts, priv->nse_ctrl);
+ if (!err) {
+ ns = timespec64_to_ns(&ts) + delta_ns;
+ ts = ns_to_timespec64(ns);
+ err = bcm_ptp_settime_locked(priv, &ts);
+ }
+ return err;
+}
+
+static int bcm_ptp_adjtime(struct ptp_clock_info *info, s64 delta_ns)
+{
+ struct bcm_ptp_private *priv = ptp2priv(info);
+ int err;
+
+ mutex_lock(&priv->mutex);
+ err = bcm_ptp_adjtime_locked(priv, delta_ns);
+ mutex_unlock(&priv->mutex);
+
+ return err;
+}
+
+/* A 125Mhz clock should adjust 8ns per pulse.
+ * The frequency adjustment base is 0x8000 0000, or 8*2^28.
+ *
+ * Frequency adjustment is
+ * adj = scaled_ppm * 8*2^28 / (10^6 * 2^16)
+ * which simplifies to:
+ * adj = scaled_ppm * 2^9 / 5^6
+ */
+static int bcm_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+ struct bcm_ptp_private *priv = ptp2priv(info);
+ int neg_adj = 0;
+ u32 diff, freq;
+ u16 ctrl;
+ u64 adj;
+
+ if (scaled_ppm < 0) {
+ neg_adj = 1;
+ scaled_ppm = -scaled_ppm;
+ }
+
+ adj = scaled_ppm << 9;
+ diff = div_u64(adj, 15625);
+ freq = (8 << 28) + (neg_adj ? -diff : diff);
+
+ mutex_lock(&priv->mutex);
+
+ ctrl = bcm_ptp_framesync_disable(priv->phydev, priv->nse_ctrl);
+
+ bcm_phy_write_exp(priv->phydev, NCO_FREQ_LSB, freq);
+ bcm_phy_write_exp(priv->phydev, NCO_FREQ_MSB, freq >> 16);
+
+ bcm_phy_write_exp(priv->phydev, NCO_TIME_2_CTRL, FREQ_MDIO_SEL);
+
+ /* load on next framesync */
+ bcm_phy_write_exp(priv->phydev, SHADOW_LOAD, FREQ_LOAD);
+
+ bcm_ptp_framesync(priv->phydev, ctrl);
+
+ /* clear load */
+ bcm_phy_write_exp(priv->phydev, SHADOW_LOAD, 0);
+
+ bcm_ptp_framesync_restore(priv->phydev, priv->nse_ctrl);
+
+ mutex_unlock(&priv->mutex);
+
+ return 0;
+}
+
+static bool bcm_ptp_rxtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct bcm_ptp_private *priv = mii2priv(mii_ts);
+ struct skb_shared_hwtstamps *hwts;
+ struct ptp_header *header;
+ u32 sec, nsec;
+ u8 *data;
+ int off;
+
+ if (!priv->hwts_rx)
+ return false;
+
+ header = ptp_parse_header(skb, type);
+ if (!header)
+ return false;
+
+ data = (u8 *)(header + 1);
+ sec = get_unaligned_be32(data);
+ nsec = get_unaligned_be32(data + 4);
+
+ hwts = skb_hwtstamps(skb);
+ hwts->hwtstamp = ktime_set(sec, nsec);
+
+ off = data - skb->data + 8;
+ if (off < skb->len) {
+ memmove(data, data + 8, skb->len - off);
+ __pskb_trim(skb, skb->len - 8);
+ }
+
+ return false;
+}
+
+static bool bcm_ptp_get_tstamp(struct bcm_ptp_private *priv,
+ struct bcm_ptp_capture *capts)
+{
+ struct phy_device *phydev = priv->phydev;
+ u16 ts[4], reg;
+ u32 sec, nsec;
+
+ mutex_lock(&priv->mutex);
+
+ reg = bcm_phy_read_exp(phydev, INTR_STATUS);
+ if ((reg & INTC_SOP) == 0) {
+ mutex_unlock(&priv->mutex);
+ return false;
+ }
+
+ bcm_phy_write_exp(phydev, TS_READ_CTRL, TS_READ_START);
+
+ ts[0] = bcm_phy_read_exp(phydev, TS_REG_0);
+ ts[1] = bcm_phy_read_exp(phydev, TS_REG_1);
+ ts[2] = bcm_phy_read_exp(phydev, TS_REG_2);
+ ts[3] = bcm_phy_read_exp(phydev, TS_REG_3);
+
+ /* not in be32 format for some reason */
+ capts->seq_id = bcm_phy_read_exp(priv->phydev, TS_INFO_0);
+
+ reg = bcm_phy_read_exp(phydev, TS_INFO_1);
+ capts->msgtype = reg >> 12;
+ capts->tx_dir = !!(reg & BIT(11));
+
+ bcm_phy_write_exp(phydev, TS_READ_CTRL, TS_READ_END);
+ bcm_phy_write_exp(phydev, TS_READ_CTRL, 0);
+
+ mutex_unlock(&priv->mutex);
+
+ sec = (ts[3] << 16) | ts[2];
+ nsec = (ts[1] << 16) | ts[0];
+ capts->hwtstamp = ktime_set(sec, nsec);
+
+ return true;
+}
+
+static void bcm_ptp_match_tstamp(struct bcm_ptp_private *priv,
+ struct bcm_ptp_capture *capts)
+{
+ struct skb_shared_hwtstamps hwts;
+ struct sk_buff *skb, *ts_skb;
+ unsigned long flags;
+ bool first = false;
+
+ ts_skb = NULL;
+ spin_lock_irqsave(&priv->tx_queue.lock, flags);
+ skb_queue_walk(&priv->tx_queue, skb) {
+ if (BCM_SKB_CB(skb)->seq_id == capts->seq_id &&
+ BCM_SKB_CB(skb)->msgtype == capts->msgtype) {
+ first = skb_queue_is_first(&priv->tx_queue, skb);
+ __skb_unlink(skb, &priv->tx_queue);
+ ts_skb = skb;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+
+ /* TX captures one-step packets, discard them if needed. */
+ if (ts_skb) {
+ if (BCM_SKB_CB(ts_skb)->discard) {
+ kfree_skb(ts_skb);
+ } else {
+ memset(&hwts, 0, sizeof(hwts));
+ hwts.hwtstamp = capts->hwtstamp;
+ skb_complete_tx_timestamp(ts_skb, &hwts);
+ }
+ }
+
+ /* not first match, try and expire entries */
+ if (!first) {
+ while ((skb = skb_dequeue(&priv->tx_queue))) {
+ if (!time_after(jiffies, BCM_SKB_CB(skb)->timeout)) {
+ skb_queue_head(&priv->tx_queue, skb);
+ break;
+ }
+ kfree_skb(skb);
+ }
+ }
+}
+
+static long bcm_ptp_do_aux_work(struct ptp_clock_info *info)
+{
+ struct bcm_ptp_private *priv = ptp2priv(info);
+ struct bcm_ptp_capture capts;
+ bool reschedule = false;
+
+ while (!skb_queue_empty_lockless(&priv->tx_queue)) {
+ if (!bcm_ptp_get_tstamp(priv, &capts)) {
+ reschedule = true;
+ break;
+ }
+ bcm_ptp_match_tstamp(priv, &capts);
+ }
+
+ return reschedule ? 1 : -1;
+}
+
+static int bcm_ptp_cancel_func(struct bcm_ptp_private *priv)
+{
+ if (!priv->pin_active)
+ return 0;
+
+ priv->pin_active = false;
+
+ priv->nse_ctrl &= ~(NSE_SYNC_OUT_MASK | NSE_SYNC1_FRAMESYNC |
+ NSE_CAPTURE_EN);
+ bcm_phy_write_exp(priv->phydev, NSE_CTRL, priv->nse_ctrl);
+
+ cancel_delayed_work_sync(&priv->pin_work);
+
+ return 0;
+}
+
+static void bcm_ptp_perout_work(struct work_struct *pin_work)
+{
+ struct bcm_ptp_private *priv =
+ container_of(pin_work, struct bcm_ptp_private, pin_work.work);
+ struct phy_device *phydev = priv->phydev;
+ struct timespec64 ts;
+ u64 ns, next;
+ u16 ctrl;
+
+ mutex_lock(&priv->mutex);
+
+ /* no longer running */
+ if (!priv->pin_active) {
+ mutex_unlock(&priv->mutex);
+ return;
+ }
+
+ bcm_ptp_framesync_ts(phydev, NULL, &ts, priv->nse_ctrl);
+
+ /* this is 1PPS only */
+ next = NSEC_PER_SEC - ts.tv_nsec;
+ ts.tv_sec += next < NSEC_PER_MSEC ? 2 : 1;
+ ts.tv_nsec = 0;
+
+ ns = timespec64_to_ns(&ts);
+
+ /* force 0->1 transition for ONESHOT */
+ ctrl = bcm_ptp_framesync_disable(phydev,
+ priv->nse_ctrl & ~NSE_ONESHOT_EN);
+
+ bcm_phy_write_exp(phydev, SYNOUT_TS_0, ns & 0xfff0);
+ bcm_phy_write_exp(phydev, SYNOUT_TS_1, ns >> 16);
+ bcm_phy_write_exp(phydev, SYNOUT_TS_2, ns >> 32);
+
+ /* load values on next framesync */
+ bcm_phy_write_exp(phydev, SHADOW_LOAD, SYNC_OUT_LOAD);
+
+ bcm_ptp_framesync(phydev, ctrl | NSE_ONESHOT_EN | NSE_INIT);
+
+ priv->nse_ctrl |= NSE_ONESHOT_EN;
+ bcm_ptp_framesync_restore(phydev, priv->nse_ctrl);
+
+ mutex_unlock(&priv->mutex);
+
+ next = next + NSEC_PER_MSEC;
+ schedule_delayed_work(&priv->pin_work, nsecs_to_jiffies(next));
+}
+
+static int bcm_ptp_perout_locked(struct bcm_ptp_private *priv,
+ struct ptp_perout_request *req, int on)
+{
+ struct phy_device *phydev = priv->phydev;
+ u64 period, pulse;
+ u16 val;
+
+ if (!on)
+ return bcm_ptp_cancel_func(priv);
+
+ /* 1PPS */
+ if (req->period.sec != 1 || req->period.nsec != 0)
+ return -EINVAL;
+
+ period = BCM_MAX_PERIOD_8NS; /* write nonzero value */
+
+ if (req->flags & PTP_PEROUT_PHASE)
+ return -EOPNOTSUPP;
+
+ if (req->flags & PTP_PEROUT_DUTY_CYCLE)
+ pulse = ktime_to_ns(ktime_set(req->on.sec, req->on.nsec));
+ else
+ pulse = (u64)BCM_MAX_PULSE_8NS << 3;
+
+ /* convert to 8ns units */
+ pulse >>= 3;
+
+ if (!pulse || pulse > period || pulse > BCM_MAX_PULSE_8NS)
+ return -EINVAL;
+
+ bcm_phy_write_exp(phydev, SYNC_OUT_0, period);
+
+ val = ((pulse & 0x3) << 14) | ((period >> 16) & 0x3fff);
+ bcm_phy_write_exp(phydev, SYNC_OUT_1, val);
+
+ val = ((pulse >> 2) & 0x7f) | (pulse << 7);
+ bcm_phy_write_exp(phydev, SYNC_OUT_2, val);
+
+ if (priv->pin_active)
+ cancel_delayed_work_sync(&priv->pin_work);
+
+ priv->pin_active = true;
+ INIT_DELAYED_WORK(&priv->pin_work, bcm_ptp_perout_work);
+ schedule_delayed_work(&priv->pin_work, 0);
+
+ return 0;
+}
+
+static void bcm_ptp_extts_work(struct work_struct *pin_work)
+{
+ struct bcm_ptp_private *priv =
+ container_of(pin_work, struct bcm_ptp_private, pin_work.work);
+ struct phy_device *phydev = priv->phydev;
+ struct ptp_clock_event event;
+ struct timespec64 ts;
+ u16 reg;
+
+ mutex_lock(&priv->mutex);
+
+ /* no longer running */
+ if (!priv->pin_active) {
+ mutex_unlock(&priv->mutex);
+ return;
+ }
+
+ reg = bcm_phy_read_exp(phydev, INTR_STATUS);
+ if ((reg & INTC_FSYNC) == 0)
+ goto out;
+
+ bcm_ptp_get_framesync_ts(phydev, &ts);
+
+ event.index = 0;
+ event.type = PTP_CLOCK_EXTTS;
+ event.timestamp = timespec64_to_ns(&ts);
+ ptp_clock_event(priv->ptp_clock, &event);
+
+out:
+ mutex_unlock(&priv->mutex);
+ schedule_delayed_work(&priv->pin_work, HZ / 4);
+}
+
+static int bcm_ptp_extts_locked(struct bcm_ptp_private *priv, int on)
+{
+ struct phy_device *phydev = priv->phydev;
+
+ if (!on)
+ return bcm_ptp_cancel_func(priv);
+
+ if (priv->pin_active)
+ cancel_delayed_work_sync(&priv->pin_work);
+
+ bcm_ptp_framesync_disable(phydev, priv->nse_ctrl);
+
+ priv->nse_ctrl |= NSE_SYNC1_FRAMESYNC | NSE_CAPTURE_EN;
+
+ bcm_ptp_framesync_restore(phydev, priv->nse_ctrl);
+
+ priv->pin_active = true;
+ INIT_DELAYED_WORK(&priv->pin_work, bcm_ptp_extts_work);
+ schedule_delayed_work(&priv->pin_work, 0);
+
+ return 0;
+}
+
+static int bcm_ptp_enable(struct ptp_clock_info *info,
+ struct ptp_clock_request *rq, int on)
+{
+ struct bcm_ptp_private *priv = ptp2priv(info);
+ int err = -EBUSY;
+
+ mutex_lock(&priv->mutex);
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_PEROUT:
+ if (priv->pin.func == PTP_PF_PEROUT)
+ err = bcm_ptp_perout_locked(priv, &rq->perout, on);
+ break;
+ case PTP_CLK_REQ_EXTTS:
+ if (priv->pin.func == PTP_PF_EXTTS)
+ err = bcm_ptp_extts_locked(priv, on);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ mutex_unlock(&priv->mutex);
+
+ return err;
+}
+
+static int bcm_ptp_verify(struct ptp_clock_info *info, unsigned int pin,
+ enum ptp_pin_function func, unsigned int chan)
+{
+ switch (func) {
+ case PTP_PF_NONE:
+ case PTP_PF_EXTTS:
+ case PTP_PF_PEROUT:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static const struct ptp_clock_info bcm_ptp_clock_info = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .max_adj = 100000000,
+ .gettimex64 = bcm_ptp_gettimex,
+ .settime64 = bcm_ptp_settime,
+ .adjtime = bcm_ptp_adjtime,
+ .adjfine = bcm_ptp_adjfine,
+ .enable = bcm_ptp_enable,
+ .verify = bcm_ptp_verify,
+ .do_aux_work = bcm_ptp_do_aux_work,
+ .n_pins = 1,
+ .n_per_out = 1,
+ .n_ext_ts = 1,
+};
+
+static void bcm_ptp_txtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct bcm_ptp_private *priv = mii2priv(mii_ts);
+ struct ptp_header *hdr;
+ bool discard = false;
+ int msgtype;
+
+ hdr = ptp_parse_header(skb, type);
+ if (!hdr)
+ goto out;
+ msgtype = ptp_get_msgtype(hdr, type);
+
+ switch (priv->tx_type) {
+ case HWTSTAMP_TX_ONESTEP_P2P:
+ if (msgtype == PTP_MSGTYPE_PDELAY_RESP)
+ discard = true;
+ fallthrough;
+ case HWTSTAMP_TX_ONESTEP_SYNC:
+ if (msgtype == PTP_MSGTYPE_SYNC)
+ discard = true;
+ fallthrough;
+ case HWTSTAMP_TX_ON:
+ BCM_SKB_CB(skb)->timeout = jiffies + SKB_TS_TIMEOUT;
+ BCM_SKB_CB(skb)->seq_id = be16_to_cpu(hdr->sequence_id);
+ BCM_SKB_CB(skb)->msgtype = msgtype;
+ BCM_SKB_CB(skb)->discard = discard;
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ skb_queue_tail(&priv->tx_queue, skb);
+ ptp_schedule_worker(priv->ptp_clock, 0);
+ return;
+ default:
+ break;
+ }
+
+out:
+ kfree_skb(skb);
+}
+
+static int bcm_ptp_hwtstamp(struct mii_timestamper *mii_ts,
+ struct ifreq *ifr)
+{
+ struct bcm_ptp_private *priv = mii2priv(mii_ts);
+ struct hwtstamp_config cfg;
+ u16 mode, ctrl;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ priv->hwts_rx = false;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+ priv->hwts_rx = true;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ priv->tx_type = cfg.tx_type;
+
+ ctrl = priv->hwts_rx ? SLICE_RX_EN : 0;
+ ctrl |= priv->tx_type != HWTSTAMP_TX_OFF ? SLICE_TX_EN : 0;
+
+ mode = TX_MODE_SEL(PORT, SYNC, REPLACE_TS) |
+ TX_MODE_SEL(PORT, DELAY_REQ, REPLACE_TS) |
+ TX_MODE_SEL(PORT, PDELAY_REQ, REPLACE_TS) |
+ TX_MODE_SEL(PORT, PDELAY_RESP, REPLACE_TS);
+
+ bcm_phy_write_exp(priv->phydev, TX_EVENT_MODE, mode);
+
+ mode = RX_MODE_SEL(PORT, SYNC, INSERT_TS_64) |
+ RX_MODE_SEL(PORT, DELAY_REQ, INSERT_TS_64) |
+ RX_MODE_SEL(PORT, PDELAY_REQ, INSERT_TS_64) |
+ RX_MODE_SEL(PORT, PDELAY_RESP, INSERT_TS_64);
+
+ bcm_phy_write_exp(priv->phydev, RX_EVENT_MODE, mode);
+
+ bcm_phy_write_exp(priv->phydev, SLICE_CTRL, ctrl);
+
+ if (ctrl & SLICE_TX_EN)
+ bcm_phy_write_exp(priv->phydev, TX_TS_CAPTURE, TX_TS_CAP_EN);
+ else
+ ptp_cancel_worker_sync(priv->ptp_clock);
+
+ /* purge existing data */
+ skb_queue_purge(&priv->tx_queue);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static int bcm_ptp_ts_info(struct mii_timestamper *mii_ts,
+ struct ethtool_ts_info *ts_info)
+{
+ struct bcm_ptp_private *priv = mii2priv(mii_ts);
+
+ ts_info->phc_index = ptp_clock_index(priv->ptp_clock);
+ ts_info->so_timestamping =
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+ ts_info->tx_types =
+ BIT(HWTSTAMP_TX_ON) |
+ BIT(HWTSTAMP_TX_OFF) |
+ BIT(HWTSTAMP_TX_ONESTEP_SYNC) |
+ BIT(HWTSTAMP_TX_ONESTEP_P2P);
+ ts_info->rx_filters =
+ BIT(HWTSTAMP_FILTER_NONE) |
+ BIT(HWTSTAMP_FILTER_PTP_V2_EVENT);
+
+ return 0;
+}
+
+void bcm_ptp_stop(struct bcm_ptp_private *priv)
+{
+ ptp_cancel_worker_sync(priv->ptp_clock);
+ bcm_ptp_cancel_func(priv);
+}
+EXPORT_SYMBOL_GPL(bcm_ptp_stop);
+
+void bcm_ptp_config_init(struct phy_device *phydev)
+{
+ /* init network sync engine */
+ bcm_phy_write_exp(phydev, NSE_CTRL, NSE_GMODE_EN | NSE_INIT);
+
+ /* enable time sync (TX/RX SOP capture) */
+ bcm_phy_write_exp(phydev, TIME_SYNC, TIME_SYNC_EN);
+
+ /* use sec.nsec heartbeat capture */
+ bcm_phy_write_exp(phydev, DPLL_SELECT, DPLL_HB_MODE2);
+
+ /* use 64 bit timecode for TX */
+ bcm_phy_write_exp(phydev, TIMECODE_CTRL, TX_TIMECODE_SEL);
+
+ /* always allow FREQ_LOAD on framesync */
+ bcm_phy_write_exp(phydev, SHADOW_CTRL, FREQ_LOAD);
+
+ bcm_phy_write_exp(phydev, SYNC_IN_DIVIDER, 1);
+}
+EXPORT_SYMBOL_GPL(bcm_ptp_config_init);
+
+static void bcm_ptp_init(struct bcm_ptp_private *priv)
+{
+ priv->nse_ctrl = NSE_GMODE_EN;
+
+ mutex_init(&priv->mutex);
+ skb_queue_head_init(&priv->tx_queue);
+
+ priv->mii_ts.rxtstamp = bcm_ptp_rxtstamp;
+ priv->mii_ts.txtstamp = bcm_ptp_txtstamp;
+ priv->mii_ts.hwtstamp = bcm_ptp_hwtstamp;
+ priv->mii_ts.ts_info = bcm_ptp_ts_info;
+
+ priv->phydev->mii_ts = &priv->mii_ts;
+}
+
+struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev)
+{
+ struct bcm_ptp_private *priv;
+ struct ptp_clock *clock;
+
+ switch (BRCM_PHY_MODEL(phydev)) {
+ case PHY_ID_BCM54210E:
+ break;
+ default:
+ return NULL;
+ }
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ priv->ptp_info = bcm_ptp_clock_info;
+
+ snprintf(priv->pin.name, sizeof(priv->pin.name), "SYNC_OUT");
+ priv->ptp_info.pin_config = &priv->pin;
+
+ clock = ptp_clock_register(&priv->ptp_info, &phydev->mdio.dev);
+ if (IS_ERR(clock))
+ return ERR_CAST(clock);
+ priv->ptp_clock = clock;
+
+ priv->phydev = phydev;
+ bcm_ptp_init(priv);
+
+ return priv;
+}
+EXPORT_SYMBOL_GPL(bcm_ptp_probe);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/bcm54140.c b/drivers/net/phy/bcm54140.c
new file mode 100644
index 000000000..d43076592
--- /dev/null
+++ b/drivers/net/phy/bcm54140.c
@@ -0,0 +1,886 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Broadcom BCM54140 Quad SGMII/QSGMII Copper/Fiber Gigabit PHY
+ *
+ * Copyright (c) 2020 Michael Walle <michael@walle.cc>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/brcmphy.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#include "bcm-phy-lib.h"
+
+/* RDB per-port registers
+ */
+#define BCM54140_RDB_ISR 0x00a /* interrupt status */
+#define BCM54140_RDB_IMR 0x00b /* interrupt mask */
+#define BCM54140_RDB_INT_LINK BIT(1) /* link status changed */
+#define BCM54140_RDB_INT_SPEED BIT(2) /* link speed change */
+#define BCM54140_RDB_INT_DUPLEX BIT(3) /* duplex mode changed */
+#define BCM54140_RDB_SPARE1 0x012 /* spare control 1 */
+#define BCM54140_RDB_SPARE1_LSLM BIT(2) /* link speed LED mode */
+#define BCM54140_RDB_SPARE2 0x014 /* spare control 2 */
+#define BCM54140_RDB_SPARE2_WS_RTRY_DIS BIT(8) /* wirespeed retry disable */
+#define BCM54140_RDB_SPARE2_WS_RTRY_LIMIT GENMASK(4, 2) /* retry limit */
+#define BCM54140_RDB_SPARE3 0x015 /* spare control 3 */
+#define BCM54140_RDB_SPARE3_BIT0 BIT(0)
+#define BCM54140_RDB_LED_CTRL 0x019 /* LED control */
+#define BCM54140_RDB_LED_CTRL_ACTLINK0 BIT(4)
+#define BCM54140_RDB_LED_CTRL_ACTLINK1 BIT(8)
+#define BCM54140_RDB_C_APWR 0x01a /* auto power down control */
+#define BCM54140_RDB_C_APWR_SINGLE_PULSE BIT(8) /* single pulse */
+#define BCM54140_RDB_C_APWR_APD_MODE_DIS 0 /* ADP disable */
+#define BCM54140_RDB_C_APWR_APD_MODE_EN 1 /* ADP enable */
+#define BCM54140_RDB_C_APWR_APD_MODE_DIS2 2 /* ADP disable */
+#define BCM54140_RDB_C_APWR_APD_MODE_EN_ANEG 3 /* ADP enable w/ aneg */
+#define BCM54140_RDB_C_APWR_APD_MODE_MASK GENMASK(6, 5)
+#define BCM54140_RDB_C_APWR_SLP_TIM_MASK BIT(4)/* sleep timer */
+#define BCM54140_RDB_C_APWR_SLP_TIM_2_7 0 /* 2.7s */
+#define BCM54140_RDB_C_APWR_SLP_TIM_5_4 1 /* 5.4s */
+#define BCM54140_RDB_C_PWR 0x02a /* copper power control */
+#define BCM54140_RDB_C_PWR_ISOLATE BIT(5) /* super isolate mode */
+#define BCM54140_RDB_C_MISC_CTRL 0x02f /* misc copper control */
+#define BCM54140_RDB_C_MISC_CTRL_WS_EN BIT(4) /* wirespeed enable */
+
+/* RDB global registers
+ */
+#define BCM54140_RDB_TOP_IMR 0x82d /* interrupt mask */
+#define BCM54140_RDB_TOP_IMR_PORT0 BIT(4)
+#define BCM54140_RDB_TOP_IMR_PORT1 BIT(5)
+#define BCM54140_RDB_TOP_IMR_PORT2 BIT(6)
+#define BCM54140_RDB_TOP_IMR_PORT3 BIT(7)
+#define BCM54140_RDB_MON_CTRL 0x831 /* monitor control */
+#define BCM54140_RDB_MON_CTRL_V_MODE BIT(3) /* voltage mode */
+#define BCM54140_RDB_MON_CTRL_SEL_MASK GENMASK(2, 1)
+#define BCM54140_RDB_MON_CTRL_SEL_TEMP 0 /* meassure temperature */
+#define BCM54140_RDB_MON_CTRL_SEL_1V0 1 /* meassure AVDDL 1.0V */
+#define BCM54140_RDB_MON_CTRL_SEL_3V3 2 /* meassure AVDDH 3.3V */
+#define BCM54140_RDB_MON_CTRL_SEL_RR 3 /* meassure all round-robin */
+#define BCM54140_RDB_MON_CTRL_PWR_DOWN BIT(0) /* power-down monitor */
+#define BCM54140_RDB_MON_TEMP_VAL 0x832 /* temperature value */
+#define BCM54140_RDB_MON_TEMP_MAX 0x833 /* temperature high thresh */
+#define BCM54140_RDB_MON_TEMP_MIN 0x834 /* temperature low thresh */
+#define BCM54140_RDB_MON_TEMP_DATA_MASK GENMASK(9, 0)
+#define BCM54140_RDB_MON_1V0_VAL 0x835 /* AVDDL 1.0V value */
+#define BCM54140_RDB_MON_1V0_MAX 0x836 /* AVDDL 1.0V high thresh */
+#define BCM54140_RDB_MON_1V0_MIN 0x837 /* AVDDL 1.0V low thresh */
+#define BCM54140_RDB_MON_1V0_DATA_MASK GENMASK(10, 0)
+#define BCM54140_RDB_MON_3V3_VAL 0x838 /* AVDDH 3.3V value */
+#define BCM54140_RDB_MON_3V3_MAX 0x839 /* AVDDH 3.3V high thresh */
+#define BCM54140_RDB_MON_3V3_MIN 0x83a /* AVDDH 3.3V low thresh */
+#define BCM54140_RDB_MON_3V3_DATA_MASK GENMASK(11, 0)
+#define BCM54140_RDB_MON_ISR 0x83b /* interrupt status */
+#define BCM54140_RDB_MON_ISR_3V3 BIT(2) /* AVDDH 3.3V alarm */
+#define BCM54140_RDB_MON_ISR_1V0 BIT(1) /* AVDDL 1.0V alarm */
+#define BCM54140_RDB_MON_ISR_TEMP BIT(0) /* temperature alarm */
+
+/* According to the datasheet the formula is:
+ * T = 413.35 - (0.49055 * bits[9:0])
+ */
+#define BCM54140_HWMON_TO_TEMP(v) (413350L - (v) * 491)
+#define BCM54140_HWMON_FROM_TEMP(v) DIV_ROUND_CLOSEST_ULL(413350L - (v), 491)
+
+/* According to the datasheet the formula is:
+ * U = bits[11:0] / 1024 * 220 / 0.2
+ *
+ * Normalized:
+ * U = bits[11:0] / 4096 * 2514
+ */
+#define BCM54140_HWMON_TO_IN_1V0(v) ((v) * 2514 >> 11)
+#define BCM54140_HWMON_FROM_IN_1V0(v) DIV_ROUND_CLOSEST_ULL(((v) << 11), 2514)
+
+/* According to the datasheet the formula is:
+ * U = bits[10:0] / 1024 * 880 / 0.7
+ *
+ * Normalized:
+ * U = bits[10:0] / 2048 * 4400
+ */
+#define BCM54140_HWMON_TO_IN_3V3(v) ((v) * 4400 >> 12)
+#define BCM54140_HWMON_FROM_IN_3V3(v) DIV_ROUND_CLOSEST_ULL(((v) << 12), 4400)
+
+#define BCM54140_HWMON_TO_IN(ch, v) ((ch) ? BCM54140_HWMON_TO_IN_3V3(v) \
+ : BCM54140_HWMON_TO_IN_1V0(v))
+#define BCM54140_HWMON_FROM_IN(ch, v) ((ch) ? BCM54140_HWMON_FROM_IN_3V3(v) \
+ : BCM54140_HWMON_FROM_IN_1V0(v))
+#define BCM54140_HWMON_IN_MASK(ch) ((ch) ? BCM54140_RDB_MON_3V3_DATA_MASK \
+ : BCM54140_RDB_MON_1V0_DATA_MASK)
+#define BCM54140_HWMON_IN_VAL_REG(ch) ((ch) ? BCM54140_RDB_MON_3V3_VAL \
+ : BCM54140_RDB_MON_1V0_VAL)
+#define BCM54140_HWMON_IN_MIN_REG(ch) ((ch) ? BCM54140_RDB_MON_3V3_MIN \
+ : BCM54140_RDB_MON_1V0_MIN)
+#define BCM54140_HWMON_IN_MAX_REG(ch) ((ch) ? BCM54140_RDB_MON_3V3_MAX \
+ : BCM54140_RDB_MON_1V0_MAX)
+#define BCM54140_HWMON_IN_ALARM_BIT(ch) ((ch) ? BCM54140_RDB_MON_ISR_3V3 \
+ : BCM54140_RDB_MON_ISR_1V0)
+
+/* This PHY has two different PHY IDs depening on its MODE_SEL pin. This
+ * pin choses between 4x SGMII and QSGMII mode:
+ * AE02_5009 4x SGMII
+ * AE02_5019 QSGMII
+ */
+#define BCM54140_PHY_ID_MASK 0xffffffe8
+
+#define BCM54140_PHY_ID_REV(phy_id) ((phy_id) & 0x7)
+#define BCM54140_REV_B0 1
+
+#define BCM54140_DEFAULT_DOWNSHIFT 5
+#define BCM54140_MAX_DOWNSHIFT 9
+
+struct bcm54140_priv {
+ int port;
+ int base_addr;
+#if IS_ENABLED(CONFIG_HWMON)
+ /* protect the alarm bits */
+ struct mutex alarm_lock;
+ u16 alarm;
+#endif
+};
+
+#if IS_ENABLED(CONFIG_HWMON)
+static umode_t bcm54140_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_min:
+ case hwmon_in_max:
+ return 0644;
+ case hwmon_in_label:
+ case hwmon_in_input:
+ case hwmon_in_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_alarm:
+ return 0444;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static int bcm54140_hwmon_read_alarm(struct device *dev, unsigned int bit,
+ long *val)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ struct bcm54140_priv *priv = phydev->priv;
+ int tmp, ret = 0;
+
+ mutex_lock(&priv->alarm_lock);
+
+ /* latch any alarm bits */
+ tmp = bcm_phy_read_rdb(phydev, BCM54140_RDB_MON_ISR);
+ if (tmp < 0) {
+ ret = tmp;
+ goto out;
+ }
+ priv->alarm |= tmp;
+
+ *val = !!(priv->alarm & bit);
+ priv->alarm &= ~bit;
+
+out:
+ mutex_unlock(&priv->alarm_lock);
+ return ret;
+}
+
+static int bcm54140_hwmon_read_temp(struct device *dev, u32 attr, long *val)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ u16 reg;
+ int tmp;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = BCM54140_RDB_MON_TEMP_VAL;
+ break;
+ case hwmon_temp_min:
+ reg = BCM54140_RDB_MON_TEMP_MIN;
+ break;
+ case hwmon_temp_max:
+ reg = BCM54140_RDB_MON_TEMP_MAX;
+ break;
+ case hwmon_temp_alarm:
+ return bcm54140_hwmon_read_alarm(dev,
+ BCM54140_RDB_MON_ISR_TEMP,
+ val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ tmp = bcm_phy_read_rdb(phydev, reg);
+ if (tmp < 0)
+ return tmp;
+
+ *val = BCM54140_HWMON_TO_TEMP(tmp & BCM54140_RDB_MON_TEMP_DATA_MASK);
+
+ return 0;
+}
+
+static int bcm54140_hwmon_read_in(struct device *dev, u32 attr,
+ int channel, long *val)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ u16 bit, reg;
+ int tmp;
+
+ switch (attr) {
+ case hwmon_in_input:
+ reg = BCM54140_HWMON_IN_VAL_REG(channel);
+ break;
+ case hwmon_in_min:
+ reg = BCM54140_HWMON_IN_MIN_REG(channel);
+ break;
+ case hwmon_in_max:
+ reg = BCM54140_HWMON_IN_MAX_REG(channel);
+ break;
+ case hwmon_in_alarm:
+ bit = BCM54140_HWMON_IN_ALARM_BIT(channel);
+ return bcm54140_hwmon_read_alarm(dev, bit, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ tmp = bcm_phy_read_rdb(phydev, reg);
+ if (tmp < 0)
+ return tmp;
+
+ tmp &= BCM54140_HWMON_IN_MASK(channel);
+ *val = BCM54140_HWMON_TO_IN(channel, tmp);
+
+ return 0;
+}
+
+static int bcm54140_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long *val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return bcm54140_hwmon_read_temp(dev, attr, val);
+ case hwmon_in:
+ return bcm54140_hwmon_read_in(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const char *const bcm54140_hwmon_in_labels[] = {
+ "AVDDL",
+ "AVDDH",
+};
+
+static int bcm54140_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_label:
+ *str = bcm54140_hwmon_in_labels[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int bcm54140_hwmon_write_temp(struct device *dev, u32 attr,
+ int channel, long val)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ u16 mask = BCM54140_RDB_MON_TEMP_DATA_MASK;
+ u16 reg;
+
+ val = clamp_val(val, BCM54140_HWMON_TO_TEMP(mask),
+ BCM54140_HWMON_TO_TEMP(0));
+
+ switch (attr) {
+ case hwmon_temp_min:
+ reg = BCM54140_RDB_MON_TEMP_MIN;
+ break;
+ case hwmon_temp_max:
+ reg = BCM54140_RDB_MON_TEMP_MAX;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return bcm_phy_modify_rdb(phydev, reg, mask,
+ BCM54140_HWMON_FROM_TEMP(val));
+}
+
+static int bcm54140_hwmon_write_in(struct device *dev, u32 attr,
+ int channel, long val)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ u16 mask = BCM54140_HWMON_IN_MASK(channel);
+ u16 reg;
+
+ val = clamp_val(val, 0, BCM54140_HWMON_TO_IN(channel, mask));
+
+ switch (attr) {
+ case hwmon_in_min:
+ reg = BCM54140_HWMON_IN_MIN_REG(channel);
+ break;
+ case hwmon_in_max:
+ reg = BCM54140_HWMON_IN_MAX_REG(channel);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return bcm_phy_modify_rdb(phydev, reg, mask,
+ BCM54140_HWMON_FROM_IN(channel, val));
+}
+
+static int bcm54140_hwmon_write(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ switch (type) {
+ case hwmon_temp:
+ return bcm54140_hwmon_write_temp(dev, attr, channel, val);
+ case hwmon_in:
+ return bcm54140_hwmon_write_in(dev, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_channel_info * const bcm54140_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
+ HWMON_T_ALARM),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
+ HWMON_I_ALARM | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
+ HWMON_I_ALARM | HWMON_I_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops bcm54140_hwmon_ops = {
+ .is_visible = bcm54140_hwmon_is_visible,
+ .read = bcm54140_hwmon_read,
+ .read_string = bcm54140_hwmon_read_string,
+ .write = bcm54140_hwmon_write,
+};
+
+static const struct hwmon_chip_info bcm54140_chip_info = {
+ .ops = &bcm54140_hwmon_ops,
+ .info = bcm54140_hwmon_info,
+};
+
+static int bcm54140_enable_monitoring(struct phy_device *phydev)
+{
+ u16 mask, set;
+
+ /* 3.3V voltage mode */
+ set = BCM54140_RDB_MON_CTRL_V_MODE;
+
+ /* select round-robin */
+ mask = BCM54140_RDB_MON_CTRL_SEL_MASK;
+ set |= FIELD_PREP(BCM54140_RDB_MON_CTRL_SEL_MASK,
+ BCM54140_RDB_MON_CTRL_SEL_RR);
+
+ /* remove power-down bit */
+ mask |= BCM54140_RDB_MON_CTRL_PWR_DOWN;
+
+ return bcm_phy_modify_rdb(phydev, BCM54140_RDB_MON_CTRL, mask, set);
+}
+
+static int bcm54140_probe_once(struct phy_device *phydev)
+{
+ struct device *hwmon;
+ int ret;
+
+ /* enable hardware monitoring */
+ ret = bcm54140_enable_monitoring(phydev);
+ if (ret)
+ return ret;
+
+ hwmon = devm_hwmon_device_register_with_info(&phydev->mdio.dev,
+ "BCM54140", phydev,
+ &bcm54140_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+#endif
+
+static int bcm54140_base_read_rdb(struct phy_device *phydev, u16 rdb)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_package_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
+ if (ret < 0)
+ goto out;
+
+ ret = __phy_package_read(phydev, MII_BCM54XX_RDB_DATA);
+
+out:
+ phy_unlock_mdio_bus(phydev);
+ return ret;
+}
+
+static int bcm54140_base_write_rdb(struct phy_device *phydev,
+ u16 rdb, u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_package_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
+ if (ret < 0)
+ goto out;
+
+ ret = __phy_package_write(phydev, MII_BCM54XX_RDB_DATA, val);
+
+out:
+ phy_unlock_mdio_bus(phydev);
+ return ret;
+}
+
+/* Under some circumstances a core PLL may not lock, this will then prevent
+ * a successful link establishment. Restart the PLL after the voltages are
+ * stable to workaround this issue.
+ */
+static int bcm54140_b0_workaround(struct phy_device *phydev)
+{
+ int spare3;
+ int ret;
+
+ spare3 = bcm_phy_read_rdb(phydev, BCM54140_RDB_SPARE3);
+ if (spare3 < 0)
+ return spare3;
+
+ spare3 &= ~BCM54140_RDB_SPARE3_BIT0;
+
+ ret = bcm_phy_write_rdb(phydev, BCM54140_RDB_SPARE3, spare3);
+ if (ret)
+ return ret;
+
+ ret = phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN);
+ if (ret)
+ return ret;
+
+ ret = phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0);
+ if (ret)
+ return ret;
+
+ spare3 |= BCM54140_RDB_SPARE3_BIT0;
+
+ return bcm_phy_write_rdb(phydev, BCM54140_RDB_SPARE3, spare3);
+}
+
+/* The BCM54140 is a quad PHY where only the first port has access to the
+ * global register. Thus we need to find out its PHY address.
+ *
+ */
+static int bcm54140_get_base_addr_and_port(struct phy_device *phydev)
+{
+ struct bcm54140_priv *priv = phydev->priv;
+ struct mii_bus *bus = phydev->mdio.bus;
+ int addr, min_addr, max_addr;
+ int step = 1;
+ u32 phy_id;
+ int tmp;
+
+ min_addr = phydev->mdio.addr;
+ max_addr = phydev->mdio.addr;
+ addr = phydev->mdio.addr;
+
+ /* We scan forward and backwards and look for PHYs which have the
+ * same phy_id like we do. Step 1 will scan forward, step 2
+ * backwards. Once we are finished, we have a min_addr and
+ * max_addr which resembles the range of PHY addresses of the same
+ * type of PHY. There is one caveat; there may be many PHYs of
+ * the same type, but we know that each PHY takes exactly 4
+ * consecutive addresses. Therefore we can deduce our offset
+ * to the base address of this quad PHY.
+ */
+
+ while (1) {
+ if (step == 3) {
+ break;
+ } else if (step == 1) {
+ max_addr = addr;
+ addr++;
+ } else {
+ min_addr = addr;
+ addr--;
+ }
+
+ if (addr < 0 || addr >= PHY_MAX_ADDR) {
+ addr = phydev->mdio.addr;
+ step++;
+ continue;
+ }
+
+ /* read the PHY id */
+ tmp = mdiobus_read(bus, addr, MII_PHYSID1);
+ if (tmp < 0)
+ return tmp;
+ phy_id = tmp << 16;
+ tmp = mdiobus_read(bus, addr, MII_PHYSID2);
+ if (tmp < 0)
+ return tmp;
+ phy_id |= tmp;
+
+ /* see if it is still the same PHY */
+ if ((phy_id & phydev->drv->phy_id_mask) !=
+ (phydev->drv->phy_id & phydev->drv->phy_id_mask)) {
+ addr = phydev->mdio.addr;
+ step++;
+ }
+ }
+
+ /* The range we get should be a multiple of four. Please note that both
+ * the min_addr and max_addr are inclusive. So we have to add one if we
+ * subtract them.
+ */
+ if ((max_addr - min_addr + 1) % 4) {
+ dev_err(&phydev->mdio.dev,
+ "Detected Quad PHY IDs %d..%d doesn't make sense.\n",
+ min_addr, max_addr);
+ return -EINVAL;
+ }
+
+ priv->port = (phydev->mdio.addr - min_addr) % 4;
+ priv->base_addr = phydev->mdio.addr - priv->port;
+
+ return 0;
+}
+
+static int bcm54140_probe(struct phy_device *phydev)
+{
+ struct bcm54140_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ ret = bcm54140_get_base_addr_and_port(phydev);
+ if (ret)
+ return ret;
+
+ devm_phy_package_join(&phydev->mdio.dev, phydev, priv->base_addr, 0);
+
+#if IS_ENABLED(CONFIG_HWMON)
+ mutex_init(&priv->alarm_lock);
+
+ if (phy_package_init_once(phydev)) {
+ ret = bcm54140_probe_once(phydev);
+ if (ret)
+ return ret;
+ }
+#endif
+
+ phydev_dbg(phydev, "probed (port %d, base PHY address %d)\n",
+ priv->port, priv->base_addr);
+
+ return 0;
+}
+
+static int bcm54140_config_init(struct phy_device *phydev)
+{
+ u16 reg = 0xffff;
+ int ret;
+
+ /* Apply hardware errata */
+ if (BCM54140_PHY_ID_REV(phydev->phy_id) == BCM54140_REV_B0) {
+ ret = bcm54140_b0_workaround(phydev);
+ if (ret)
+ return ret;
+ }
+
+ /* Unmask events we are interested in. */
+ reg &= ~(BCM54140_RDB_INT_DUPLEX |
+ BCM54140_RDB_INT_SPEED |
+ BCM54140_RDB_INT_LINK);
+ ret = bcm_phy_write_rdb(phydev, BCM54140_RDB_IMR, reg);
+ if (ret)
+ return ret;
+
+ /* LED1=LINKSPD[1], LED2=LINKSPD[2], LED3=LINK/ACTIVITY */
+ ret = bcm_phy_modify_rdb(phydev, BCM54140_RDB_SPARE1,
+ 0, BCM54140_RDB_SPARE1_LSLM);
+ if (ret)
+ return ret;
+
+ ret = bcm_phy_modify_rdb(phydev, BCM54140_RDB_LED_CTRL,
+ 0, BCM54140_RDB_LED_CTRL_ACTLINK0);
+ if (ret)
+ return ret;
+
+ /* disable super isolate mode */
+ return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_PWR,
+ BCM54140_RDB_C_PWR_ISOLATE, 0);
+}
+
+static irqreturn_t bcm54140_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, irq_mask;
+
+ irq_status = bcm_phy_read_rdb(phydev, BCM54140_RDB_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_mask = bcm_phy_read_rdb(phydev, BCM54140_RDB_IMR);
+ if (irq_mask < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ irq_mask = ~irq_mask;
+
+ if (!(irq_status & irq_mask))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int bcm54140_ack_intr(struct phy_device *phydev)
+{
+ int reg;
+
+ /* clear pending interrupts */
+ reg = bcm_phy_read_rdb(phydev, BCM54140_RDB_ISR);
+ if (reg < 0)
+ return reg;
+
+ return 0;
+}
+
+static int bcm54140_config_intr(struct phy_device *phydev)
+{
+ struct bcm54140_priv *priv = phydev->priv;
+ static const u16 port_to_imr_bit[] = {
+ BCM54140_RDB_TOP_IMR_PORT0, BCM54140_RDB_TOP_IMR_PORT1,
+ BCM54140_RDB_TOP_IMR_PORT2, BCM54140_RDB_TOP_IMR_PORT3,
+ };
+ int reg, err;
+
+ if (priv->port >= ARRAY_SIZE(port_to_imr_bit))
+ return -EINVAL;
+
+ reg = bcm54140_base_read_rdb(phydev, BCM54140_RDB_TOP_IMR);
+ if (reg < 0)
+ return reg;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = bcm54140_ack_intr(phydev);
+ if (err)
+ return err;
+
+ reg &= ~port_to_imr_bit[priv->port];
+ err = bcm54140_base_write_rdb(phydev, BCM54140_RDB_TOP_IMR, reg);
+ } else {
+ reg |= port_to_imr_bit[priv->port];
+ err = bcm54140_base_write_rdb(phydev, BCM54140_RDB_TOP_IMR, reg);
+ if (err)
+ return err;
+
+ err = bcm54140_ack_intr(phydev);
+ }
+
+ return err;
+}
+
+static int bcm54140_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val;
+
+ val = bcm_phy_read_rdb(phydev, BCM54140_RDB_C_MISC_CTRL);
+ if (val < 0)
+ return val;
+
+ if (!(val & BCM54140_RDB_C_MISC_CTRL_WS_EN)) {
+ *data = DOWNSHIFT_DEV_DISABLE;
+ return 0;
+ }
+
+ val = bcm_phy_read_rdb(phydev, BCM54140_RDB_SPARE2);
+ if (val < 0)
+ return val;
+
+ if (val & BCM54140_RDB_SPARE2_WS_RTRY_DIS)
+ *data = 1;
+ else
+ *data = FIELD_GET(BCM54140_RDB_SPARE2_WS_RTRY_LIMIT, val) + 2;
+
+ return 0;
+}
+
+static int bcm54140_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ u16 mask, set;
+ int ret;
+
+ if (cnt > BCM54140_MAX_DOWNSHIFT && cnt != DOWNSHIFT_DEV_DEFAULT_COUNT)
+ return -EINVAL;
+
+ if (!cnt)
+ return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_MISC_CTRL,
+ BCM54140_RDB_C_MISC_CTRL_WS_EN, 0);
+
+ if (cnt == DOWNSHIFT_DEV_DEFAULT_COUNT)
+ cnt = BCM54140_DEFAULT_DOWNSHIFT;
+
+ if (cnt == 1) {
+ mask = 0;
+ set = BCM54140_RDB_SPARE2_WS_RTRY_DIS;
+ } else {
+ mask = BCM54140_RDB_SPARE2_WS_RTRY_DIS;
+ mask |= BCM54140_RDB_SPARE2_WS_RTRY_LIMIT;
+ set = FIELD_PREP(BCM54140_RDB_SPARE2_WS_RTRY_LIMIT, cnt - 2);
+ }
+ ret = bcm_phy_modify_rdb(phydev, BCM54140_RDB_SPARE2,
+ mask, set);
+ if (ret)
+ return ret;
+
+ return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_MISC_CTRL,
+ 0, BCM54140_RDB_C_MISC_CTRL_WS_EN);
+}
+
+static int bcm54140_get_edpd(struct phy_device *phydev, u16 *tx_interval)
+{
+ int val;
+
+ val = bcm_phy_read_rdb(phydev, BCM54140_RDB_C_APWR);
+ if (val < 0)
+ return val;
+
+ switch (FIELD_GET(BCM54140_RDB_C_APWR_APD_MODE_MASK, val)) {
+ case BCM54140_RDB_C_APWR_APD_MODE_DIS:
+ case BCM54140_RDB_C_APWR_APD_MODE_DIS2:
+ *tx_interval = ETHTOOL_PHY_EDPD_DISABLE;
+ break;
+ case BCM54140_RDB_C_APWR_APD_MODE_EN:
+ case BCM54140_RDB_C_APWR_APD_MODE_EN_ANEG:
+ switch (FIELD_GET(BCM54140_RDB_C_APWR_SLP_TIM_MASK, val)) {
+ case BCM54140_RDB_C_APWR_SLP_TIM_2_7:
+ *tx_interval = 2700;
+ break;
+ case BCM54140_RDB_C_APWR_SLP_TIM_5_4:
+ *tx_interval = 5400;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int bcm54140_set_edpd(struct phy_device *phydev, u16 tx_interval)
+{
+ u16 mask, set;
+
+ mask = BCM54140_RDB_C_APWR_APD_MODE_MASK;
+ if (tx_interval == ETHTOOL_PHY_EDPD_DISABLE)
+ set = FIELD_PREP(BCM54140_RDB_C_APWR_APD_MODE_MASK,
+ BCM54140_RDB_C_APWR_APD_MODE_DIS);
+ else
+ set = FIELD_PREP(BCM54140_RDB_C_APWR_APD_MODE_MASK,
+ BCM54140_RDB_C_APWR_APD_MODE_EN_ANEG);
+
+ /* enable single pulse mode */
+ set |= BCM54140_RDB_C_APWR_SINGLE_PULSE;
+
+ /* set sleep timer */
+ mask |= BCM54140_RDB_C_APWR_SLP_TIM_MASK;
+ switch (tx_interval) {
+ case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
+ case ETHTOOL_PHY_EDPD_DISABLE:
+ case 2700:
+ set |= BCM54140_RDB_C_APWR_SLP_TIM_2_7;
+ break;
+ case 5400:
+ set |= BCM54140_RDB_C_APWR_SLP_TIM_5_4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return bcm_phy_modify_rdb(phydev, BCM54140_RDB_C_APWR, mask, set);
+}
+
+static int bcm54140_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return bcm54140_get_downshift(phydev, data);
+ case ETHTOOL_PHY_EDPD:
+ return bcm54140_get_edpd(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int bcm54140_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return bcm54140_set_downshift(phydev, *(const u8 *)data);
+ case ETHTOOL_PHY_EDPD:
+ return bcm54140_set_edpd(phydev, *(const u16 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static struct phy_driver bcm54140_drivers[] = {
+ {
+ .phy_id = PHY_ID_BCM54140,
+ .phy_id_mask = BCM54140_PHY_ID_MASK,
+ .name = "Broadcom BCM54140",
+ .flags = PHY_POLL_CABLE_TEST,
+ .features = PHY_GBIT_FEATURES,
+ .config_init = bcm54140_config_init,
+ .handle_interrupt = bcm54140_handle_interrupt,
+ .config_intr = bcm54140_config_intr,
+ .probe = bcm54140_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .soft_reset = genphy_soft_reset,
+ .get_tunable = bcm54140_get_tunable,
+ .set_tunable = bcm54140_set_tunable,
+ .cable_test_start = bcm_phy_cable_test_start_rdb,
+ .cable_test_get_status = bcm_phy_cable_test_get_status_rdb,
+ },
+};
+module_phy_driver(bcm54140_drivers);
+
+static struct mdio_device_id __maybe_unused bcm54140_tbl[] = {
+ { PHY_ID_BCM54140, BCM54140_PHY_ID_MASK },
+ { }
+};
+
+MODULE_AUTHOR("Michael Walle");
+MODULE_DESCRIPTION("Broadcom BCM54140 PHY driver");
+MODULE_DEVICE_TABLE(mdio, bcm54140_tbl);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/bcm63xx.c b/drivers/net/phy/bcm63xx.c
new file mode 100644
index 000000000..0eb33be82
--- /dev/null
+++ b/drivers/net/phy/bcm63xx.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Broadcom 63xx SOCs integrated PHYs
+ */
+#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) {
+ err = bcm_phy_ack_intr(phydev);
+ if (err)
+ return err;
+
+ reg &= ~MII_BCM63XX_IR_GMASK;
+ err = phy_write(phydev, MII_BCM63XX_IR, reg);
+ } else {
+ reg |= MII_BCM63XX_IR_GMASK;
+ err = phy_write(phydev, MII_BCM63XX_IR, reg);
+ if (err)
+ return err;
+
+ err = bcm_phy_ack_intr(phydev);
+ }
+
+ return err;
+}
+
+static int bcm63xx_config_init(struct phy_device *phydev)
+{
+ int reg, err;
+
+ /* ASYM_PAUSE bit is marked RO in datasheet, so don't cheat */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+
+ 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)",
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_IS_INTERNAL,
+ .config_init = bcm63xx_config_init,
+ .config_intr = bcm63xx_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+}, {
+ /* same phy as above, with just a different OUI */
+ .phy_id = 0x002bdc00,
+ .phy_id_mask = 0xfffffc00,
+ .name = "Broadcom BCM63XX (2)",
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_IS_INTERNAL,
+ .config_init = bcm63xx_config_init,
+ .config_intr = bcm63xx_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+} };
+
+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..97638ba7a
--- /dev/null
+++ b/drivers/net/phy/bcm7xxx.c
@@ -0,0 +1,962 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Broadcom BCM7xxx internal transceivers support.
+ *
+ * Copyright (C) 2014-2017 Broadcom
+ */
+
+#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/clk.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))
+
+struct bcm7xxx_phy_priv {
+ u64 *stats;
+};
+
+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 */
+ bcm_phy_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 */
+ bcm_phy_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);
+
+ bcm_phy_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 = bcm_phy_28nm_a0b0_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_enable_jumbo(phydev);
+ 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 phy_set_clr_bits(struct phy_device *dev, int location,
+ int set_mask, int clr_mask)
+{
+ int ret;
+
+ mutex_lock(&dev->mdio.bus->mdio_lock);
+ ret = __phy_set_clr_bits(dev, location, set_mask, clr_mask);
+ mutex_unlock(&dev->mdio.bus->mdio_lock);
+
+ return ret;
+}
+
+static int bcm7xxx_28nm_ephy_01_afe_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* set shadow mode 2 */
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+ MII_BCM7XXX_SHD_MODE_2, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Set current trim values INT_trim = -1, Ext_trim =0 */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_BIAS_TRIM, 0x3BE0);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ /* Cal reset */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+ MII_BCM7XXX_SHD_3_TL4);
+ if (ret < 0)
+ goto reset_shadow_mode;
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+ MII_BCM7XXX_TL4_RST_MSK, 0);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ /* Cal reset disable */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+ MII_BCM7XXX_SHD_3_TL4);
+ if (ret < 0)
+ goto reset_shadow_mode;
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+ 0, MII_BCM7XXX_TL4_RST_MSK);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+reset_shadow_mode:
+ /* reset shadow mode 2 */
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0,
+ MII_BCM7XXX_SHD_MODE_2);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* The 28nm EPHY does not support Clause 45 (MMD) used by bcm-phy-lib */
+static int bcm7xxx_28nm_ephy_apd_enable(struct phy_device *phydev)
+{
+ int ret;
+
+ /* set shadow mode 1 */
+ ret = phy_set_clr_bits(phydev, MII_BRCM_FET_BRCMTEST,
+ MII_BRCM_FET_BT_SRE, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Enable auto-power down */
+ ret = phy_set_clr_bits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2,
+ MII_BRCM_FET_SHDW_AS2_APDE, 0);
+ if (ret < 0)
+ return ret;
+
+ /* reset shadow mode 1 */
+ ret = phy_set_clr_bits(phydev, MII_BRCM_FET_BRCMTEST, 0,
+ MII_BRCM_FET_BT_SRE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int bcm7xxx_28nm_ephy_eee_enable(struct phy_device *phydev)
+{
+ int ret;
+
+ /* set shadow mode 2 */
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
+ MII_BCM7XXX_SHD_MODE_2, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Advertise supported modes */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+ MII_BCM7XXX_SHD_3_AN_EEE_ADV);
+ if (ret < 0)
+ goto reset_shadow_mode;
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+ MDIO_EEE_100TX);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ /* Restore Defaults */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+ MII_BCM7XXX_SHD_3_PCS_CTRL_2);
+ if (ret < 0)
+ goto reset_shadow_mode;
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+ MII_BCM7XXX_PCS_CTRL_2_DEF);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+ MII_BCM7XXX_SHD_3_EEE_THRESH);
+ if (ret < 0)
+ goto reset_shadow_mode;
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+ MII_BCM7XXX_EEE_THRESH_DEF);
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+ /* Enable EEE autonegotiation */
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_ADDR_CTRL,
+ MII_BCM7XXX_SHD_3_AN_STAT);
+ if (ret < 0)
+ goto reset_shadow_mode;
+ ret = phy_write(phydev, MII_BCM7XXX_SHD_2_CTRL_STAT,
+ (MII_BCM7XXX_AN_NULL_MSG_EN | MII_BCM7XXX_AN_EEE_EN));
+ if (ret < 0)
+ goto reset_shadow_mode;
+
+reset_shadow_mode:
+ /* reset shadow mode 2 */
+ ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, 0,
+ MII_BCM7XXX_SHD_MODE_2);
+ if (ret < 0)
+ return ret;
+
+ /* Restart autoneg */
+ phy_write(phydev, MII_BMCR,
+ (BMCR_SPEED100 | BMCR_ANENABLE | BMCR_ANRESTART));
+
+ return 0;
+}
+
+static int bcm7xxx_28nm_ephy_config_init(struct phy_device *phydev)
+{
+ u8 rev = phydev->phy_id & ~phydev->drv->phy_id_mask;
+ int ret = 0;
+
+ pr_info_once("%s: %s PHY revision: 0x%02x\n",
+ phydev_name(phydev), phydev->drv->name, rev);
+
+ /* Dummy read to a register to workaround a possible issue upon reset
+ * where the internal inverter may not allow the first MDIO transaction
+ * to pass the MDIO management controller and make us return 0xffff for
+ * such reads.
+ */
+ phy_read(phydev, MII_BMSR);
+
+ /* Apply AFE software work-around if necessary */
+ if (rev == 0x01) {
+ ret = bcm7xxx_28nm_ephy_01_afe_config_init(phydev);
+ if (ret)
+ return ret;
+ }
+
+ ret = bcm7xxx_28nm_ephy_eee_enable(phydev);
+ if (ret)
+ return ret;
+
+ return bcm7xxx_28nm_ephy_apd_enable(phydev);
+}
+
+static int bcm7xxx_16nm_ephy_afe_config(struct phy_device *phydev)
+{
+ int tmp, rcalcode, rcalnewcodelp, rcalnewcode11, rcalnewcode11d2;
+
+ /* Reset PHY */
+ tmp = genphy_soft_reset(phydev);
+ if (tmp)
+ return tmp;
+
+ /* Reset AFE and PLL */
+ bcm_phy_write_exp_sel(phydev, 0x0003, 0x0006);
+ /* Clear reset */
+ bcm_phy_write_exp_sel(phydev, 0x0003, 0x0000);
+
+ /* Write PLL/AFE control register to select 54MHz crystal */
+ bcm_phy_write_misc(phydev, 0x0030, 0x0001, 0x0000);
+ bcm_phy_write_misc(phydev, 0x0031, 0x0000, 0x044a);
+
+ /* Change Ka,Kp,Ki to pdiv=1 */
+ bcm_phy_write_misc(phydev, 0x0033, 0x0002, 0x71a1);
+ /* Configuration override */
+ bcm_phy_write_misc(phydev, 0x0033, 0x0001, 0x8000);
+
+ /* Change PLL_NDIV and PLL_NUDGE */
+ bcm_phy_write_misc(phydev, 0x0031, 0x0001, 0x2f68);
+ bcm_phy_write_misc(phydev, 0x0031, 0x0002, 0x0000);
+
+ /* Reference frequency is 54Mhz, config_mode[15:14] = 3 (low
+ * phase)
+ */
+ bcm_phy_write_misc(phydev, 0x0030, 0x0003, 0xc036);
+
+ /* Initialize bypass mode */
+ bcm_phy_write_misc(phydev, 0x0032, 0x0003, 0x0000);
+ /* Bypass code, default: VCOCLK enabled */
+ bcm_phy_write_misc(phydev, 0x0033, 0x0000, 0x0002);
+ /* LDOs at default setting */
+ bcm_phy_write_misc(phydev, 0x0030, 0x0002, 0x01c0);
+ /* Release PLL reset */
+ bcm_phy_write_misc(phydev, 0x0030, 0x0001, 0x0001);
+
+ /* Bandgap curvature correction to correct default */
+ bcm_phy_write_misc(phydev, 0x0038, 0x0000, 0x0010);
+
+ /* Run RCAL */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0003, 0x0038);
+ bcm_phy_write_misc(phydev, 0x0039, 0x0003, 0x003b);
+ udelay(2);
+ bcm_phy_write_misc(phydev, 0x0039, 0x0003, 0x003f);
+ mdelay(5);
+
+ /* AFE_CAL_CONFIG_0, Vref=1000, Target=10, averaging enabled */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0001, 0x1c82);
+ /* AFE_CAL_CONFIG_0, no reset and analog powerup */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0001, 0x9e82);
+ udelay(2);
+ /* AFE_CAL_CONFIG_0, start calibration */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0001, 0x9f82);
+ udelay(100);
+ /* AFE_CAL_CONFIG_0, clear start calibration, set HiBW */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0001, 0x9e86);
+ udelay(2);
+ /* AFE_CAL_CONFIG_0, start calibration with hi BW mode set */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0001, 0x9f86);
+ udelay(100);
+
+ /* Adjust 10BT amplitude additional +7% and 100BT +2% */
+ bcm_phy_write_misc(phydev, 0x0038, 0x0001, 0xe7ea);
+ /* Adjust 1G mode amplitude and 1G testmode1 */
+ bcm_phy_write_misc(phydev, 0x0038, 0x0002, 0xede0);
+
+ /* Read CORE_EXPA9 */
+ tmp = bcm_phy_read_exp_sel(phydev, 0x00a9);
+ /* CORE_EXPA9[6:1] is rcalcode[5:0] */
+ rcalcode = (tmp & 0x7e) / 2;
+ /* Correct RCAL code + 1 is -1% rprogr, LP: +16 */
+ rcalnewcodelp = rcalcode + 16;
+ /* Correct RCAL code + 1 is -15 rprogr, 11: +10 */
+ rcalnewcode11 = rcalcode + 10;
+ /* Saturate if necessary */
+ if (rcalnewcodelp > 0x3f)
+ rcalnewcodelp = 0x3f;
+ if (rcalnewcode11 > 0x3f)
+ rcalnewcode11 = 0x3f;
+ /* REXT=1 BYP=1 RCAL_st1<5:0>=new rcal code */
+ tmp = 0x00f8 + rcalnewcodelp * 256;
+ /* Program into AFE_CAL_CONFIG_2 */
+ bcm_phy_write_misc(phydev, 0x0039, 0x0003, tmp);
+ /* AFE_BIAS_CONFIG_0 10BT bias code (Bias: E4) */
+ bcm_phy_write_misc(phydev, 0x0038, 0x0001, 0xe7e4);
+ /* invert adc clock output and 'adc refp ldo current To correct
+ * default
+ */
+ bcm_phy_write_misc(phydev, 0x003b, 0x0000, 0x8002);
+ /* 100BT stair case, high BW, 1G stair case, alternate encode */
+ bcm_phy_write_misc(phydev, 0x003c, 0x0003, 0xf882);
+ /* 1000BT DAC transition method per Erol, bits[32], DAC Shuffle
+ * sequence 1 + 10BT imp adjust bits
+ */
+ bcm_phy_write_misc(phydev, 0x003d, 0x0000, 0x3201);
+ /* Non-overlap fix */
+ bcm_phy_write_misc(phydev, 0x003a, 0x0002, 0x0c00);
+
+ /* pwdb override (rxconfig<5>) to turn on RX LDO indpendent of
+ * pwdb controls from DSP_TAP10
+ */
+ bcm_phy_write_misc(phydev, 0x003a, 0x0001, 0x0020);
+
+ /* Remove references to channel 2 and 3 */
+ bcm_phy_write_misc(phydev, 0x003b, 0x0002, 0x0000);
+ bcm_phy_write_misc(phydev, 0x003b, 0x0003, 0x0000);
+
+ /* Set cal_bypassb bit rxconfig<43> */
+ bcm_phy_write_misc(phydev, 0x003a, 0x0003, 0x0800);
+ udelay(2);
+
+ /* Revert pwdb_override (rxconfig<5>) to 0 so that the RX pwr
+ * is controlled by DSP.
+ */
+ bcm_phy_write_misc(phydev, 0x003a, 0x0001, 0x0000);
+
+ /* Drop LSB */
+ rcalnewcode11d2 = (rcalnewcode11 & 0xfffe) / 2;
+ tmp = bcm_phy_read_misc(phydev, 0x003d, 0x0001);
+ /* Clear bits [11:5] */
+ tmp &= ~0xfe0;
+ /* set txcfg_ch0<5>=1 (enable + set local rcal) */
+ tmp |= 0x0020 | (rcalnewcode11d2 * 64);
+ bcm_phy_write_misc(phydev, 0x003d, 0x0001, tmp);
+ bcm_phy_write_misc(phydev, 0x003d, 0x0002, tmp);
+
+ tmp = bcm_phy_read_misc(phydev, 0x003d, 0x0000);
+ /* set txcfg<45:44>=11 (enable Rextra + invert fullscaledetect)
+ */
+ tmp &= ~0x3000;
+ tmp |= 0x3000;
+ bcm_phy_write_misc(phydev, 0x003d, 0x0000, tmp);
+
+ return 0;
+}
+
+static int bcm7xxx_16nm_ephy_config_init(struct phy_device *phydev)
+{
+ int ret, val;
+
+ ret = bcm7xxx_16nm_ephy_afe_config(phydev);
+ if (ret)
+ return ret;
+
+ ret = bcm_phy_set_eee(phydev, true);
+ if (ret)
+ return ret;
+
+ ret = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
+ if (ret < 0)
+ return ret;
+
+ val = ret;
+
+ /* Auto power down of DLL enabled,
+ * TXC/RXC disabled during auto power down.
+ */
+ val &= ~BCM54XX_SHD_SCR3_DLLAPD_DIS;
+ val |= BIT(8);
+
+ ret = bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val);
+ if (ret < 0)
+ return ret;
+
+ return bcm_phy_enable_apd(phydev, true);
+}
+
+static int bcm7xxx_16nm_ephy_resume(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Re-apply workarounds coming out suspend/resume */
+ ret = bcm7xxx_16nm_ephy_config_init(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_config_aneg(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;
+ struct clk *clk;
+ int ret = 0;
+
+ 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;
+
+ clk = devm_clk_get_optional_enabled(&phydev->mdio.dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ /* 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. This is needed to ensure that any subsequent reads to the
+ * PHY will succeed.
+ */
+ phy_read(phydev, MII_BMSR);
+
+ return ret;
+}
+
+#define BCM7XXX_28NM_GPHY(_oui, _name) \
+{ \
+ .phy_id = (_oui), \
+ .phy_id_mask = 0xfffffff0, \
+ .name = _name, \
+ /* 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, \
+ /* 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, \
+ /* PHY_BASIC_FEATURES */ \
+ .flags = PHY_IS_INTERNAL, \
+ .soft_reset = genphy_soft_reset, \
+ .config_init = bcm7xxx_config_init, \
+ .suspend = bcm7xxx_suspend, \
+ .resume = bcm7xxx_config_init, \
+}
+
+#define BCM7XXX_16NM_EPHY(_oui, _name) \
+{ \
+ .phy_id = (_oui), \
+ .phy_id_mask = 0xfffffff0, \
+ .name = _name, \
+ /* PHY_BASIC_FEATURES */ \
+ .flags = PHY_IS_INTERNAL, \
+ .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, \
+ .config_init = bcm7xxx_16nm_ephy_config_init, \
+ .config_aneg = genphy_config_aneg, \
+ .read_status = genphy_read_status, \
+ .resume = bcm7xxx_16nm_ephy_resume, \
+}
+
+static struct phy_driver bcm7xxx_driver[] = {
+ BCM7XXX_28NM_EPHY(PHY_ID_BCM72113, "Broadcom BCM72113"),
+ BCM7XXX_28NM_EPHY(PHY_ID_BCM72116, "Broadcom BCM72116"),
+ BCM7XXX_16NM_EPHY(PHY_ID_BCM72165, "Broadcom BCM72165"),
+ BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"),
+ BCM7XXX_28NM_EPHY(PHY_ID_BCM7255, "Broadcom BCM7255"),
+ 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_16NM_EPHY(PHY_ID_BCM74165, "Broadcom BCM74165"),
+ 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_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"),
+ BCM7XXX_16NM_EPHY(PHY_ID_BCM7712, "Broadcom BCM7712"),
+};
+
+static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
+ { PHY_ID_BCM72113, 0xfffffff0 },
+ { PHY_ID_BCM72116, 0xfffffff0, },
+ { PHY_ID_BCM72165, 0xfffffff0, },
+ { PHY_ID_BCM7250, 0xfffffff0, },
+ { PHY_ID_BCM7255, 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, },
+ { PHY_ID_BCM7712, 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/bcm84881.c b/drivers/net/phy/bcm84881.c
new file mode 100644
index 000000000..9717a1626
--- /dev/null
+++ b/drivers/net/phy/bcm84881.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+// Broadcom BCM84881 NBASE-T PHY driver, as found on a SFP+ module.
+// Copyright (C) 2019 Russell King, Deep Blue Solutions Ltd.
+//
+// Like the Marvell 88x3310, the Broadcom 84881 changes its host-side
+// interface according to the operating speed between 10GBASE-R,
+// 2500BASE-X and SGMII (but unlike the 88x3310, without the control
+// word).
+//
+// This driver only supports those aspects of the PHY that I'm able to
+// observe and test with the SFP+ module, which is an incomplete subset
+// of what this PHY is able to support. For example, I only assume it
+// supports a single lane Serdes connection, but it may be that the PHY
+// is able to support more than that.
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+enum {
+ MDIO_AN_C22 = 0xffe0,
+};
+
+static int bcm84881_wait_init(struct phy_device *phydev)
+{
+ int val;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+ val, !(val & MDIO_CTRL1_RESET),
+ 100000, 2000000, false);
+}
+
+static int bcm84881_config_init(struct phy_device *phydev)
+{
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_10GBASER:
+ break;
+ default:
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int bcm84881_probe(struct phy_device *phydev)
+{
+ /* This driver requires PMAPMD and AN blocks */
+ const u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+ if (!phydev->is_c45 ||
+ (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int bcm84881_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* Although the PHY sets bit 1.11.8, it does not support 10M modes */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ phydev->supported);
+
+ return 0;
+}
+
+static int bcm84881_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ u32 adv;
+ int ret;
+
+ /* Wait for the PHY to finish initialising, otherwise our
+ * advertisement may be overwritten.
+ */
+ ret = bcm84881_wait_init(phydev);
+ if (ret)
+ return ret;
+
+ /* We don't support manual MDI control */
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ /* disabled autoneg doesn't seem to work with this PHY */
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return -EINVAL;
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+ MDIO_AN_C22 + MII_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ adv);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int bcm84881_aneg_done(struct phy_device *phydev)
+{
+ int bmsr, val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
+ if (bmsr < 0)
+ return val;
+
+ return !!(val & MDIO_AN_STAT1_COMPLETE) &&
+ !!(bmsr & BMSR_ANEGCOMPLETE);
+}
+
+static int bcm84881_read_status(struct phy_device *phydev)
+{
+ unsigned int mode;
+ int bmsr, val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_CTRL1_RESTART) {
+ phydev->link = 0;
+ return 0;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
+ if (bmsr < 0)
+ return val;
+
+ phydev->autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) &&
+ !!(bmsr & BMSR_ANEGCOMPLETE);
+ phydev->link = !!(val & MDIO_STAT1_LSTATUS) &&
+ !!(bmsr & BMSR_LSTATUS);
+ if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete)
+ phydev->link = false;
+
+ linkmode_zero(phydev->lp_advertising);
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ phydev->mdix = 0;
+
+ if (!phydev->link)
+ return 0;
+
+ if (phydev->autoneg_complete) {
+ val = genphy_c45_read_lpa(phydev);
+ if (val < 0)
+ return val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN,
+ MDIO_AN_C22 + MII_STAT1000);
+ if (val < 0)
+ return val;
+
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+
+ if (phydev->autoneg == AUTONEG_ENABLE)
+ phy_resolve_aneg_linkmode(phydev);
+ }
+
+ if (phydev->autoneg == AUTONEG_DISABLE) {
+ /* disabled autoneg doesn't seem to work, so force the link
+ * down.
+ */
+ phydev->link = 0;
+ return 0;
+ }
+
+ /* Set the host link mode - we set the phy interface mode and
+ * the speed according to this register so that downshift works.
+ * We leave the duplex setting as per the resolution from the
+ * above.
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x4011);
+ mode = (val & 0x1e) >> 1;
+ if (mode == 1 || mode == 2)
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ else if (mode == 3)
+ phydev->interface = PHY_INTERFACE_MODE_10GBASER;
+ else if (mode == 4)
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ switch (mode & 7) {
+ case 1:
+ phydev->speed = SPEED_100;
+ break;
+ case 2:
+ phydev->speed = SPEED_1000;
+ break;
+ case 3:
+ phydev->speed = SPEED_10000;
+ break;
+ case 4:
+ phydev->speed = SPEED_2500;
+ break;
+ case 5:
+ phydev->speed = SPEED_5000;
+ break;
+ }
+
+ return genphy_c45_read_mdix(phydev);
+}
+
+static struct phy_driver bcm84881_drivers[] = {
+ {
+ .phy_id = 0xae025150,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM84881",
+ .config_init = bcm84881_config_init,
+ .probe = bcm84881_probe,
+ .get_features = bcm84881_get_features,
+ .config_aneg = bcm84881_config_aneg,
+ .aneg_done = bcm84881_aneg_done,
+ .read_status = bcm84881_read_status,
+ },
+};
+
+module_phy_driver(bcm84881_drivers);
+
+/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */
+static struct mdio_device_id __maybe_unused bcm84881_tbl[] = {
+ { 0xae025150, 0xfffffff0 },
+ { },
+};
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Broadcom BCM84881 PHY driver");
+MODULE_DEVICE_TABLE(mdio, bcm84881_tbl);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/bcm87xx.c b/drivers/net/phy/bcm87xx.c
new file mode 100644
index 000000000..cc2858107
--- /dev/null
+++ b/drivers/net/phy/bcm87xx.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 0x000a
+#define BCM87XX_10GBASER_PCS_STATUS 0x0020
+#define BCM87XX_XGXS_LANE_STATUS 0x0018
+
+#define BCM87XX_LASI_CONTROL 0x9002
+#define BCM87XX_LASI_STATUS 0x9005
+
+#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 = 0;
+
+ if (mask) {
+ val = phy_read_mmd(phydev, devid, reg);
+ if (val < 0) {
+ ret = val;
+ goto err;
+ }
+ val &= mask;
+ }
+ val |= val_bits;
+
+ ret = phy_write_mmd(phydev, devid, reg, 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_get_features(struct phy_device *phydev)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
+ phydev->supported);
+ return 0;
+}
+
+static int bcm87xx_config_init(struct phy_device *phydev)
+{
+ return bcm87xx_of_reg_init(phydev);
+}
+
+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_mmd(phydev, MDIO_MMD_PMAPMD,
+ 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_mmd(phydev, MDIO_MMD_PCS,
+ BCM87XX_10GBASER_PCS_STATUS);
+ if (pcs_status < 0)
+ return pcs_status;
+
+ if ((pcs_status & 1) == 0)
+ goto no_link;
+
+ xgxs_lane_status = phy_read_mmd(phydev, MDIO_MMD_PHYXS,
+ 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_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_CONTROL);
+
+ if (reg < 0)
+ return reg;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_STATUS);
+ if (err)
+ return err;
+
+ reg |= 1;
+ err = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ BCM87XX_LASI_CONTROL, reg);
+ } else {
+ reg &= ~1;
+ err = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ BCM87XX_LASI_CONTROL, reg);
+ if (err)
+ return err;
+
+ err = phy_read_mmd(phydev, MDIO_MMD_PCS, BCM87XX_LASI_STATUS);
+ }
+
+ return err;
+}
+
+static irqreturn_t bcm87xx_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, BCM87XX_LASI_STATUS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (irq_status == 0)
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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",
+ .get_features = bcm87xx_get_features,
+ .config_init = bcm87xx_config_init,
+ .config_aneg = bcm87xx_config_aneg,
+ .read_status = bcm87xx_read_status,
+ .config_intr = bcm87xx_config_intr,
+ .handle_interrupt = bcm87xx_handle_interrupt,
+ .match_phy_device = bcm8706_match_phy_device,
+}, {
+ .phy_id = PHY_ID_BCM8727,
+ .phy_id_mask = 0xffffffff,
+ .name = "Broadcom BCM8727",
+ .get_features = bcm87xx_get_features,
+ .config_init = bcm87xx_config_init,
+ .config_aneg = bcm87xx_config_aneg,
+ .read_status = bcm87xx_read_status,
+ .config_intr = bcm87xx_config_intr,
+ .handle_interrupt = bcm87xx_handle_interrupt,
+ .match_phy_device = bcm8727_match_phy_device,
+} };
+
+module_phy_driver(bcm87xx_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
new file mode 100644
index 000000000..04b2e6eeb
--- /dev/null
+++ b/drivers/net/phy/broadcom.c
@@ -0,0 +1,1307 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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.
+ */
+
+#include "bcm-phy-lib.h"
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/pm_wakeup.h>
+#include <linux/brcmphy.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio/consumer.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");
+
+struct bcm54xx_phy_priv {
+ u64 *stats;
+ struct bcm_ptp_private *ptp;
+ int wake_irq;
+ bool wake_irq_enabled;
+};
+
+static bool bcm54xx_phy_can_wakeup(struct phy_device *phydev)
+{
+ struct bcm54xx_phy_priv *priv = phydev->priv;
+
+ return phy_interrupt_is_valid(phydev) || priv->wake_irq >= 0;
+}
+
+static int bcm54xx_config_clock_delay(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;
+}
+
+static int bcm54210e_config_init(struct phy_device *phydev)
+{
+ int val;
+
+ bcm54xx_config_clock_delay(phydev);
+
+ 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;
+
+ bcm54xx_config_clock_delay(phydev);
+
+ /* 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 bcm54616s_config_init(struct phy_device *phydev)
+{
+ int rc, val;
+
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_1000BASEX)
+ return 0;
+
+ /* Ensure proper interface mode is selected. */
+ /* Disable RGMII mode */
+ val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
+ if (val < 0)
+ return val;
+ val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_EN;
+ val |= MII_BCM54XX_AUXCTL_MISC_WREN;
+ rc = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
+ val);
+ if (rc < 0)
+ return rc;
+
+ /* Select 1000BASE-X register set (primary SerDes) */
+ val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE);
+ if (val < 0)
+ return val;
+ val |= BCM54XX_SHD_MODE_1000BX;
+ rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);
+ if (rc < 0)
+ return rc;
+
+ /* Power down SerDes interface */
+ rc = phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
+ if (rc < 0)
+ return rc;
+
+ /* Select proper interface mode */
+ val &= ~BCM54XX_SHD_INTF_SEL_MASK;
+ val |= phydev->interface == PHY_INTERFACE_MODE_SGMII ?
+ BCM54XX_SHD_INTF_SEL_SGMII :
+ BCM54XX_SHD_INTF_SEL_GBIC;
+ rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);
+ if (rc < 0)
+ return rc;
+
+ /* Power up SerDes interface */
+ rc = phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+ if (rc < 0)
+ return rc;
+
+ /* Select copper register set */
+ val &= ~BCM54XX_SHD_MODE_1000BX;
+ rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);
+ if (rc < 0)
+ return rc;
+
+ /* Power up copper interface */
+ return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+}
+
+/* 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 &&
+ BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54210E &&
+ BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54810 &&
+ BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811)
+ 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) {
+ if (BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811) {
+ /* 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) {
+ if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54210E ||
+ BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54810 ||
+ BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54811)
+ val |= BCM54XX_SHD_SCR3_RXCTXC_DIS;
+ else
+ 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 void bcm54xx_ptp_stop(struct phy_device *phydev)
+{
+ struct bcm54xx_phy_priv *priv = phydev->priv;
+
+ if (priv->ptp)
+ bcm_ptp_stop(priv->ptp);
+}
+
+static void bcm54xx_ptp_config_init(struct phy_device *phydev)
+{
+ struct bcm54xx_phy_priv *priv = phydev->priv;
+
+ if (priv->ptp)
+ bcm_ptp_config_init(phydev);
+}
+
+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);
+
+ bcm54xx_adjust_rxrefclk(phydev);
+
+ switch (BRCM_PHY_MODEL(phydev)) {
+ case PHY_ID_BCM50610:
+ case PHY_ID_BCM50610M:
+ err = bcm54xx_config_clock_delay(phydev);
+ break;
+ case PHY_ID_BCM54210E:
+ err = bcm54210e_config_init(phydev);
+ break;
+ case PHY_ID_BCM54612E:
+ err = bcm54612e_config_init(phydev);
+ break;
+ case PHY_ID_BCM54616S:
+ err = bcm54616s_config_init(phydev);
+ break;
+ case 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);
+ break;
+ }
+ if (err)
+ return err;
+
+ bcm54xx_phydsp_config(phydev);
+
+ /* For non-SFP setups, encode link speed into LED1 and LED3 pair
+ * (green/amber).
+ * Also flash these two LEDs on activity. This means configuring
+ * them for MULTICOLOR and encoding link/activity into them.
+ * Don't do this for devices on an SFP module, since some of these
+ * use the LED outputs to control the SFP LOS signal, and changing
+ * these settings will cause LOS to malfunction.
+ */
+ if (!phy_on_sfp(phydev)) {
+ val = BCM54XX_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1) |
+ BCM54XX_SHD_LEDS1_LED3(BCM_LED_SRC_MULTICOLOR1);
+ bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS1, val);
+
+ val = BCM_LED_MULTICOLOR_IN_PHASE |
+ BCM54XX_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) |
+ BCM54XX_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT);
+ bcm_phy_write_exp(phydev, BCM_EXP_MULTICOLOR, val);
+ }
+
+ bcm54xx_ptp_config_init(phydev);
+
+ /* Acknowledge any left over interrupt and charge the device for
+ * wake-up.
+ */
+ err = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
+ if (err < 0)
+ return err;
+
+ if (err)
+ pm_wakeup_event(&phydev->mdio.dev, 0);
+
+ return 0;
+}
+
+static int bcm54xx_iddq_set(struct phy_device *phydev, bool enable)
+{
+ int ret = 0;
+
+ if (!(phydev->dev_flags & PHY_BRCM_IDDQ_SUSPEND))
+ return ret;
+
+ ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_IDDQ_CTRL);
+ if (ret < 0)
+ goto out;
+
+ if (enable)
+ ret |= BCM54XX_TOP_MISC_IDDQ_SR | BCM54XX_TOP_MISC_IDDQ_LP;
+ else
+ ret &= ~(BCM54XX_TOP_MISC_IDDQ_SR | BCM54XX_TOP_MISC_IDDQ_LP);
+
+ ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_IDDQ_CTRL, ret);
+out:
+ return ret;
+}
+
+static int bcm54xx_set_wakeup_irq(struct phy_device *phydev, bool state)
+{
+ struct bcm54xx_phy_priv *priv = phydev->priv;
+ int ret = 0;
+
+ if (!bcm54xx_phy_can_wakeup(phydev))
+ return ret;
+
+ if (priv->wake_irq_enabled != state) {
+ if (state)
+ ret = enable_irq_wake(priv->wake_irq);
+ else
+ ret = disable_irq_wake(priv->wake_irq);
+ priv->wake_irq_enabled = state;
+ }
+
+ return ret;
+}
+
+static int bcm54xx_suspend(struct phy_device *phydev)
+{
+ int ret = 0;
+
+ bcm54xx_ptp_stop(phydev);
+
+ /* Acknowledge any Wake-on-LAN interrupt prior to suspend */
+ ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->wol_enabled)
+ return bcm54xx_set_wakeup_irq(phydev, true);
+
+ /* We cannot use a read/modify/write here otherwise the PHY gets into
+ * a bad state where its LEDs keep flashing, thus defeating the purpose
+ * of low power mode.
+ */
+ ret = phy_write(phydev, MII_BMCR, BMCR_PDOWN);
+ if (ret < 0)
+ return ret;
+
+ return bcm54xx_iddq_set(phydev, true);
+}
+
+static int bcm54xx_resume(struct phy_device *phydev)
+{
+ int ret = 0;
+
+ if (phydev->wol_enabled) {
+ ret = bcm54xx_set_wakeup_irq(phydev, false);
+ if (ret)
+ return ret;
+ }
+
+ ret = bcm54xx_iddq_set(phydev, false);
+ if (ret < 0)
+ return ret;
+
+ /* Writes to register other than BMCR would be ignored
+ * unless we clear the PDOWN bit first
+ */
+ ret = genphy_resume(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* Upon exiting power down, the PHY remains in an internal reset state
+ * for 40us
+ */
+ fsleep(40);
+
+ /* Issue a soft reset after clearing the power down bit
+ * and before doing any other configuration.
+ */
+ if (phydev->dev_flags & PHY_BRCM_IDDQ_SUSPEND) {
+ ret = genphy_soft_reset(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return bcm54xx_config_init(phydev);
+}
+
+static int bcm54810_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
+{
+ return -EOPNOTSUPP;
+}
+
+static int bcm54810_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
+ u16 val)
+{
+ return -EOPNOTSUPP;
+}
+
+static int bcm54811_config_init(struct phy_device *phydev)
+{
+ int err, reg;
+
+ /* Disable BroadR-Reach function. */
+ reg = bcm_phy_read_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL);
+ reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;
+ err = bcm_phy_write_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL,
+ reg);
+ if (err < 0)
+ return err;
+
+ err = bcm54xx_config_init(phydev);
+
+ /* Enable CLK125 MUX on LED4 if ref clock is enabled. */
+ if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) {
+ 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 err;
+}
+
+static int bcm5481_config_aneg(struct phy_device *phydev)
+{
+ struct device_node *np = phydev->mdio.dev.of_node;
+ int ret;
+
+ /* Aneg firstly. */
+ ret = genphy_config_aneg(phydev);
+
+ /* Then we can set up the delay. */
+ bcm54xx_config_clock_delay(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;
+}
+
+struct bcm54616s_phy_priv {
+ bool mode_1000bx_en;
+};
+
+static int bcm54616s_probe(struct phy_device *phydev)
+{
+ struct bcm54616s_phy_priv *priv;
+ int val;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE);
+ if (val < 0)
+ return val;
+
+ /* The PHY is strapped in RGMII-fiber mode when INTERF_SEL[1:0]
+ * is 01b, and the link between PHY and its link partner can be
+ * either 1000Base-X or 100Base-FX.
+ * RGMII-1000Base-X is properly supported, but RGMII-100Base-FX
+ * support is still missing as of now.
+ */
+ if ((val & BCM54XX_SHD_INTF_SEL_MASK) == BCM54XX_SHD_INTF_SEL_RGMII) {
+ val = bcm_phy_read_shadow(phydev, BCM54616S_SHD_100FX_CTRL);
+ if (val < 0)
+ return val;
+
+ /* Bit 0 of the SerDes 100-FX Control register, when set
+ * to 1, sets the MII/RGMII -> 100BASE-FX configuration.
+ * When this bit is set to 0, it sets the GMII/RGMII ->
+ * 1000BASE-X configuration.
+ */
+ if (!(val & BCM54616S_100FX_MODE))
+ priv->mode_1000bx_en = true;
+
+ phydev->port = PORT_FIBRE;
+ }
+
+ return 0;
+}
+
+static int bcm54616s_config_aneg(struct phy_device *phydev)
+{
+ struct bcm54616s_phy_priv *priv = phydev->priv;
+ int ret;
+
+ /* Aneg firstly. */
+ if (priv->mode_1000bx_en)
+ ret = genphy_c37_config_aneg(phydev);
+ else
+ ret = genphy_config_aneg(phydev);
+
+ /* Then we can set up the delay. */
+ bcm54xx_config_clock_delay(phydev);
+
+ return ret;
+}
+
+static int bcm54616s_read_status(struct phy_device *phydev)
+{
+ struct bcm54616s_phy_priv *priv = phydev->priv;
+ int err;
+
+ if (priv->mode_1000bx_en)
+ err = genphy_c37_read_status(phydev);
+ else
+ err = genphy_read_status(phydev);
+
+ return err;
+}
+
+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 = phy_set_bits(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 = phy_set_bits(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) {
+ err = brcm_fet_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ reg &= ~MII_BRCM_FET_IR_MASK;
+ err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);
+ } else {
+ reg |= MII_BRCM_FET_IR_MASK;
+ err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);
+ if (err)
+ return err;
+
+ err = brcm_fet_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t brcm_fet_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_BRCM_FET_INTREG);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (irq_status == 0)
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int brcm_fet_suspend(struct phy_device *phydev)
+{
+ int reg, err, err2, brcmtest;
+
+ /* We cannot use a read/modify/write here otherwise the PHY continues
+ * to drive LEDs which defeats the purpose of low power mode.
+ */
+ err = phy_write(phydev, MII_BMCR, BMCR_PDOWN);
+ 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 standby mode */
+ err = phy_modify(phydev, MII_BRCM_FET_SHDW_AUXMODE4,
+ MII_BRCM_FET_SHDW_AM4_STANDBY,
+ MII_BRCM_FET_SHDW_AM4_STANDBY);
+
+ /* Disable shadow register access */
+ err2 = phy_write(phydev, MII_BRCM_FET_BRCMTEST, brcmtest);
+ if (!err)
+ err = err2;
+
+ return err;
+}
+
+static void bcm54xx_phy_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ /* We cannot wake-up if we do not have a dedicated PHY interrupt line
+ * or an out of band GPIO descriptor for wake-up. Zeroing
+ * wol->supported allows the caller (MAC driver) to play through and
+ * offer its own Wake-on-LAN scheme if available.
+ */
+ if (!bcm54xx_phy_can_wakeup(phydev)) {
+ wol->supported = 0;
+ return;
+ }
+
+ bcm_phy_get_wol(phydev, wol);
+}
+
+static int bcm54xx_phy_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int ret;
+
+ /* We cannot wake-up if we do not have a dedicated PHY interrupt line
+ * or an out of band GPIO descriptor for wake-up. Returning -EOPNOTSUPP
+ * allows the caller (MAC driver) to play through and offer its own
+ * Wake-on-LAN scheme if available.
+ */
+ if (!bcm54xx_phy_can_wakeup(phydev))
+ return -EOPNOTSUPP;
+
+ ret = bcm_phy_set_wol(phydev, wol);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int bcm54xx_phy_probe(struct phy_device *phydev)
+{
+ struct bcm54xx_phy_priv *priv;
+ struct gpio_desc *wakeup_gpio;
+ int ret = 0;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wake_irq = -ENXIO;
+
+ 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;
+
+ priv->ptp = bcm_ptp_probe(phydev);
+ if (IS_ERR(priv->ptp))
+ return PTR_ERR(priv->ptp);
+
+ /* We cannot utilize the _optional variant here since we want to know
+ * whether the GPIO descriptor exists or not to advertise Wake-on-LAN
+ * support or not.
+ */
+ wakeup_gpio = devm_gpiod_get(&phydev->mdio.dev, "wakeup", GPIOD_IN);
+ if (PTR_ERR(wakeup_gpio) == -EPROBE_DEFER)
+ return PTR_ERR(wakeup_gpio);
+
+ if (!IS_ERR(wakeup_gpio)) {
+ priv->wake_irq = gpiod_to_irq(wakeup_gpio);
+
+ /* Dummy interrupt handler which is not enabled but is provided
+ * in order for the interrupt descriptor to be fully set-up.
+ */
+ ret = devm_request_irq(&phydev->mdio.dev, priv->wake_irq,
+ bcm_phy_wol_isr,
+ IRQF_TRIGGER_LOW | IRQF_NO_AUTOEN,
+ dev_name(&phydev->mdio.dev), phydev);
+ if (ret)
+ return ret;
+ }
+
+ /* If we do not have a main interrupt or a side-band wake-up interrupt,
+ * then the device cannot be marked as wake-up capable.
+ */
+ if (!bcm54xx_phy_can_wakeup(phydev))
+ return 0;
+
+ return device_init_wakeup(&phydev->mdio.dev, true);
+}
+
+static void bcm54xx_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct bcm54xx_phy_priv *priv = phydev->priv;
+
+ bcm_phy_get_stats(phydev, priv->stats, stats, data);
+}
+
+static void bcm54xx_link_change_notify(struct phy_device *phydev)
+{
+ u16 mask = MII_BCM54XX_EXP_EXP08_EARLY_DAC_WAKE |
+ MII_BCM54XX_EXP_EXP08_FORCE_DAC_WAKE;
+ int ret;
+
+ if (phydev->state != PHY_RUNNING)
+ return;
+
+ /* Don't change the DAC wake settings if auto power down
+ * is not requested.
+ */
+ if (!(phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))
+ return;
+
+ ret = bcm_phy_read_exp(phydev, MII_BCM54XX_EXP_EXP08);
+ if (ret < 0)
+ return;
+
+ /* Enable/disable 10BaseT auto and forced early DAC wake depending
+ * on the negotiated speed, those settings should only be done
+ * for 10Mbits/sec.
+ */
+ if (phydev->speed == SPEED_10)
+ ret |= mask;
+ else
+ ret &= ~mask;
+ bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08, ret);
+}
+
+static struct phy_driver broadcom_drivers[] = {
+{
+ .phy_id = PHY_ID_BCM5411,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5411",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+}, {
+ .phy_id = PHY_ID_BCM5421,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5421",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+}, {
+ .phy_id = PHY_ID_BCM54210E,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM54210E",
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_ALWAYS_CALL_SUSPEND,
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .suspend = bcm54xx_suspend,
+ .resume = bcm54xx_resume,
+ .get_wol = bcm54xx_phy_get_wol,
+ .set_wol = bcm54xx_phy_set_wol,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM5461,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5461",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM54612E,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM54612E",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM54616S,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM54616S",
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = genphy_soft_reset,
+ .config_init = bcm54xx_config_init,
+ .config_aneg = bcm54616s_config_aneg,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .read_status = bcm54616s_read_status,
+ .probe = bcm54616s_probe,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM5464,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5464",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM5481,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5481",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_aneg = bcm5481_config_aneg,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM54810,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM54810",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .read_mmd = bcm54810_read_mmd,
+ .write_mmd = bcm54810_write_mmd,
+ .config_init = bcm54xx_config_init,
+ .config_aneg = bcm5481_config_aneg,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .suspend = bcm54xx_suspend,
+ .resume = bcm54xx_resume,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM54811,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM54811",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54811_config_init,
+ .config_aneg = bcm5481_config_aneg,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .suspend = bcm54xx_suspend,
+ .resume = bcm54xx_resume,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM5482,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5482",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM50610,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM50610",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .suspend = bcm54xx_suspend,
+ .resume = bcm54xx_resume,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM50610M,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM50610M",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .suspend = bcm54xx_suspend,
+ .resume = bcm54xx_resume,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM57780,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM57780",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCMAC131,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCMAC131",
+ /* PHY_BASIC_FEATURES */
+ .config_init = brcm_fet_config_init,
+ .config_intr = brcm_fet_config_intr,
+ .handle_interrupt = brcm_fet_handle_interrupt,
+ .suspend = brcm_fet_suspend,
+ .resume = brcm_fet_config_init,
+}, {
+ .phy_id = PHY_ID_BCM5241,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5241",
+ /* PHY_BASIC_FEATURES */
+ .config_init = brcm_fet_config_init,
+ .config_intr = brcm_fet_config_intr,
+ .handle_interrupt = brcm_fet_handle_interrupt,
+ .suspend = brcm_fet_suspend,
+ .resume = brcm_fet_config_init,
+}, {
+ .phy_id = PHY_ID_BCM5395,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM5395",
+ .flags = PHY_IS_INTERNAL,
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM53125,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM53125",
+ .flags = PHY_IS_INTERNAL,
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM53128,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM53128",
+ .flags = PHY_IS_INTERNAL,
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+ .led_brightness_set = bcm_phy_led_brightness_set,
+}, {
+ .phy_id = PHY_ID_BCM89610,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM89610",
+ /* PHY_GBIT_FEATURES */
+ .get_sset_count = bcm_phy_get_sset_count,
+ .get_strings = bcm_phy_get_strings,
+ .get_stats = bcm54xx_get_stats,
+ .probe = bcm54xx_phy_probe,
+ .config_init = bcm54xx_config_init,
+ .config_intr = bcm_phy_config_intr,
+ .handle_interrupt = bcm_phy_handle_interrupt,
+ .link_change_notify = bcm54xx_link_change_notify,
+} };
+
+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_BCM54811, 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_BCM53125, 0xfffffff0 },
+ { PHY_ID_BCM53128, 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..ef5f412e1
--- /dev/null
+++ b/drivers/net/phy/cicada.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/cicada.c
+ *
+ * Driver for Cicada PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+#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 = cis820x_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_CIS8201_IMASK,
+ MII_CIS8201_IMASK_MASK);
+ } else {
+ err = phy_write(phydev, MII_CIS8201_IMASK, 0);
+ if (err)
+ return err;
+
+ err = cis820x_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t cis820x_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_CIS8201_ISTAT);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_CIS8201_IMASK_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+/* Cicada 8201, a.k.a Vitesse VSC8201 */
+static struct phy_driver cis820x_driver[] = {
+{
+ .phy_id = 0x000fc410,
+ .name = "Cicada Cis8201",
+ .phy_id_mask = 0x000ffff0,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &cis820x_config_init,
+ .config_intr = &cis820x_config_intr,
+ .handle_interrupt = &cis820x_handle_interrupt,
+}, {
+ .phy_id = 0x000fc440,
+ .name = "Cicada Cis8204",
+ .phy_id_mask = 0x000fffc0,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &cis820x_config_init,
+ .config_intr = &cis820x_config_intr,
+ .handle_interrupt = &cis820x_handle_interrupt,
+} };
+
+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..40514a94e
--- /dev/null
+++ b/drivers/net/phy/cortina.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2017 NXP
+ *
+ * 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_c45_read(phydev->mdio.bus, phydev->mdio.addr, 0, 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",
+ .features = PHY_10GBIT_FEATURES,
+ .config_aneg = gen10g_config_aneg,
+ .read_status = cortina_read_status,
+ .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..4ac4bce1b
--- /dev/null
+++ b/drivers/net/phy/davicom.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/davicom.c
+ *
+ * Driver for Davicom PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+#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)
+#define MII_DM9161_INTR_CHANGE \
+ (MII_DM9161_INTR_DPLX_CHANGE | \
+ MII_DM9161_INTR_SPD_CHANGE | \
+ MII_DM9161_INTR_LINK_CHANGE)
+
+/* 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");
+
+
+static int dm9161_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, MII_DM9161_INTR);
+
+ return (err < 0) ? err : 0;
+}
+
+#define DM9161_DELAY 1
+static int dm9161_config_intr(struct phy_device *phydev)
+{
+ int temp, err;
+
+ temp = phy_read(phydev, MII_DM9161_INTR);
+
+ if (temp < 0)
+ return temp;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = dm9161_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ temp &= ~(MII_DM9161_INTR_STOP);
+ err = phy_write(phydev, MII_DM9161_INTR, temp);
+ } else {
+ temp |= MII_DM9161_INTR_STOP;
+ err = phy_write(phydev, MII_DM9161_INTR, temp);
+ if (err)
+ return err;
+
+ err = dm9161_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t dm9161_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_DM9161_INTR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_DM9161_INTR_CHANGE))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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 struct phy_driver dm91xx_driver[] = {
+{
+ .phy_id = 0x0181b880,
+ .name = "Davicom DM9161E",
+ .phy_id_mask = 0x0ffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_init = dm9161_config_init,
+ .config_aneg = dm9161_config_aneg,
+ .config_intr = dm9161_config_intr,
+ .handle_interrupt = dm9161_handle_interrupt,
+}, {
+ .phy_id = 0x0181b8b0,
+ .name = "Davicom DM9161B/C",
+ .phy_id_mask = 0x0ffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_init = dm9161_config_init,
+ .config_aneg = dm9161_config_aneg,
+ .config_intr = dm9161_config_intr,
+ .handle_interrupt = dm9161_handle_interrupt,
+}, {
+ .phy_id = 0x0181b8a0,
+ .name = "Davicom DM9161A",
+ .phy_id_mask = 0x0ffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_init = dm9161_config_init,
+ .config_aneg = dm9161_config_aneg,
+ .config_intr = dm9161_config_intr,
+ .handle_interrupt = dm9161_handle_interrupt,
+}, {
+ .phy_id = 0x00181b80,
+ .name = "Davicom DM9131",
+ .phy_id_mask = 0x0ffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_intr = dm9161_config_intr,
+ .handle_interrupt = dm9161_handle_interrupt,
+} };
+
+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..2657be7cc
--- /dev/null
+++ b/drivers/net/phy/dp83640.c
@@ -0,0 +1,1556 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the National Semiconductor DP83640 PHYTER
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ */
+
+#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
+#define MII_DP83640_MISR_ANC_INT 0x400
+#define MII_DP83640_MISR_DUP_INT 0x800
+#define MII_DP83640_MISR_SPD_INT 0x1000
+#define MII_DP83640_MISR_LINK_INT 0x2000
+#define MII_DP83640_MISR_INT_MASK (MII_DP83640_MISR_ANC_INT |\
+ MII_DP83640_MISR_DUP_INT |\
+ MII_DP83640_MISR_SPD_INT |\
+ MII_DP83640_MISR_LINK_INT)
+
+/* 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 mii_timestamper mii_ts;
+ 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:
+ /* Reject requests with unsupported flags */
+ if (rq->extts.flags & ~(PTP_ENABLE_FEATURE |
+ PTP_RISING_EDGE |
+ PTP_FALLING_EDGE |
+ PTP_STRICT_FLAGS))
+ return -EOPNOTSUPP;
+
+ /* Reject requests to enable time stamping on both edges. */
+ if ((rq->extts.flags & PTP_STRICT_FLAGS) &&
+ (rq->extts.flags & PTP_ENABLE_FEATURE) &&
+ (rq->extts.flags & PTP_EXTTS_EDGES) == PTP_EXTTS_EDGES)
+ return -EOPNOTSUPP;
+
+ 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:
+ /* Reject requests with unsupported flags */
+ if (rq->perout.flags)
+ return -EOPNOTSUPP;
+ 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) {
+ phydev_warn(phydev,
+ "expected to find an attached netdevice\n");
+ return;
+ }
+
+ if (on) {
+ if (dev_mc_add(phydev->attached_dev, status_frame_dst))
+ phydev_warn(phydev, "failed to add mc address\n");
+ } else {
+ if (dev_mc_del(phydev->attached_dev, status_frame_dst))
+ phydev_warn(phydev, "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 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_unlocked(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_entry(tmp, &clock->phylist, 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_entry(tmp, &clock->phylist, 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);
+ phydev_info(master, "master PTP_STS 0x%04hx\n", val);
+ val = ext_read(master, PAGE4, PTP_ESTS);
+ phydev_info(master, "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_entry(tmp, &clock->phylist, list) {
+ val = ext_read(tmp->phydev, PAGE4, PTP_STS);
+ phydev_info(tmp->phydev, "slave PTP_STS 0x%04hx\n", val);
+ val = ext_read(tmp->phydev, PAGE4, PTP_ESTS);
+ phydev_info(tmp->phydev, "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);
+ phydev_info(tmp->phydev, "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_entry(tmp, &clock->phylist, 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;
+ fallthrough;
+ case 2:
+ dp83640->edata.sec_lo = phy_txts->sec_lo;
+ fallthrough;
+ case 1:
+ dp83640->edata.ns_hi = phy_txts->ns_hi;
+ fallthrough;
+ 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_LEN 10
+
+static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
+{
+ struct ptp_header *hdr;
+ u8 msgtype;
+ u16 seqid;
+ u16 hash;
+
+ /* check sequenceID, messageType, 12 bit hash of offset 20-29 */
+
+ hdr = ptp_parse_header(skb, type);
+ if (!hdr)
+ return 0;
+
+ msgtype = ptp_get_msgtype(hdr, type);
+
+ if (rxts->msgtype != (msgtype & 0xf))
+ return 0;
+
+ seqid = be16_to_cpu(hdr->sequence_id);
+ if (rxts->seqid != seqid)
+ return 0;
+
+ hash = ether_crc(DP83640_PACKET_HASH_LEN,
+ (unsigned char *)&hdr->source_port_identity) >> 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(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 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_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) {
+ err = dp83640_ack_interrupt(phydev);
+ if (err)
+ 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);
+ 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);
+ err = phy_write(phydev, MII_DP83640_MISR, misr);
+ if (err)
+ return err;
+
+ return dp83640_ack_interrupt(phydev);
+ }
+}
+
+static irqreturn_t dp83640_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_DP83640_MISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_DP83640_MISR_INT_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
+{
+ struct dp83640_private *dp83640 =
+ container_of(mii_ts, struct dp83640_private, mii_ts);
+ struct hwtstamp_config cfg;
+ u16 txcfg0, rxcfg0;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ 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, dp83640->phydev, PAGE5, PTP_TXCFG0, txcfg0);
+ ext_write(0, dp83640->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(skb);
+ }
+
+ if (!skb_queue_empty(&dp83640->rx_queue))
+ schedule_delayed_work(&dp83640->ts_work, SKB_TIMESTAMP_TIMEOUT);
+}
+
+static bool dp83640_rxtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 =
+ container_of(mii_ts, struct dp83640_private, mii_ts);
+ 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(skb);
+ }
+
+ return true;
+}
+
+static void dp83640_txtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb;
+ struct dp83640_private *dp83640 =
+ container_of(mii_ts, struct dp83640_private, mii_ts);
+
+ switch (dp83640->hwts_tx_en) {
+
+ case HWTSTAMP_TX_ONESTEP_SYNC:
+ if (ptp_msg_is_sync(skb, type)) {
+ kfree_skb(skb);
+ return;
+ }
+ fallthrough;
+ 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 mii_timestamper *mii_ts,
+ struct ethtool_ts_info *info)
+{
+ struct dp83640_private *dp83640 =
+ container_of(mii_ts, struct dp83640_private, mii_ts);
+
+ 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 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;
+ dp83640->mii_ts.rxtstamp = dp83640_rxtstamp;
+ dp83640->mii_ts.txtstamp = dp83640_txtstamp;
+ dp83640->mii_ts.hwtstamp = dp83640_hwtstamp;
+ dp83640->mii_ts.ts_info = dp83640_ts_info;
+
+ 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->mii_ts = &dp83640->mii_ts;
+ 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;
+
+ phydev->mii_ts = NULL;
+
+ 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 struct phy_driver dp83640_driver = {
+ .phy_id = DP83640_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "NatSemi DP83640",
+ /* PHY_BASIC_FEATURES */
+ .probe = dp83640_probe,
+ .remove = dp83640_remove,
+ .soft_reset = dp83640_soft_reset,
+ .config_init = dp83640_config_init,
+ .config_intr = dp83640_config_intr,
+ .handle_interrupt = dp83640_handle_interrupt,
+};
+
+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..b7cb71817
--- /dev/null
+++ b/drivers/net/phy/dp83822.c
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for the Texas Instruments DP83822, DP83825 and DP83826 PHYs.
+ *
+ * Copyright (C) 2017 Texas Instruments Inc.
+ */
+
+#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 DP83825S_PHY_ID 0x2000a140
+#define DP83825I_PHY_ID 0x2000a150
+#define DP83825CM_PHY_ID 0x2000a160
+#define DP83825CS_PHY_ID 0x2000a170
+#define DP83826C_PHY_ID 0x2000a130
+#define DP83826NC_PHY_ID 0x2000a110
+
+#define DP83822_DEVADDR 0x1f
+
+#define MII_DP83822_CTRL_2 0x0a
+#define MII_DP83822_PHYSTS 0x10
+#define MII_DP83822_PHYSCR 0x11
+#define MII_DP83822_MISR1 0x12
+#define MII_DP83822_MISR2 0x13
+#define MII_DP83822_FCSCR 0x14
+#define MII_DP83822_RCSR 0x17
+#define MII_DP83822_RESET_CTRL 0x1f
+#define MII_DP83822_GENCFG 0x465
+#define MII_DP83822_SOR1 0x467
+
+/* GENCFG */
+#define DP83822_SIG_DET_LOW BIT(0)
+
+/* Control Register 2 bits */
+#define DP83822_FX_ENABLE BIT(14)
+
+#define DP83822_HW_RESET BIT(15)
+#define DP83822_SW_RESET BIT(14)
+
+/* PHY STS bits */
+#define DP83822_PHYSTS_DUPLEX BIT(2)
+#define DP83822_PHYSTS_10 BIT(1)
+#define DP83822_PHYSTS_LINK BIT(0)
+
+/* 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)
+
+/* RCSR bits */
+#define DP83822_RGMII_MODE_EN BIT(9)
+#define DP83822_RX_CLK_SHIFT BIT(12)
+#define DP83822_TX_CLK_SHIFT BIT(11)
+
+/* SOR1 mode */
+#define DP83822_STRAP_MODE1 0
+#define DP83822_STRAP_MODE2 BIT(0)
+#define DP83822_STRAP_MODE3 BIT(1)
+#define DP83822_STRAP_MODE4 GENMASK(1, 0)
+
+#define DP83822_COL_STRAP_MASK GENMASK(11, 10)
+#define DP83822_COL_SHIFT 10
+#define DP83822_RX_ER_STR_MASK GENMASK(9, 8)
+#define DP83822_RX_ER_SHIFT 8
+
+#define MII_DP83822_FIBER_ADVERTISE (ADVERTISED_TP | ADVERTISED_MII | \
+ ADVERTISED_FIBRE | \
+ ADVERTISED_Pause | ADVERTISED_Asym_Pause)
+
+struct dp83822_private {
+ bool fx_signal_det_low;
+ int fx_enabled;
+ u16 fx_sd_enable;
+};
+
+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;
+ }
+
+ /* Clear any pending WoL interrupt */
+ phy_read(phydev, MII_DP83822_MISR2);
+
+ value |= DP83822_WOL_EN | DP83822_WOL_INDICATION_SEL |
+ DP83822_WOL_CLR_INDICATION;
+
+ return phy_write_mmd(phydev, DP83822_DEVADDR,
+ MII_DP83822_WOL_CFG, value);
+ } else {
+ return phy_clear_bits_mmd(phydev, DP83822_DEVADDR,
+ MII_DP83822_WOL_CFG, DP83822_WOL_EN);
+ }
+}
+
+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)
+{
+ struct dp83822_private *dp83822 = phydev->priv;
+ 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_LINK_STAT_INT_EN |
+ DP83822_ENERGY_DET_INT_EN |
+ DP83822_LINK_QUAL_INT_EN);
+
+ /* Private data pointer is NULL on DP83825/26 */
+ if (!dp83822 || !dp83822->fx_enabled)
+ misr_status |= DP83822_ANEG_COMPLETE_INT_EN |
+ DP83822_DUP_MODE_CHANGE_INT_EN |
+ DP83822_SPEED_CHANGED_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_SLEEP_MODE_INT_EN |
+ DP83822_LB_FIFO_INT_EN |
+ DP83822_PAGE_RX_INT_EN |
+ DP83822_EEE_ERROR_CHANGE_INT_EN);
+
+ /* Private data pointer is NULL on DP83825/26 */
+ if (!dp83822 || !dp83822->fx_enabled)
+ misr_status |= DP83822_ANEG_ERR_INT_EN |
+ DP83822_WOL_PKT_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 irqreturn_t dp83822_handle_interrupt(struct phy_device *phydev)
+{
+ bool trigger_machine = false;
+ int irq_status;
+
+ /* The MISR1 and MISR2 registers are holding the interrupt status in
+ * the upper half (15:8), while the lower half (7:0) is used for
+ * controlling the interrupt enable state of those individual interrupt
+ * sources. To determine the possible interrupt sources, just read the
+ * MISR* register and use it directly to know which interrupts have
+ * been enabled previously or not.
+ */
+ irq_status = phy_read(phydev, MII_DP83822_MISR1);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ if (irq_status & ((irq_status & GENMASK(7, 0)) << 8))
+ trigger_machine = true;
+
+ irq_status = phy_read(phydev, MII_DP83822_MISR2);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ if (irq_status & ((irq_status & GENMASK(7, 0)) << 8))
+ trigger_machine = true;
+
+ if (!trigger_machine)
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int dp8382x_disable_wol(struct phy_device *phydev)
+{
+ return phy_clear_bits_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG,
+ DP83822_WOL_EN | DP83822_WOL_MAGIC_EN |
+ DP83822_WOL_SECURE_ON);
+}
+
+static int dp83822_read_status(struct phy_device *phydev)
+{
+ struct dp83822_private *dp83822 = phydev->priv;
+ int status = phy_read(phydev, MII_DP83822_PHYSTS);
+ int ctrl2;
+ int ret;
+
+ if (dp83822->fx_enabled) {
+ if (status & DP83822_PHYSTS_LINK) {
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ } else {
+ ctrl2 = phy_read(phydev, MII_DP83822_CTRL_2);
+ if (ctrl2 < 0)
+ return ctrl2;
+
+ if (!(ctrl2 & DP83822_FX_ENABLE)) {
+ ret = phy_write(phydev, MII_DP83822_CTRL_2,
+ DP83822_FX_ENABLE | ctrl2);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ if (status < 0)
+ return status;
+
+ if (status & DP83822_PHYSTS_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ if (status & DP83822_PHYSTS_10)
+ phydev->speed = SPEED_10;
+ else
+ phydev->speed = SPEED_100;
+
+ return 0;
+}
+
+static int dp83822_config_init(struct phy_device *phydev)
+{
+ struct dp83822_private *dp83822 = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int rgmii_delay;
+ s32 rx_int_delay;
+ s32 tx_int_delay;
+ int err = 0;
+ int bmcr;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0,
+ true);
+
+ if (rx_int_delay <= 0)
+ rgmii_delay = 0;
+ else
+ rgmii_delay = DP83822_RX_CLK_SHIFT;
+
+ tx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0,
+ false);
+ if (tx_int_delay <= 0)
+ rgmii_delay &= ~DP83822_TX_CLK_SHIFT;
+ else
+ rgmii_delay |= DP83822_TX_CLK_SHIFT;
+
+ if (rgmii_delay) {
+ err = phy_set_bits_mmd(phydev, DP83822_DEVADDR,
+ MII_DP83822_RCSR, rgmii_delay);
+ if (err)
+ return err;
+ }
+
+ phy_set_bits_mmd(phydev, DP83822_DEVADDR,
+ MII_DP83822_RCSR, DP83822_RGMII_MODE_EN);
+ } else {
+ phy_clear_bits_mmd(phydev, DP83822_DEVADDR,
+ MII_DP83822_RCSR, DP83822_RGMII_MODE_EN);
+ }
+
+ if (dp83822->fx_enabled) {
+ err = phy_modify(phydev, MII_DP83822_CTRL_2,
+ DP83822_FX_ENABLE, 1);
+ if (err < 0)
+ return err;
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->advertising);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
+ phydev->advertising);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
+ phydev->advertising);
+
+ /* Auto neg is not supported in fiber mode */
+ bmcr = phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ if (bmcr & BMCR_ANENABLE) {
+ err = phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+ if (err < 0)
+ return err;
+ }
+ phydev->autoneg = AUTONEG_DISABLE;
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->advertising);
+
+ /* Setup fiber advertisement */
+ err = phy_modify_changed(phydev, MII_ADVERTISE,
+ MII_DP83822_FIBER_ADVERTISE,
+ MII_DP83822_FIBER_ADVERTISE);
+
+ if (err < 0)
+ return err;
+
+ if (dp83822->fx_signal_det_low) {
+ err = phy_set_bits_mmd(phydev, DP83822_DEVADDR,
+ MII_DP83822_GENCFG,
+ DP83822_SIG_DET_LOW);
+ if (err)
+ return err;
+ }
+ }
+ return dp8382x_disable_wol(phydev);
+}
+
+static int dp8382x_config_init(struct phy_device *phydev)
+{
+ return dp8382x_disable_wol(phydev);
+}
+
+static int dp83822_phy_reset(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_write(phydev, MII_DP83822_RESET_CTRL, DP83822_SW_RESET);
+ if (err < 0)
+ return err;
+
+ return phydev->drv->config_init(phydev);
+}
+
+#ifdef CONFIG_OF_MDIO
+static int dp83822_of_init(struct phy_device *phydev)
+{
+ struct dp83822_private *dp83822 = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+
+ /* Signal detection for the PHY is only enabled if the FX_EN and the
+ * SD_EN pins are strapped. Signal detection can only enabled if FX_EN
+ * is strapped otherwise signal detection is disabled for the PHY.
+ */
+ if (dp83822->fx_enabled && dp83822->fx_sd_enable)
+ dp83822->fx_signal_det_low = device_property_present(dev,
+ "ti,link-loss-low");
+ if (!dp83822->fx_enabled)
+ dp83822->fx_enabled = device_property_present(dev,
+ "ti,fiber-mode");
+
+ return 0;
+}
+#else
+static int dp83822_of_init(struct phy_device *phydev)
+{
+ return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int dp83822_read_straps(struct phy_device *phydev)
+{
+ struct dp83822_private *dp83822 = phydev->priv;
+ int fx_enabled, fx_sd_enable;
+ int val;
+
+ val = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_SOR1);
+ if (val < 0)
+ return val;
+
+ phydev_dbg(phydev, "SOR1 strap register: 0x%04x\n", val);
+
+ fx_enabled = (val & DP83822_COL_STRAP_MASK) >> DP83822_COL_SHIFT;
+ if (fx_enabled == DP83822_STRAP_MODE2 ||
+ fx_enabled == DP83822_STRAP_MODE3)
+ dp83822->fx_enabled = 1;
+
+ if (dp83822->fx_enabled) {
+ fx_sd_enable = (val & DP83822_RX_ER_STR_MASK) >> DP83822_RX_ER_SHIFT;
+ if (fx_sd_enable == DP83822_STRAP_MODE3 ||
+ fx_sd_enable == DP83822_STRAP_MODE4)
+ dp83822->fx_sd_enable = 1;
+ }
+
+ return 0;
+}
+
+static int dp83822_probe(struct phy_device *phydev)
+{
+ struct dp83822_private *dp83822;
+ int ret;
+
+ dp83822 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83822),
+ GFP_KERNEL);
+ if (!dp83822)
+ return -ENOMEM;
+
+ phydev->priv = dp83822;
+
+ ret = dp83822_read_straps(phydev);
+ if (ret)
+ return ret;
+
+ dp83822_of_init(phydev);
+
+ if (dp83822->fx_enabled)
+ phydev->port = PORT_FIBRE;
+
+ 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;
+}
+
+#define DP83822_PHY_DRIVER(_id, _name) \
+ { \
+ PHY_ID_MATCH_MODEL(_id), \
+ .name = (_name), \
+ /* PHY_BASIC_FEATURES */ \
+ .probe = dp83822_probe, \
+ .soft_reset = dp83822_phy_reset, \
+ .config_init = dp83822_config_init, \
+ .read_status = dp83822_read_status, \
+ .get_wol = dp83822_get_wol, \
+ .set_wol = dp83822_set_wol, \
+ .config_intr = dp83822_config_intr, \
+ .handle_interrupt = dp83822_handle_interrupt, \
+ .suspend = dp83822_suspend, \
+ .resume = dp83822_resume, \
+ }
+
+#define DP8382X_PHY_DRIVER(_id, _name) \
+ { \
+ PHY_ID_MATCH_MODEL(_id), \
+ .name = (_name), \
+ /* PHY_BASIC_FEATURES */ \
+ .soft_reset = dp83822_phy_reset, \
+ .config_init = dp8382x_config_init, \
+ .get_wol = dp83822_get_wol, \
+ .set_wol = dp83822_set_wol, \
+ .config_intr = dp83822_config_intr, \
+ .handle_interrupt = dp83822_handle_interrupt, \
+ .suspend = dp83822_suspend, \
+ .resume = dp83822_resume, \
+ }
+
+static struct phy_driver dp83822_driver[] = {
+ DP83822_PHY_DRIVER(DP83822_PHY_ID, "TI DP83822"),
+ DP8382X_PHY_DRIVER(DP83825I_PHY_ID, "TI DP83825I"),
+ DP8382X_PHY_DRIVER(DP83826C_PHY_ID, "TI DP83826C"),
+ DP8382X_PHY_DRIVER(DP83826NC_PHY_ID, "TI DP83826NC"),
+ DP8382X_PHY_DRIVER(DP83825S_PHY_ID, "TI DP83825S"),
+ DP8382X_PHY_DRIVER(DP83825CM_PHY_ID, "TI DP83825M"),
+ DP8382X_PHY_DRIVER(DP83825CS_PHY_ID, "TI DP83825CS"),
+};
+module_phy_driver(dp83822_driver);
+
+static struct mdio_device_id __maybe_unused dp83822_tbl[] = {
+ { DP83822_PHY_ID, 0xfffffff0 },
+ { DP83825I_PHY_ID, 0xfffffff0 },
+ { DP83826C_PHY_ID, 0xfffffff0 },
+ { DP83826NC_PHY_ID, 0xfffffff0 },
+ { DP83825S_PHY_ID, 0xfffffff0 },
+ { DP83825CM_PHY_ID, 0xfffffff0 },
+ { DP83825CS_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 v2");
diff --git a/drivers/net/phy/dp83848.c b/drivers/net/phy/dp83848.c
new file mode 100644
index 000000000..937061acf
--- /dev/null
+++ b/drivers/net/phy/dp83848.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DP83848 PHY
+ *
+ * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#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)
+
+#define DP83848_MISR_RHF_INT BIT(8)
+#define DP83848_MISR_FHF_INT BIT(9)
+#define DP83848_MISR_ANC_INT BIT(10)
+#define DP83848_MISR_DUP_INT BIT(11)
+#define DP83848_MISR_SPD_INT BIT(12)
+#define DP83848_MISR_LINK_INT BIT(13)
+#define DP83848_MISR_ED_INT BIT(14)
+
+#define DP83848_INT_MASK \
+ (DP83848_MISR_ANC_INT | \
+ DP83848_MISR_DUP_INT | \
+ DP83848_MISR_SPD_INT | \
+ DP83848_MISR_LINK_INT)
+
+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) {
+ ret = dp83848_ack_interrupt(phydev);
+ if (ret)
+ return ret;
+
+ control |= DP83848_MICR_INT_OE;
+ control |= DP83848_MICR_INTEN;
+
+ ret = phy_write(phydev, DP83848_MISR, DP83848_INT_EN_MASK);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, DP83848_MICR, control);
+ } else {
+ control &= ~DP83848_MICR_INTEN;
+ ret = phy_write(phydev, DP83848_MICR, control);
+ if (ret)
+ return ret;
+
+ ret = dp83848_ack_interrupt(phydev);
+ }
+
+ return ret;
+}
+
+static irqreturn_t dp83848_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, DP83848_MISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & DP83848_INT_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int dp83848_config_init(struct phy_device *phydev)
+{
+ int val;
+
+ /* 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, \
+ /* PHY_BASIC_FEATURES */ \
+ \
+ .soft_reset = genphy_soft_reset, \
+ .config_init = _config_init, \
+ .suspend = genphy_suspend, \
+ .resume = genphy_resume, \
+ \
+ /* IRQ related */ \
+ .config_intr = dp83848_config_intr, \
+ .handle_interrupt = dp83848_handle_interrupt, \
+ }
+
+static struct phy_driver dp83848_driver[] = {
+ DP83848_PHY_DRIVER(TI_DP83848C_PHY_ID, "TI DP83848C 10/100 Mbps PHY",
+ NULL),
+ DP83848_PHY_DRIVER(NS_DP83848C_PHY_ID, "NS DP83848C 10/100 Mbps PHY",
+ NULL),
+ 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",
+ NULL),
+};
+module_phy_driver(dp83848_driver);
+
+MODULE_DESCRIPTION("Texas Instruments DP83848 PHY driver");
+MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
new file mode 100644
index 000000000..e397e7d64
--- /dev/null
+++ b/drivers/net/phy/dp83867.c
@@ -0,0 +1,1063 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for the Texas Instruments DP83867 PHY
+ *
+ * Copyright (C) 2015 Texas Instruments Inc.
+ */
+
+#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 <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/bitfield.h>
+#include <linux/nvmem-consumer.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_PHYSTS 0x11
+#define MII_DP83867_MICR 0x12
+#define MII_DP83867_ISR 0x13
+#define DP83867_CFG2 0x14
+#define DP83867_LEDCR1 0x18
+#define DP83867_LEDCR2 0x19
+#define DP83867_CFG3 0x1e
+#define DP83867_CTRL 0x1f
+
+/* Extended Registers */
+#define DP83867_FLD_THR_CFG 0x002e
+#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_STRAP_STS2 0x006f
+#define DP83867_RGMIIDCTL 0x0086
+#define DP83867_DSP_FFE_CFG 0x012c
+#define DP83867_RXFCFG 0x0134
+#define DP83867_RXFPMD1 0x0136
+#define DP83867_RXFPMD2 0x0137
+#define DP83867_RXFPMD3 0x0138
+#define DP83867_RXFSOP1 0x0139
+#define DP83867_RXFSOP2 0x013A
+#define DP83867_RXFSOP3 0x013B
+#define DP83867_IO_MUX_CFG 0x0170
+#define DP83867_SGMIICTL 0x00D3
+#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)
+
+/* SGMIICTL bits */
+#define DP83867_SGMII_TYPE BIT(14)
+
+/* RXFCFG bits*/
+#define DP83867_WOL_MAGIC_EN BIT(0)
+#define DP83867_WOL_BCAST_EN BIT(2)
+#define DP83867_WOL_UCAST_EN BIT(4)
+#define DP83867_WOL_SEC_EN BIT(5)
+#define DP83867_WOL_ENH_MAC BIT(7)
+
+/* STRAP_STS1 bits */
+#define DP83867_STRAP_STS1_RESERVED BIT(11)
+
+/* STRAP_STS2 bits */
+#define DP83867_STRAP_STS2_CLK_SKEW_TX_MASK GENMASK(6, 4)
+#define DP83867_STRAP_STS2_CLK_SKEW_TX_SHIFT 4
+#define DP83867_STRAP_STS2_CLK_SKEW_RX_MASK GENMASK(2, 0)
+#define DP83867_STRAP_STS2_CLK_SKEW_RX_SHIFT 0
+#define DP83867_STRAP_STS2_CLK_SKEW_NONE BIT(2)
+#define DP83867_STRAP_STS2_STRAP_FLD BIT(10)
+
+/* PHY CTRL bits */
+#define DP83867_PHYCR_TX_FIFO_DEPTH_SHIFT 14
+#define DP83867_PHYCR_RX_FIFO_DEPTH_SHIFT 12
+#define DP83867_PHYCR_FIFO_DEPTH_MAX 0x03
+#define DP83867_PHYCR_TX_FIFO_DEPTH_MASK GENMASK(15, 14)
+#define DP83867_PHYCR_RX_FIFO_DEPTH_MASK GENMASK(13, 12)
+#define DP83867_PHYCR_RESERVED_MASK BIT(11)
+#define DP83867_PHYCR_FORCE_LINK_GOOD BIT(10)
+
+/* RGMIIDCTL bits */
+#define DP83867_RGMII_TX_CLK_DELAY_MAX 0xf
+#define DP83867_RGMII_TX_CLK_DELAY_SHIFT 4
+#define DP83867_RGMII_TX_CLK_DELAY_INV (DP83867_RGMII_TX_CLK_DELAY_MAX + 1)
+#define DP83867_RGMII_RX_CLK_DELAY_MAX 0xf
+#define DP83867_RGMII_RX_CLK_DELAY_SHIFT 0
+#define DP83867_RGMII_RX_CLK_DELAY_INV (DP83867_RGMII_RX_CLK_DELAY_MAX + 1)
+
+/* IO_MUX_CFG bits */
+#define DP83867_IO_MUX_CFG_IO_IMPEDANCE_MASK 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_DISABLE BIT(6)
+#define DP83867_IO_MUX_CFG_CLK_O_SEL_MASK (0x1f << 8)
+#define DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT 8
+
+/* PHY STS bits */
+#define DP83867_PHYSTS_1000 BIT(15)
+#define DP83867_PHYSTS_100 BIT(14)
+#define DP83867_PHYSTS_DUPLEX BIT(13)
+#define DP83867_PHYSTS_LINK BIT(10)
+
+/* CFG2 bits */
+#define DP83867_DOWNSHIFT_EN (BIT(8) | BIT(9))
+#define DP83867_DOWNSHIFT_ATTEMPT_MASK (BIT(10) | BIT(11))
+#define DP83867_DOWNSHIFT_1_COUNT_VAL 0
+#define DP83867_DOWNSHIFT_2_COUNT_VAL 1
+#define DP83867_DOWNSHIFT_4_COUNT_VAL 2
+#define DP83867_DOWNSHIFT_8_COUNT_VAL 3
+#define DP83867_DOWNSHIFT_1_COUNT 1
+#define DP83867_DOWNSHIFT_2_COUNT 2
+#define DP83867_DOWNSHIFT_4_COUNT 4
+#define DP83867_DOWNSHIFT_8_COUNT 8
+#define DP83867_SGMII_AUTONEG_EN BIT(7)
+
+/* 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)
+
+/* FLD_THR_CFG */
+#define DP83867_FLD_THR_CFG_ENERGY_LOST_THR_MASK 0x7
+
+#define DP83867_LED_COUNT 4
+
+/* LED_DRV bits */
+#define DP83867_LED_DRV_EN(x) BIT((x) * 4)
+#define DP83867_LED_DRV_VAL(x) BIT((x) * 4 + 1)
+
+enum {
+ DP83867_PORT_MIRROING_KEEP,
+ DP83867_PORT_MIRROING_EN,
+ DP83867_PORT_MIRROING_DIS,
+};
+
+struct dp83867_private {
+ u32 rx_id_delay;
+ u32 tx_id_delay;
+ u32 tx_fifo_depth;
+ u32 rx_fifo_depth;
+ int io_impedance;
+ int port_mirroring;
+ bool rxctrl_strap_quirk;
+ bool set_clk_output;
+ u32 clk_output_sel;
+ bool sgmii_ref_clk_en;
+};
+
+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_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ u16 val_rxcfg, val_micr;
+ const u8 *mac;
+
+ val_rxcfg = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RXFCFG);
+ val_micr = phy_read(phydev, MII_DP83867_MICR);
+
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE | WAKE_UCAST |
+ WAKE_BCAST)) {
+ val_rxcfg |= DP83867_WOL_ENH_MAC;
+ val_micr |= MII_DP83867_MICR_WOL_INT_EN;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ mac = (const u8 *)ndev->dev_addr;
+
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
+
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFPMD1,
+ (mac[1] << 8 | mac[0]));
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFPMD2,
+ (mac[3] << 8 | mac[2]));
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFPMD3,
+ (mac[5] << 8 | mac[4]));
+
+ val_rxcfg |= DP83867_WOL_MAGIC_EN;
+ } else {
+ val_rxcfg &= ~DP83867_WOL_MAGIC_EN;
+ }
+
+ if (wol->wolopts & WAKE_MAGICSECURE) {
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFSOP1,
+ (wol->sopass[1] << 8) | wol->sopass[0]);
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFSOP2,
+ (wol->sopass[3] << 8) | wol->sopass[2]);
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFSOP3,
+ (wol->sopass[5] << 8) | wol->sopass[4]);
+
+ val_rxcfg |= DP83867_WOL_SEC_EN;
+ } else {
+ val_rxcfg &= ~DP83867_WOL_SEC_EN;
+ }
+
+ if (wol->wolopts & WAKE_UCAST)
+ val_rxcfg |= DP83867_WOL_UCAST_EN;
+ else
+ val_rxcfg &= ~DP83867_WOL_UCAST_EN;
+
+ if (wol->wolopts & WAKE_BCAST)
+ val_rxcfg |= DP83867_WOL_BCAST_EN;
+ else
+ val_rxcfg &= ~DP83867_WOL_BCAST_EN;
+ } else {
+ val_rxcfg &= ~DP83867_WOL_ENH_MAC;
+ val_micr &= ~MII_DP83867_MICR_WOL_INT_EN;
+ }
+
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFCFG, val_rxcfg);
+ phy_write(phydev, MII_DP83867_MICR, val_micr);
+
+ return 0;
+}
+
+static void dp83867_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ u16 value, sopass_val;
+
+ wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC |
+ WAKE_MAGICSECURE);
+ wol->wolopts = 0;
+
+ value = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RXFCFG);
+
+ if (value & DP83867_WOL_UCAST_EN)
+ wol->wolopts |= WAKE_UCAST;
+
+ if (value & DP83867_WOL_BCAST_EN)
+ wol->wolopts |= WAKE_BCAST;
+
+ if (value & DP83867_WOL_MAGIC_EN)
+ wol->wolopts |= WAKE_MAGIC;
+
+ if (value & DP83867_WOL_SEC_EN) {
+ sopass_val = phy_read_mmd(phydev, DP83867_DEVADDR,
+ DP83867_RXFSOP1);
+ wol->sopass[0] = (sopass_val & 0xff);
+ wol->sopass[1] = (sopass_val >> 8);
+
+ sopass_val = phy_read_mmd(phydev, DP83867_DEVADDR,
+ DP83867_RXFSOP2);
+ wol->sopass[2] = (sopass_val & 0xff);
+ wol->sopass[3] = (sopass_val >> 8);
+
+ sopass_val = phy_read_mmd(phydev, DP83867_DEVADDR,
+ DP83867_RXFSOP3);
+ wol->sopass[4] = (sopass_val & 0xff);
+ wol->sopass[5] = (sopass_val >> 8);
+
+ wol->wolopts |= WAKE_MAGICSECURE;
+ }
+
+ if (!(value & DP83867_WOL_ENH_MAC))
+ wol->wolopts = 0;
+}
+
+static int dp83867_config_intr(struct phy_device *phydev)
+{
+ int micr_status, err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = dp83867_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ 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);
+
+ err = phy_write(phydev, MII_DP83867_MICR, micr_status);
+ } else {
+ micr_status = 0x0;
+ err = phy_write(phydev, MII_DP83867_MICR, micr_status);
+ if (err)
+ return err;
+
+ err = dp83867_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t dp83867_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, irq_enabled;
+
+ irq_status = phy_read(phydev, MII_DP83867_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_enabled = phy_read(phydev, MII_DP83867_MICR);
+ if (irq_enabled < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & irq_enabled))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int dp83867_read_status(struct phy_device *phydev)
+{
+ int status = phy_read(phydev, MII_DP83867_PHYSTS);
+ int ret;
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ if (status < 0)
+ return status;
+
+ if (status & DP83867_PHYSTS_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ if (status & DP83867_PHYSTS_1000)
+ phydev->speed = SPEED_1000;
+ else if (status & DP83867_PHYSTS_100)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_10;
+
+ return 0;
+}
+
+static int dp83867_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val, cnt, enable, count;
+
+ val = phy_read(phydev, DP83867_CFG2);
+ if (val < 0)
+ return val;
+
+ enable = FIELD_GET(DP83867_DOWNSHIFT_EN, val);
+ cnt = FIELD_GET(DP83867_DOWNSHIFT_ATTEMPT_MASK, val);
+
+ switch (cnt) {
+ case DP83867_DOWNSHIFT_1_COUNT_VAL:
+ count = DP83867_DOWNSHIFT_1_COUNT;
+ break;
+ case DP83867_DOWNSHIFT_2_COUNT_VAL:
+ count = DP83867_DOWNSHIFT_2_COUNT;
+ break;
+ case DP83867_DOWNSHIFT_4_COUNT_VAL:
+ count = DP83867_DOWNSHIFT_4_COUNT;
+ break;
+ case DP83867_DOWNSHIFT_8_COUNT_VAL:
+ count = DP83867_DOWNSHIFT_8_COUNT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *data = enable ? count : DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int dp83867_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ int val, count;
+
+ if (cnt > DP83867_DOWNSHIFT_8_COUNT)
+ return -E2BIG;
+
+ if (!cnt)
+ return phy_clear_bits(phydev, DP83867_CFG2,
+ DP83867_DOWNSHIFT_EN);
+
+ switch (cnt) {
+ case DP83867_DOWNSHIFT_1_COUNT:
+ count = DP83867_DOWNSHIFT_1_COUNT_VAL;
+ break;
+ case DP83867_DOWNSHIFT_2_COUNT:
+ count = DP83867_DOWNSHIFT_2_COUNT_VAL;
+ break;
+ case DP83867_DOWNSHIFT_4_COUNT:
+ count = DP83867_DOWNSHIFT_4_COUNT_VAL;
+ break;
+ case DP83867_DOWNSHIFT_8_COUNT:
+ count = DP83867_DOWNSHIFT_8_COUNT_VAL;
+ break;
+ default:
+ phydev_err(phydev,
+ "Downshift count must be 1, 2, 4 or 8\n");
+ return -EINVAL;
+ }
+
+ val = DP83867_DOWNSHIFT_EN;
+ val |= FIELD_PREP(DP83867_DOWNSHIFT_ATTEMPT_MASK, count);
+
+ return phy_modify(phydev, DP83867_CFG2,
+ DP83867_DOWNSHIFT_EN | DP83867_DOWNSHIFT_ATTEMPT_MASK,
+ val);
+}
+
+static int dp83867_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return dp83867_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int dp83867_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return dp83867_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int dp83867_config_port_mirroring(struct phy_device *phydev)
+{
+ struct dp83867_private *dp83867 = phydev->priv;
+
+ if (dp83867->port_mirroring == DP83867_PORT_MIRROING_EN)
+ phy_set_bits_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4,
+ DP83867_CFG4_PORT_MIRROR_EN);
+ else
+ phy_clear_bits_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4,
+ DP83867_CFG4_PORT_MIRROR_EN);
+ return 0;
+}
+
+static int dp83867_verify_rgmii_cfg(struct phy_device *phydev)
+{
+ struct dp83867_private *dp83867 = phydev->priv;
+
+ /* Existing behavior was to use default pin strapping delay in rgmii
+ * mode, but rgmii should have meant no delay. Warn existing users.
+ */
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII) {
+ const u16 val = phy_read_mmd(phydev, DP83867_DEVADDR,
+ DP83867_STRAP_STS2);
+ const u16 txskew = (val & DP83867_STRAP_STS2_CLK_SKEW_TX_MASK) >>
+ DP83867_STRAP_STS2_CLK_SKEW_TX_SHIFT;
+ const u16 rxskew = (val & DP83867_STRAP_STS2_CLK_SKEW_RX_MASK) >>
+ DP83867_STRAP_STS2_CLK_SKEW_RX_SHIFT;
+
+ if (txskew != DP83867_STRAP_STS2_CLK_SKEW_NONE ||
+ rxskew != DP83867_STRAP_STS2_CLK_SKEW_NONE)
+ phydev_warn(phydev,
+ "PHY has delays via pin strapping, but phy-mode = 'rgmii'\n"
+ "Should be 'rgmii-id' to use internal delays txskew:%x rxskew:%x\n",
+ txskew, rxskew);
+ }
+
+ /* RX delay *must* be specified if internal delay of RX is used. */
+ if ((phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) &&
+ dp83867->rx_id_delay == DP83867_RGMII_RX_CLK_DELAY_INV) {
+ phydev_err(phydev, "ti,rx-internal-delay must be specified\n");
+ return -EINVAL;
+ }
+
+ /* TX delay *must* be specified if internal delay of TX is used. */
+ if ((phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) &&
+ dp83867->tx_id_delay == DP83867_RGMII_TX_CLK_DELAY_INV) {
+ phydev_err(phydev, "ti,tx-internal-delay must be specified\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+static int dp83867_of_init_io_impedance(struct phy_device *phydev)
+{
+ struct dp83867_private *dp83867 = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ struct device_node *of_node = dev->of_node;
+ struct nvmem_cell *cell;
+ u8 *buf, val;
+ int ret;
+
+ cell = of_nvmem_cell_get(of_node, "io_impedance_ctrl");
+ if (IS_ERR(cell)) {
+ ret = PTR_ERR(cell);
+ if (ret != -ENOENT && ret != -EOPNOTSUPP)
+ return phydev_err_probe(phydev, ret,
+ "failed to get nvmem cell io_impedance_ctrl\n");
+
+ /* If no nvmem cell, check for the boolean properties. */
+ 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;
+ else
+ dp83867->io_impedance = -1; /* leave at default */
+
+ return 0;
+ }
+
+ buf = nvmem_cell_read(cell, NULL);
+ nvmem_cell_put(cell);
+
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ val = *buf;
+ kfree(buf);
+
+ if ((val & DP83867_IO_MUX_CFG_IO_IMPEDANCE_MASK) != val) {
+ phydev_err(phydev, "nvmem cell 'io_impedance_ctrl' contents out of range\n");
+ return -ERANGE;
+ }
+ dp83867->io_impedance = val;
+
+ return 0;
+}
+
+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;
+
+ /* Optional configuration */
+ ret = of_property_read_u32(of_node, "ti,clk-output-sel",
+ &dp83867->clk_output_sel);
+ /* If not set, keep default */
+ if (!ret) {
+ dp83867->set_clk_output = true;
+ /* Valid values are 0 to DP83867_CLK_O_SEL_REF_CLK or
+ * DP83867_CLK_O_SEL_OFF.
+ */
+ if (dp83867->clk_output_sel > DP83867_CLK_O_SEL_REF_CLK &&
+ dp83867->clk_output_sel != DP83867_CLK_O_SEL_OFF) {
+ phydev_err(phydev, "ti,clk-output-sel value %u out of range\n",
+ dp83867->clk_output_sel);
+ return -EINVAL;
+ }
+ }
+
+ ret = dp83867_of_init_io_impedance(phydev);
+ if (ret)
+ return ret;
+
+ dp83867->rxctrl_strap_quirk = of_property_read_bool(of_node,
+ "ti,dp83867-rxctrl-strap-quirk");
+
+ dp83867->sgmii_ref_clk_en = of_property_read_bool(of_node,
+ "ti,sgmii-ref-clock-output-enable");
+
+ dp83867->rx_id_delay = DP83867_RGMII_RX_CLK_DELAY_INV;
+ ret = of_property_read_u32(of_node, "ti,rx-internal-delay",
+ &dp83867->rx_id_delay);
+ if (!ret && dp83867->rx_id_delay > DP83867_RGMII_RX_CLK_DELAY_MAX) {
+ phydev_err(phydev,
+ "ti,rx-internal-delay value of %u out of range\n",
+ dp83867->rx_id_delay);
+ return -EINVAL;
+ }
+
+ dp83867->tx_id_delay = DP83867_RGMII_TX_CLK_DELAY_INV;
+ ret = of_property_read_u32(of_node, "ti,tx-internal-delay",
+ &dp83867->tx_id_delay);
+ if (!ret && dp83867->tx_id_delay > DP83867_RGMII_TX_CLK_DELAY_MAX) {
+ phydev_err(phydev,
+ "ti,tx-internal-delay value of %u out of range\n",
+ dp83867->tx_id_delay);
+ return -EINVAL;
+ }
+
+ 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;
+
+ ret = of_property_read_u32(of_node, "ti,fifo-depth",
+ &dp83867->tx_fifo_depth);
+ if (ret) {
+ ret = of_property_read_u32(of_node, "tx-fifo-depth",
+ &dp83867->tx_fifo_depth);
+ if (ret)
+ dp83867->tx_fifo_depth =
+ DP83867_PHYCR_FIFO_DEPTH_4_B_NIB;
+ }
+
+ if (dp83867->tx_fifo_depth > DP83867_PHYCR_FIFO_DEPTH_MAX) {
+ phydev_err(phydev, "tx-fifo-depth value %u out of range\n",
+ dp83867->tx_fifo_depth);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(of_node, "rx-fifo-depth",
+ &dp83867->rx_fifo_depth);
+ if (ret)
+ dp83867->rx_fifo_depth = DP83867_PHYCR_FIFO_DEPTH_4_B_NIB;
+
+ if (dp83867->rx_fifo_depth > DP83867_PHYCR_FIFO_DEPTH_MAX) {
+ phydev_err(phydev, "rx-fifo-depth value %u out of range\n",
+ dp83867->rx_fifo_depth);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#else
+static int dp83867_of_init(struct phy_device *phydev)
+{
+ struct dp83867_private *dp83867 = phydev->priv;
+ u16 delay;
+
+ /* For non-OF device, the RX and TX ID values are either strapped
+ * or take from default value. So, we init RX & TX ID values here
+ * so that the RGMIIDCTL is configured correctly later in
+ * dp83867_config_init();
+ */
+ delay = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIIDCTL);
+ dp83867->rx_id_delay = delay & DP83867_RGMII_RX_CLK_DELAY_MAX;
+ dp83867->tx_id_delay = (delay >> DP83867_RGMII_TX_CLK_DELAY_SHIFT) &
+ DP83867_RGMII_TX_CLK_DELAY_MAX;
+
+ /* Per datasheet, IO impedance is default to 50-ohm, so we set the
+ * same here or else the default '0' means highest IO impedance
+ * which is wrong.
+ */
+ dp83867->io_impedance = DP83867_IO_MUX_CFG_IO_IMPEDANCE_MIN / 2;
+
+ /* For non-OF device, the RX and TX FIFO depths are taken from
+ * default value. So, we init RX & TX FIFO depths here
+ * so that it is configured correctly later in dp83867_config_init();
+ */
+ dp83867->tx_fifo_depth = DP83867_PHYCR_FIFO_DEPTH_4_B_NIB;
+ dp83867->rx_fifo_depth = DP83867_PHYCR_FIFO_DEPTH_4_B_NIB;
+
+ return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int dp83867_suspend(struct phy_device *phydev)
+{
+ /* Disable PHY Interrupts */
+ if (phy_interrupt_is_valid(phydev)) {
+ phydev->interrupts = PHY_INTERRUPT_DISABLED;
+ dp83867_config_intr(phydev);
+ }
+
+ return genphy_suspend(phydev);
+}
+
+static int dp83867_resume(struct phy_device *phydev)
+{
+ /* Enable PHY Interrupts */
+ if (phy_interrupt_is_valid(phydev)) {
+ phydev->interrupts = PHY_INTERRUPT_ENABLED;
+ dp83867_config_intr(phydev);
+ }
+
+ genphy_resume(phydev);
+
+ return 0;
+}
+
+static int dp83867_probe(struct phy_device *phydev)
+{
+ struct dp83867_private *dp83867;
+
+ dp83867 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83867),
+ GFP_KERNEL);
+ if (!dp83867)
+ return -ENOMEM;
+
+ phydev->priv = dp83867;
+
+ return dp83867_of_init(phydev);
+}
+
+static int dp83867_config_init(struct phy_device *phydev)
+{
+ struct dp83867_private *dp83867 = phydev->priv;
+ int ret, val, bs;
+ u16 delay;
+
+ /* Force speed optimization for the PHY even if it strapped */
+ ret = phy_modify(phydev, DP83867_CFG2, DP83867_DOWNSHIFT_EN,
+ DP83867_DOWNSHIFT_EN);
+ if (ret)
+ return ret;
+
+ ret = dp83867_verify_rgmii_cfg(phydev);
+ if (ret)
+ return ret;
+
+ /* RX_DV/RX_CTRL strapped in mode 1 or mode 2 workaround */
+ if (dp83867->rxctrl_strap_quirk)
+ phy_clear_bits_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4,
+ BIT(7));
+
+ bs = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_STRAP_STS2);
+ if (bs & DP83867_STRAP_STS2_STRAP_FLD) {
+ /* When using strap to enable FLD, the ENERGY_LOST_FLD_THR will
+ * be set to 0x2. This may causes the PHY link to be unstable -
+ * the default value 0x1 need to be restored.
+ */
+ ret = phy_modify_mmd(phydev, DP83867_DEVADDR,
+ DP83867_FLD_THR_CFG,
+ DP83867_FLD_THR_CFG_ENERGY_LOST_THR_MASK,
+ 0x1);
+ if (ret)
+ return ret;
+ }
+
+ if (phy_interface_is_rgmii(phydev) ||
+ phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ val = phy_read(phydev, MII_DP83867_PHYCTRL);
+ if (val < 0)
+ return val;
+
+ val &= ~DP83867_PHYCR_TX_FIFO_DEPTH_MASK;
+ val |= (dp83867->tx_fifo_depth <<
+ DP83867_PHYCR_TX_FIFO_DEPTH_SHIFT);
+
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ val &= ~DP83867_PHYCR_RX_FIFO_DEPTH_MASK;
+ val |= (dp83867->rx_fifo_depth <<
+ DP83867_PHYCR_RX_FIFO_DEPTH_SHIFT);
+ }
+
+ ret = phy_write(phydev, MII_DP83867_PHYCTRL, val);
+ if (ret)
+ return ret;
+ }
+
+ if (phy_interface_is_rgmii(phydev)) {
+ val = phy_read(phydev, MII_DP83867_PHYCTRL);
+ if (val < 0)
+ return val;
+
+ /* 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;
+
+ /* If rgmii mode with no internal delay is selected, we do NOT use
+ * aligned mode as one might expect. Instead we use the PHY's default
+ * based on pin strapping. And the "mode 0" default is to *use*
+ * internal delay with a value of 7 (2.00 ns).
+ *
+ * Set up RGMII delays
+ */
+ val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL);
+
+ val &= ~(DP83867_RGMII_TX_CLK_DELAY_EN | DP83867_RGMII_RX_CLK_DELAY_EN);
+ 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 = 0;
+ if (dp83867->rx_id_delay != DP83867_RGMII_RX_CLK_DELAY_INV)
+ delay |= dp83867->rx_id_delay;
+ if (dp83867->tx_id_delay != DP83867_RGMII_TX_CLK_DELAY_INV)
+ delay |= dp83867->tx_id_delay <<
+ DP83867_RGMII_TX_CLK_DELAY_SHIFT;
+
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIIDCTL,
+ delay);
+ }
+
+ /* If specified, set io impedance */
+ if (dp83867->io_impedance >= 0)
+ phy_modify_mmd(phydev, DP83867_DEVADDR, DP83867_IO_MUX_CFG,
+ DP83867_IO_MUX_CFG_IO_IMPEDANCE_MASK,
+ dp83867->io_impedance);
+
+ 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.
+ */
+ ret = phy_modify_mmd(phydev, DP83867_DEVADDR,
+ DP83867_10M_SGMII_CFG,
+ DP83867_10M_SGMII_RATE_ADAPT_MASK,
+ 0);
+ 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.
+ */
+ ret = phy_modify_mmd(phydev, DP83867_DEVADDR,
+ DP83867_CFG4,
+ DP83867_CFG4_SGMII_ANEG_MASK,
+ DP83867_CFG4_SGMII_ANEG_TIMER_16MS);
+
+ if (ret)
+ return ret;
+
+ val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_SGMIICTL);
+ /* SGMII type is set to 4-wire mode by default.
+ * If we place appropriate property in dts (see above)
+ * switch on 6-wire mode.
+ */
+ if (dp83867->sgmii_ref_clk_en)
+ val |= DP83867_SGMII_TYPE;
+ else
+ val &= ~DP83867_SGMII_TYPE;
+ phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_SGMIICTL, val);
+
+ /* This is a SW workaround for link instability if RX_CTRL is
+ * not strapped to mode 3 or 4 in HW. This is required for SGMII
+ * in addition to clearing bit 7, handled above.
+ */
+ if (dp83867->rxctrl_strap_quirk)
+ phy_set_bits_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4,
+ BIT(8));
+ }
+
+ 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->set_clk_output) {
+ u16 mask = DP83867_IO_MUX_CFG_CLK_O_DISABLE;
+
+ if (dp83867->clk_output_sel == DP83867_CLK_O_SEL_OFF) {
+ val = DP83867_IO_MUX_CFG_CLK_O_DISABLE;
+ } else {
+ mask |= DP83867_IO_MUX_CFG_CLK_O_SEL_MASK;
+ val = dp83867->clk_output_sel <<
+ DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT;
+ }
+
+ phy_modify_mmd(phydev, DP83867_DEVADDR, DP83867_IO_MUX_CFG,
+ mask, 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;
+
+ usleep_range(10, 20);
+
+ err = phy_modify(phydev, MII_DP83867_PHYCTRL,
+ DP83867_PHYCR_FORCE_LINK_GOOD, 0);
+ if (err < 0)
+ return err;
+
+ /* Configure the DSP Feedforward Equalizer Configuration register to
+ * improve short cable (< 1 meter) performance. This will not affect
+ * long cable performance.
+ */
+ err = phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_DSP_FFE_CFG,
+ 0x0e81);
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, DP83867_CTRL, DP83867_SW_RESTART);
+ if (err < 0)
+ return err;
+
+ usleep_range(10, 20);
+
+ return 0;
+}
+
+static void dp83867_link_change_notify(struct phy_device *phydev)
+{
+ /* There is a limitation in DP83867 PHY device where SGMII AN is
+ * only triggered once after the device is booted up. Even after the
+ * PHY TPI is down and up again, SGMII AN is not triggered and
+ * hence no new in-band message from PHY to MAC side SGMII.
+ * This could cause an issue during power up, when PHY is up prior
+ * to MAC. At this condition, once MAC side SGMII is up, MAC side
+ * SGMII wouldn`t receive new in-band message from TI PHY with
+ * correct link status, speed and duplex info.
+ * Thus, implemented a SW solution here to retrigger SGMII Auto-Neg
+ * whenever there is a link change.
+ */
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ int val = 0;
+
+ val = phy_clear_bits(phydev, DP83867_CFG2,
+ DP83867_SGMII_AUTONEG_EN);
+ if (val < 0)
+ return;
+
+ phy_set_bits(phydev, DP83867_CFG2,
+ DP83867_SGMII_AUTONEG_EN);
+ }
+}
+
+static int dp83867_loopback(struct phy_device *phydev, bool enable)
+{
+ return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
+ enable ? BMCR_LOOPBACK : 0);
+}
+
+static int
+dp83867_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness brightness)
+{
+ u32 val;
+
+ if (index >= DP83867_LED_COUNT)
+ return -EINVAL;
+
+ /* DRV_EN==1: output is DRV_VAL */
+ val = DP83867_LED_DRV_EN(index);
+
+ if (brightness)
+ val |= DP83867_LED_DRV_VAL(index);
+
+ return phy_modify(phydev, DP83867_LEDCR2,
+ DP83867_LED_DRV_VAL(index) |
+ DP83867_LED_DRV_EN(index),
+ val);
+}
+
+static struct phy_driver dp83867_driver[] = {
+ {
+ .phy_id = DP83867_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "TI DP83867",
+ /* PHY_GBIT_FEATURES */
+
+ .probe = dp83867_probe,
+ .config_init = dp83867_config_init,
+ .soft_reset = dp83867_phy_reset,
+
+ .read_status = dp83867_read_status,
+ .get_tunable = dp83867_get_tunable,
+ .set_tunable = dp83867_set_tunable,
+
+ .get_wol = dp83867_get_wol,
+ .set_wol = dp83867_set_wol,
+
+ /* IRQ related */
+ .config_intr = dp83867_config_intr,
+ .handle_interrupt = dp83867_handle_interrupt,
+
+ .suspend = dp83867_suspend,
+ .resume = dp83867_resume,
+
+ .link_change_notify = dp83867_link_change_notify,
+ .set_loopback = dp83867_loopback,
+
+ .led_brightness_set = dp83867_led_brightness_set,
+ },
+};
+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 v2");
diff --git a/drivers/net/phy/dp83869.c b/drivers/net/phy/dp83869.c
new file mode 100644
index 000000000..fa8c6fdcf
--- /dev/null
+++ b/drivers/net/phy/dp83869.c
@@ -0,0 +1,926 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for the Texas Instruments DP83869 PHY
+ * Copyright (C) 2019 Texas Instruments Inc.
+ */
+
+#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/delay.h>
+#include <linux/bitfield.h>
+
+#include <dt-bindings/net/ti-dp83869.h>
+
+#define DP83869_PHY_ID 0x2000a0f1
+#define DP83561_PHY_ID 0x2000a1a4
+#define DP83869_DEVADDR 0x1f
+
+#define MII_DP83869_PHYCTRL 0x10
+#define MII_DP83869_MICR 0x12
+#define MII_DP83869_ISR 0x13
+#define DP83869_CFG2 0x14
+#define DP83869_CTRL 0x1f
+#define DP83869_CFG4 0x1e
+
+/* Extended Registers */
+#define DP83869_GEN_CFG3 0x0031
+#define DP83869_RGMIICTL 0x0032
+#define DP83869_STRAP_STS1 0x006e
+#define DP83869_RGMIIDCTL 0x0086
+#define DP83869_RXFCFG 0x0134
+#define DP83869_RXFPMD1 0x0136
+#define DP83869_RXFPMD2 0x0137
+#define DP83869_RXFPMD3 0x0138
+#define DP83869_RXFSOP1 0x0139
+#define DP83869_RXFSOP2 0x013A
+#define DP83869_RXFSOP3 0x013B
+#define DP83869_IO_MUX_CFG 0x0170
+#define DP83869_OP_MODE 0x01df
+#define DP83869_FX_CTRL 0x0c00
+
+#define DP83869_SW_RESET BIT(15)
+#define DP83869_SW_RESTART BIT(14)
+
+/* MICR Interrupt bits */
+#define MII_DP83869_MICR_AN_ERR_INT_EN BIT(15)
+#define MII_DP83869_MICR_SPEED_CHNG_INT_EN BIT(14)
+#define MII_DP83869_MICR_DUP_MODE_CHNG_INT_EN BIT(13)
+#define MII_DP83869_MICR_PAGE_RXD_INT_EN BIT(12)
+#define MII_DP83869_MICR_AUTONEG_COMP_INT_EN BIT(11)
+#define MII_DP83869_MICR_LINK_STS_CHNG_INT_EN BIT(10)
+#define MII_DP83869_MICR_FALSE_CARRIER_INT_EN BIT(8)
+#define MII_DP83869_MICR_SLEEP_MODE_CHNG_INT_EN BIT(4)
+#define MII_DP83869_MICR_WOL_INT_EN BIT(3)
+#define MII_DP83869_MICR_XGMII_ERR_INT_EN BIT(2)
+#define MII_DP83869_MICR_POL_CHNG_INT_EN BIT(1)
+#define MII_DP83869_MICR_JABBER_INT_EN BIT(0)
+
+#define MII_DP83869_BMCR_DEFAULT (BMCR_ANENABLE | \
+ BMCR_FULLDPLX | \
+ BMCR_SPEED1000)
+
+#define MII_DP83869_FIBER_ADVERTISE (ADVERTISED_FIBRE | \
+ ADVERTISED_Pause | \
+ ADVERTISED_Asym_Pause)
+
+/* This is the same bit mask as the BMCR so re-use the BMCR default */
+#define DP83869_FX_CTRL_DEFAULT MII_DP83869_BMCR_DEFAULT
+
+/* CFG1 bits */
+#define DP83869_CFG1_DEFAULT (ADVERTISE_1000HALF | \
+ ADVERTISE_1000FULL | \
+ CTL1000_AS_MASTER)
+
+/* RGMIICTL bits */
+#define DP83869_RGMII_TX_CLK_DELAY_EN BIT(1)
+#define DP83869_RGMII_RX_CLK_DELAY_EN BIT(0)
+
+/* RGMIIDCTL */
+#define DP83869_RGMII_CLK_DELAY_SHIFT 4
+#define DP83869_CLK_DELAY_DEF 7
+
+/* STRAP_STS1 bits */
+#define DP83869_STRAP_OP_MODE_MASK GENMASK(2, 0)
+#define DP83869_STRAP_STS1_RESERVED BIT(11)
+#define DP83869_STRAP_MIRROR_ENABLED BIT(12)
+
+/* PHYCTRL bits */
+#define DP83869_RX_FIFO_SHIFT 12
+#define DP83869_TX_FIFO_SHIFT 14
+
+/* PHY_CTRL lower bytes 0x48 are declared as reserved */
+#define DP83869_PHY_CTRL_DEFAULT 0x48
+#define DP83869_PHYCR_FIFO_DEPTH_MASK GENMASK(15, 12)
+#define DP83869_PHYCR_RESERVED_MASK BIT(11)
+
+/* IO_MUX_CFG bits */
+#define DP83869_IO_MUX_CFG_IO_IMPEDANCE_CTRL 0x1f
+
+#define DP83869_IO_MUX_CFG_IO_IMPEDANCE_MAX 0x0
+#define DP83869_IO_MUX_CFG_IO_IMPEDANCE_MIN 0x1f
+#define DP83869_IO_MUX_CFG_CLK_O_SEL_MASK (0x1f << 8)
+#define DP83869_IO_MUX_CFG_CLK_O_SEL_SHIFT 8
+
+/* CFG3 bits */
+#define DP83869_CFG3_PORT_MIRROR_EN BIT(0)
+
+/* CFG4 bits */
+#define DP83869_INT_OE BIT(7)
+
+/* OP MODE */
+#define DP83869_OP_MODE_MII BIT(5)
+#define DP83869_SGMII_RGMII_BRIDGE BIT(6)
+
+/* RXFCFG bits*/
+#define DP83869_WOL_MAGIC_EN BIT(0)
+#define DP83869_WOL_PATTERN_EN BIT(1)
+#define DP83869_WOL_BCAST_EN BIT(2)
+#define DP83869_WOL_UCAST_EN BIT(4)
+#define DP83869_WOL_SEC_EN BIT(5)
+#define DP83869_WOL_ENH_MAC BIT(7)
+
+/* CFG2 bits */
+#define DP83869_DOWNSHIFT_EN (BIT(8) | BIT(9))
+#define DP83869_DOWNSHIFT_ATTEMPT_MASK (BIT(10) | BIT(11))
+#define DP83869_DOWNSHIFT_1_COUNT_VAL 0
+#define DP83869_DOWNSHIFT_2_COUNT_VAL 1
+#define DP83869_DOWNSHIFT_4_COUNT_VAL 2
+#define DP83869_DOWNSHIFT_8_COUNT_VAL 3
+#define DP83869_DOWNSHIFT_1_COUNT 1
+#define DP83869_DOWNSHIFT_2_COUNT 2
+#define DP83869_DOWNSHIFT_4_COUNT 4
+#define DP83869_DOWNSHIFT_8_COUNT 8
+
+enum {
+ DP83869_PORT_MIRRORING_KEEP,
+ DP83869_PORT_MIRRORING_EN,
+ DP83869_PORT_MIRRORING_DIS,
+};
+
+struct dp83869_private {
+ int tx_fifo_depth;
+ int rx_fifo_depth;
+ s32 rx_int_delay;
+ s32 tx_int_delay;
+ int io_impedance;
+ int port_mirroring;
+ bool rxctrl_strap_quirk;
+ int clk_output_sel;
+ int mode;
+};
+
+static int dp83869_read_status(struct phy_device *phydev)
+{
+ struct dp83869_private *dp83869 = phydev->priv;
+ int ret;
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported)) {
+ if (phydev->link) {
+ if (dp83869->mode == DP83869_RGMII_100_BASE)
+ phydev->speed = SPEED_100;
+ } else {
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ }
+ }
+
+ return 0;
+}
+
+static int dp83869_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, MII_DP83869_ISR);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int dp83869_config_intr(struct phy_device *phydev)
+{
+ int micr_status = 0, err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = dp83869_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ micr_status = phy_read(phydev, MII_DP83869_MICR);
+ if (micr_status < 0)
+ return micr_status;
+
+ micr_status |=
+ (MII_DP83869_MICR_AN_ERR_INT_EN |
+ MII_DP83869_MICR_SPEED_CHNG_INT_EN |
+ MII_DP83869_MICR_AUTONEG_COMP_INT_EN |
+ MII_DP83869_MICR_LINK_STS_CHNG_INT_EN |
+ MII_DP83869_MICR_DUP_MODE_CHNG_INT_EN |
+ MII_DP83869_MICR_SLEEP_MODE_CHNG_INT_EN);
+
+ err = phy_write(phydev, MII_DP83869_MICR, micr_status);
+ } else {
+ err = phy_write(phydev, MII_DP83869_MICR, micr_status);
+ if (err)
+ return err;
+
+ err = dp83869_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t dp83869_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, irq_enabled;
+
+ irq_status = phy_read(phydev, MII_DP83869_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_enabled = phy_read(phydev, MII_DP83869_MICR);
+ if (irq_enabled < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & irq_enabled))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int dp83869_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ int val_rxcfg, val_micr;
+ const u8 *mac;
+ int ret;
+
+ val_rxcfg = phy_read_mmd(phydev, DP83869_DEVADDR, DP83869_RXFCFG);
+ if (val_rxcfg < 0)
+ return val_rxcfg;
+
+ val_micr = phy_read(phydev, MII_DP83869_MICR);
+ if (val_micr < 0)
+ return val_micr;
+
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE | WAKE_UCAST |
+ WAKE_BCAST)) {
+ val_rxcfg |= DP83869_WOL_ENH_MAC;
+ val_micr |= MII_DP83869_MICR_WOL_INT_EN;
+
+ if (wol->wolopts & WAKE_MAGIC ||
+ wol->wolopts & WAKE_MAGICSECURE) {
+ mac = (const u8 *)ndev->dev_addr;
+
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFPMD1,
+ mac[1] << 8 | mac[0]);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFPMD2,
+ mac[3] << 8 | mac[2]);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFPMD3,
+ mac[5] << 8 | mac[4]);
+ if (ret)
+ return ret;
+
+ val_rxcfg |= DP83869_WOL_MAGIC_EN;
+ } else {
+ val_rxcfg &= ~DP83869_WOL_MAGIC_EN;
+ }
+
+ if (wol->wolopts & WAKE_MAGICSECURE) {
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFSOP1,
+ (wol->sopass[1] << 8) | wol->sopass[0]);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFSOP2,
+ (wol->sopass[3] << 8) | wol->sopass[2]);
+ if (ret)
+ return ret;
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFSOP3,
+ (wol->sopass[5] << 8) | wol->sopass[4]);
+ if (ret)
+ return ret;
+
+ val_rxcfg |= DP83869_WOL_SEC_EN;
+ } else {
+ val_rxcfg &= ~DP83869_WOL_SEC_EN;
+ }
+
+ if (wol->wolopts & WAKE_UCAST)
+ val_rxcfg |= DP83869_WOL_UCAST_EN;
+ else
+ val_rxcfg &= ~DP83869_WOL_UCAST_EN;
+
+ if (wol->wolopts & WAKE_BCAST)
+ val_rxcfg |= DP83869_WOL_BCAST_EN;
+ else
+ val_rxcfg &= ~DP83869_WOL_BCAST_EN;
+ } else {
+ val_rxcfg &= ~DP83869_WOL_ENH_MAC;
+ val_micr &= ~MII_DP83869_MICR_WOL_INT_EN;
+ }
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR, DP83869_RXFCFG, val_rxcfg);
+ if (ret)
+ return ret;
+
+ return phy_write(phydev, MII_DP83869_MICR, val_micr);
+}
+
+static void dp83869_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int value, sopass_val;
+
+ wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC |
+ WAKE_MAGICSECURE);
+ wol->wolopts = 0;
+
+ value = phy_read_mmd(phydev, DP83869_DEVADDR, DP83869_RXFCFG);
+ if (value < 0) {
+ phydev_err(phydev, "Failed to read RX CFG\n");
+ return;
+ }
+
+ if (value & DP83869_WOL_UCAST_EN)
+ wol->wolopts |= WAKE_UCAST;
+
+ if (value & DP83869_WOL_BCAST_EN)
+ wol->wolopts |= WAKE_BCAST;
+
+ if (value & DP83869_WOL_MAGIC_EN)
+ wol->wolopts |= WAKE_MAGIC;
+
+ if (value & DP83869_WOL_SEC_EN) {
+ sopass_val = phy_read_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFSOP1);
+ if (sopass_val < 0) {
+ phydev_err(phydev, "Failed to read RX SOP 1\n");
+ return;
+ }
+
+ wol->sopass[0] = (sopass_val & 0xff);
+ wol->sopass[1] = (sopass_val >> 8);
+
+ sopass_val = phy_read_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFSOP2);
+ if (sopass_val < 0) {
+ phydev_err(phydev, "Failed to read RX SOP 2\n");
+ return;
+ }
+
+ wol->sopass[2] = (sopass_val & 0xff);
+ wol->sopass[3] = (sopass_val >> 8);
+
+ sopass_val = phy_read_mmd(phydev, DP83869_DEVADDR,
+ DP83869_RXFSOP3);
+ if (sopass_val < 0) {
+ phydev_err(phydev, "Failed to read RX SOP 3\n");
+ return;
+ }
+
+ wol->sopass[4] = (sopass_val & 0xff);
+ wol->sopass[5] = (sopass_val >> 8);
+
+ wol->wolopts |= WAKE_MAGICSECURE;
+ }
+
+ if (!(value & DP83869_WOL_ENH_MAC))
+ wol->wolopts = 0;
+}
+
+static int dp83869_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val, cnt, enable, count;
+
+ val = phy_read(phydev, DP83869_CFG2);
+ if (val < 0)
+ return val;
+
+ enable = FIELD_GET(DP83869_DOWNSHIFT_EN, val);
+ cnt = FIELD_GET(DP83869_DOWNSHIFT_ATTEMPT_MASK, val);
+
+ switch (cnt) {
+ case DP83869_DOWNSHIFT_1_COUNT_VAL:
+ count = DP83869_DOWNSHIFT_1_COUNT;
+ break;
+ case DP83869_DOWNSHIFT_2_COUNT_VAL:
+ count = DP83869_DOWNSHIFT_2_COUNT;
+ break;
+ case DP83869_DOWNSHIFT_4_COUNT_VAL:
+ count = DP83869_DOWNSHIFT_4_COUNT;
+ break;
+ case DP83869_DOWNSHIFT_8_COUNT_VAL:
+ count = DP83869_DOWNSHIFT_8_COUNT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *data = enable ? count : DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int dp83869_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ int val, count;
+
+ if (cnt > DP83869_DOWNSHIFT_8_COUNT)
+ return -EINVAL;
+
+ if (!cnt)
+ return phy_clear_bits(phydev, DP83869_CFG2,
+ DP83869_DOWNSHIFT_EN);
+
+ switch (cnt) {
+ case DP83869_DOWNSHIFT_1_COUNT:
+ count = DP83869_DOWNSHIFT_1_COUNT_VAL;
+ break;
+ case DP83869_DOWNSHIFT_2_COUNT:
+ count = DP83869_DOWNSHIFT_2_COUNT_VAL;
+ break;
+ case DP83869_DOWNSHIFT_4_COUNT:
+ count = DP83869_DOWNSHIFT_4_COUNT_VAL;
+ break;
+ case DP83869_DOWNSHIFT_8_COUNT:
+ count = DP83869_DOWNSHIFT_8_COUNT_VAL;
+ break;
+ default:
+ phydev_err(phydev,
+ "Downshift count must be 1, 2, 4 or 8\n");
+ return -EINVAL;
+ }
+
+ val = DP83869_DOWNSHIFT_EN;
+ val |= FIELD_PREP(DP83869_DOWNSHIFT_ATTEMPT_MASK, count);
+
+ return phy_modify(phydev, DP83869_CFG2,
+ DP83869_DOWNSHIFT_EN | DP83869_DOWNSHIFT_ATTEMPT_MASK,
+ val);
+}
+
+static int dp83869_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return dp83869_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int dp83869_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return dp83869_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int dp83869_config_port_mirroring(struct phy_device *phydev)
+{
+ struct dp83869_private *dp83869 = phydev->priv;
+
+ if (dp83869->port_mirroring == DP83869_PORT_MIRRORING_EN)
+ return phy_set_bits_mmd(phydev, DP83869_DEVADDR,
+ DP83869_GEN_CFG3,
+ DP83869_CFG3_PORT_MIRROR_EN);
+ else
+ return phy_clear_bits_mmd(phydev, DP83869_DEVADDR,
+ DP83869_GEN_CFG3,
+ DP83869_CFG3_PORT_MIRROR_EN);
+}
+
+static int dp83869_set_strapped_mode(struct phy_device *phydev)
+{
+ struct dp83869_private *dp83869 = phydev->priv;
+ int val;
+
+ val = phy_read_mmd(phydev, DP83869_DEVADDR, DP83869_STRAP_STS1);
+ if (val < 0)
+ return val;
+
+ dp83869->mode = val & DP83869_STRAP_OP_MODE_MASK;
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+static const int dp83869_internal_delay[] = {250, 500, 750, 1000, 1250, 1500,
+ 1750, 2000, 2250, 2500, 2750, 3000,
+ 3250, 3500, 3750, 4000};
+
+static int dp83869_of_init(struct phy_device *phydev)
+{
+ struct dp83869_private *dp83869 = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ struct device_node *of_node = dev->of_node;
+ int delay_size = ARRAY_SIZE(dp83869_internal_delay);
+ int ret;
+
+ if (!of_node)
+ return -ENODEV;
+
+ dp83869->io_impedance = -EINVAL;
+
+ /* Optional configuration */
+ ret = of_property_read_u32(of_node, "ti,clk-output-sel",
+ &dp83869->clk_output_sel);
+ if (ret || dp83869->clk_output_sel > DP83869_CLK_O_SEL_REF_CLK)
+ dp83869->clk_output_sel = DP83869_CLK_O_SEL_REF_CLK;
+
+ ret = of_property_read_u32(of_node, "ti,op-mode", &dp83869->mode);
+ if (ret == 0) {
+ if (dp83869->mode < DP83869_RGMII_COPPER_ETHERNET ||
+ dp83869->mode > DP83869_SGMII_COPPER_ETHERNET)
+ return -EINVAL;
+ } else {
+ ret = dp83869_set_strapped_mode(phydev);
+ if (ret)
+ return ret;
+ }
+
+ if (of_property_read_bool(of_node, "ti,max-output-impedance"))
+ dp83869->io_impedance = DP83869_IO_MUX_CFG_IO_IMPEDANCE_MAX;
+ else if (of_property_read_bool(of_node, "ti,min-output-impedance"))
+ dp83869->io_impedance = DP83869_IO_MUX_CFG_IO_IMPEDANCE_MIN;
+
+ if (of_property_read_bool(of_node, "enet-phy-lane-swap")) {
+ dp83869->port_mirroring = DP83869_PORT_MIRRORING_EN;
+ } else {
+ /* If the lane swap is not in the DT then check the straps */
+ ret = phy_read_mmd(phydev, DP83869_DEVADDR, DP83869_STRAP_STS1);
+ if (ret < 0)
+ return ret;
+
+ if (ret & DP83869_STRAP_MIRROR_ENABLED)
+ dp83869->port_mirroring = DP83869_PORT_MIRRORING_EN;
+ else
+ dp83869->port_mirroring = DP83869_PORT_MIRRORING_DIS;
+
+ ret = 0;
+ }
+
+ if (of_property_read_u32(of_node, "rx-fifo-depth",
+ &dp83869->rx_fifo_depth))
+ dp83869->rx_fifo_depth = DP83869_PHYCR_FIFO_DEPTH_4_B_NIB;
+
+ if (of_property_read_u32(of_node, "tx-fifo-depth",
+ &dp83869->tx_fifo_depth))
+ dp83869->tx_fifo_depth = DP83869_PHYCR_FIFO_DEPTH_4_B_NIB;
+
+ dp83869->rx_int_delay = phy_get_internal_delay(phydev, dev,
+ &dp83869_internal_delay[0],
+ delay_size, true);
+ if (dp83869->rx_int_delay < 0)
+ dp83869->rx_int_delay = DP83869_CLK_DELAY_DEF;
+
+ dp83869->tx_int_delay = phy_get_internal_delay(phydev, dev,
+ &dp83869_internal_delay[0],
+ delay_size, false);
+ if (dp83869->tx_int_delay < 0)
+ dp83869->tx_int_delay = DP83869_CLK_DELAY_DEF;
+
+ return ret;
+}
+#else
+static int dp83869_of_init(struct phy_device *phydev)
+{
+ return dp83869_set_strapped_mode(phydev);
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int dp83869_configure_rgmii(struct phy_device *phydev,
+ struct dp83869_private *dp83869)
+{
+ int ret = 0, val;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ val = phy_read(phydev, MII_DP83869_PHYCTRL);
+ if (val < 0)
+ return val;
+
+ val &= ~DP83869_PHYCR_FIFO_DEPTH_MASK;
+ val |= (dp83869->tx_fifo_depth << DP83869_TX_FIFO_SHIFT);
+ val |= (dp83869->rx_fifo_depth << DP83869_RX_FIFO_SHIFT);
+
+ ret = phy_write(phydev, MII_DP83869_PHYCTRL, val);
+ if (ret)
+ return ret;
+ }
+
+ if (dp83869->io_impedance >= 0)
+ ret = phy_modify_mmd(phydev, DP83869_DEVADDR,
+ DP83869_IO_MUX_CFG,
+ DP83869_IO_MUX_CFG_IO_IMPEDANCE_CTRL,
+ dp83869->io_impedance &
+ DP83869_IO_MUX_CFG_IO_IMPEDANCE_CTRL);
+
+ return ret;
+}
+
+static int dp83869_configure_fiber(struct phy_device *phydev,
+ struct dp83869_private *dp83869)
+{
+ int bmcr;
+ int ret;
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported);
+ linkmode_set_bit(ADVERTISED_FIBRE, phydev->advertising);
+
+ if (dp83869->mode == DP83869_RGMII_1000_BASE) {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->supported);
+ } else {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
+ phydev->supported);
+
+ /* Auto neg is not supported in 100base FX mode */
+ bmcr = phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ phydev->autoneg = AUTONEG_DISABLE;
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->advertising);
+
+ if (bmcr & BMCR_ANENABLE) {
+ ret = phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Update advertising from supported */
+ linkmode_or(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ return 0;
+}
+
+static int dp83869_configure_mode(struct phy_device *phydev,
+ struct dp83869_private *dp83869)
+{
+ int phy_ctrl_val;
+ int ret;
+
+ if (dp83869->mode < DP83869_RGMII_COPPER_ETHERNET ||
+ dp83869->mode > DP83869_SGMII_COPPER_ETHERNET)
+ return -EINVAL;
+
+ /* Below init sequence for each operational mode is defined in
+ * section 9.4.8 of the datasheet.
+ */
+ phy_ctrl_val = dp83869->mode;
+ if (phydev->interface == PHY_INTERFACE_MODE_MII) {
+ if (dp83869->mode == DP83869_100M_MEDIA_CONVERT ||
+ dp83869->mode == DP83869_RGMII_100_BASE) {
+ phy_ctrl_val |= DP83869_OP_MODE_MII;
+ } else {
+ phydev_err(phydev, "selected op-mode is not valid with MII mode\n");
+ return -EINVAL;
+ }
+ }
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR, DP83869_OP_MODE,
+ phy_ctrl_val);
+ if (ret)
+ return ret;
+
+ ret = phy_write(phydev, MII_BMCR, MII_DP83869_BMCR_DEFAULT);
+ if (ret)
+ return ret;
+
+ phy_ctrl_val = (dp83869->rx_fifo_depth << DP83869_RX_FIFO_SHIFT |
+ dp83869->tx_fifo_depth << DP83869_TX_FIFO_SHIFT |
+ DP83869_PHY_CTRL_DEFAULT);
+
+ switch (dp83869->mode) {
+ case DP83869_RGMII_COPPER_ETHERNET:
+ ret = phy_write(phydev, MII_DP83869_PHYCTRL,
+ phy_ctrl_val);
+ if (ret)
+ return ret;
+
+ ret = phy_write(phydev, MII_CTRL1000, DP83869_CFG1_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = dp83869_configure_rgmii(phydev, dp83869);
+ if (ret)
+ return ret;
+ break;
+ case DP83869_RGMII_SGMII_BRIDGE:
+ ret = phy_modify_mmd(phydev, DP83869_DEVADDR, DP83869_OP_MODE,
+ DP83869_SGMII_RGMII_BRIDGE,
+ DP83869_SGMII_RGMII_BRIDGE);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_FX_CTRL, DP83869_FX_CTRL_DEFAULT);
+ if (ret)
+ return ret;
+
+ break;
+ case DP83869_1000M_MEDIA_CONVERT:
+ ret = phy_write(phydev, MII_DP83869_PHYCTRL,
+ phy_ctrl_val);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_FX_CTRL, DP83869_FX_CTRL_DEFAULT);
+ if (ret)
+ return ret;
+ break;
+ case DP83869_100M_MEDIA_CONVERT:
+ ret = phy_write(phydev, MII_DP83869_PHYCTRL,
+ phy_ctrl_val);
+ if (ret)
+ return ret;
+ break;
+ case DP83869_SGMII_COPPER_ETHERNET:
+ ret = phy_write(phydev, MII_DP83869_PHYCTRL,
+ phy_ctrl_val);
+ if (ret)
+ return ret;
+
+ ret = phy_write(phydev, MII_CTRL1000, DP83869_CFG1_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR,
+ DP83869_FX_CTRL, DP83869_FX_CTRL_DEFAULT);
+ if (ret)
+ return ret;
+
+ break;
+ case DP83869_RGMII_1000_BASE:
+ case DP83869_RGMII_100_BASE:
+ ret = dp83869_configure_fiber(phydev, dp83869);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int dp83869_config_init(struct phy_device *phydev)
+{
+ struct dp83869_private *dp83869 = phydev->priv;
+ int ret, val;
+
+ /* Force speed optimization for the PHY even if it strapped */
+ ret = phy_modify(phydev, DP83869_CFG2, DP83869_DOWNSHIFT_EN,
+ DP83869_DOWNSHIFT_EN);
+ if (ret)
+ return ret;
+
+ ret = dp83869_configure_mode(phydev, dp83869);
+ if (ret)
+ return ret;
+
+ /* Enable Interrupt output INT_OE in CFG4 register */
+ if (phy_interrupt_is_valid(phydev)) {
+ val = phy_read(phydev, DP83869_CFG4);
+ val |= DP83869_INT_OE;
+ phy_write(phydev, DP83869_CFG4, val);
+ }
+
+ if (dp83869->port_mirroring != DP83869_PORT_MIRRORING_KEEP)
+ dp83869_config_port_mirroring(phydev);
+
+ /* Clock output selection if muxing property is set */
+ if (dp83869->clk_output_sel != DP83869_CLK_O_SEL_REF_CLK)
+ ret = phy_modify_mmd(phydev,
+ DP83869_DEVADDR, DP83869_IO_MUX_CFG,
+ DP83869_IO_MUX_CFG_CLK_O_SEL_MASK,
+ dp83869->clk_output_sel <<
+ DP83869_IO_MUX_CFG_CLK_O_SEL_SHIFT);
+
+ if (phy_interface_is_rgmii(phydev)) {
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR, DP83869_RGMIIDCTL,
+ dp83869->rx_int_delay |
+ dp83869->tx_int_delay << DP83869_RGMII_CLK_DELAY_SHIFT);
+ if (ret)
+ return ret;
+
+ val = phy_read_mmd(phydev, DP83869_DEVADDR, DP83869_RGMIICTL);
+ val |= (DP83869_RGMII_TX_CLK_DELAY_EN |
+ DP83869_RGMII_RX_CLK_DELAY_EN);
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+ val &= ~(DP83869_RGMII_TX_CLK_DELAY_EN |
+ DP83869_RGMII_RX_CLK_DELAY_EN);
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+ val &= ~DP83869_RGMII_TX_CLK_DELAY_EN;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+ val &= ~DP83869_RGMII_RX_CLK_DELAY_EN;
+
+ ret = phy_write_mmd(phydev, DP83869_DEVADDR, DP83869_RGMIICTL,
+ val);
+ }
+
+ return ret;
+}
+
+static int dp83869_probe(struct phy_device *phydev)
+{
+ struct dp83869_private *dp83869;
+ int ret;
+
+ dp83869 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83869),
+ GFP_KERNEL);
+ if (!dp83869)
+ return -ENOMEM;
+
+ phydev->priv = dp83869;
+
+ ret = dp83869_of_init(phydev);
+ if (ret)
+ return ret;
+
+ if (dp83869->mode == DP83869_RGMII_100_BASE ||
+ dp83869->mode == DP83869_RGMII_1000_BASE)
+ phydev->port = PORT_FIBRE;
+
+ return dp83869_config_init(phydev);
+}
+
+static int dp83869_phy_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_write(phydev, DP83869_CTRL, DP83869_SW_RESET);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(10, 20);
+
+ /* Global sw reset sets all registers to default.
+ * Need to set the registers in the PHY to the right config.
+ */
+ return dp83869_config_init(phydev);
+}
+
+
+#define DP83869_PHY_DRIVER(_id, _name) \
+{ \
+ PHY_ID_MATCH_MODEL(_id), \
+ .name = (_name), \
+ .probe = dp83869_probe, \
+ .config_init = dp83869_config_init, \
+ .soft_reset = dp83869_phy_reset, \
+ .config_intr = dp83869_config_intr, \
+ .handle_interrupt = dp83869_handle_interrupt, \
+ .read_status = dp83869_read_status, \
+ .get_tunable = dp83869_get_tunable, \
+ .set_tunable = dp83869_set_tunable, \
+ .get_wol = dp83869_get_wol, \
+ .set_wol = dp83869_set_wol, \
+ .suspend = genphy_suspend, \
+ .resume = genphy_resume, \
+}
+
+static struct phy_driver dp83869_driver[] = {
+ DP83869_PHY_DRIVER(DP83869_PHY_ID, "TI DP83869"),
+ DP83869_PHY_DRIVER(DP83561_PHY_ID, "TI DP83561-SP"),
+
+};
+module_phy_driver(dp83869_driver);
+
+static struct mdio_device_id __maybe_unused dp83869_tbl[] = {
+ { PHY_ID_MATCH_MODEL(DP83869_PHY_ID) },
+ { PHY_ID_MATCH_MODEL(DP83561_PHY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, dp83869_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments DP83869 PHY driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/dp83tc811.c b/drivers/net/phy/dp83tc811.c
new file mode 100644
index 000000000..7ea32fb77
--- /dev/null
+++ b/drivers/net/phy/dp83tc811.c
@@ -0,0 +1,414 @@
+// 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;
+ }
+
+ /* Clear any pending WoL interrupt */
+ phy_read(phydev, MII_DP83811_INT_STAT1);
+
+ value |= DP83811_WOL_EN | DP83811_WOL_INDICATION_SEL |
+ DP83811_WOL_CLR_INDICATION;
+
+ return phy_write_mmd(phydev, DP83811_DEVADDR,
+ MII_DP83811_WOL_CFG, value);
+ } else {
+ return phy_clear_bits_mmd(phydev, DP83811_DEVADDR,
+ MII_DP83811_WOL_CFG, DP83811_WOL_EN);
+ }
+
+}
+
+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) {
+ err = dp83811_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ 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);
+ if (err < 0)
+ return err;
+
+ err = dp83811_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t dp83811_handle_interrupt(struct phy_device *phydev)
+{
+ bool trigger_machine = false;
+ int irq_status;
+
+ /* The INT_STAT registers 1, 2 and 3 are holding the interrupt status
+ * in the upper half (15:8), while the lower half (7:0) is used for
+ * controlling the interrupt enable state of those individual interrupt
+ * sources. To determine the possible interrupt sources, just read the
+ * INT_STAT* register and use it directly to know which interrupts have
+ * been enabled previously or not.
+ */
+ irq_status = phy_read(phydev, MII_DP83811_INT_STAT1);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ if (irq_status & ((irq_status & GENMASK(7, 0)) << 8))
+ trigger_machine = true;
+
+ irq_status = phy_read(phydev, MII_DP83811_INT_STAT2);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ if (irq_status & ((irq_status & GENMASK(7, 0)) << 8))
+ trigger_machine = true;
+
+ irq_status = phy_read(phydev, MII_DP83811_INT_STAT3);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ if (irq_status & ((irq_status & GENMASK(7, 0)) << 8))
+ trigger_machine = true;
+
+ if (!trigger_machine)
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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;
+
+ 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_clear_bits_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)
+{
+ genphy_resume(phydev);
+
+ phy_set_bits_mmd(phydev, DP83811_DEVADDR, MII_DP83811_WOL_CFG,
+ DP83811_WOL_CLR_INDICATION);
+
+ return 0;
+}
+
+static struct phy_driver dp83811_driver[] = {
+ {
+ .phy_id = DP83TC811_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "TI DP83TC811",
+ /* PHY_BASIC_FEATURES */
+ .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,
+ .config_intr = dp83811_config_intr,
+ .handle_interrupt = dp83811_handle_interrupt,
+ .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/dp83td510.c b/drivers/net/phy/dp83td510.c
new file mode 100644
index 000000000..d7616b13c
--- /dev/null
+++ b/drivers/net/phy/dp83td510.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for the Texas Instruments DP83TD510 PHY
+ * Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define DP83TD510E_PHY_ID 0x20000181
+
+/* MDIO_MMD_VEND2 registers */
+#define DP83TD510E_PHY_STS 0x10
+/* Bit 7 - mii_interrupt, active high. Clears on read.
+ * Note: Clearing does not necessarily deactivate IRQ pin if interrupts pending.
+ * This differs from the DP83TD510E datasheet (2020) which states this bit
+ * clears on write 0.
+ */
+#define DP83TD510E_STS_MII_INT BIT(7)
+#define DP83TD510E_LINK_STATUS BIT(0)
+
+#define DP83TD510E_GEN_CFG 0x11
+#define DP83TD510E_GENCFG_INT_POLARITY BIT(3)
+#define DP83TD510E_GENCFG_INT_EN BIT(1)
+#define DP83TD510E_GENCFG_INT_OE BIT(0)
+
+#define DP83TD510E_INTERRUPT_REG_1 0x12
+#define DP83TD510E_INT1_LINK BIT(13)
+#define DP83TD510E_INT1_LINK_EN BIT(5)
+
+#define DP83TD510E_AN_STAT_1 0x60c
+#define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15)
+
+#define DP83TD510E_MSE_DETECT 0xa85
+
+#define DP83TD510_SQI_MAX 7
+
+/* Register values are converted to SNR(dB) as suggested by
+ * "Application Report - DP83TD510E Cable Diagnostics Toolkit":
+ * SNR(dB) = -10 * log10 (VAL/2^17) - 1.76 dB.
+ * SQI ranges are implemented according to "OPEN ALLIANCE - Advanced diagnostic
+ * features for 100BASE-T1 automotive Ethernet PHYs"
+ */
+static const u16 dp83td510_mse_sqi_map[] = {
+ 0x0569, /* < 18dB */
+ 0x044c, /* 18dB =< SNR < 19dB */
+ 0x0369, /* 19dB =< SNR < 20dB */
+ 0x02b6, /* 20dB =< SNR < 21dB */
+ 0x0227, /* 21dB =< SNR < 22dB */
+ 0x01b6, /* 22dB =< SNR < 23dB */
+ 0x015b, /* 23dB =< SNR < 24dB */
+ 0x0000 /* 24dB =< SNR */
+};
+
+static int dp83td510_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ DP83TD510E_INTERRUPT_REG_1,
+ DP83TD510E_INT1_LINK_EN);
+ if (ret)
+ return ret;
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ DP83TD510E_GEN_CFG,
+ DP83TD510E_GENCFG_INT_POLARITY |
+ DP83TD510E_GENCFG_INT_EN |
+ DP83TD510E_GENCFG_INT_OE);
+ } else {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ DP83TD510E_INTERRUPT_REG_1, 0x0);
+ if (ret)
+ return ret;
+
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+ DP83TD510E_GEN_CFG,
+ DP83TD510E_GENCFG_INT_EN);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static irqreturn_t dp83td510_handle_interrupt(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Read the current enabled interrupts */
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_INTERRUPT_REG_1);
+ if (ret < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ } else if (!(ret & DP83TD510E_INT1_LINK_EN) ||
+ !(ret & DP83TD510E_INT1_LINK)) {
+ return IRQ_NONE;
+ }
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int dp83td510_read_status(struct phy_device *phydev)
+{
+ u16 phy_sts;
+ int ret;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ linkmode_zero(phydev->lp_advertising);
+
+ phy_sts = phy_read(phydev, DP83TD510E_PHY_STS);
+
+ phydev->link = !!(phy_sts & DP83TD510E_LINK_STATUS);
+ if (phydev->link) {
+ /* This PHY supports only one link mode: 10BaseT1L_Full */
+ phydev->duplex = DUPLEX_FULL;
+ phydev->speed = SPEED_10;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ ret = genphy_c45_read_lpa(phydev);
+ if (ret)
+ return ret;
+
+ phy_resolve_aneg_linkmode(phydev);
+ }
+ }
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ ret = genphy_c45_baset1_read_status(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ DP83TD510E_AN_STAT_1);
+ if (ret < 0)
+ return ret;
+
+ if (ret & DP83TD510E_MASTER_SLAVE_RESOL_FAIL)
+ phydev->master_slave_state = MASTER_SLAVE_STATE_ERR;
+ } else {
+ return genphy_c45_pma_baset1_read_master_slave(phydev);
+ }
+
+ return 0;
+}
+
+static int dp83td510_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ int ret;
+
+ ret = genphy_c45_pma_baset1_setup_master_slave(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return genphy_c45_an_disable_aneg(phydev);
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int dp83td510_get_sqi(struct phy_device *phydev)
+{
+ int sqi, ret;
+ u16 mse_val;
+
+ if (!phydev->link)
+ return 0;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_MSE_DETECT);
+ if (ret < 0)
+ return ret;
+
+ mse_val = 0xFFFF & ret;
+ for (sqi = 0; sqi < ARRAY_SIZE(dp83td510_mse_sqi_map); sqi++) {
+ if (mse_val >= dp83td510_mse_sqi_map[sqi])
+ return sqi;
+ }
+
+ return -EINVAL;
+}
+
+static int dp83td510_get_sqi_max(struct phy_device *phydev)
+{
+ return DP83TD510_SQI_MAX;
+}
+
+static int dp83td510_get_features(struct phy_device *phydev)
+{
+ /* This PHY can't respond on MDIO bus if no RMII clock is enabled.
+ * In case RMII mode is used (most meaningful mode for this PHY) and
+ * the PHY do not have own XTAL, and CLK providing MAC is not probed,
+ * we won't be able to read all needed ability registers.
+ * So provide it manually.
+ */
+
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported);
+
+ return 0;
+}
+
+static struct phy_driver dp83td510_driver[] = {
+{
+ PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID),
+ .name = "TI DP83TD510E",
+
+ .config_aneg = dp83td510_config_aneg,
+ .read_status = dp83td510_read_status,
+ .get_features = dp83td510_get_features,
+ .config_intr = dp83td510_config_intr,
+ .handle_interrupt = dp83td510_handle_interrupt,
+ .get_sqi = dp83td510_get_sqi,
+ .get_sqi_max = dp83td510_get_sqi_max,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+} };
+module_phy_driver(dp83td510_driver);
+
+static struct mdio_device_id __maybe_unused dp83td510_tbl[] = {
+ { PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, dp83td510_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments DP83TD510E PHY driver");
+MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/et1011c.c b/drivers/net/phy/et1011c.c
new file mode 100644
index 000000000..be1b71d7c
--- /dev/null
+++ b/drivers/net/phy/et1011c.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/et1011c.c
+ *
+ * Driver for LSI ET1011C PHYs
+ *
+ * Author: Chaithrika U S
+ *
+ * Copyright (c) 2008 Texas Instruments
+ */
+#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 = 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)
+{
+ static int speed;
+ int ret;
+ u32 val;
+
+ 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,
+ /* PHY_GBIT_FEATURES */
+ .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..aef739c20
--- /dev/null
+++ b/drivers/net/phy/fixed_phy.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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.
+ */
+
+#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/consumer.h>
+#include <linux/idr.h>
+#include <linux/netdevice.h>
+#include <linux/linkmode.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;
+ struct fixed_phy_status status;
+ bool no_carrier;
+ int (*link_update)(struct net_device *, struct fixed_phy_status *);
+ struct list_head node;
+ struct gpio_desc *link_gpiod;
+};
+
+static struct platform_device *pdev;
+static struct fixed_mdio_bus platform_fmb = {
+ .phys = LIST_HEAD_INIT(platform_fmb.phys),
+};
+
+int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier)
+{
+ struct fixed_mdio_bus *fmb = &platform_fmb;
+ struct phy_device *phydev = dev->phydev;
+ 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->no_carrier = !new_carrier;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(fixed_phy_change_carrier);
+
+static void fixed_phy_update(struct fixed_phy *fp)
+{
+ if (!fp->no_carrier && fp->link_gpiod)
+ fp->status.link = !!gpiod_get_value_cansleep(fp->link_gpiod);
+}
+
+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;
+
+ fp->status.link = !fp->no_carrier;
+
+ /* 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;
+
+ 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);
+
+static int fixed_phy_add_gpiod(unsigned int irq, int phy_addr,
+ struct fixed_phy_status *status,
+ struct gpio_desc *gpiod)
+{
+ 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;
+
+ if (irq != PHY_POLL)
+ fmb->mii_bus->irq[phy_addr] = irq;
+
+ fp->addr = phy_addr;
+ fp->status = *status;
+ fp->link_gpiod = gpiod;
+
+ fixed_phy_update(fp);
+
+ list_add_tail(&fp->node, &fmb->phys);
+
+ return 0;
+}
+
+int fixed_phy_add(unsigned int irq, int phy_addr,
+ struct fixed_phy_status *status)
+{
+ return fixed_phy_add_gpiod(irq, phy_addr, status, NULL);
+}
+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 (fp->link_gpiod)
+ gpiod_put(fp->link_gpiod);
+ kfree(fp);
+ ida_free(&phy_fixed_ida, phy_addr);
+ return;
+ }
+ }
+}
+
+#ifdef CONFIG_OF_GPIO
+static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np)
+{
+ struct device_node *fixed_link_node;
+ struct gpio_desc *gpiod;
+
+ if (!np)
+ return NULL;
+
+ fixed_link_node = of_get_child_by_name(np, "fixed-link");
+ if (!fixed_link_node)
+ return NULL;
+
+ /*
+ * As the fixed link is just a device tree node without any
+ * Linux device associated with it, we simply have obtain
+ * the GPIO descriptor from the device tree like this.
+ */
+ gpiod = fwnode_gpiod_get_index(of_fwnode_handle(fixed_link_node),
+ "link", 0, GPIOD_IN, "mdio");
+ if (IS_ERR(gpiod) && PTR_ERR(gpiod) != -EPROBE_DEFER) {
+ if (PTR_ERR(gpiod) != -ENOENT)
+ pr_err("error getting GPIO for fixed link %pOF, proceed without\n",
+ fixed_link_node);
+ gpiod = NULL;
+ }
+ of_node_put(fixed_link_node);
+
+ return gpiod;
+}
+#else
+static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np)
+{
+ return NULL;
+}
+#endif
+
+static struct phy_device *__fixed_phy_register(unsigned int irq,
+ struct fixed_phy_status *status,
+ struct device_node *np,
+ struct gpio_desc *gpiod)
+{
+ 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);
+
+ /* Check if we have a GPIO associated with this fixed phy */
+ if (!gpiod) {
+ gpiod = fixed_phy_get_gpiod(np);
+ if (IS_ERR(gpiod))
+ return ERR_CAST(gpiod);
+ }
+
+ /* Get the next available PHY address, up to PHY_MAX_ADDR */
+ phy_addr = ida_alloc_max(&phy_fixed_ida, PHY_MAX_ADDR - 1, GFP_KERNEL);
+ if (phy_addr < 0)
+ return ERR_PTR(phy_addr);
+
+ ret = fixed_phy_add_gpiod(irq, phy_addr, status, gpiod);
+ if (ret < 0) {
+ ida_free(&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:
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phy->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phy->supported);
+ fallthrough;
+ case SPEED_100:
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ phy->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ phy->supported);
+ fallthrough;
+ case SPEED_10:
+ default:
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ phy->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ phy->supported);
+ }
+
+ phy_advertise_supported(phy);
+
+ 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;
+}
+
+struct phy_device *fixed_phy_register(unsigned int irq,
+ struct fixed_phy_status *status,
+ struct device_node *np)
+{
+ return __fixed_phy_register(irq, status, np, NULL);
+}
+EXPORT_SYMBOL_GPL(fixed_phy_register);
+
+struct phy_device *
+fixed_phy_register_with_gpiod(unsigned int irq,
+ struct fixed_phy_status *status,
+ struct gpio_desc *gpiod)
+{
+ return __fixed_phy_register(irq, status, NULL, gpiod);
+}
+EXPORT_SYMBOL_GPL(fixed_phy_register_with_gpiod);
+
+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;
+ fmb->mii_bus->phy_mask = ~0;
+
+ 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..a00a66745
--- /dev/null
+++ b/drivers/net/phy/icplus.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for ICPlus PHYs
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ */
+#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/property.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 BIT(0) /* Add delay on RX_CLK */
+#define IP1001_TXPHASE_SEL BIT(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 BIT(1) /* IP101A/G APS Mode bit */
+#define IP101A_G_AUTO_MDIX_DIS BIT(11)
+#define IP101A_G_IRQ_CONF_STATUS 0x11 /* Conf Info IRQ & Status Reg */
+#define IP101A_G_IRQ_PIN_USED BIT(15) /* INTR pin used */
+#define IP101A_G_IRQ_ALL_MASK BIT(11) /* IRQ's inactive */
+#define IP101A_G_IRQ_SPEED_CHANGE BIT(2)
+#define IP101A_G_IRQ_DUPLEX_CHANGE BIT(1)
+#define IP101A_G_IRQ_LINK_CHANGE BIT(0)
+#define IP101A_G_PHY_STATUS 18
+#define IP101A_G_MDIX BIT(9)
+#define IP101A_G_PHY_SPEC_CTRL 30
+#define IP101A_G_FORCE_MDIX BIT(3)
+
+#define IP101G_PAGE_CONTROL 0x14
+#define IP101G_PAGE_CONTROL_MASK GENMASK(4, 0)
+#define IP101G_DIGITAL_IO_SPEC_CTRL 0x1d
+#define IP101G_DIGITAL_IO_SPEC_CTRL_SEL_INTR32 BIT(2)
+
+#define IP101G_DEFAULT_PAGE 16
+
+#define IP101G_P1_CNT_CTRL 17
+#define CNT_CTRL_RX_EN BIT(13)
+#define IP101G_P8_CNT_CTRL 17
+#define CNT_CTRL_RDCLR_EN BIT(15)
+#define IP101G_CNT_REG 18
+
+#define IP175C_PHY_ID 0x02430d80
+#define IP1001_PHY_ID 0x02430d90
+#define IP101A_PHY_ID 0x02430c54
+
+/* The 32-pin IP101GR package can re-configure the mode of the RXER/INTR_32 pin
+ * (pin number 21). The hardware default is RXER (receive error) mode. But it
+ * can be configured to interrupt mode manually.
+ */
+enum ip101gr_sel_intr32 {
+ IP101GR_SEL_INTR32_KEEP,
+ IP101GR_SEL_INTR32_INTR,
+ IP101GR_SEL_INTR32_RXER,
+};
+
+struct ip101g_hw_stat {
+ const char *name;
+ int page;
+};
+
+static struct ip101g_hw_stat ip101g_hw_stats[] = {
+ { "phy_crc_errors", 1 },
+ { "phy_symbol_errors", 11, },
+};
+
+struct ip101a_g_phy_priv {
+ enum ip101gr_sel_intr32 sel_intr32;
+ u64 stats[ARRAY_SIZE(ip101g_hw_stats)];
+};
+
+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 ip1001_config_init(struct phy_device *phydev)
+{
+ int 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 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_MAC_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_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct ip101a_g_phy_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Both functions (RX error and interrupt status) are sharing the same
+ * pin on the 32-pin IP101GR, so this is an exclusive choice.
+ */
+ if (device_property_read_bool(dev, "icplus,select-rx-error") &&
+ device_property_read_bool(dev, "icplus,select-interrupt")) {
+ dev_err(dev,
+ "RXER and INTR mode cannot be selected together\n");
+ return -EINVAL;
+ }
+
+ if (device_property_read_bool(dev, "icplus,select-rx-error"))
+ priv->sel_intr32 = IP101GR_SEL_INTR32_RXER;
+ else if (device_property_read_bool(dev, "icplus,select-interrupt"))
+ priv->sel_intr32 = IP101GR_SEL_INTR32_INTR;
+ else
+ priv->sel_intr32 = IP101GR_SEL_INTR32_KEEP;
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
+static int ip101a_g_config_intr_pin(struct phy_device *phydev)
+{
+ struct ip101a_g_phy_priv *priv = phydev->priv;
+ int oldpage, err = 0;
+
+ oldpage = phy_select_page(phydev, IP101G_DEFAULT_PAGE);
+ if (oldpage < 0)
+ goto out;
+
+ /* configure the RXER/INTR_32 pin of the 32-pin IP101GR if needed: */
+ switch (priv->sel_intr32) {
+ case IP101GR_SEL_INTR32_RXER:
+ err = __phy_modify(phydev, IP101G_DIGITAL_IO_SPEC_CTRL,
+ IP101G_DIGITAL_IO_SPEC_CTRL_SEL_INTR32, 0);
+ if (err < 0)
+ goto out;
+ break;
+
+ case IP101GR_SEL_INTR32_INTR:
+ err = __phy_modify(phydev, IP101G_DIGITAL_IO_SPEC_CTRL,
+ IP101G_DIGITAL_IO_SPEC_CTRL_SEL_INTR32,
+ IP101G_DIGITAL_IO_SPEC_CTRL_SEL_INTR32);
+ if (err < 0)
+ goto out;
+ break;
+
+ default:
+ /* Don't touch IP101G_DIGITAL_IO_SPEC_CTRL because it's not
+ * documented on IP101A and it's not clear whether this would
+ * cause problems.
+ * For the 32-pin IP101GR we simply keep the SEL_INTR32
+ * configuration as set by the bootloader when not configured
+ * to one of the special functions.
+ */
+ break;
+ }
+
+out:
+ return phy_restore_page(phydev, oldpage, err);
+}
+
+static int ip101a_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Enable Auto Power Saving mode */
+ ret = phy_set_bits(phydev, IP10XX_SPEC_CTRL_STATUS, IP101A_G_APS_ON);
+ if (ret)
+ return ret;
+
+ return ip101a_g_config_intr_pin(phydev);
+}
+
+static int ip101g_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Enable the PHY counters */
+ ret = phy_modify_paged(phydev, 1, IP101G_P1_CNT_CTRL,
+ CNT_CTRL_RX_EN, CNT_CTRL_RX_EN);
+ if (ret)
+ return ret;
+
+ /* Clear error counters on read */
+ ret = phy_modify_paged(phydev, 8, IP101G_P8_CNT_CTRL,
+ CNT_CTRL_RDCLR_EN, CNT_CTRL_RDCLR_EN);
+ if (ret)
+ return ret;
+
+ return ip101a_g_config_intr_pin(phydev);
+}
+
+static int ip101a_g_read_status(struct phy_device *phydev)
+{
+ int oldpage, ret, stat1, stat2;
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ oldpage = phy_select_page(phydev, IP101G_DEFAULT_PAGE);
+ if (oldpage < 0)
+ goto out;
+
+ ret = __phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
+ if (ret < 0)
+ goto out;
+ stat1 = ret;
+
+ ret = __phy_read(phydev, IP101A_G_PHY_SPEC_CTRL);
+ if (ret < 0)
+ goto out;
+ stat2 = ret;
+
+ if (stat1 & IP101A_G_AUTO_MDIX_DIS) {
+ if (stat2 & IP101A_G_FORCE_MDIX)
+ phydev->mdix_ctrl = ETH_TP_MDI_X;
+ else
+ phydev->mdix_ctrl = ETH_TP_MDI;
+ } else {
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ }
+
+ if (stat2 & IP101A_G_MDIX)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+
+ ret = 0;
+
+out:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+static int ip101a_g_config_mdix(struct phy_device *phydev)
+{
+ u16 ctrl = 0, ctrl2 = 0;
+ int oldpage;
+ int ret = 0;
+
+ switch (phydev->mdix_ctrl) {
+ case ETH_TP_MDI:
+ ctrl = IP101A_G_AUTO_MDIX_DIS;
+ break;
+ case ETH_TP_MDI_X:
+ ctrl = IP101A_G_AUTO_MDIX_DIS;
+ ctrl2 = IP101A_G_FORCE_MDIX;
+ break;
+ case ETH_TP_MDI_AUTO:
+ break;
+ default:
+ return 0;
+ }
+
+ oldpage = phy_select_page(phydev, IP101G_DEFAULT_PAGE);
+ if (oldpage < 0)
+ goto out;
+
+ ret = __phy_modify(phydev, IP10XX_SPEC_CTRL_STATUS,
+ IP101A_G_AUTO_MDIX_DIS, ctrl);
+ if (ret)
+ goto out;
+
+ ret = __phy_modify(phydev, IP101A_G_PHY_SPEC_CTRL,
+ IP101A_G_FORCE_MDIX, ctrl2);
+
+out:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+static int ip101a_g_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = ip101a_g_config_mdix(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_config_aneg(phydev);
+}
+
+static int ip101a_g_ack_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_read_paged(phydev, IP101G_DEFAULT_PAGE,
+ IP101A_G_IRQ_CONF_STATUS);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int ip101a_g_config_intr(struct phy_device *phydev)
+{
+ u16 val;
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = ip101a_g_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ /* INTR pin used: Speed/link/duplex will cause an interrupt */
+ val = IP101A_G_IRQ_PIN_USED;
+ err = phy_write_paged(phydev, IP101G_DEFAULT_PAGE,
+ IP101A_G_IRQ_CONF_STATUS, val);
+ } else {
+ val = IP101A_G_IRQ_ALL_MASK;
+ err = phy_write_paged(phydev, IP101G_DEFAULT_PAGE,
+ IP101A_G_IRQ_CONF_STATUS, val);
+ if (err)
+ return err;
+
+ err = ip101a_g_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t ip101a_g_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read_paged(phydev, IP101G_DEFAULT_PAGE,
+ IP101A_G_IRQ_CONF_STATUS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & (IP101A_G_IRQ_SPEED_CHANGE |
+ IP101A_G_IRQ_DUPLEX_CHANGE |
+ IP101A_G_IRQ_LINK_CHANGE)))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+/* The IP101A doesn't really have a page register. We just pretend to have one
+ * so we can use the paged versions of the callbacks of the IP101G.
+ */
+static int ip101a_read_page(struct phy_device *phydev)
+{
+ return IP101G_DEFAULT_PAGE;
+}
+
+static int ip101a_write_page(struct phy_device *phydev, int page)
+{
+ WARN_ONCE(page != IP101G_DEFAULT_PAGE, "wrong page selected\n");
+
+ return 0;
+}
+
+static int ip101g_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, IP101G_PAGE_CONTROL);
+}
+
+static int ip101g_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, IP101G_PAGE_CONTROL, page);
+}
+
+static int ip101a_g_has_page_register(struct phy_device *phydev)
+{
+ int oldval, val, ret;
+
+ oldval = phy_read(phydev, IP101G_PAGE_CONTROL);
+ if (oldval < 0)
+ return oldval;
+
+ ret = phy_write(phydev, IP101G_PAGE_CONTROL, 0xffff);
+ if (ret)
+ return ret;
+
+ val = phy_read(phydev, IP101G_PAGE_CONTROL);
+ if (val < 0)
+ return val;
+
+ ret = phy_write(phydev, IP101G_PAGE_CONTROL, oldval);
+ if (ret)
+ return ret;
+
+ return val == IP101G_PAGE_CONTROL_MASK;
+}
+
+static int ip101a_g_match_phy_device(struct phy_device *phydev, bool ip101a)
+{
+ int ret;
+
+ if (phydev->phy_id != IP101A_PHY_ID)
+ return 0;
+
+ /* The IP101A and the IP101G share the same PHY identifier.The IP101G
+ * seems to be a successor of the IP101A and implements more functions.
+ * Amongst other things there is a page select register, which is not
+ * available on the IP101A. Use this to distinguish these two.
+ */
+ ret = ip101a_g_has_page_register(phydev);
+ if (ret < 0)
+ return ret;
+
+ return ip101a == !ret;
+}
+
+static int ip101a_match_phy_device(struct phy_device *phydev)
+{
+ return ip101a_g_match_phy_device(phydev, true);
+}
+
+static int ip101g_match_phy_device(struct phy_device *phydev)
+{
+ return ip101a_g_match_phy_device(phydev, false);
+}
+
+static int ip101g_get_sset_count(struct phy_device *phydev)
+{
+ return ARRAY_SIZE(ip101g_hw_stats);
+}
+
+static void ip101g_get_strings(struct phy_device *phydev, u8 *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ip101g_hw_stats); i++)
+ strscpy(data + i * ETH_GSTRING_LEN,
+ ip101g_hw_stats[i].name, ETH_GSTRING_LEN);
+}
+
+static u64 ip101g_get_stat(struct phy_device *phydev, int i)
+{
+ struct ip101g_hw_stat stat = ip101g_hw_stats[i];
+ struct ip101a_g_phy_priv *priv = phydev->priv;
+ int val;
+ u64 ret;
+
+ val = phy_read_paged(phydev, stat.page, IP101G_CNT_REG);
+ if (val < 0) {
+ ret = U64_MAX;
+ } else {
+ priv->stats[i] += val;
+ ret = priv->stats[i];
+ }
+
+ return ret;
+}
+
+static void ip101g_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ip101g_hw_stats); i++)
+ data[i] = ip101g_get_stat(phydev, i);
+}
+
+static struct phy_driver icplus_driver[] = {
+{
+ PHY_ID_MATCH_MODEL(IP175C_PHY_ID),
+ .name = "ICPlus IP175C",
+ /* 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_MATCH_MODEL(IP1001_PHY_ID),
+ .name = "ICPlus IP1001",
+ /* PHY_GBIT_FEATURES */
+ .config_init = ip1001_config_init,
+ .soft_reset = genphy_soft_reset,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .name = "ICPlus IP101A",
+ .match_phy_device = ip101a_match_phy_device,
+ .probe = ip101a_g_probe,
+ .read_page = ip101a_read_page,
+ .write_page = ip101a_write_page,
+ .config_intr = ip101a_g_config_intr,
+ .handle_interrupt = ip101a_g_handle_interrupt,
+ .config_init = ip101a_config_init,
+ .config_aneg = ip101a_g_config_aneg,
+ .read_status = ip101a_g_read_status,
+ .soft_reset = genphy_soft_reset,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .name = "ICPlus IP101G",
+ .match_phy_device = ip101g_match_phy_device,
+ .probe = ip101a_g_probe,
+ .read_page = ip101g_read_page,
+ .write_page = ip101g_write_page,
+ .config_intr = ip101a_g_config_intr,
+ .handle_interrupt = ip101a_g_handle_interrupt,
+ .config_init = ip101g_config_init,
+ .config_aneg = ip101a_g_config_aneg,
+ .read_status = ip101a_g_read_status,
+ .soft_reset = genphy_soft_reset,
+ .get_sset_count = ip101g_get_sset_count,
+ .get_strings = ip101g_get_strings,
+ .get_stats = ip101g_get_stats,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+} };
+
+module_phy_driver(icplus_driver);
+
+static struct mdio_device_id __maybe_unused icplus_tbl[] = {
+ { PHY_ID_MATCH_MODEL(IP175C_PHY_ID) },
+ { PHY_ID_MATCH_MODEL(IP1001_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(IP101A_PHY_ID) },
+ { }
+};
+
+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..3c032868e
--- /dev/null
+++ b/drivers/net/phy/intel-xway.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2012 Daniel Schwierzeck <daniel.schwierzeck@googlemail.com>
+ * Copyright (C) 2016 Hauke Mehrtens <hauke@hauke-m.de>
+ */
+
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/bitfield.h>
+
+#define XWAY_MDIO_MIICTRL 0x17 /* mii control */
+#define XWAY_MDIO_IMASK 0x19 /* interrupt mask */
+#define XWAY_MDIO_ISTAT 0x1A /* interrupt status */
+#define XWAY_MDIO_LED 0x1B /* led control */
+
+#define XWAY_MDIO_MIICTRL_RXSKEW_MASK GENMASK(14, 12)
+#define XWAY_MDIO_MIICTRL_TXSKEW_MASK GENMASK(10, 8)
+
+/* bit 15:12 are reserved */
+#define XWAY_MDIO_LED_LED3_EN BIT(11) /* Enable the integrated function of LED3 */
+#define XWAY_MDIO_LED_LED2_EN BIT(10) /* Enable the integrated function of LED2 */
+#define XWAY_MDIO_LED_LED1_EN BIT(9) /* Enable the integrated function of LED1 */
+#define XWAY_MDIO_LED_LED0_EN BIT(8) /* Enable the integrated function of LED0 */
+/* bit 7:4 are reserved */
+#define XWAY_MDIO_LED_LED3_DA BIT(3) /* Direct Access to LED3 */
+#define XWAY_MDIO_LED_LED2_DA BIT(2) /* Direct Access to LED2 */
+#define XWAY_MDIO_LED_LED1_DA BIT(1) /* Direct Access to LED1 */
+#define XWAY_MDIO_LED_LED0_DA BIT(0) /* Direct Access to LED0 */
+
+#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 const int xway_internal_delay[] = {0, 500, 1000, 1500, 2000, 2500,
+ 3000, 3500};
+
+static int xway_gphy_rgmii_init(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ unsigned int delay_size = ARRAY_SIZE(xway_internal_delay);
+ s32 int_delay;
+ int val = 0;
+
+ if (!phy_interface_is_rgmii(phydev))
+ return 0;
+
+ /* Existing behavior was to use default pin strapping delay in rgmii
+ * mode, but rgmii should have meant no delay. Warn existing users,
+ * but do not change anything at the moment.
+ */
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII) {
+ u16 txskew, rxskew;
+
+ val = phy_read(phydev, XWAY_MDIO_MIICTRL);
+ if (val < 0)
+ return val;
+
+ txskew = FIELD_GET(XWAY_MDIO_MIICTRL_TXSKEW_MASK, val);
+ rxskew = FIELD_GET(XWAY_MDIO_MIICTRL_RXSKEW_MASK, val);
+
+ if (txskew > 0 || rxskew > 0)
+ phydev_warn(phydev,
+ "PHY has delays (e.g. via pin strapping), but phy-mode = 'rgmii'\n"
+ "Should be 'rgmii-id' to use internal delays txskew:%d ps rxskew:%d ps\n",
+ xway_internal_delay[txskew],
+ xway_internal_delay[rxskew]);
+ return 0;
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+ int_delay = phy_get_internal_delay(phydev, dev,
+ xway_internal_delay,
+ delay_size, true);
+
+ /* if rx-internal-delay-ps is missing, use default of 2.0 ns */
+ if (int_delay < 0)
+ int_delay = 4; /* 2000 ps */
+
+ val |= FIELD_PREP(XWAY_MDIO_MIICTRL_RXSKEW_MASK, int_delay);
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+ int_delay = phy_get_internal_delay(phydev, dev,
+ xway_internal_delay,
+ delay_size, false);
+
+ /* if tx-internal-delay-ps is missing, use default of 2.0 ns */
+ if (int_delay < 0)
+ int_delay = 4; /* 2000 ps */
+
+ val |= FIELD_PREP(XWAY_MDIO_MIICTRL_TXSKEW_MASK, int_delay);
+ }
+
+ return phy_modify(phydev, XWAY_MDIO_MIICTRL,
+ XWAY_MDIO_MIICTRL_RXSKEW_MASK |
+ XWAY_MDIO_MIICTRL_TXSKEW_MASK, val);
+}
+
+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);
+
+ /* Ensure that integrated led function is enabled for all leds */
+ err = phy_write(phydev, XWAY_MDIO_LED,
+ XWAY_MDIO_LED_LED0_EN |
+ XWAY_MDIO_LED_LED1_EN |
+ XWAY_MDIO_LED_LED2_EN |
+ XWAY_MDIO_LED_LED3_EN);
+ if (err)
+ return err;
+
+ 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);
+
+ err = xway_gphy_rgmii_init(phydev);
+ if (err)
+ return err;
+
+ 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_config_intr(struct phy_device *phydev)
+{
+ u16 mask = 0;
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = xway_gphy_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ mask = XWAY_MDIO_INIT_MASK;
+ err = phy_write(phydev, XWAY_MDIO_IMASK, mask);
+ } else {
+ err = phy_write(phydev, XWAY_MDIO_IMASK, mask);
+ if (err)
+ return err;
+
+ err = xway_gphy_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t xway_gphy_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, XWAY_MDIO_ISTAT);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & XWAY_MDIO_INIT_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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",
+ /* PHY_GBIT_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .config_aneg = xway_gphy14_config_aneg,
+ .handle_interrupt = xway_gphy_handle_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",
+ /* PHY_BASIC_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .config_aneg = xway_gphy14_config_aneg,
+ .handle_interrupt = xway_gphy_handle_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",
+ /* PHY_GBIT_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .config_aneg = xway_gphy14_config_aneg,
+ .handle_interrupt = xway_gphy_handle_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",
+ /* PHY_BASIC_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .config_aneg = xway_gphy14_config_aneg,
+ .handle_interrupt = xway_gphy_handle_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",
+ /* PHY_GBIT_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .handle_interrupt = xway_gphy_handle_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",
+ /* PHY_BASIC_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .handle_interrupt = xway_gphy_handle_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)",
+ /* PHY_GBIT_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .handle_interrupt = xway_gphy_handle_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)",
+ /* PHY_BASIC_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .handle_interrupt = xway_gphy_handle_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)",
+ /* PHY_GBIT_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .handle_interrupt = xway_gphy_handle_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)",
+ /* PHY_BASIC_FEATURES */
+ .config_init = xway_gphy_config_init,
+ .handle_interrupt = xway_gphy_handle_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/linkmode.c b/drivers/net/phy/linkmode.c
new file mode 100644
index 000000000..f60560fe3
--- /dev/null
+++ b/drivers/net/phy/linkmode.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/linkmode.h>
+
+/**
+ * linkmode_resolve_pause - resolve the allowable pause modes
+ * @local_adv: local advertisement in ethtool format
+ * @partner_adv: partner advertisement in ethtool format
+ * @tx_pause: pointer to bool to indicate whether transmit pause should be
+ * enabled.
+ * @rx_pause: pointer to bool to indicate whether receive pause should be
+ * enabled.
+ *
+ * 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
+ * 0 X 0 X Disabled
+ * 0 1 1 0 Disabled
+ * 0 1 1 1 TX
+ * 1 0 0 X Disabled
+ * 1 X 1 X TX+RX
+ * 1 1 0 1 RX
+ */
+void linkmode_resolve_pause(const unsigned long *local_adv,
+ const unsigned long *partner_adv,
+ bool *tx_pause, bool *rx_pause)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(m);
+
+ linkmode_and(m, local_adv, partner_adv);
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, m)) {
+ *tx_pause = true;
+ *rx_pause = true;
+ } else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, m)) {
+ *tx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ partner_adv);
+ *rx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ local_adv);
+ } else {
+ *tx_pause = false;
+ *rx_pause = false;
+ }
+}
+EXPORT_SYMBOL_GPL(linkmode_resolve_pause);
+
+/**
+ * linkmode_set_pause - set the pause mode advertisement
+ * @advertisement: advertisement in ethtool format
+ * @tx: boolean from ethtool struct ethtool_pauseparam tx_pause member
+ * @rx: boolean from ethtool struct ethtool_pauseparam rx_pause member
+ *
+ * Configure the advertised Pause and Asym_Pause bits according to the
+ * capabilities of provided in @tx and @rx.
+ *
+ * We convert as follows:
+ * tx rx Pause AsymDir
+ * 0 0 0 0
+ * 0 1 1 1
+ * 1 0 0 1
+ * 1 1 1 0
+ *
+ * Note: this translation from ethtool tx/rx notation to the advertisement
+ * is actually very problematical. Here are some examples:
+ *
+ * For tx=0 rx=1, meaning transmit is unsupported, receive is supported:
+ *
+ * Local device Link partner
+ * Pause AsymDir Pause AsymDir Result
+ * 1 1 1 0 TX + RX - but we have no TX support.
+ * 1 1 0 1 Only this gives RX only
+ *
+ * For tx=1 rx=1, meaning we have the capability to transmit and receive
+ * pause frames:
+ *
+ * Local device Link partner
+ * Pause AsymDir Pause AsymDir Result
+ * 1 0 0 1 Disabled - but since we do support tx and rx,
+ * this should resolve to RX only.
+ *
+ * Hence, asking for:
+ * rx=1 tx=0 gives Pause+AsymDir advertisement, but we may end up
+ * resolving to tx+rx pause or only rx pause depending on
+ * the partners advertisement.
+ * rx=0 tx=1 gives AsymDir only, which will only give tx pause if
+ * the partners advertisement allows it.
+ * rx=1 tx=1 gives Pause only, which will only allow tx+rx pause
+ * if the other end also advertises Pause.
+ */
+void linkmode_set_pause(unsigned long *advertisement, bool tx, bool rx)
+{
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertisement, rx);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertisement,
+ rx ^ tx);
+}
+EXPORT_SYMBOL_GPL(linkmode_set_pause);
diff --git a/drivers/net/phy/lxt.c b/drivers/net/phy/lxt.c
new file mode 100644
index 000000000..e3bf827b7
--- /dev/null
+++ b/drivers/net/phy/lxt.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/lxt.c
+ *
+ * Driver for Intel LXT PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+#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_IRS_MINT BIT(15)
+
+#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 */
+#define MII_LXT971_ISR_MASK 0x00f0
+
+/* 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)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = lxt970_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN);
+ } else {
+ err = phy_write(phydev, MII_LXT970_IER, 0);
+ if (err)
+ return err;
+
+ err = lxt970_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t lxt970_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ /* The interrupt status register is cleared by reading BMSR
+ * followed by MII_LXT970_ISR
+ */
+ irq_status = phy_read(phydev, MII_BMSR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_status = phy_read(phydev, MII_LXT970_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_LXT970_IRS_MINT))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = lxt971_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN);
+ } else {
+ err = phy_write(phydev, MII_LXT971_IER, 0);
+ if (err)
+ return err;
+
+ err = lxt971_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t lxt971_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_LXT971_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_LXT971_ISR_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * 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--);
+
+ mii_lpa_to_linkmode_lpa_t(phydev->lp_advertising, 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;
+ }
+
+ phy_resolve_aneg_pause(phydev);
+ } else {
+ err = genphy_read_status_fixed(phydev);
+ if (err < 0)
+ return err;
+
+ phydev->pause = phydev->asym_pause = 0;
+ linkmode_zero(phydev->lp_advertising);
+ }
+
+ 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;
+ phydev->port = PORT_FIBRE;
+ } 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,
+ /* PHY_BASIC_FEATURES */
+ .config_init = lxt970_config_init,
+ .config_intr = lxt970_config_intr,
+ .handle_interrupt = lxt970_handle_interrupt,
+}, {
+ .phy_id = 0x001378e0,
+ .name = "LXT971",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_intr = lxt971_config_intr,
+ .handle_interrupt = lxt971_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = 0x00137a10,
+ .name = "LXT973-A2",
+ .phy_id_mask = 0xffffffff,
+ /* PHY_BASIC_FEATURES */
+ .flags = 0,
+ .probe = lxt973_probe,
+ .config_aneg = lxt973_config_aneg,
+ .read_status = lxt973a2_read_status,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = 0x00137a10,
+ .name = "LXT973",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .flags = 0,
+ .probe = lxt973_probe,
+ .config_aneg = lxt973_config_aneg,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+} };
+
+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-88q2xxx.c b/drivers/net/phy/marvell-88q2xxx.c
new file mode 100644
index 000000000..1c3ff77de
--- /dev/null
+++ b/drivers/net/phy/marvell-88q2xxx.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Marvell 88Q2XXX automotive 100BASE-T1/1000BASE-T1 PHY driver
+ */
+#include <linux/ethtool_netlink.h>
+#include <linux/marvell_phy.h>
+#include <linux/phy.h>
+
+#define MDIO_MMD_AN_MV_STAT 32769
+#define MDIO_MMD_AN_MV_STAT_ANEG 0x0100
+#define MDIO_MMD_AN_MV_STAT_LOCAL_RX 0x1000
+#define MDIO_MMD_AN_MV_STAT_REMOTE_RX 0x2000
+#define MDIO_MMD_AN_MV_STAT_LOCAL_MASTER 0x4000
+#define MDIO_MMD_AN_MV_STAT_MS_CONF_FAULT 0x8000
+
+#define MDIO_MMD_PCS_MV_100BT1_STAT1 33032
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_IDLE_ERROR 0x00FF
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_JABBER 0x0100
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_LINK 0x0200
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_LOCAL_RX 0x1000
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_REMOTE_RX 0x2000
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_LOCAL_MASTER 0x4000
+
+#define MDIO_MMD_PCS_MV_100BT1_STAT2 33033
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_JABBER 0x0001
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_POL 0x0002
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_LINK 0x0004
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_ANGE 0x0008
+
+static int mv88q2xxx_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+ int val;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MDIO_PCS_1000BT1_CTRL, MDIO_PCS_1000BT1_CTRL_RESET);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PCS,
+ MDIO_PCS_1000BT1_CTRL, val,
+ !(val & MDIO_PCS_1000BT1_CTRL_RESET),
+ 50000, 600000, true);
+}
+
+static int mv88q2xxx_read_link_gbit(struct phy_device *phydev)
+{
+ int ret;
+ bool link = false;
+
+ /* Read vendor specific Auto-Negotiation status register to get local
+ * and remote receiver status according to software initialization
+ * guide.
+ */
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_MMD_AN_MV_STAT);
+ if (ret < 0) {
+ return ret;
+ } else if ((ret & MDIO_MMD_AN_MV_STAT_LOCAL_RX) &&
+ (ret & MDIO_MMD_AN_MV_STAT_REMOTE_RX)) {
+ /* 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 except
+ * the link was already down.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_1000BT1_STAT);
+ if (ret < 0)
+ return ret;
+ else if (ret & MDIO_PCS_1000BT1_STAT_LINK)
+ link = true;
+ }
+
+ if (!link) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_1000BT1_STAT);
+ if (ret < 0)
+ return ret;
+ else if (ret & MDIO_PCS_1000BT1_STAT_LINK)
+ link = true;
+ }
+ }
+
+ phydev->link = link;
+
+ return 0;
+}
+
+static int mv88q2xxx_read_link_100m(struct phy_device *phydev)
+{
+ int ret;
+
+ /* 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 except
+ * the link was already down. In case we are not polling,
+ * we always read the realtime status.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_100BT1_STAT1);
+ if (ret < 0)
+ return ret;
+ else if (ret & MDIO_MMD_PCS_MV_100BT1_STAT1_LINK)
+ goto out;
+ }
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_100BT1_STAT1);
+ if (ret < 0)
+ return ret;
+
+out:
+ /* Check if we have link and if the remote and local receiver are ok */
+ if ((ret & MDIO_MMD_PCS_MV_100BT1_STAT1_LINK) &&
+ (ret & MDIO_MMD_PCS_MV_100BT1_STAT1_LOCAL_RX) &&
+ (ret & MDIO_MMD_PCS_MV_100BT1_STAT1_REMOTE_RX))
+ phydev->link = true;
+ else
+ phydev->link = false;
+
+ return 0;
+}
+
+static int mv88q2xxx_read_link(struct phy_device *phydev)
+{
+ int ret;
+
+ /* The 88Q2XXX PHYs do not have the PMA/PMD status register available,
+ * therefore we need to read the link status from the vendor specific
+ * registers depending on the speed.
+ */
+ if (phydev->speed == SPEED_1000)
+ ret = mv88q2xxx_read_link_gbit(phydev);
+ else
+ ret = mv88q2xxx_read_link_100m(phydev);
+
+ return ret;
+}
+
+static int mv88q2xxx_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = mv88q2xxx_read_link(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_c45_read_pma(phydev);
+}
+
+static int mv88q2xxx_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* We need to read the baset1 extended abilities manually because the
+ * PHY does not signalize it has the extended abilities register
+ * available.
+ */
+ ret = genphy_c45_pma_baset1_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* The PHY signalizes it supports autonegotiation. Unfortunately, so
+ * far it was not possible to get a link even when following the init
+ * sequence provided by Marvell. Disable it for now until a proper
+ * workaround is found or a new PHY revision is released.
+ */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+
+ return 0;
+}
+
+static int mv88q2xxx_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_config_aneg(phydev);
+ if (ret)
+ return ret;
+
+ return mv88q2xxx_soft_reset(phydev);
+}
+
+static int mv88q2xxx_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* The 88Q2XXX PHYs do have the extended ability register available, but
+ * register MDIO_PMA_EXTABLE where they should signalize it does not
+ * work according to specification. Therefore, we force it here.
+ */
+ phydev->pma_extable = MDIO_PMA_EXTABLE_BT1;
+
+ /* Read the current PHY configuration */
+ ret = genphy_c45_read_pma(phydev);
+ if (ret)
+ return ret;
+
+ return mv88q2xxx_config_aneg(phydev);
+}
+
+static int mv88q2xxxx_get_sqi(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->speed == SPEED_100) {
+ /* Read the SQI from the vendor specific receiver status
+ * register
+ */
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x8230);
+ if (ret < 0)
+ return ret;
+
+ ret = ret >> 12;
+ } else {
+ /* Read from vendor specific registers, they are not documented
+ * but can be found in the Software Initialization Guide. Only
+ * revisions >= A0 are supported.
+ */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, 0xFC5D, 0x00FF, 0x00AC);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, 0xfc88);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret & 0x0F;
+}
+
+static int mv88q2xxxx_get_sqi_max(struct phy_device *phydev)
+{
+ return 15;
+}
+
+static struct phy_driver mv88q2xxx_driver[] = {
+ {
+ .phy_id = MARVELL_PHY_ID_88Q2110,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "mv88q2110",
+ .get_features = mv88q2xxx_get_features,
+ .config_aneg = mv88q2xxx_config_aneg,
+ .config_init = mv88q2xxx_config_init,
+ .read_status = mv88q2xxx_read_status,
+ .soft_reset = mv88q2xxx_soft_reset,
+ .set_loopback = genphy_c45_loopback,
+ .get_sqi = mv88q2xxxx_get_sqi,
+ .get_sqi_max = mv88q2xxxx_get_sqi_max,
+ },
+};
+
+module_phy_driver(mv88q2xxx_driver);
+
+static struct mdio_device_id __maybe_unused mv88q2xxx_tbl[] = {
+ { MARVELL_PHY_ID_88Q2110, MARVELL_PHY_ID_MASK },
+ { /*sentinel*/ }
+};
+MODULE_DEVICE_TABLE(mdio, mv88q2xxx_tbl);
+
+MODULE_DESCRIPTION("Marvell 88Q2XXX 100/1000BASE-T1 Automotive Ethernet PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c
new file mode 100644
index 000000000..e3aa30dad
--- /dev/null
+++ b/drivers/net/phy/marvell-88x2222.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Marvell 88x2222 dual-port multi-speed ethernet transceiver.
+ *
+ * Supports:
+ * XAUI on the host side.
+ * 1000Base-X or 10GBase-R on the line side.
+ * SGMII over 1000Base-X.
+ */
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/mdio.h>
+#include <linux/marvell_phy.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/sfp.h>
+#include <linux/netdevice.h>
+
+/* Port PCS Configuration */
+#define MV_PCS_CONFIG 0xF002
+#define MV_PCS_HOST_XAUI 0x73
+#define MV_PCS_LINE_10GBR (0x71 << 8)
+#define MV_PCS_LINE_1GBX_AN (0x7B << 8)
+#define MV_PCS_LINE_SGMII_AN (0x7F << 8)
+
+/* Port Reset and Power Down */
+#define MV_PORT_RST 0xF003
+#define MV_LINE_RST_SW BIT(15)
+#define MV_HOST_RST_SW BIT(7)
+#define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW)
+
+/* PMD Receive Signal Detect */
+#define MV_RX_SIGNAL_DETECT 0x000A
+#define MV_RX_SIGNAL_DETECT_GLOBAL BIT(0)
+
+/* 1000Base-X/SGMII Control Register */
+#define MV_1GBX_CTRL (0x2000 + MII_BMCR)
+
+/* 1000BASE-X/SGMII Status Register */
+#define MV_1GBX_STAT (0x2000 + MII_BMSR)
+
+/* 1000Base-X Auto-Negotiation Advertisement Register */
+#define MV_1GBX_ADVERTISE (0x2000 + MII_ADVERTISE)
+
+/* 1000Base-X PHY Specific Status Register */
+#define MV_1GBX_PHY_STAT 0xA003
+#define MV_1GBX_PHY_STAT_AN_RESOLVED BIT(11)
+#define MV_1GBX_PHY_STAT_DUPLEX BIT(13)
+#define MV_1GBX_PHY_STAT_SPEED100 BIT(14)
+#define MV_1GBX_PHY_STAT_SPEED1000 BIT(15)
+
+#define AUTONEG_TIMEOUT 3
+
+struct mv2222_data {
+ phy_interface_t line_interface;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+ bool sfp_link;
+};
+
+/* SFI PMA transmit enable */
+static int mv2222_tx_enable(struct phy_device *phydev)
+{
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_TXDIS,
+ MDIO_PMD_TXDIS_GLOBAL);
+}
+
+/* SFI PMA transmit disable */
+static int mv2222_tx_disable(struct phy_device *phydev)
+{
+ return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_TXDIS,
+ MDIO_PMD_TXDIS_GLOBAL);
+}
+
+static int mv2222_soft_reset(struct phy_device *phydev)
+{
+ int val, ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PORT_RST,
+ MV_PORT_RST_SW);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND2, MV_PORT_RST,
+ val, !(val & MV_PORT_RST_SW),
+ 5000, 1000000, true);
+}
+
+static int mv2222_disable_aneg(struct phy_device *phydev)
+{
+ int ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL,
+ BMCR_ANENABLE | BMCR_ANRESTART);
+ if (ret < 0)
+ return ret;
+
+ return mv2222_soft_reset(phydev);
+}
+
+static int mv2222_enable_aneg(struct phy_device *phydev)
+{
+ int ret = phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL,
+ BMCR_ANENABLE | BMCR_RESET);
+ if (ret < 0)
+ return ret;
+
+ return mv2222_soft_reset(phydev);
+}
+
+static int mv2222_set_sgmii_speed(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+
+ switch (phydev->speed) {
+ default:
+ case SPEED_1000:
+ if ((linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ priv->supported)))
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ MV_1GBX_CTRL,
+ BMCR_SPEED1000 | BMCR_SPEED100,
+ BMCR_SPEED1000);
+
+ fallthrough;
+ case SPEED_100:
+ if ((linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ priv->supported)))
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ MV_1GBX_CTRL,
+ BMCR_SPEED1000 | BMCR_SPEED100,
+ BMCR_SPEED100);
+ fallthrough;
+ case SPEED_10:
+ if ((linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ priv->supported)))
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS,
+ MV_1GBX_CTRL,
+ BMCR_SPEED1000 | BMCR_SPEED100,
+ BMCR_SPEED10);
+
+ return -EINVAL;
+ }
+}
+
+static bool mv2222_is_10g_capable(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+
+ return (linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
+ priv->supported));
+}
+
+static bool mv2222_is_1gbx_capable(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+
+ return linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ priv->supported);
+}
+
+static bool mv2222_is_sgmii_capable(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+
+ return (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ priv->supported) ||
+ linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ priv->supported));
+}
+
+static int mv2222_config_line(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+
+ switch (priv->line_interface) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG,
+ MV_PCS_HOST_XAUI | MV_PCS_LINE_10GBR);
+ case PHY_INTERFACE_MODE_1000BASEX:
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG,
+ MV_PCS_HOST_XAUI | MV_PCS_LINE_1GBX_AN);
+ case PHY_INTERFACE_MODE_SGMII:
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG,
+ MV_PCS_HOST_XAUI | MV_PCS_LINE_SGMII_AN);
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Switch between 1G (1000Base-X/SGMII) and 10G (10GBase-R) modes */
+static int mv2222_swap_line_type(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+ bool changed = false;
+ int ret;
+
+ switch (priv->line_interface) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ if (mv2222_is_1gbx_capable(phydev)) {
+ priv->line_interface = PHY_INTERFACE_MODE_1000BASEX;
+ changed = true;
+ }
+
+ if (mv2222_is_sgmii_capable(phydev)) {
+ priv->line_interface = PHY_INTERFACE_MODE_SGMII;
+ changed = true;
+ }
+
+ break;
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ if (mv2222_is_10g_capable(phydev)) {
+ priv->line_interface = PHY_INTERFACE_MODE_10GBASER;
+ changed = true;
+ }
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (changed) {
+ ret = mv2222_config_line(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mv2222_setup_forced(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+ int ret;
+
+ if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) {
+ if (phydev->speed < SPEED_10000 &&
+ phydev->speed != SPEED_UNKNOWN) {
+ ret = mv2222_swap_line_type(phydev);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ if (priv->line_interface == PHY_INTERFACE_MODE_SGMII) {
+ ret = mv2222_set_sgmii_speed(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return mv2222_disable_aneg(phydev);
+}
+
+static int mv2222_config_aneg(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+ int ret, adv;
+
+ /* SFP is not present, do nothing */
+ if (priv->line_interface == PHY_INTERFACE_MODE_NA)
+ return 0;
+
+ if (phydev->autoneg == AUTONEG_DISABLE ||
+ priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
+ return mv2222_setup_forced(phydev);
+
+ adv = linkmode_adv_to_mii_adv_x(priv->supported,
+ ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
+
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_ADVERTISE,
+ ADVERTISE_1000XFULL |
+ ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM,
+ adv);
+ if (ret < 0)
+ return ret;
+
+ return mv2222_enable_aneg(phydev);
+}
+
+static int mv2222_aneg_done(struct phy_device *phydev)
+{
+ int ret;
+
+ if (mv2222_is_10g_capable(phydev)) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
+ if (ret < 0)
+ return ret;
+
+ if (ret & MDIO_STAT1_LSTATUS)
+ return 1;
+ }
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT);
+ if (ret < 0)
+ return ret;
+
+ return (ret & BMSR_ANEGCOMPLETE);
+}
+
+/* Returns negative on error, 0 if link is down, 1 if link is up */
+static int mv2222_read_status_10g(struct phy_device *phydev)
+{
+ static int timeout;
+ int val, link = 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_STAT1_LSTATUS) {
+ link = 1;
+
+ /* 10GBASE-R do not support auto-negotiation */
+ phydev->autoneg = AUTONEG_DISABLE;
+ phydev->speed = SPEED_10000;
+ phydev->duplex = DUPLEX_FULL;
+ } else {
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ timeout++;
+
+ if (timeout > AUTONEG_TIMEOUT) {
+ timeout = 0;
+
+ val = mv2222_swap_line_type(phydev);
+ if (val < 0)
+ return val;
+
+ return mv2222_config_aneg(phydev);
+ }
+ }
+ }
+
+ return link;
+}
+
+/* Returns negative on error, 0 if link is down, 1 if link is up */
+static int mv2222_read_status_1g(struct phy_device *phydev)
+{
+ static int timeout;
+ int val, link = 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT);
+ if (val < 0)
+ return val;
+
+ if (phydev->autoneg == AUTONEG_ENABLE &&
+ !(val & BMSR_ANEGCOMPLETE)) {
+ timeout++;
+
+ if (timeout > AUTONEG_TIMEOUT) {
+ timeout = 0;
+
+ val = mv2222_swap_line_type(phydev);
+ if (val < 0)
+ return val;
+
+ return mv2222_config_aneg(phydev);
+ }
+
+ return 0;
+ }
+
+ if (!(val & BMSR_LSTATUS))
+ return 0;
+
+ link = 1;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_PHY_STAT);
+ if (val < 0)
+ return val;
+
+ if (val & MV_1GBX_PHY_STAT_AN_RESOLVED) {
+ if (val & MV_1GBX_PHY_STAT_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ if (val & MV_1GBX_PHY_STAT_SPEED1000)
+ phydev->speed = SPEED_1000;
+ else if (val & MV_1GBX_PHY_STAT_SPEED100)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_10;
+ }
+
+ return link;
+}
+
+static bool mv2222_link_is_operational(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_RX_SIGNAL_DETECT);
+ if (val < 0 || !(val & MV_RX_SIGNAL_DETECT_GLOBAL))
+ return false;
+
+ if (phydev->sfp_bus && !priv->sfp_link)
+ return false;
+
+ return true;
+}
+
+static int mv2222_read_status(struct phy_device *phydev)
+{
+ struct mv2222_data *priv = phydev->priv;
+ int link;
+
+ phydev->link = 0;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+
+ if (!mv2222_link_is_operational(phydev))
+ return 0;
+
+ if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
+ link = mv2222_read_status_10g(phydev);
+ else
+ link = mv2222_read_status_1g(phydev);
+
+ if (link < 0)
+ return link;
+
+ phydev->link = link;
+
+ return 0;
+}
+
+static int mv2222_resume(struct phy_device *phydev)
+{
+ return mv2222_tx_enable(phydev);
+}
+
+static int mv2222_suspend(struct phy_device *phydev)
+{
+ return mv2222_tx_disable(phydev);
+}
+
+static int mv2222_get_features(struct phy_device *phydev)
+{
+ /* All supported linkmodes are set at probe */
+
+ return 0;
+}
+
+static int mv2222_config_init(struct phy_device *phydev)
+{
+ if (phydev->interface != PHY_INTERFACE_MODE_XAUI)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mv2222_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
+ struct phy_device *phydev = upstream;
+ phy_interface_t sfp_interface;
+ struct mv2222_data *priv;
+ struct device *dev;
+ int ret;
+
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_supported) = { 0, };
+
+ priv = phydev->priv;
+ dev = &phydev->mdio.dev;
+
+ sfp_parse_support(phydev->sfp_bus, id, sfp_supported, interfaces);
+ phydev->port = sfp_parse_port(phydev->sfp_bus, id, sfp_supported);
+ sfp_interface = sfp_select_interface(phydev->sfp_bus, sfp_supported);
+
+ dev_info(dev, "%s SFP module inserted\n", phy_modes(sfp_interface));
+
+ if (sfp_interface != PHY_INTERFACE_MODE_10GBASER &&
+ sfp_interface != PHY_INTERFACE_MODE_1000BASEX &&
+ sfp_interface != PHY_INTERFACE_MODE_SGMII) {
+ dev_err(dev, "Incompatible SFP module inserted\n");
+
+ return -EINVAL;
+ }
+
+ priv->line_interface = sfp_interface;
+ linkmode_and(priv->supported, phydev->supported, sfp_supported);
+
+ ret = mv2222_config_line(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (mutex_trylock(&phydev->lock)) {
+ ret = mv2222_config_aneg(phydev);
+ mutex_unlock(&phydev->lock);
+ }
+
+ return ret;
+}
+
+static void mv2222_sfp_remove(void *upstream)
+{
+ struct phy_device *phydev = upstream;
+ struct mv2222_data *priv;
+
+ priv = phydev->priv;
+
+ priv->line_interface = PHY_INTERFACE_MODE_NA;
+ linkmode_zero(priv->supported);
+ phydev->port = PORT_NONE;
+}
+
+static void mv2222_sfp_link_up(void *upstream)
+{
+ struct phy_device *phydev = upstream;
+ struct mv2222_data *priv;
+
+ priv = phydev->priv;
+ priv->sfp_link = true;
+}
+
+static void mv2222_sfp_link_down(void *upstream)
+{
+ struct phy_device *phydev = upstream;
+ struct mv2222_data *priv;
+
+ priv = phydev->priv;
+ priv->sfp_link = false;
+}
+
+static const struct sfp_upstream_ops sfp_phy_ops = {
+ .module_insert = mv2222_sfp_insert,
+ .module_remove = mv2222_sfp_remove,
+ .link_up = mv2222_sfp_link_up,
+ .link_down = mv2222_sfp_link_down,
+ .attach = phy_sfp_attach,
+ .detach = phy_sfp_detach,
+};
+
+static int mv2222_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct mv2222_data *priv = NULL;
+
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
+
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, supported);
+
+ linkmode_copy(phydev->supported, supported);
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->line_interface = PHY_INTERFACE_MODE_NA;
+ phydev->priv = priv;
+
+ return phy_sfp_probe(phydev, &sfp_phy_ops);
+}
+
+static struct phy_driver mv2222_drivers[] = {
+ {
+ .phy_id = MARVELL_PHY_ID_88X2222,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88X2222",
+ .get_features = mv2222_get_features,
+ .soft_reset = mv2222_soft_reset,
+ .config_init = mv2222_config_init,
+ .config_aneg = mv2222_config_aneg,
+ .aneg_done = mv2222_aneg_done,
+ .probe = mv2222_probe,
+ .suspend = mv2222_suspend,
+ .resume = mv2222_resume,
+ .read_status = mv2222_read_status,
+ },
+};
+module_phy_driver(mv2222_drivers);
+
+static struct mdio_device_id __maybe_unused mv2222_tbl[] = {
+ { MARVELL_PHY_ID_88X2222, MARVELL_PHY_ID_MASK },
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, mv2222_tbl);
+
+MODULE_DESCRIPTION("Marvell 88x2222 ethernet transceiver driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
new file mode 100644
index 000000000..eba652a4c
--- /dev/null
+++ b/drivers/net/phy/marvell.c
@@ -0,0 +1,3764 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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>
+ */
+#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/ethtool_netlink.h>
+#include <linux/phy.h>
+#include <linux/marvell_phy.h>
+#include <linux/bitfield.h>
+#include <linux/of.h>
+#include <linux/sfp.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_VCT5_PAGE 0x05
+#define MII_MARVELL_MISC_TEST_PAGE 0x06
+#define MII_MARVELL_VCT7_PAGE 0x07
+#define MII_MARVELL_WOL_PAGE 0x11
+#define MII_MARVELL_MODE_PAGE 0x12
+
+#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_MASK GENMASK(14, 12)
+#define MII_M1011_PHY_SCR_DOWNSHIFT_MAX 8
+#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_M1011_PHY_SSR 0x11
+#define MII_M1011_PHY_SSR_DOWNSHIFT BIT(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_PHY_EXT_CR_DOWNSHIFT_MASK GENMASK(11, 9)
+#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_MAX 8
+#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN BIT(8)
+#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_1000X_AN 0x8
+#define MII_M1111_HWCFG_MODE_COPPER_RTBI 0x9
+#define MII_M1111_HWCFG_MODE_COPPER_RGMII 0xb
+#define MII_M1111_HWCFG_MODE_COPPER_1000X_NOAN 0xc
+#define MII_M1111_HWCFG_SERIAL_AN_BYPASS BIT(12)
+#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_88E1540_COPPER_CTRL3 0x1a
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK GENMASK(11, 10)
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS 0
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS 1
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS 2
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS 3
+#define MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN BIT(9)
+
+#define MII_88E6390_MISC_TEST 0x1b
+#define MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE_SAMPLE_1S (0x0 << 14)
+#define MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE (0x1 << 14)
+#define MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE_ONESHOT (0x2 << 14)
+#define MII_88E6390_MISC_TEST_TEMP_SENSOR_DISABLE (0x3 << 14)
+#define MII_88E6390_MISC_TEST_TEMP_SENSOR_MASK (0x3 << 14)
+#define MII_88E6393_MISC_TEST_SAMPLES_2048 (0x0 << 11)
+#define MII_88E6393_MISC_TEST_SAMPLES_4096 (0x1 << 11)
+#define MII_88E6393_MISC_TEST_SAMPLES_8192 (0x2 << 11)
+#define MII_88E6393_MISC_TEST_SAMPLES_16384 (0x3 << 11)
+#define MII_88E6393_MISC_TEST_SAMPLES_MASK (0x3 << 11)
+#define MII_88E6393_MISC_TEST_RATE_2_3MS (0x5 << 8)
+#define MII_88E6393_MISC_TEST_RATE_6_4MS (0x6 << 8)
+#define MII_88E6393_MISC_TEST_RATE_11_9MS (0x7 << 8)
+#define MII_88E6393_MISC_TEST_RATE_MASK (0x7 << 8)
+
+#define MII_88E6390_TEMP_SENSOR 0x1c
+#define MII_88E6393_TEMP_SENSOR_THRESHOLD_MASK 0xff00
+#define MII_88E6393_TEMP_SENSOR_THRESHOLD_SHIFT 8
+#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)
+
+#define MII_88E1318S_PHY_LED_FUNC 0x10
+#define MII_88E1318S_PHY_LED_FUNC_OFF (0x8)
+#define MII_88E1318S_PHY_LED_FUNC_ON (0x9)
+#define MII_88E1318S_PHY_LED_FUNC_HI_Z (0xa)
+#define MII_88E1318S_PHY_LED_FUNC_BLINK (0xb)
+#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_LINK_UP_ENABLE BIT(13)
+#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_88E1510_PHY_LED0_LINK_LED1_ACTIVE 0x1040
+
+#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_RGMII 0x0 /* RGMII to copper */
+#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */
+/* RGMII to 1000BASE-X */
+#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X 0x2
+/* RGMII to 100BASE-FX */
+#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX 0x3
+/* RGMII to SGMII */
+#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII 0x4
+#define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */
+
+#define MII_88E1510_MSCR_2 0x15
+
+#define MII_VCT5_TX_RX_MDI0_COUPLING 0x10
+#define MII_VCT5_TX_RX_MDI1_COUPLING 0x11
+#define MII_VCT5_TX_RX_MDI2_COUPLING 0x12
+#define MII_VCT5_TX_RX_MDI3_COUPLING 0x13
+#define MII_VCT5_TX_RX_AMPLITUDE_MASK 0x7f00
+#define MII_VCT5_TX_RX_AMPLITUDE_SHIFT 8
+#define MII_VCT5_TX_RX_COUPLING_POSITIVE_REFLECTION BIT(15)
+
+#define MII_VCT5_CTRL 0x17
+#define MII_VCT5_CTRL_ENABLE BIT(15)
+#define MII_VCT5_CTRL_COMPLETE BIT(14)
+#define MII_VCT5_CTRL_TX_SAME_CHANNEL (0x0 << 11)
+#define MII_VCT5_CTRL_TX0_CHANNEL (0x4 << 11)
+#define MII_VCT5_CTRL_TX1_CHANNEL (0x5 << 11)
+#define MII_VCT5_CTRL_TX2_CHANNEL (0x6 << 11)
+#define MII_VCT5_CTRL_TX3_CHANNEL (0x7 << 11)
+#define MII_VCT5_CTRL_SAMPLES_2 (0x0 << 8)
+#define MII_VCT5_CTRL_SAMPLES_4 (0x1 << 8)
+#define MII_VCT5_CTRL_SAMPLES_8 (0x2 << 8)
+#define MII_VCT5_CTRL_SAMPLES_16 (0x3 << 8)
+#define MII_VCT5_CTRL_SAMPLES_32 (0x4 << 8)
+#define MII_VCT5_CTRL_SAMPLES_64 (0x5 << 8)
+#define MII_VCT5_CTRL_SAMPLES_128 (0x6 << 8)
+#define MII_VCT5_CTRL_SAMPLES_DEFAULT (0x6 << 8)
+#define MII_VCT5_CTRL_SAMPLES_256 (0x7 << 8)
+#define MII_VCT5_CTRL_SAMPLES_SHIFT 8
+#define MII_VCT5_CTRL_MODE_MAXIMUM_PEEK (0x0 << 6)
+#define MII_VCT5_CTRL_MODE_FIRST_LAST_PEEK (0x1 << 6)
+#define MII_VCT5_CTRL_MODE_OFFSET (0x2 << 6)
+#define MII_VCT5_CTRL_SAMPLE_POINT (0x3 << 6)
+#define MII_VCT5_CTRL_PEEK_HYST_DEFAULT 3
+
+#define MII_VCT5_SAMPLE_POINT_DISTANCE 0x18
+#define MII_VCT5_SAMPLE_POINT_DISTANCE_MAX 511
+#define MII_VCT5_TX_PULSE_CTRL 0x1c
+#define MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN BIT(12)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS (0x0 << 10)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_96nS (0x1 << 10)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_64nS (0x2 << 10)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS (0x3 << 10)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_SHIFT 10
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_1000mV (0x0 << 8)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_750mV (0x1 << 8)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_500mV (0x2 << 8)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_250mV (0x3 << 8)
+#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_SHIFT 8
+#define MII_VCT5_TX_PULSE_CTRL_MAX_AMP BIT(7)
+#define MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV (0x6 << 0)
+
+/* For TDR measurements less than 11 meters, a short pulse should be
+ * used.
+ */
+#define TDR_SHORT_CABLE_LENGTH 11
+
+#define MII_VCT7_PAIR_0_DISTANCE 0x10
+#define MII_VCT7_PAIR_1_DISTANCE 0x11
+#define MII_VCT7_PAIR_2_DISTANCE 0x12
+#define MII_VCT7_PAIR_3_DISTANCE 0x13
+
+#define MII_VCT7_RESULTS 0x14
+#define MII_VCT7_RESULTS_PAIR3_MASK 0xf000
+#define MII_VCT7_RESULTS_PAIR2_MASK 0x0f00
+#define MII_VCT7_RESULTS_PAIR1_MASK 0x00f0
+#define MII_VCT7_RESULTS_PAIR0_MASK 0x000f
+#define MII_VCT7_RESULTS_PAIR3_SHIFT 12
+#define MII_VCT7_RESULTS_PAIR2_SHIFT 8
+#define MII_VCT7_RESULTS_PAIR1_SHIFT 4
+#define MII_VCT7_RESULTS_PAIR0_SHIFT 0
+#define MII_VCT7_RESULTS_INVALID 0
+#define MII_VCT7_RESULTS_OK 1
+#define MII_VCT7_RESULTS_OPEN 2
+#define MII_VCT7_RESULTS_SAME_SHORT 3
+#define MII_VCT7_RESULTS_CROSS_SHORT 4
+#define MII_VCT7_RESULTS_BUSY 9
+
+#define MII_VCT7_CTRL 0x15
+#define MII_VCT7_CTRL_RUN_NOW BIT(15)
+#define MII_VCT7_CTRL_RUN_ANEG BIT(14)
+#define MII_VCT7_CTRL_DISABLE_CROSS BIT(13)
+#define MII_VCT7_CTRL_RUN_AFTER_BREAK_LINK BIT(12)
+#define MII_VCT7_CTRL_IN_PROGRESS BIT(11)
+#define MII_VCT7_CTRL_METERS BIT(10)
+#define MII_VCT7_CTRL_CENTIMETERS 0
+
+#define LPA_PAUSE_FIBER 0x180
+#define LPA_PAUSE_ASYM_FIBER 0x100
+
+#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;
+ bool cable_test_tdr;
+ u32 first;
+ u32 last;
+ u32 step;
+ s8 pair;
+};
+
+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 = marvell_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_M1011_IMASK,
+ MII_M1011_IMASK_INIT);
+ } else {
+ err = phy_write(phydev, MII_M1011_IMASK,
+ MII_M1011_IMASK_CLEAR);
+ if (err)
+ return err;
+
+ err = marvell_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_M1011_IEVENT);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_M1011_IMASK_INIT))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int marvell_set_polarity(struct phy_device *phydev, int polarity)
+{
+ u16 val;
+
+ 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;
+ }
+
+ return phy_modify_changed(phydev, MII_M1011_PHY_SCR,
+ MII_M1011_PHY_SCR_AUTO_CROSS, val);
+}
+
+static int marvell_config_aneg(struct phy_device *phydev)
+{
+ int changed = 0;
+ int err;
+
+ err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+ if (err < 0)
+ return err;
+
+ changed = 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 || changed) {
+ /* 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);
+}
+
+#if IS_ENABLED(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_changed(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 changed = 0;
+ int err = 0;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ err = m88e1121_config_aneg_rgmii_delays(phydev);
+ if (err < 0)
+ return err;
+ }
+
+ changed = err;
+
+ err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
+ if (err < 0)
+ return err;
+
+ changed |= err;
+
+ err = genphy_config_aneg(phydev);
+ if (err < 0)
+ return err;
+
+ if (phydev->autoneg != AUTONEG_ENABLE || changed) {
+ /* A software reset is used to ensure a "commit" of the
+ * changes is done.
+ */
+ err = genphy_soft_reset(phydev);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+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);
+}
+
+/**
+ * linkmode_adv_to_fiber_adv_t
+ * @advertise: the linkmode advertisement settings
+ *
+ * A small helper function that translates linkmode advertisement
+ * settings to phy autonegotiation advertisements for the MII_ADV
+ * register for fiber link.
+ */
+static inline u32 linkmode_adv_to_fiber_adv_t(unsigned long *advertise)
+{
+ u32 result = 0;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, advertise))
+ result |= ADVERTISE_1000XHALF;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, advertise))
+ result |= ADVERTISE_1000XFULL;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertise) &&
+ linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertise))
+ result |= ADVERTISE_1000XPSE_ASYM;
+ else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertise))
+ result |= ADVERTISE_1000XPAUSE;
+
+ 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;
+ u16 adv;
+
+ if (phydev->autoneg != AUTONEG_ENABLE)
+ return genphy_setup_forced(phydev);
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ adv = linkmode_adv_to_fiber_adv_t(phydev->advertising);
+
+ /* Setup fiber advertisement */
+ err = phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_1000XHALF | ADVERTISE_1000XFULL |
+ ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ changed = 1;
+
+ return genphy_check_and_restart_aneg(phydev, changed);
+}
+
+static int m88e1111_config_aneg(struct phy_device *phydev)
+{
+ int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+ int err;
+
+ if (extsr < 0)
+ return extsr;
+
+ /* If not using SGMII or copper 1000BaseX modes, use normal process.
+ * Steps below are only required for these modes.
+ */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+ (extsr & MII_M1111_HWCFG_MODE_MASK) !=
+ MII_M1111_HWCFG_MODE_COPPER_1000X_AN)
+ return marvell_config_aneg(phydev);
+
+ err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+ if (err < 0)
+ goto error;
+
+ /* Configure the copper link first */
+ err = marvell_config_aneg(phydev);
+ if (err < 0)
+ goto error;
+
+ /* Then the fiber link */
+ err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
+ if (err < 0)
+ goto error;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII)
+ /* Do not touch the fiber advertisement if we're in copper->sgmii mode.
+ * Just ensure that SGMII-side autonegotiation is enabled.
+ * If we switched from some other mode to SGMII it may not be.
+ */
+ err = genphy_check_and_restart_aneg(phydev, false);
+ else
+ 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 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):
+ if (phydev->dev_flags & MARVELL_PHY_LED0_LINK_LED1_ACTIVE)
+ def_config = MII_88E1510_PHY_LED0_LINK_LED1_ACTIVE;
+ else
+ 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)
+ phydev_warn(phydev, "Fail to config marvell phy LED.\n");
+}
+
+static int marvell_config_init(struct phy_device *phydev)
+{
+ /* Set default LED */
+ marvell_config_led(phydev);
+
+ /* Set registers from marvell,reg-init DT property */
+ return marvell_of_reg_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;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ delay = MII_M1111_RGMII_RX_DELAY | MII_M1111_RGMII_TX_DELAY;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ delay = MII_M1111_RGMII_RX_DELAY;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ delay = MII_M1111_RGMII_TX_DELAY;
+ break;
+ default:
+ delay = 0;
+ break;
+ }
+
+ 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_1000basex(struct phy_device *phydev)
+{
+ int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+ int err, mode;
+
+ if (extsr < 0)
+ return extsr;
+
+ /* If using copper mode, ensure 1000BaseX auto-negotiation is enabled */
+ mode = extsr & MII_M1111_HWCFG_MODE_MASK;
+ if (mode == MII_M1111_HWCFG_MODE_COPPER_1000X_NOAN) {
+ err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
+ MII_M1111_HWCFG_MODE_MASK |
+ MII_M1111_HWCFG_SERIAL_AN_BYPASS,
+ MII_M1111_HWCFG_MODE_COPPER_1000X_AN |
+ MII_M1111_HWCFG_SERIAL_AN_BYPASS);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+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;
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_1000BASEX) {
+ err = m88e1111_config_init_1000basex(phydev);
+ if (err < 0)
+ return err;
+ }
+
+ err = marvell_of_reg_init(phydev);
+ if (err < 0)
+ return err;
+
+ err = genphy_soft_reset(phydev);
+ if (err < 0)
+ return err;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ /* If the HWCFG_MODE was changed from another mode (such as
+ * 1000BaseX) to SGMII, the state of the support bits may have
+ * also changed now that the PHY has been reset.
+ * Update the PHY abilities accordingly.
+ */
+ err = genphy_read_abilities(phydev);
+ linkmode_or(phydev->advertising, phydev->advertising,
+ phydev->supported);
+ }
+ return err;
+}
+
+static int m88e1111_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val, cnt, enable;
+
+ val = phy_read(phydev, MII_M1111_PHY_EXT_CR);
+ if (val < 0)
+ return val;
+
+ enable = FIELD_GET(MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN, val);
+ cnt = FIELD_GET(MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, val) + 1;
+
+ *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int m88e1111_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ int val, err;
+
+ if (cnt > MII_M1111_PHY_EXT_CR_DOWNSHIFT_MAX)
+ return -E2BIG;
+
+ if (!cnt) {
+ err = phy_clear_bits(phydev, MII_M1111_PHY_EXT_CR,
+ MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN);
+ } else {
+ val = MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN;
+ val |= FIELD_PREP(MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, cnt - 1);
+
+ err = phy_modify(phydev, MII_M1111_PHY_EXT_CR,
+ MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN |
+ MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK,
+ val);
+ }
+
+ if (err < 0)
+ return err;
+
+ return genphy_soft_reset(phydev);
+}
+
+static int m88e1111_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return m88e1111_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int m88e1111_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return m88e1111_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int m88e1011_get_downshift(struct phy_device *phydev, u8 *data)
+{
+ int val, cnt, enable;
+
+ val = phy_read(phydev, MII_M1011_PHY_SCR);
+ if (val < 0)
+ return val;
+
+ enable = FIELD_GET(MII_M1011_PHY_SCR_DOWNSHIFT_EN, val);
+ cnt = FIELD_GET(MII_M1011_PHY_SCR_DOWNSHIFT_MASK, val) + 1;
+
+ *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int m88e1011_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+ int val, err;
+
+ if (cnt > MII_M1011_PHY_SCR_DOWNSHIFT_MAX)
+ return -E2BIG;
+
+ if (!cnt) {
+ err = phy_clear_bits(phydev, MII_M1011_PHY_SCR,
+ MII_M1011_PHY_SCR_DOWNSHIFT_EN);
+ } else {
+ val = MII_M1011_PHY_SCR_DOWNSHIFT_EN;
+ val |= FIELD_PREP(MII_M1011_PHY_SCR_DOWNSHIFT_MASK, cnt - 1);
+
+ err = phy_modify(phydev, MII_M1011_PHY_SCR,
+ MII_M1011_PHY_SCR_DOWNSHIFT_EN |
+ MII_M1011_PHY_SCR_DOWNSHIFT_MASK,
+ val);
+ }
+
+ if (err < 0)
+ return err;
+
+ return genphy_soft_reset(phydev);
+}
+
+static int m88e1011_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return m88e1011_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int m88e1011_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return m88e1011_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int m88e1112_config_init(struct phy_device *phydev)
+{
+ int err;
+
+ err = m88e1011_set_downshift(phydev, 3);
+ if (err < 0)
+ return err;
+
+ return m88e1111_config_init(phydev);
+}
+
+static int m88e1111gbe_config_init(struct phy_device *phydev)
+{
+ int err;
+
+ err = m88e1111_set_downshift(phydev, 3);
+ if (err < 0)
+ return err;
+
+ return m88e1111_config_init(phydev);
+}
+
+static int marvell_1011gbe_config_init(struct phy_device *phydev)
+{
+ int err;
+
+ err = m88e1011_set_downshift(phydev, 3);
+ if (err < 0)
+ return err;
+
+ return marvell_config_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 = m88e1011_set_downshift(phydev, 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 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)
+{
+ static const struct {
+ u16 reg17, reg16;
+ } errata_vals[] = {
+ { 0x214b, 0x2144 },
+ { 0x0c28, 0x2146 },
+ { 0xb233, 0x214d },
+ { 0xcc0c, 0x2159 },
+ };
+ int err;
+ int i;
+
+ /* As per Marvell Release Notes - Alaska 88E1510/88E1518/88E1512/
+ * 88E1514 Rev A0, Errata Section 5.1:
+ * If EEE is intended to be used, the following register writes
+ * must be done once after every hardware reset.
+ */
+ err = marvell_set_page(phydev, 0x00FF);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(errata_vals); ++i) {
+ err = phy_write(phydev, 17, errata_vals[i].reg17);
+ if (err)
+ return err;
+ err = phy_write(phydev, 16, errata_vals[i].reg16);
+ if (err)
+ return err;
+ }
+
+ err = marvell_set_page(phydev, 0x00FB);
+ if (err < 0)
+ return err;
+ err = phy_write(phydev, 07, 0xC00D);
+ if (err < 0)
+ return err;
+ err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
+ if (err < 0)
+ return 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_set_bits(phydev, MII_88E1510_GEN_CTRL_REG_1,
+ 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;
+ }
+ err = m88e1011_set_downshift(phydev, 3);
+ 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)
+{
+ u16 leds;
+ int err;
+
+ /* Enable 1000 Mbit */
+ err = phy_write_paged(phydev, MII_MARVELL_MSCR_PAGE,
+ MII_88E1121_PHY_MSCR_REG, 0x1070);
+ 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)
+ leds = 0x1100;
+ else
+ leds = 0x021e;
+
+ err = phy_write_paged(phydev, MII_MARVELL_LED_PAGE, 0x10, leds);
+ if (err < 0)
+ return err;
+
+ err = marvell_of_reg_init(phydev);
+ if (err < 0)
+ return err;
+
+ /* Reset page register */
+ 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 = m88e1111_set_downshift(phydev, 3);
+ if (err < 0)
+ return err;
+
+ err = marvell_of_reg_init(phydev);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int m88e1540_get_fld(struct phy_device *phydev, u8 *msecs)
+{
+ int val;
+
+ val = phy_read(phydev, MII_88E1540_COPPER_CTRL3);
+ if (val < 0)
+ return val;
+
+ if (!(val & MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN)) {
+ *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF;
+ return 0;
+ }
+
+ val = FIELD_GET(MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
+
+ switch (val) {
+ case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS:
+ *msecs = 0;
+ break;
+ case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS:
+ *msecs = 10;
+ break;
+ case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS:
+ *msecs = 20;
+ break;
+ case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS:
+ *msecs = 40;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int m88e1540_set_fld(struct phy_device *phydev, const u8 *msecs)
+{
+ struct ethtool_eee eee;
+ int val, ret;
+
+ if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
+ return phy_clear_bits(phydev, MII_88E1540_COPPER_CTRL3,
+ MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN);
+
+ /* According to the Marvell data sheet EEE must be disabled for
+ * Fast Link Down detection to work properly
+ */
+ ret = genphy_c45_ethtool_get_eee(phydev, &eee);
+ if (!ret && eee.eee_enabled) {
+ phydev_warn(phydev, "Fast Link Down detection requires EEE to be disabled!\n");
+ return -EBUSY;
+ }
+
+ if (*msecs <= 5)
+ val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS;
+ else if (*msecs <= 15)
+ val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS;
+ else if (*msecs <= 30)
+ val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS;
+ else
+ val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS;
+
+ val = FIELD_PREP(MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
+
+ ret = phy_modify(phydev, MII_88E1540_COPPER_CTRL3,
+ MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
+ if (ret)
+ return ret;
+
+ return phy_set_bits(phydev, MII_88E1540_COPPER_CTRL3,
+ MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN);
+}
+
+static int m88e1540_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_FAST_LINK_DOWN:
+ return m88e1540_get_fld(phydev, data);
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return m88e1011_get_downshift(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int m88e1540_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_FAST_LINK_DOWN:
+ return m88e1540_set_fld(phydev, data);
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return m88e1011_set_downshift(phydev, *(const u8 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* 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_mod_linkmode_lpa_t
+ * @advertising: the linkmode advertisement settings
+ * @lpa: value of the MII_LPA register for fiber link
+ *
+ * A small helper function that translates MII_LPA bits to linkmode LP
+ * advertisement settings. Other bits in advertising are left
+ * unchanged.
+ */
+static void fiber_lpa_mod_linkmode_lpa_t(unsigned long *advertising, u32 lpa)
+{
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ advertising, lpa & LPA_1000XHALF);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ advertising, lpa & LPA_1000XFULL);
+}
+
+static int marvell_read_status_page_an(struct phy_device *phydev,
+ int fiber, int status)
+{
+ int lpa;
+ int err;
+
+ if (!(status & MII_M1011_PHY_STATUS_RESOLVED)) {
+ phydev->link = 0;
+ return 0;
+ }
+
+ if (status & MII_M1011_PHY_STATUS_FULLDUPLEX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ switch (status & MII_M1011_PHY_STATUS_SPD_MASK) {
+ 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) {
+ err = genphy_read_lpa(phydev);
+ if (err < 0)
+ return err;
+
+ phy_resolve_aneg_pause(phydev);
+ } else {
+ lpa = phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ /* The fiber link is only 1000M capable */
+ fiber_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 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;
+}
+
+/* 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 status;
+ int fiber;
+ int err;
+
+ status = phy_read(phydev, MII_M1011_PHY_STATUS);
+ if (status < 0)
+ return status;
+
+ /* Use the generic register for copper link status,
+ * and the PHY status register for fiber link status.
+ */
+ if (page == MII_MARVELL_FIBER_PAGE) {
+ phydev->link = !!(status & MII_M1011_PHY_STATUS_LINK);
+ } else {
+ err = genphy_update_link(phydev);
+ if (err)
+ return err;
+ }
+
+ if (page == MII_MARVELL_FIBER_PAGE)
+ fiber = 1;
+ else
+ fiber = 0;
+
+ linkmode_zero(phydev->lp_advertising);
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->port = fiber ? PORT_FIBRE : PORT_TP;
+
+ if (phydev->autoneg == AUTONEG_ENABLE)
+ err = marvell_read_status_page_an(phydev, fiber, status);
+ else
+ err = genphy_read_status_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 (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->supported) &&
+ 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 (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->supported)) {
+ 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 (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->supported)) {
+ 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 void m88e1318_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int ret;
+
+ wol->supported = WAKE_MAGIC | WAKE_PHY;
+ wol->wolopts = 0;
+
+ ret = phy_read_paged(phydev, MII_MARVELL_WOL_PAGE,
+ MII_88E1318S_PHY_WOL_CTRL);
+ if (ret < 0)
+ return;
+
+ if (ret & MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE)
+ wol->wolopts |= WAKE_MAGIC;
+
+ if (ret & MII_88E1318S_PHY_WOL_CTRL_LINK_UP_ENABLE)
+ wol->wolopts |= WAKE_PHY;
+}
+
+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 | WAKE_PHY)) {
+ /* 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_set_bits(phydev, MII_88E1318S_PHY_CSIER,
+ 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;
+ }
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ 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_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
+ 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;
+ }
+
+ if (wol->wolopts & WAKE_PHY) {
+ err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
+ if (err < 0)
+ goto error;
+
+ /* Clear WOL status and enable link up event */
+ err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL, 0,
+ MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS |
+ MII_88E1318S_PHY_WOL_CTRL_LINK_UP_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 link up event */
+ err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL,
+ MII_88E1318S_PHY_WOL_CTRL_LINK_UP_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 (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->supported))
+ 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++) {
+ strscpy(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);
+}
+
+static int m88e1510_loopback(struct phy_device *phydev, bool enable)
+{
+ int err;
+
+ if (enable) {
+ u16 bmcr_ctl, mscr2_ctl = 0;
+
+ bmcr_ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
+
+ err = phy_write(phydev, MII_BMCR, bmcr_ctl);
+ if (err < 0)
+ return err;
+
+ if (phydev->speed == SPEED_1000)
+ mscr2_ctl = BMCR_SPEED1000;
+ else if (phydev->speed == SPEED_100)
+ mscr2_ctl = BMCR_SPEED100;
+
+ err = phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE,
+ MII_88E1510_MSCR_2, BMCR_SPEED1000 |
+ BMCR_SPEED100, mscr2_ctl);
+ if (err < 0)
+ return err;
+
+ /* Need soft reset to have speed configuration takes effect */
+ err = genphy_soft_reset(phydev);
+ if (err < 0)
+ return err;
+
+ err = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
+ BMCR_LOOPBACK);
+
+ if (!err) {
+ /* It takes some time for PHY device to switch
+ * into/out-of loopback mode.
+ */
+ msleep(1000);
+ }
+ return err;
+ } else {
+ err = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, 0);
+ if (err < 0)
+ return err;
+
+ return phy_config_aneg(phydev);
+ }
+}
+
+static int marvell_vct5_wait_complete(struct phy_device *phydev)
+{
+ int i;
+ int val;
+
+ for (i = 0; i < 32; i++) {
+ val = __phy_read(phydev, MII_VCT5_CTRL);
+ if (val < 0)
+ return val;
+
+ if (val & MII_VCT5_CTRL_COMPLETE)
+ return 0;
+ }
+
+ phydev_err(phydev, "Timeout while waiting for cable test to finish\n");
+ return -ETIMEDOUT;
+}
+
+static int marvell_vct5_amplitude(struct phy_device *phydev, int pair)
+{
+ int amplitude;
+ int val;
+ int reg;
+
+ reg = MII_VCT5_TX_RX_MDI0_COUPLING + pair;
+ val = __phy_read(phydev, reg);
+
+ if (val < 0)
+ return 0;
+
+ amplitude = (val & MII_VCT5_TX_RX_AMPLITUDE_MASK) >>
+ MII_VCT5_TX_RX_AMPLITUDE_SHIFT;
+
+ if (!(val & MII_VCT5_TX_RX_COUPLING_POSITIVE_REFLECTION))
+ amplitude = -amplitude;
+
+ return 1000 * amplitude / 128;
+}
+
+static u32 marvell_vct5_distance2cm(int distance)
+{
+ return distance * 805 / 10;
+}
+
+static u32 marvell_vct5_cm2distance(int cm)
+{
+ return cm * 10 / 805;
+}
+
+static int marvell_vct5_amplitude_distance(struct phy_device *phydev,
+ int distance, int pair)
+{
+ u16 reg;
+ int err;
+ int mV;
+ int i;
+
+ err = __phy_write(phydev, MII_VCT5_SAMPLE_POINT_DISTANCE,
+ distance);
+ if (err)
+ return err;
+
+ reg = MII_VCT5_CTRL_ENABLE |
+ MII_VCT5_CTRL_TX_SAME_CHANNEL |
+ MII_VCT5_CTRL_SAMPLES_DEFAULT |
+ MII_VCT5_CTRL_SAMPLE_POINT |
+ MII_VCT5_CTRL_PEEK_HYST_DEFAULT;
+ err = __phy_write(phydev, MII_VCT5_CTRL, reg);
+ if (err)
+ return err;
+
+ err = marvell_vct5_wait_complete(phydev);
+ if (err)
+ return err;
+
+ for (i = 0; i < 4; i++) {
+ if (pair != PHY_PAIR_ALL && i != pair)
+ continue;
+
+ mV = marvell_vct5_amplitude(phydev, i);
+ ethnl_cable_test_amplitude(phydev, i, mV);
+ }
+
+ return 0;
+}
+
+static int marvell_vct5_amplitude_graph(struct phy_device *phydev)
+{
+ struct marvell_priv *priv = phydev->priv;
+ int distance;
+ u16 width;
+ int page;
+ int err;
+ u16 reg;
+
+ if (priv->first <= TDR_SHORT_CABLE_LENGTH)
+ width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS;
+ else
+ width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS;
+
+ reg = MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV |
+ MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN |
+ MII_VCT5_TX_PULSE_CTRL_MAX_AMP | width;
+
+ err = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE,
+ MII_VCT5_TX_PULSE_CTRL, reg);
+ if (err)
+ return err;
+
+ /* Reading the TDR data is very MDIO heavy. We need to optimize
+ * access to keep the time to a minimum. So lock the bus once,
+ * and don't release it until complete. We can then avoid having
+ * to change the page for every access, greatly speeding things
+ * up.
+ */
+ page = phy_select_page(phydev, MII_MARVELL_VCT5_PAGE);
+ if (page < 0)
+ goto restore_page;
+
+ for (distance = priv->first;
+ distance <= priv->last;
+ distance += priv->step) {
+ err = marvell_vct5_amplitude_distance(phydev, distance,
+ priv->pair);
+ if (err)
+ goto restore_page;
+
+ if (distance > TDR_SHORT_CABLE_LENGTH &&
+ width == MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS) {
+ width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS;
+ reg = MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV |
+ MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN |
+ MII_VCT5_TX_PULSE_CTRL_MAX_AMP | width;
+ err = __phy_write(phydev, MII_VCT5_TX_PULSE_CTRL, reg);
+ if (err)
+ goto restore_page;
+ }
+ }
+
+restore_page:
+ return phy_restore_page(phydev, page, err);
+}
+
+static int marvell_cable_test_start_common(struct phy_device *phydev)
+{
+ int bmcr, bmsr, ret;
+
+ /* If auto-negotiation is enabled, but not complete, the cable
+ * test never completes. So disable auto-neg.
+ */
+ bmcr = phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ bmsr = phy_read(phydev, MII_BMSR);
+
+ if (bmsr < 0)
+ return bmsr;
+
+ if (bmcr & BMCR_ANENABLE) {
+ ret = phy_clear_bits(phydev, MII_BMCR, BMCR_ANENABLE);
+ if (ret < 0)
+ return ret;
+ ret = genphy_soft_reset(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* If the link is up, allow it some time to go down */
+ if (bmsr & BMSR_LSTATUS)
+ msleep(1500);
+
+ return 0;
+}
+
+static int marvell_vct7_cable_test_start(struct phy_device *phydev)
+{
+ struct marvell_priv *priv = phydev->priv;
+ int ret;
+
+ ret = marvell_cable_test_start_common(phydev);
+ if (ret)
+ return ret;
+
+ priv->cable_test_tdr = false;
+
+ /* Reset the VCT5 API control to defaults, otherwise
+ * VCT7 does not work correctly.
+ */
+ ret = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE,
+ MII_VCT5_CTRL,
+ MII_VCT5_CTRL_TX_SAME_CHANNEL |
+ MII_VCT5_CTRL_SAMPLES_DEFAULT |
+ MII_VCT5_CTRL_MODE_MAXIMUM_PEEK |
+ MII_VCT5_CTRL_PEEK_HYST_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE,
+ MII_VCT5_SAMPLE_POINT_DISTANCE, 0);
+ if (ret)
+ return ret;
+
+ return phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE,
+ MII_VCT7_CTRL,
+ MII_VCT7_CTRL_RUN_NOW |
+ MII_VCT7_CTRL_CENTIMETERS);
+}
+
+static int marvell_vct5_cable_test_tdr_start(struct phy_device *phydev,
+ const struct phy_tdr_config *cfg)
+{
+ struct marvell_priv *priv = phydev->priv;
+ int ret;
+
+ priv->cable_test_tdr = true;
+ priv->first = marvell_vct5_cm2distance(cfg->first);
+ priv->last = marvell_vct5_cm2distance(cfg->last);
+ priv->step = marvell_vct5_cm2distance(cfg->step);
+ priv->pair = cfg->pair;
+
+ if (priv->first > MII_VCT5_SAMPLE_POINT_DISTANCE_MAX)
+ return -EINVAL;
+
+ if (priv->last > MII_VCT5_SAMPLE_POINT_DISTANCE_MAX)
+ return -EINVAL;
+
+ /* Disable VCT7 */
+ ret = phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE,
+ MII_VCT7_CTRL, 0);
+ if (ret)
+ return ret;
+
+ ret = marvell_cable_test_start_common(phydev);
+ if (ret)
+ return ret;
+
+ ret = ethnl_cable_test_pulse(phydev, 1000);
+ if (ret)
+ return ret;
+
+ return ethnl_cable_test_step(phydev,
+ marvell_vct5_distance2cm(priv->first),
+ marvell_vct5_distance2cm(priv->last),
+ marvell_vct5_distance2cm(priv->step));
+}
+
+static int marvell_vct7_distance_to_length(int distance, bool meter)
+{
+ if (meter)
+ distance *= 100;
+
+ return distance;
+}
+
+static bool marvell_vct7_distance_valid(int result)
+{
+ switch (result) {
+ case MII_VCT7_RESULTS_OPEN:
+ case MII_VCT7_RESULTS_SAME_SHORT:
+ case MII_VCT7_RESULTS_CROSS_SHORT:
+ return true;
+ }
+ return false;
+}
+
+static int marvell_vct7_report_length(struct phy_device *phydev,
+ int pair, bool meter)
+{
+ int length;
+ int ret;
+
+ ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
+ MII_VCT7_PAIR_0_DISTANCE + pair);
+ if (ret < 0)
+ return ret;
+
+ length = marvell_vct7_distance_to_length(ret, meter);
+
+ ethnl_cable_test_fault_length(phydev, pair, length);
+
+ return 0;
+}
+
+static int marvell_vct7_cable_test_report_trans(int result)
+{
+ switch (result) {
+ case MII_VCT7_RESULTS_OK:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case MII_VCT7_RESULTS_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case MII_VCT7_RESULTS_SAME_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ case MII_VCT7_RESULTS_CROSS_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static int marvell_vct7_cable_test_report(struct phy_device *phydev)
+{
+ int pair0, pair1, pair2, pair3;
+ bool meter;
+ int ret;
+
+ ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
+ MII_VCT7_RESULTS);
+ if (ret < 0)
+ return ret;
+
+ pair3 = (ret & MII_VCT7_RESULTS_PAIR3_MASK) >>
+ MII_VCT7_RESULTS_PAIR3_SHIFT;
+ pair2 = (ret & MII_VCT7_RESULTS_PAIR2_MASK) >>
+ MII_VCT7_RESULTS_PAIR2_SHIFT;
+ pair1 = (ret & MII_VCT7_RESULTS_PAIR1_MASK) >>
+ MII_VCT7_RESULTS_PAIR1_SHIFT;
+ pair0 = (ret & MII_VCT7_RESULTS_PAIR0_MASK) >>
+ MII_VCT7_RESULTS_PAIR0_SHIFT;
+
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ marvell_vct7_cable_test_report_trans(pair0));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+ marvell_vct7_cable_test_report_trans(pair1));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+ marvell_vct7_cable_test_report_trans(pair2));
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+ marvell_vct7_cable_test_report_trans(pair3));
+
+ ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE, MII_VCT7_CTRL);
+ if (ret < 0)
+ return ret;
+
+ meter = ret & MII_VCT7_CTRL_METERS;
+
+ if (marvell_vct7_distance_valid(pair0))
+ marvell_vct7_report_length(phydev, 0, meter);
+ if (marvell_vct7_distance_valid(pair1))
+ marvell_vct7_report_length(phydev, 1, meter);
+ if (marvell_vct7_distance_valid(pair2))
+ marvell_vct7_report_length(phydev, 2, meter);
+ if (marvell_vct7_distance_valid(pair3))
+ marvell_vct7_report_length(phydev, 3, meter);
+
+ return 0;
+}
+
+static int marvell_vct7_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ struct marvell_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->cable_test_tdr) {
+ ret = marvell_vct5_amplitude_graph(phydev);
+ *finished = true;
+ return ret;
+ }
+
+ *finished = false;
+
+ ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
+ MII_VCT7_CTRL);
+
+ if (ret < 0)
+ return ret;
+
+ if (!(ret & MII_VCT7_CTRL_IN_PROGRESS)) {
+ *finished = true;
+
+ return marvell_vct7_cable_test_report(phydev);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_HWMON
+struct marvell_hwmon_ops {
+ int (*config)(struct phy_device *phydev);
+ int (*get_temp)(struct phy_device *phydev, long *temp);
+ int (*get_temp_critical)(struct phy_device *phydev, long *temp);
+ int (*set_temp_critical)(struct phy_device *phydev, long temp);
+ int (*get_temp_alarm)(struct phy_device *phydev, long *alarm);
+};
+
+static const struct marvell_hwmon_ops *
+to_marvell_hwmon_ops(const struct phy_device *phydev)
+{
+ return phydev->drv->driver_data;
+}
+
+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 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 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 &= ~MII_88E6390_MISC_TEST_TEMP_SENSOR_MASK;
+ ret |= MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE_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_TEMP_SENSOR_MASK;
+ ret |= MII_88E6390_MISC_TEST_TEMP_SENSOR_DISABLE;
+
+ ret = __phy_write(phydev, MII_88E6390_MISC_TEST, ret);
+
+error:
+ phy_restore_page(phydev, oldpage, ret);
+
+ return ret;
+}
+
+static int m88e6393_get_temp(struct phy_device *phydev, long *temp)
+{
+ int err;
+
+ err = m88e1510_get_temp(phydev, temp);
+
+ /* 88E1510 measures T + 25, while the PHY on 88E6393X switch
+ * T + 75, so we have to subtract another 50
+ */
+ *temp -= 50000;
+
+ return err;
+}
+
+static int m88e6393_get_temp_critical(struct phy_device *phydev, long *temp)
+{
+ int ret;
+
+ *temp = 0;
+
+ ret = phy_read_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+ MII_88E6390_TEMP_SENSOR);
+ if (ret < 0)
+ return ret;
+
+ *temp = (((ret & MII_88E6393_TEMP_SENSOR_THRESHOLD_MASK) >>
+ MII_88E6393_TEMP_SENSOR_THRESHOLD_SHIFT) - 75) * 1000;
+
+ return 0;
+}
+
+static int m88e6393_set_temp_critical(struct phy_device *phydev, long temp)
+{
+ temp = (temp / 1000) + 75;
+
+ return phy_modify_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+ MII_88E6390_TEMP_SENSOR,
+ MII_88E6393_TEMP_SENSOR_THRESHOLD_MASK,
+ temp << MII_88E6393_TEMP_SENSOR_THRESHOLD_SHIFT);
+}
+
+static int m88e6393_hwmon_config(struct phy_device *phydev)
+{
+ int err;
+
+ err = m88e6393_set_temp_critical(phydev, 100000);
+ if (err)
+ return err;
+
+ return phy_modify_paged(phydev, MII_MARVELL_MISC_TEST_PAGE,
+ MII_88E6390_MISC_TEST,
+ MII_88E6390_MISC_TEST_TEMP_SENSOR_MASK |
+ MII_88E6393_MISC_TEST_SAMPLES_MASK |
+ MII_88E6393_MISC_TEST_RATE_MASK,
+ MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE |
+ MII_88E6393_MISC_TEST_SAMPLES_2048 |
+ MII_88E6393_MISC_TEST_RATE_2_3MS);
+}
+
+static int marvell_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *temp)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ const struct marvell_hwmon_ops *ops = to_marvell_hwmon_ops(phydev);
+ int err = -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ if (ops->get_temp)
+ err = ops->get_temp(phydev, temp);
+ break;
+ case hwmon_temp_crit:
+ if (ops->get_temp_critical)
+ err = ops->get_temp_critical(phydev, temp);
+ break;
+ case hwmon_temp_max_alarm:
+ if (ops->get_temp_alarm)
+ err = ops->get_temp_alarm(phydev, temp);
+ break;
+ }
+
+ return err;
+}
+
+static int marvell_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long temp)
+{
+ struct phy_device *phydev = dev_get_drvdata(dev);
+ const struct marvell_hwmon_ops *ops = to_marvell_hwmon_ops(phydev);
+ int err = -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_temp_crit:
+ if (ops->set_temp_critical)
+ err = ops->set_temp_critical(phydev, temp);
+ break;
+ }
+
+ return err;
+}
+
+static umode_t marvell_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct phy_device *phydev = data;
+ const struct marvell_hwmon_ops *ops = to_marvell_hwmon_ops(phydev);
+
+ if (type != hwmon_temp)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ return ops->get_temp ? 0444 : 0;
+ case hwmon_temp_max_alarm:
+ return ops->get_temp_alarm ? 0444 : 0;
+ case hwmon_temp_crit:
+ return (ops->get_temp_critical ? 0444 : 0) |
+ (ops->set_temp_critical ? 0200 : 0);
+ default:
+ return 0;
+ }
+}
+
+static u32 marvell_hwmon_chip_config[] = {
+ HWMON_C_REGISTER_TZ,
+ 0
+};
+
+static const struct hwmon_channel_info marvell_hwmon_chip = {
+ .type = hwmon_chip,
+ .config = marvell_hwmon_chip_config,
+};
+
+/* we can define HWMON_T_CRIT and HWMON_T_MAX_ALARM even though these are not
+ * defined for all PHYs, because the hwmon code checks whether the attributes
+ * exists via the .is_visible method
+ */
+static u32 marvell_hwmon_temp_config[] = {
+ HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_MAX_ALARM,
+ 0
+};
+
+static const struct hwmon_channel_info marvell_hwmon_temp = {
+ .type = hwmon_temp,
+ .config = marvell_hwmon_temp_config,
+};
+
+static const struct hwmon_channel_info * const marvell_hwmon_info[] = {
+ &marvell_hwmon_chip,
+ &marvell_hwmon_temp,
+ NULL
+};
+
+static const struct hwmon_ops marvell_hwmon_hwmon_ops = {
+ .is_visible = marvell_hwmon_is_visible,
+ .read = marvell_hwmon_read,
+ .write = marvell_hwmon_write,
+};
+
+static const struct hwmon_chip_info marvell_hwmon_chip_info = {
+ .ops = &marvell_hwmon_hwmon_ops,
+ .info = marvell_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 marvell_hwmon_ops *ops = to_marvell_hwmon_ops(phydev);
+ struct marvell_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int err;
+
+ if (!ops)
+ return 0;
+
+ err = marvell_hwmon_name(phydev);
+ if (err)
+ return err;
+
+ priv->hwmon_dev = devm_hwmon_device_register_with_info(
+ dev, priv->hwmon_name, phydev, &marvell_hwmon_chip_info, NULL);
+ if (IS_ERR(priv->hwmon_dev))
+ return PTR_ERR(priv->hwmon_dev);
+
+ if (ops->config)
+ err = ops->config(phydev);
+
+ return err;
+}
+
+static const struct marvell_hwmon_ops m88e1121_hwmon_ops = {
+ .get_temp = m88e1121_get_temp,
+};
+
+static const struct marvell_hwmon_ops m88e1510_hwmon_ops = {
+ .get_temp = m88e1510_get_temp,
+ .get_temp_critical = m88e1510_get_temp_critical,
+ .set_temp_critical = m88e1510_set_temp_critical,
+ .get_temp_alarm = m88e1510_get_temp_alarm,
+};
+
+static const struct marvell_hwmon_ops m88e6390_hwmon_ops = {
+ .get_temp = m88e6390_get_temp,
+};
+
+static const struct marvell_hwmon_ops m88e6393_hwmon_ops = {
+ .config = m88e6393_hwmon_config,
+ .get_temp = m88e6393_get_temp,
+ .get_temp_critical = m88e6393_get_temp_critical,
+ .set_temp_critical = m88e6393_set_temp_critical,
+ .get_temp_alarm = m88e1510_get_temp_alarm,
+};
+
+#define DEF_MARVELL_HWMON_OPS(s) (&(s))
+
+#else
+
+#define DEF_MARVELL_HWMON_OPS(s) NULL
+
+static int marvell_hwmon_probe(struct phy_device *phydev)
+{
+ return 0;
+}
+#endif
+
+static int m88e1318_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ int reg;
+
+ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC);
+ if (reg < 0)
+ return reg;
+
+ switch (index) {
+ case 0:
+ case 1:
+ case 2:
+ reg &= ~(0xf << (4 * index));
+ if (value == LED_OFF)
+ reg |= MII_88E1318S_PHY_LED_FUNC_OFF << (4 * index);
+ else
+ reg |= MII_88E1318S_PHY_LED_FUNC_ON << (4 * index);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC, reg);
+}
+
+static int m88e1318_led_blink_set(struct phy_device *phydev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ int reg;
+
+ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC);
+ if (reg < 0)
+ return reg;
+
+ switch (index) {
+ case 0:
+ case 1:
+ case 2:
+ reg &= ~(0xf << (4 * index));
+ reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
+ /* Reset default is 84ms */
+ *delay_on = 84 / 2;
+ *delay_off = 84 / 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC, reg);
+}
+
+struct marvell_led_rules {
+ int mode;
+ unsigned long rules;
+};
+
+static const struct marvell_led_rules marvell_led0[] = {
+ {
+ .mode = 0,
+ .rules = BIT(TRIGGER_NETDEV_LINK),
+ },
+ {
+ .mode = 1,
+ .rules = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 3,
+ .rules = (BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 4,
+ .rules = (BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 5,
+ .rules = BIT(TRIGGER_NETDEV_TX),
+ },
+ {
+ .mode = 6,
+ .rules = BIT(TRIGGER_NETDEV_LINK),
+ },
+ {
+ .mode = 7,
+ .rules = BIT(TRIGGER_NETDEV_LINK_1000),
+ },
+ {
+ .mode = 8,
+ .rules = 0,
+ },
+};
+
+static const struct marvell_led_rules marvell_led1[] = {
+ {
+ .mode = 1,
+ .rules = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 2,
+ .rules = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_RX)),
+ },
+ {
+ .mode = 3,
+ .rules = (BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 4,
+ .rules = (BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 6,
+ .rules = (BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000)),
+ },
+ {
+ .mode = 7,
+ .rules = BIT(TRIGGER_NETDEV_LINK_100),
+ },
+ {
+ .mode = 8,
+ .rules = 0,
+ },
+};
+
+static const struct marvell_led_rules marvell_led2[] = {
+ {
+ .mode = 0,
+ .rules = BIT(TRIGGER_NETDEV_LINK),
+ },
+ {
+ .mode = 1,
+ .rules = (BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 3,
+ .rules = (BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 4,
+ .rules = (BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX)),
+ },
+ {
+ .mode = 5,
+ .rules = BIT(TRIGGER_NETDEV_TX),
+ },
+ {
+ .mode = 6,
+ .rules = (BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_1000)),
+ },
+ {
+ .mode = 7,
+ .rules = BIT(TRIGGER_NETDEV_LINK_10),
+ },
+ {
+ .mode = 8,
+ .rules = 0,
+ },
+};
+
+static int marvell_find_led_mode(unsigned long rules,
+ const struct marvell_led_rules *marvell_rules,
+ int count,
+ int *mode)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (marvell_rules[i].rules == rules) {
+ *mode = marvell_rules[i].mode;
+ return 0;
+ }
+ }
+ return -EOPNOTSUPP;
+}
+
+static int marvell_get_led_mode(u8 index, unsigned long rules, int *mode)
+{
+ int ret;
+
+ switch (index) {
+ case 0:
+ ret = marvell_find_led_mode(rules, marvell_led0,
+ ARRAY_SIZE(marvell_led0), mode);
+ break;
+ case 1:
+ ret = marvell_find_led_mode(rules, marvell_led1,
+ ARRAY_SIZE(marvell_led1), mode);
+ break;
+ case 2:
+ ret = marvell_find_led_mode(rules, marvell_led2,
+ ARRAY_SIZE(marvell_led2), mode);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int marvell_find_led_rules(unsigned long *rules,
+ const struct marvell_led_rules *marvell_rules,
+ int count,
+ int mode)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (marvell_rules[i].mode == mode) {
+ *rules = marvell_rules[i].rules;
+ return 0;
+ }
+ }
+ return -EOPNOTSUPP;
+}
+
+static int marvell_get_led_rules(u8 index, unsigned long *rules, int mode)
+{
+ int ret;
+
+ switch (index) {
+ case 0:
+ ret = marvell_find_led_rules(rules, marvell_led0,
+ ARRAY_SIZE(marvell_led0), mode);
+ break;
+ case 1:
+ ret = marvell_find_led_rules(rules, marvell_led1,
+ ARRAY_SIZE(marvell_led1), mode);
+ break;
+ case 2:
+ ret = marvell_find_led_rules(rules, marvell_led2,
+ ARRAY_SIZE(marvell_led2), mode);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int m88e1318_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ int mode, ret;
+
+ switch (index) {
+ case 0:
+ case 1:
+ case 2:
+ ret = marvell_get_led_mode(index, rules, &mode);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int m88e1318_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ int mode, ret, reg;
+
+ switch (index) {
+ case 0:
+ case 1:
+ case 2:
+ ret = marvell_get_led_mode(index, rules, &mode);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC);
+ if (reg < 0)
+ return reg;
+
+ reg &= ~(0xf << (4 * index));
+ reg |= mode << (4 * index);
+ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC, reg);
+}
+
+static int m88e1318_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ int mode, reg;
+
+ if (index > 2)
+ return -EINVAL;
+
+ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
+ MII_88E1318S_PHY_LED_FUNC);
+ if (reg < 0)
+ return reg;
+
+ mode = (reg >> (4 * index)) & 0xf;
+
+ return marvell_get_led_rules(index, rules, mode);
+}
+
+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 marvell_hwmon_probe(phydev);
+}
+
+static int m88e1510_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
+ struct phy_device *phydev = upstream;
+ phy_interface_t interface;
+ struct device *dev;
+ int oldpage;
+ int ret = 0;
+ u16 mode;
+
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
+
+ dev = &phydev->mdio.dev;
+
+ sfp_parse_support(phydev->sfp_bus, id, supported, interfaces);
+ interface = sfp_select_interface(phydev->sfp_bus, supported);
+
+ dev_info(dev, "%s SFP module inserted\n", phy_modes(interface));
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X;
+
+ break;
+ case PHY_INTERFACE_MODE_100BASEX:
+ mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX;
+
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII;
+
+ break;
+ default:
+ dev_err(dev, "Incompatible SFP module inserted\n");
+
+ return -EINVAL;
+ }
+
+ oldpage = phy_select_page(phydev, MII_MARVELL_MODE_PAGE);
+ if (oldpage < 0)
+ goto error;
+
+ ret = __phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1,
+ MII_88E1510_GEN_CTRL_REG_1_MODE_MASK, mode);
+ if (ret < 0)
+ goto error;
+
+ ret = __phy_set_bits(phydev, MII_88E1510_GEN_CTRL_REG_1,
+ MII_88E1510_GEN_CTRL_REG_1_RESET);
+
+error:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+static void m88e1510_sfp_remove(void *upstream)
+{
+ struct phy_device *phydev = upstream;
+ int oldpage;
+ int ret = 0;
+
+ oldpage = phy_select_page(phydev, MII_MARVELL_MODE_PAGE);
+ if (oldpage < 0)
+ goto error;
+
+ ret = __phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1,
+ MII_88E1510_GEN_CTRL_REG_1_MODE_MASK,
+ MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII);
+ if (ret < 0)
+ goto error;
+
+ ret = __phy_set_bits(phydev, MII_88E1510_GEN_CTRL_REG_1,
+ MII_88E1510_GEN_CTRL_REG_1_RESET);
+
+error:
+ phy_restore_page(phydev, oldpage, ret);
+}
+
+static const struct sfp_upstream_ops m88e1510_sfp_ops = {
+ .module_insert = m88e1510_sfp_insert,
+ .module_remove = m88e1510_sfp_remove,
+ .attach = phy_sfp_attach,
+ .detach = phy_sfp_detach,
+};
+
+static int m88e1510_probe(struct phy_device *phydev)
+{
+ int err;
+
+ err = marvell_probe(phydev);
+ if (err)
+ return err;
+
+ return phy_sfp_probe(phydev, &m88e1510_sfp_ops);
+}
+
+static struct phy_driver marvell_drivers[] = {
+ {
+ .phy_id = MARVELL_PHY_ID_88E1101,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1101",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = marvell_config_init,
+ .config_aneg = m88e1101_config_aneg,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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_88E1112,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1112",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1112_config_init,
+ .config_aneg = marvell_config_aneg,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1011_get_tunable,
+ .set_tunable = m88e1011_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1111,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1111",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1111_get_tunable,
+ .set_tunable = m88e1111_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1111_FINISAR,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1111 (Finisar)",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1111gbe_config_init,
+ .config_aneg = m88e1111_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1111_get_tunable,
+ .set_tunable = m88e1111_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1118,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1118",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1118_config_init,
+ .config_aneg = m88e1118_config_aneg,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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_88E1121R,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1121R",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1121_hwmon_ops),
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e1121_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1011_get_tunable,
+ .set_tunable = m88e1011_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1318S,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1318S",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1318_config_init,
+ .config_aneg = m88e1318_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
+ .led_hw_is_supported = m88e1318_led_hw_is_supported,
+ .led_hw_control_set = m88e1318_led_hw_control_set,
+ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1145",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1145_config_init,
+ .config_aneg = m88e1101_config_aneg,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1111_get_tunable,
+ .set_tunable = m88e1111_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1149R,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1149R",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1149_config_init,
+ .config_aneg = m88e1118_config_aneg,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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_88E1240,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1240",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1112_config_init,
+ .config_aneg = marvell_config_aneg,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1011_get_tunable,
+ .set_tunable = m88e1011_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1116R,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1116R",
+ /* PHY_GBIT_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e1116r_config_init,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1011_get_tunable,
+ .set_tunable = m88e1011_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1510,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1510",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
+ .features = PHY_GBIT_FIBRE_FEATURES,
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = m88e1510_probe,
+ .config_init = m88e1510_config_init,
+ .config_aneg = m88e1510_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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 = m88e1510_loopback,
+ .get_tunable = m88e1011_get_tunable,
+ .set_tunable = m88e1011_set_tunable,
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
+ .led_hw_is_supported = m88e1318_led_hw_is_supported,
+ .led_hw_control_set = m88e1318_led_hw_control_set,
+ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1540",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = marvell_probe,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e1510_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
+ .led_hw_is_supported = m88e1318_led_hw_is_supported,
+ .led_hw_control_set = m88e1318_led_hw_control_set,
+ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1545",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
+ .probe = marvell_probe,
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e1510_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
+ .led_hw_is_supported = m88e1318_led_hw_is_supported,
+ .led_hw_control_set = m88e1318_led_hw_control_set,
+ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E3016",
+ /* PHY_BASIC_FEATURES */
+ .probe = marvell_probe,
+ .config_init = m88e3016_config_init,
+ .aneg_done = marvell_aneg_done,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = marvell_probe,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e6390_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E6390_FAMILY,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E6390 Family",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e6390_hwmon_ops),
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = marvell_probe,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e6390_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E6393_FAMILY,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E6393 Family",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e6393_hwmon_ops),
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = marvell_probe,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e1510_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1340S,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1340S",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
+ .probe = marvell_probe,
+ /* PHY_GBIT_FEATURES */
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e1510_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1548P,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1548P",
+ .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
+ .probe = marvell_probe,
+ .features = PHY_GBIT_FIBRE_FEATURES,
+ .config_init = marvell_1011gbe_config_init,
+ .config_aneg = m88e1510_config_aneg,
+ .read_status = marvell_read_status,
+ .config_intr = marvell_config_intr,
+ .handle_interrupt = marvell_handle_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,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .led_brightness_set = m88e1318_led_brightness_set,
+ .led_blink_set = m88e1318_led_blink_set,
+ .led_hw_is_supported = m88e1318_led_hw_is_supported,
+ .led_hw_control_set = m88e1318_led_hw_control_set,
+ .led_hw_control_get = m88e1318_led_hw_control_get,
+ },
+};
+
+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_88E1111_FINISAR, 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 },
+ { MARVELL_PHY_ID_88E6393_FAMILY, MARVELL_PHY_ID_MASK },
+ { MARVELL_PHY_ID_88E1340S, MARVELL_PHY_ID_MASK },
+ { MARVELL_PHY_ID_88E1548P, 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..d4bb90d76
--- /dev/null
+++ b/drivers/net/phy/marvell10g.c
@@ -0,0 +1,1431 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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/bitfield.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/marvell_phy.h>
+#include <linux/phy.h>
+#include <linux/sfp.h>
+#include <linux/netdevice.h>
+
+#define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe
+#define MV_PHY_ALASKA_NBT_QUIRK_REV (MARVELL_PHY_ID_88X3310 | 0xa)
+
+#define MV_VERSION(a,b,c,d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
+
+enum {
+ MV_PMA_FW_VER0 = 0xc011,
+ MV_PMA_FW_VER1 = 0xc012,
+ MV_PMA_21X0_PORT_CTRL = 0xc04a,
+ MV_PMA_21X0_PORT_CTRL_SWRST = BIT(15),
+ MV_PMA_21X0_PORT_CTRL_MACTYPE_MASK = 0x7,
+ MV_PMA_21X0_PORT_CTRL_MACTYPE_USXGMII = 0x0,
+ MV_PMA_2180_PORT_CTRL_MACTYPE_DXGMII = 0x1,
+ MV_PMA_2180_PORT_CTRL_MACTYPE_QXGMII = 0x2,
+ MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER = 0x4,
+ MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER_NO_SGMII_AN = 0x5,
+ MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH = 0x6,
+ 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_CSCR1 = 0x8000,
+ MV_PCS_CSCR1_ED_MASK = 0x0300,
+ MV_PCS_CSCR1_ED_OFF = 0x0000,
+ MV_PCS_CSCR1_ED_RX = 0x0200,
+ MV_PCS_CSCR1_ED_NLP = 0x0300,
+ MV_PCS_CSCR1_MDIX_MASK = 0x0060,
+ MV_PCS_CSCR1_MDIX_MDI = 0x0000,
+ MV_PCS_CSCR1_MDIX_MDIX = 0x0020,
+ MV_PCS_CSCR1_MDIX_AUTO = 0x0060,
+
+ MV_PCS_DSC1 = 0x8003,
+ MV_PCS_DSC1_ENABLE = BIT(9),
+ MV_PCS_DSC1_10GBT = 0x01c0,
+ MV_PCS_DSC1_1GBR = 0x0038,
+ MV_PCS_DSC1_100BTX = 0x0007,
+ MV_PCS_DSC2 = 0x8004,
+ MV_PCS_DSC2_2P5G = 0xf000,
+ MV_PCS_DSC2_5G = 0x0f00,
+
+ MV_PCS_CSSR1 = 0x8008,
+ MV_PCS_CSSR1_SPD1_MASK = 0xc000,
+ MV_PCS_CSSR1_SPD1_SPD2 = 0xc000,
+ MV_PCS_CSSR1_SPD1_1000 = 0x8000,
+ MV_PCS_CSSR1_SPD1_100 = 0x4000,
+ MV_PCS_CSSR1_SPD1_10 = 0x0000,
+ MV_PCS_CSSR1_DUPLEX_FULL= BIT(13),
+ MV_PCS_CSSR1_RESOLVED = BIT(11),
+ MV_PCS_CSSR1_MDIX = BIT(6),
+ MV_PCS_CSSR1_SPD2_MASK = 0x000c,
+ MV_PCS_CSSR1_SPD2_5000 = 0x0008,
+ MV_PCS_CSSR1_SPD2_2500 = 0x0004,
+ MV_PCS_CSSR1_SPD2_10000 = 0x0000,
+
+ /* Temperature read register (88E2110 only) */
+ MV_PCS_TEMP = 0x8042,
+
+ /* Number of ports on the device */
+ MV_PCS_PORT_INFO = 0xd00d,
+ MV_PCS_PORT_INFO_NPORTS_MASK = 0x0380,
+ MV_PCS_PORT_INFO_NPORTS_SHIFT = 7,
+
+ /* SerDes reinitialization 88E21X0 */
+ MV_AN_21X0_SERDES_CTRL2 = 0x800f,
+ MV_AN_21X0_SERDES_CTRL2_AUTO_INIT_DIS = BIT(13),
+ MV_AN_21X0_SERDES_CTRL2_RUN_INIT = BIT(15),
+
+ /* 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_PORT_CTRL = 0xf001,
+ MV_V2_PORT_CTRL_PWRDOWN = BIT(11),
+ MV_V2_33X0_PORT_CTRL_SWRST = BIT(15),
+ MV_V2_33X0_PORT_CTRL_MACTYPE_MASK = 0x7,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI = 0x0,
+ MV_V2_3310_PORT_CTRL_MACTYPE_XAUI_RATE_MATCH = 0x1,
+ MV_V2_3340_PORT_CTRL_MACTYPE_RXAUI_NO_SGMII_AN = 0x1,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI_RATE_MATCH = 0x2,
+ MV_V2_3310_PORT_CTRL_MACTYPE_XAUI = 0x3,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER = 0x4,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_NO_SGMII_AN = 0x5,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH = 0x6,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_USXGMII = 0x7,
+ MV_V2_PORT_INTR_STS = 0xf040,
+ MV_V2_PORT_INTR_MASK = 0xf043,
+ MV_V2_PORT_INTR_STS_WOL_EN = BIT(8),
+ MV_V2_MAGIC_PKT_WORD0 = 0xf06b,
+ MV_V2_MAGIC_PKT_WORD1 = 0xf06c,
+ MV_V2_MAGIC_PKT_WORD2 = 0xf06d,
+ /* Wake on LAN registers */
+ MV_V2_WOL_CTRL = 0xf06e,
+ MV_V2_WOL_CTRL_CLEAR_STS = BIT(15),
+ MV_V2_WOL_CTRL_MAGIC_PKT_EN = BIT(0),
+ /* Temperature control/read registers (88X3310 only) */
+ 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_chip {
+ bool (*has_downshift)(struct phy_device *phydev);
+ void (*init_supported_interfaces)(unsigned long *mask);
+ int (*get_mactype)(struct phy_device *phydev);
+ int (*set_mactype)(struct phy_device *phydev, int mactype);
+ int (*select_mactype)(unsigned long *interfaces);
+ int (*init_interface)(struct phy_device *phydev, int mactype);
+
+#ifdef CONFIG_HWMON
+ int (*hwmon_read_temp_reg)(struct phy_device *phydev);
+#endif
+};
+
+struct mv3310_priv {
+ DECLARE_BITMAP(supported_interfaces, PHY_INTERFACE_MODE_MAX);
+
+ u32 firmware_ver;
+ bool has_downshift;
+ bool rate_match;
+ phy_interface_t const_interface;
+
+ struct device *hwmon_dev;
+ char *hwmon_name;
+};
+
+static const struct mv3310_chip *to_mv3310_chip(struct phy_device *phydev)
+{
+ return phydev->drv->driver_data;
+}
+
+#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_temp_reg(struct phy_device *phydev)
+{
+ return phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP);
+}
+
+static int mv2110_hwmon_read_temp_reg(struct phy_device *phydev)
+{
+ return phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_TEMP);
+}
+
+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);
+ const struct mv3310_chip *chip = to_mv3310_chip(phydev);
+ 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 = chip->hwmon_read_temp_reg(phydev);
+ 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 * const 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;
+
+ if (phydev->drv->phy_id != MARVELL_PHY_ID_88X3310)
+ return 0;
+
+ 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;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, MV_V2_TEMP_CTRL,
+ MV_V2_TEMP_CTRL_MASK, val);
+}
+
+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;
+
+ 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_power_down(struct phy_device *phydev)
+{
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
+ MV_V2_PORT_CTRL_PWRDOWN);
+}
+
+static int mv3310_power_up(struct phy_device *phydev)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+ int ret;
+
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
+ MV_V2_PORT_CTRL_PWRDOWN);
+
+ /* Sometimes, the power down bit doesn't clear immediately, and
+ * a read of this register causes the bit not to clear. Delay
+ * 100us to allow the PHY to come out of power down mode before
+ * the next access.
+ */
+ udelay(100);
+
+ if (phydev->drv->phy_id != MARVELL_PHY_ID_88X3310 ||
+ priv->firmware_ver < 0x00030000)
+ return ret;
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
+ MV_V2_33X0_PORT_CTRL_SWRST);
+}
+
+static int mv3310_reset(struct phy_device *phydev, u32 unit)
+{
+ int val, err;
+
+ err = phy_modify_mmd(phydev, MDIO_MMD_PCS, unit + MDIO_CTRL1,
+ MDIO_CTRL1_RESET, MDIO_CTRL1_RESET);
+ if (err < 0)
+ return err;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PCS,
+ unit + MDIO_CTRL1, val,
+ !(val & MDIO_CTRL1_RESET),
+ 5000, 100000, true);
+}
+
+static int mv3310_get_downshift(struct phy_device *phydev, u8 *ds)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+ int val;
+
+ if (!priv->has_downshift)
+ return -EOPNOTSUPP;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_DSC1);
+ if (val < 0)
+ return val;
+
+ if (val & MV_PCS_DSC1_ENABLE)
+ /* assume that all fields are the same */
+ *ds = 1 + FIELD_GET(MV_PCS_DSC1_10GBT, (u16)val);
+ else
+ *ds = DOWNSHIFT_DEV_DISABLE;
+
+ return 0;
+}
+
+static int mv3310_set_downshift(struct phy_device *phydev, u8 ds)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+ u16 val;
+ int err;
+
+ if (!priv->has_downshift)
+ return -EOPNOTSUPP;
+
+ if (ds == DOWNSHIFT_DEV_DISABLE)
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MV_PCS_DSC1,
+ MV_PCS_DSC1_ENABLE);
+
+ /* DOWNSHIFT_DEV_DEFAULT_COUNT is confusing. It looks like it should
+ * set the default settings for the PHY. However, it is used for
+ * "ethtool --set-phy-tunable ethN downshift on". The intention is
+ * to enable downshift at a default number of retries. The default
+ * settings for 88x3310 are for two retries with downshift disabled.
+ * So let's use two retries with downshift enabled.
+ */
+ if (ds == DOWNSHIFT_DEV_DEFAULT_COUNT)
+ ds = 2;
+
+ if (ds > 8)
+ return -E2BIG;
+
+ ds -= 1;
+ val = FIELD_PREP(MV_PCS_DSC2_2P5G, ds);
+ val |= FIELD_PREP(MV_PCS_DSC2_5G, ds);
+ err = phy_modify_mmd(phydev, MDIO_MMD_PCS, MV_PCS_DSC2,
+ MV_PCS_DSC2_2P5G | MV_PCS_DSC2_5G, val);
+ if (err < 0)
+ return err;
+
+ val = MV_PCS_DSC1_ENABLE;
+ val |= FIELD_PREP(MV_PCS_DSC1_10GBT, ds);
+ val |= FIELD_PREP(MV_PCS_DSC1_1GBR, ds);
+ val |= FIELD_PREP(MV_PCS_DSC1_100BTX, ds);
+
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS, MV_PCS_DSC1,
+ MV_PCS_DSC1_ENABLE | MV_PCS_DSC1_10GBT |
+ MV_PCS_DSC1_1GBR | MV_PCS_DSC1_100BTX, val);
+}
+
+static int mv3310_get_edpd(struct phy_device *phydev, u16 *edpd)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_CSCR1);
+ if (val < 0)
+ return val;
+
+ switch (val & MV_PCS_CSCR1_ED_MASK) {
+ case MV_PCS_CSCR1_ED_NLP:
+ *edpd = 1000;
+ break;
+ case MV_PCS_CSCR1_ED_RX:
+ *edpd = ETHTOOL_PHY_EDPD_NO_TX;
+ break;
+ default:
+ *edpd = ETHTOOL_PHY_EDPD_DISABLE;
+ break;
+ }
+ return 0;
+}
+
+static int mv3310_set_edpd(struct phy_device *phydev, u16 edpd)
+{
+ u16 val;
+ int err;
+
+ switch (edpd) {
+ case 1000:
+ case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
+ val = MV_PCS_CSCR1_ED_NLP;
+ break;
+
+ case ETHTOOL_PHY_EDPD_NO_TX:
+ val = MV_PCS_CSCR1_ED_RX;
+ break;
+
+ case ETHTOOL_PHY_EDPD_DISABLE:
+ val = MV_PCS_CSCR1_ED_OFF;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ err = phy_modify_mmd_changed(phydev, MDIO_MMD_PCS, MV_PCS_CSCR1,
+ MV_PCS_CSCR1_ED_MASK, val);
+ if (err > 0)
+ err = mv3310_reset(phydev, MV_PCS_BASE_T);
+
+ return err;
+}
+
+static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+ struct phy_device *phydev = upstream;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
+ phy_interface_t iface;
+
+ sfp_parse_support(phydev->sfp_bus, id, support, interfaces);
+ iface = sfp_select_interface(phydev->sfp_bus, support);
+
+ if (iface != PHY_INTERFACE_MODE_10GBASER) {
+ dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct sfp_upstream_ops mv3310_sfp_ops = {
+ .attach = phy_sfp_attach,
+ .detach = phy_sfp_detach,
+ .module_insert = mv3310_sfp_insert,
+};
+
+static int mv3310_probe(struct phy_device *phydev)
+{
+ const struct mv3310_chip *chip = to_mv3310_chip(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 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_FW_VER0);
+ if (ret < 0)
+ return ret;
+
+ priv->firmware_ver = ret << 16;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_FW_VER1);
+ if (ret < 0)
+ return ret;
+
+ priv->firmware_ver |= ret;
+
+ phydev_info(phydev, "Firmware version %u.%u.%u.%u\n",
+ priv->firmware_ver >> 24, (priv->firmware_ver >> 16) & 255,
+ (priv->firmware_ver >> 8) & 255, priv->firmware_ver & 255);
+
+ if (chip->has_downshift)
+ priv->has_downshift = chip->has_downshift(phydev);
+
+ /* Powering down the port when not in use saves about 600mW */
+ ret = mv3310_power_down(phydev);
+ if (ret)
+ return ret;
+
+ ret = mv3310_hwmon_probe(phydev);
+ if (ret)
+ return ret;
+
+ chip->init_supported_interfaces(priv->supported_interfaces);
+
+ return phy_sfp_probe(phydev, &mv3310_sfp_ops);
+}
+
+static void mv3310_remove(struct phy_device *phydev)
+{
+ mv3310_hwmon_config(phydev, false);
+}
+
+static int mv3310_suspend(struct phy_device *phydev)
+{
+ return mv3310_power_down(phydev);
+}
+
+static int mv3310_resume(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = mv3310_power_up(phydev);
+ if (ret)
+ return ret;
+
+ return mv3310_hwmon_config(phydev, true);
+}
+
+/* Some PHYs in the Alaska family such as the 88X3310 and the 88E2010
+ * don't set bit 14 in PMA Extended Abilities (1.11), although they do
+ * support 2.5GBASET and 5GBASET. For these models, we can still read their
+ * 2.5G/5G extended abilities register (1.21). We detect these models based on
+ * the PMA device identifier, with a mask matching models known to have this
+ * issue
+ */
+static bool mv3310_has_pma_ngbaset_quirk(struct phy_device *phydev)
+{
+ if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_PMAPMD))
+ return false;
+
+ /* Only some revisions of the 88X3310 family PMA seem to be impacted */
+ return (phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] &
+ MV_PHY_ALASKA_NBT_QUIRK_MASK) == MV_PHY_ALASKA_NBT_QUIRK_REV;
+}
+
+static int mv2110_get_mactype(struct phy_device *phydev)
+{
+ int mactype;
+
+ mactype = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_21X0_PORT_CTRL);
+ if (mactype < 0)
+ return mactype;
+
+ return mactype & MV_PMA_21X0_PORT_CTRL_MACTYPE_MASK;
+}
+
+static int mv2110_set_mactype(struct phy_device *phydev, int mactype)
+{
+ int err, val;
+
+ mactype &= MV_PMA_21X0_PORT_CTRL_MACTYPE_MASK;
+ err = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_21X0_PORT_CTRL,
+ MV_PMA_21X0_PORT_CTRL_SWRST |
+ MV_PMA_21X0_PORT_CTRL_MACTYPE_MASK,
+ MV_PMA_21X0_PORT_CTRL_SWRST | mactype);
+ if (err)
+ return err;
+
+ err = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MV_AN_21X0_SERDES_CTRL2,
+ MV_AN_21X0_SERDES_CTRL2_AUTO_INIT_DIS |
+ MV_AN_21X0_SERDES_CTRL2_RUN_INIT);
+ if (err)
+ return err;
+
+ err = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_AN,
+ MV_AN_21X0_SERDES_CTRL2, val,
+ !(val &
+ MV_AN_21X0_SERDES_CTRL2_RUN_INIT),
+ 5000, 100000, true);
+ if (err)
+ return err;
+
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MV_AN_21X0_SERDES_CTRL2,
+ MV_AN_21X0_SERDES_CTRL2_AUTO_INIT_DIS);
+}
+
+static int mv2110_select_mactype(unsigned long *interfaces)
+{
+ if (test_bit(PHY_INTERFACE_MODE_USXGMII, interfaces))
+ return MV_PMA_21X0_PORT_CTRL_MACTYPE_USXGMII;
+ else if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces) &&
+ !test_bit(PHY_INTERFACE_MODE_10GBASER, interfaces))
+ return MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER;
+ else if (test_bit(PHY_INTERFACE_MODE_10GBASER, interfaces))
+ return MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH;
+ else
+ return -1;
+}
+
+static int mv3310_get_mactype(struct phy_device *phydev)
+{
+ int mactype;
+
+ mactype = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL);
+ if (mactype < 0)
+ return mactype;
+
+ return mactype & MV_V2_33X0_PORT_CTRL_MACTYPE_MASK;
+}
+
+static int mv3310_set_mactype(struct phy_device *phydev, int mactype)
+{
+ int ret;
+
+ mactype &= MV_V2_33X0_PORT_CTRL_MACTYPE_MASK;
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
+ MV_V2_33X0_PORT_CTRL_MACTYPE_MASK,
+ mactype);
+ if (ret <= 0)
+ return ret;
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
+ MV_V2_33X0_PORT_CTRL_SWRST);
+}
+
+static int mv3310_select_mactype(unsigned long *interfaces)
+{
+ if (test_bit(PHY_INTERFACE_MODE_USXGMII, interfaces))
+ return MV_V2_33X0_PORT_CTRL_MACTYPE_USXGMII;
+ else if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces) &&
+ test_bit(PHY_INTERFACE_MODE_10GBASER, interfaces))
+ return MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER;
+ else if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces) &&
+ test_bit(PHY_INTERFACE_MODE_RXAUI, interfaces))
+ return MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI;
+ else if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces) &&
+ test_bit(PHY_INTERFACE_MODE_XAUI, interfaces))
+ return MV_V2_3310_PORT_CTRL_MACTYPE_XAUI;
+ else if (test_bit(PHY_INTERFACE_MODE_10GBASER, interfaces))
+ return MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH;
+ else if (test_bit(PHY_INTERFACE_MODE_RXAUI, interfaces))
+ return MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI_RATE_MATCH;
+ else if (test_bit(PHY_INTERFACE_MODE_XAUI, interfaces))
+ return MV_V2_3310_PORT_CTRL_MACTYPE_XAUI_RATE_MATCH;
+ else if (test_bit(PHY_INTERFACE_MODE_SGMII, interfaces))
+ return MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER;
+ else
+ return -1;
+}
+
+static int mv2110_init_interface(struct phy_device *phydev, int mactype)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ priv->rate_match = false;
+
+ if (mactype == MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH)
+ priv->rate_match = true;
+
+ if (mactype == MV_PMA_21X0_PORT_CTRL_MACTYPE_USXGMII)
+ priv->const_interface = PHY_INTERFACE_MODE_USXGMII;
+ else if (mactype == MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH)
+ priv->const_interface = PHY_INTERFACE_MODE_10GBASER;
+ else if (mactype == MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER ||
+ mactype == MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER_NO_SGMII_AN)
+ priv->const_interface = PHY_INTERFACE_MODE_NA;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mv3310_init_interface(struct phy_device *phydev, int mactype)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ priv->rate_match = false;
+
+ if (mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH ||
+ mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI_RATE_MATCH ||
+ mactype == MV_V2_3310_PORT_CTRL_MACTYPE_XAUI_RATE_MATCH)
+ priv->rate_match = true;
+
+ if (mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_USXGMII)
+ priv->const_interface = PHY_INTERFACE_MODE_USXGMII;
+ else if (mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH ||
+ mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_NO_SGMII_AN ||
+ mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER)
+ priv->const_interface = PHY_INTERFACE_MODE_10GBASER;
+ else if (mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI_RATE_MATCH ||
+ mactype == MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI)
+ priv->const_interface = PHY_INTERFACE_MODE_RXAUI;
+ else if (mactype == MV_V2_3310_PORT_CTRL_MACTYPE_XAUI_RATE_MATCH ||
+ mactype == MV_V2_3310_PORT_CTRL_MACTYPE_XAUI)
+ priv->const_interface = PHY_INTERFACE_MODE_XAUI;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mv3340_init_interface(struct phy_device *phydev, int mactype)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+ int err = 0;
+
+ priv->rate_match = false;
+
+ if (mactype == MV_V2_3340_PORT_CTRL_MACTYPE_RXAUI_NO_SGMII_AN)
+ priv->const_interface = PHY_INTERFACE_MODE_RXAUI;
+ else
+ err = mv3310_init_interface(phydev, mactype);
+
+ return err;
+}
+
+static int mv3310_config_init(struct phy_device *phydev)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+ const struct mv3310_chip *chip = to_mv3310_chip(phydev);
+ int err, mactype;
+
+ /* Check that the PHY interface type is compatible */
+ if (!test_bit(phydev->interface, priv->supported_interfaces))
+ return -ENODEV;
+
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ /* Power up so reset works */
+ err = mv3310_power_up(phydev);
+ if (err)
+ return err;
+
+ /* If host provided host supported interface modes, try to select the
+ * best one
+ */
+ if (!phy_interface_empty(phydev->host_interfaces)) {
+ mactype = chip->select_mactype(phydev->host_interfaces);
+ if (mactype >= 0) {
+ phydev_info(phydev, "Changing MACTYPE to %i\n",
+ mactype);
+ err = chip->set_mactype(phydev, mactype);
+ if (err)
+ return err;
+ }
+ }
+
+ mactype = chip->get_mactype(phydev);
+ if (mactype < 0)
+ return mactype;
+
+ err = chip->init_interface(phydev, mactype);
+ if (err) {
+ phydev_err(phydev, "MACTYPE configuration invalid\n");
+ return err;
+ }
+
+ /* Enable EDPD mode - saving 600mW */
+ err = mv3310_set_edpd(phydev, ETHTOOL_PHY_EDPD_DFLT_TX_MSECS);
+ if (err)
+ return err;
+
+ /* Allow downshift */
+ err = mv3310_set_downshift(phydev, DOWNSHIFT_DEV_DEFAULT_COUNT);
+ if (err && err != -EOPNOTSUPP)
+ return err;
+
+ return 0;
+}
+
+static int mv3310_get_features(struct phy_device *phydev)
+{
+ int ret, val;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ if (mv3310_has_pma_ngbaset_quirk(phydev)) {
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
+ MDIO_PMA_NG_EXTABLE);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_NG_EXTABLE_2_5GBT);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_NG_EXTABLE_5GBT);
+ }
+
+ return 0;
+}
+
+static int mv3310_config_mdix(struct phy_device *phydev)
+{
+ u16 val;
+ int err;
+
+ switch (phydev->mdix_ctrl) {
+ case ETH_TP_MDI_AUTO:
+ val = MV_PCS_CSCR1_MDIX_AUTO;
+ break;
+ case ETH_TP_MDI_X:
+ val = MV_PCS_CSCR1_MDIX_MDIX;
+ break;
+ case ETH_TP_MDI:
+ val = MV_PCS_CSCR1_MDIX_MDI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ err = phy_modify_mmd_changed(phydev, MDIO_MMD_PCS, MV_PCS_CSCR1,
+ MV_PCS_CSCR1_MDIX_MASK, val);
+ if (err > 0)
+ err = mv3310_reset(phydev, MV_PCS_BASE_T);
+
+ return err;
+}
+
+static int mv3310_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ u16 reg;
+ int ret;
+
+ ret = mv3310_config_mdix(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return genphy_c45_pma_setup_forced(phydev);
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ /* Clause 45 has no standardized support for 1000BaseT, therefore
+ * use vendor registers for this mode.
+ */
+ reg = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MV_AN_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF, reg);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+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)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ if (!phydev->link)
+ return;
+
+ /* In all of the "* with Rate Matching" modes the PHY interface is fixed
+ * at 10Gb. The PHY adapts the rate to actual wire speed with help of
+ * internal 16KB buffer.
+ *
+ * In USXGMII mode the PHY interface mode is also fixed.
+ */
+ if (priv->rate_match ||
+ priv->const_interface == PHY_INTERFACE_MODE_USXGMII) {
+ phydev->interface = priv->const_interface;
+ return;
+ }
+
+ /* The PHY automatically switches its serdes interface (and active PHYXS
+ * instance) between Cisco SGMII, 2500BaseX, 5GBase-R and 10GBase-R /
+ * xaui / rxaui modes according to the speed.
+ * Florian suggests setting phydev->interface to communicate this to the
+ * MAC. Only do this if we are already in one of the above modes.
+ */
+ switch (phydev->speed) {
+ case SPEED_10000:
+ phydev->interface = priv->const_interface;
+ break;
+ case SPEED_5000:
+ phydev->interface = PHY_INTERFACE_MODE_5GBASER;
+ break;
+ case SPEED_2500:
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ break;
+ case SPEED_1000:
+ case SPEED_100:
+ case SPEED_10:
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ break;
+ default:
+ break;
+ }
+}
+
+/* 10GBASE-ER,LR,LRM,SR do not support autonegotiation. */
+static int mv3310_read_status_10gbaser(struct phy_device *phydev)
+{
+ phydev->link = 1;
+ phydev->speed = SPEED_10000;
+ phydev->duplex = DUPLEX_FULL;
+ phydev->port = PORT_FIBRE;
+
+ return 0;
+}
+
+static int mv3310_read_status_copper(struct phy_device *phydev)
+{
+ int cssr1, speed, val;
+
+ val = genphy_c45_read_link(phydev);
+ if (val < 0)
+ return val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ cssr1 = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_CSSR1);
+ if (cssr1 < 0)
+ return cssr1;
+
+ /* If the link settings are not resolved, mark the link down */
+ if (!(cssr1 & MV_PCS_CSSR1_RESOLVED)) {
+ phydev->link = 0;
+ return 0;
+ }
+
+ /* Read the copper link settings */
+ speed = cssr1 & MV_PCS_CSSR1_SPD1_MASK;
+ if (speed == MV_PCS_CSSR1_SPD1_SPD2)
+ speed |= cssr1 & MV_PCS_CSSR1_SPD2_MASK;
+
+ switch (speed) {
+ case MV_PCS_CSSR1_SPD1_SPD2 | MV_PCS_CSSR1_SPD2_10000:
+ phydev->speed = SPEED_10000;
+ break;
+
+ case MV_PCS_CSSR1_SPD1_SPD2 | MV_PCS_CSSR1_SPD2_5000:
+ phydev->speed = SPEED_5000;
+ break;
+
+ case MV_PCS_CSSR1_SPD1_SPD2 | MV_PCS_CSSR1_SPD2_2500:
+ phydev->speed = SPEED_2500;
+ break;
+
+ case MV_PCS_CSSR1_SPD1_1000:
+ phydev->speed = SPEED_1000;
+ break;
+
+ case MV_PCS_CSSR1_SPD1_100:
+ phydev->speed = SPEED_100;
+ break;
+
+ case MV_PCS_CSSR1_SPD1_10:
+ phydev->speed = SPEED_10;
+ break;
+ }
+
+ phydev->duplex = cssr1 & MV_PCS_CSSR1_DUPLEX_FULL ?
+ DUPLEX_FULL : DUPLEX_HALF;
+ phydev->port = PORT_TP;
+ phydev->mdix = cssr1 & MV_PCS_CSSR1_MDIX ?
+ ETH_TP_MDI_X : ETH_TP_MDI;
+
+ 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;
+
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+
+ /* Update the pause status */
+ phy_resolve_aneg_pause(phydev);
+ }
+
+ return 0;
+}
+
+static int mv3310_read_status(struct phy_device *phydev)
+{
+ int err, val;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ linkmode_zero(phydev->lp_advertising);
+ phydev->link = 0;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ phydev->mdix = ETH_TP_MDI_INVALID;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_STAT1_LSTATUS)
+ err = mv3310_read_status_10gbaser(phydev);
+ else
+ err = mv3310_read_status_copper(phydev);
+ if (err < 0)
+ return err;
+
+ if (phydev->link)
+ mv3310_update_interface(phydev);
+
+ return 0;
+}
+
+static int mv3310_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return mv3310_get_downshift(phydev, data);
+ case ETHTOOL_PHY_EDPD:
+ return mv3310_get_edpd(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int mv3310_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_DOWNSHIFT:
+ return mv3310_set_downshift(phydev, *(u8 *)data);
+ case ETHTOOL_PHY_EDPD:
+ return mv3310_set_edpd(phydev, *(u16 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static bool mv3310_has_downshift(struct phy_device *phydev)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ /* Fails to downshift with firmware older than v0.3.5.0 */
+ return priv->firmware_ver >= MV_VERSION(0,3,5,0);
+}
+
+static void mv3310_init_supported_interfaces(unsigned long *mask)
+{
+ __set_bit(PHY_INTERFACE_MODE_SGMII, mask);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, mask);
+ __set_bit(PHY_INTERFACE_MODE_5GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_XAUI, mask);
+ __set_bit(PHY_INTERFACE_MODE_RXAUI, mask);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, mask);
+}
+
+static void mv3340_init_supported_interfaces(unsigned long *mask)
+{
+ __set_bit(PHY_INTERFACE_MODE_SGMII, mask);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, mask);
+ __set_bit(PHY_INTERFACE_MODE_5GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_RXAUI, mask);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, mask);
+}
+
+static void mv2110_init_supported_interfaces(unsigned long *mask)
+{
+ __set_bit(PHY_INTERFACE_MODE_SGMII, mask);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, mask);
+ __set_bit(PHY_INTERFACE_MODE_5GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, mask);
+}
+
+static void mv2111_init_supported_interfaces(unsigned long *mask)
+{
+ __set_bit(PHY_INTERFACE_MODE_SGMII, mask);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, mask);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, mask);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, mask);
+}
+
+static const struct mv3310_chip mv3310_type = {
+ .has_downshift = mv3310_has_downshift,
+ .init_supported_interfaces = mv3310_init_supported_interfaces,
+ .get_mactype = mv3310_get_mactype,
+ .set_mactype = mv3310_set_mactype,
+ .select_mactype = mv3310_select_mactype,
+ .init_interface = mv3310_init_interface,
+
+#ifdef CONFIG_HWMON
+ .hwmon_read_temp_reg = mv3310_hwmon_read_temp_reg,
+#endif
+};
+
+static const struct mv3310_chip mv3340_type = {
+ .has_downshift = mv3310_has_downshift,
+ .init_supported_interfaces = mv3340_init_supported_interfaces,
+ .get_mactype = mv3310_get_mactype,
+ .set_mactype = mv3310_set_mactype,
+ .select_mactype = mv3310_select_mactype,
+ .init_interface = mv3340_init_interface,
+
+#ifdef CONFIG_HWMON
+ .hwmon_read_temp_reg = mv3310_hwmon_read_temp_reg,
+#endif
+};
+
+static const struct mv3310_chip mv2110_type = {
+ .init_supported_interfaces = mv2110_init_supported_interfaces,
+ .get_mactype = mv2110_get_mactype,
+ .set_mactype = mv2110_set_mactype,
+ .select_mactype = mv2110_select_mactype,
+ .init_interface = mv2110_init_interface,
+
+#ifdef CONFIG_HWMON
+ .hwmon_read_temp_reg = mv2110_hwmon_read_temp_reg,
+#endif
+};
+
+static const struct mv3310_chip mv2111_type = {
+ .init_supported_interfaces = mv2111_init_supported_interfaces,
+ .get_mactype = mv2110_get_mactype,
+ .set_mactype = mv2110_set_mactype,
+ .select_mactype = mv2110_select_mactype,
+ .init_interface = mv2110_init_interface,
+
+#ifdef CONFIG_HWMON
+ .hwmon_read_temp_reg = mv2110_hwmon_read_temp_reg,
+#endif
+};
+
+static int mv3310_get_number_of_ports(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_PORT_INFO);
+ if (ret < 0)
+ return ret;
+
+ ret &= MV_PCS_PORT_INFO_NPORTS_MASK;
+ ret >>= MV_PCS_PORT_INFO_NPORTS_SHIFT;
+
+ return ret + 1;
+}
+
+static int mv3310_match_phy_device(struct phy_device *phydev)
+{
+ if ((phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] &
+ MARVELL_PHY_ID_MASK) != MARVELL_PHY_ID_88X3310)
+ return 0;
+
+ return mv3310_get_number_of_ports(phydev) == 1;
+}
+
+static int mv3340_match_phy_device(struct phy_device *phydev)
+{
+ if ((phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] &
+ MARVELL_PHY_ID_MASK) != MARVELL_PHY_ID_88X3310)
+ return 0;
+
+ return mv3310_get_number_of_ports(phydev) == 4;
+}
+
+static int mv211x_match_phy_device(struct phy_device *phydev, bool has_5g)
+{
+ int val;
+
+ if ((phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] &
+ MARVELL_PHY_ID_MASK) != MARVELL_PHY_ID_88E2110)
+ return 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_SPEED);
+ if (val < 0)
+ return val;
+
+ return !!(val & MDIO_PCS_SPEED_5G) == has_5g;
+}
+
+static int mv2110_match_phy_device(struct phy_device *phydev)
+{
+ return mv211x_match_phy_device(phydev, true);
+}
+
+static int mv2111_match_phy_device(struct phy_device *phydev)
+{
+ return mv211x_match_phy_device(phydev, false);
+}
+
+static void mv3110_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int ret;
+
+ wol->supported = WAKE_MAGIC;
+ wol->wolopts = 0;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_V2_WOL_CTRL);
+ if (ret < 0)
+ return;
+
+ if (ret & MV_V2_WOL_CTRL_MAGIC_PKT_EN)
+ wol->wolopts |= WAKE_MAGIC;
+}
+
+static int mv3110_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int ret;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ /* Enable the WOL interrupt */
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_PORT_INTR_MASK,
+ MV_V2_PORT_INTR_STS_WOL_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Store the device address for the magic packet */
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_MAGIC_PKT_WORD2,
+ ((phydev->attached_dev->dev_addr[5] << 8) |
+ phydev->attached_dev->dev_addr[4]));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_MAGIC_PKT_WORD1,
+ ((phydev->attached_dev->dev_addr[3] << 8) |
+ phydev->attached_dev->dev_addr[2]));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_MAGIC_PKT_WORD0,
+ ((phydev->attached_dev->dev_addr[1] << 8) |
+ phydev->attached_dev->dev_addr[0]));
+ if (ret < 0)
+ return ret;
+
+ /* Clear WOL status and enable magic packet matching */
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_WOL_CTRL,
+ MV_V2_WOL_CTRL_MAGIC_PKT_EN |
+ MV_V2_WOL_CTRL_CLEAR_STS);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* Disable magic packet matching & reset WOL status bit */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_WOL_CTRL,
+ MV_V2_WOL_CTRL_MAGIC_PKT_EN,
+ MV_V2_WOL_CTRL_CLEAR_STS);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Reset the clear WOL status bit as it does not self-clear */
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+ MV_V2_WOL_CTRL,
+ MV_V2_WOL_CTRL_CLEAR_STS);
+}
+
+static struct phy_driver mv3310_drivers[] = {
+ {
+ .phy_id = MARVELL_PHY_ID_88X3310,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .match_phy_device = mv3310_match_phy_device,
+ .name = "mv88x3310",
+ .driver_data = &mv3310_type,
+ .get_features = mv3310_get_features,
+ .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,
+ .get_tunable = mv3310_get_tunable,
+ .set_tunable = mv3310_set_tunable,
+ .remove = mv3310_remove,
+ .set_loopback = genphy_c45_loopback,
+ .get_wol = mv3110_get_wol,
+ .set_wol = mv3110_set_wol,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88X3310,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .match_phy_device = mv3340_match_phy_device,
+ .name = "mv88x3340",
+ .driver_data = &mv3340_type,
+ .get_features = mv3310_get_features,
+ .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,
+ .get_tunable = mv3310_get_tunable,
+ .set_tunable = mv3310_set_tunable,
+ .remove = mv3310_remove,
+ .set_loopback = genphy_c45_loopback,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E2110,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .match_phy_device = mv2110_match_phy_device,
+ .name = "mv88e2110",
+ .driver_data = &mv2110_type,
+ .probe = mv3310_probe,
+ .suspend = mv3310_suspend,
+ .resume = mv3310_resume,
+ .config_init = mv3310_config_init,
+ .config_aneg = mv3310_config_aneg,
+ .aneg_done = mv3310_aneg_done,
+ .read_status = mv3310_read_status,
+ .get_tunable = mv3310_get_tunable,
+ .set_tunable = mv3310_set_tunable,
+ .remove = mv3310_remove,
+ .set_loopback = genphy_c45_loopback,
+ .get_wol = mv3110_get_wol,
+ .set_wol = mv3110_set_wol,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E2110,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .match_phy_device = mv2111_match_phy_device,
+ .name = "mv88e2111",
+ .driver_data = &mv2111_type,
+ .probe = mv3310_probe,
+ .suspend = mv3310_suspend,
+ .resume = mv3310_resume,
+ .config_init = mv3310_config_init,
+ .config_aneg = mv3310_config_aneg,
+ .aneg_done = mv3310_aneg_done,
+ .read_status = mv3310_read_status,
+ .get_tunable = mv3310_get_tunable,
+ .set_tunable = mv3310_set_tunable,
+ .remove = mv3310_remove,
+ .set_loopback = genphy_c45_loopback,
+ },
+};
+
+module_phy_driver(mv3310_drivers);
+
+static struct mdio_device_id __maybe_unused mv3310_tbl[] = {
+ { MARVELL_PHY_ID_88X3310, MARVELL_PHY_ID_MASK },
+ { MARVELL_PHY_ID_88E2110, MARVELL_PHY_ID_MASK },
+ { },
+};
+MODULE_DEVICE_TABLE(mdio, mv3310_tbl);
+MODULE_DESCRIPTION("Marvell Alaska X/M multi-gigabit Ethernet PHY driver");
+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..2de679a68
--- /dev/null
+++ b/drivers/net/phy/mdio-boardinfo.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * mdio-boardinfo - Collect pre-declarations for MDIO devices
+ */
+
+#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
+ * @bus: Bus the board_info belongs to
+ * @cb: Callback to create device on bus
+ * 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);
+
+/**
+ * mdiobus_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-open-alliance.h b/drivers/net/phy/mdio-open-alliance.h
new file mode 100644
index 000000000..931e14660
--- /dev/null
+++ b/drivers/net/phy/mdio-open-alliance.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * mdio-open-alliance.h - definition of OPEN Alliance SIG standard registers
+ */
+
+#ifndef __MDIO_OPEN_ALLIANCE__
+#define __MDIO_OPEN_ALLIANCE__
+
+#include <linux/mdio.h>
+
+/* NOTE: all OATC14 registers are located in MDIO_MMD_VEND2 */
+
+/* Open Alliance TC14 (10BASE-T1S) registers */
+#define MDIO_OATC14_PLCA_IDVER 0xca00 /* PLCA ID and version */
+#define MDIO_OATC14_PLCA_CTRL0 0xca01 /* PLCA Control register 0 */
+#define MDIO_OATC14_PLCA_CTRL1 0xca02 /* PLCA Control register 1 */
+#define MDIO_OATC14_PLCA_STATUS 0xca03 /* PLCA Status register */
+#define MDIO_OATC14_PLCA_TOTMR 0xca04 /* PLCA TO Timer register */
+#define MDIO_OATC14_PLCA_BURST 0xca05 /* PLCA BURST mode register */
+
+/* Open Alliance TC14 PLCA IDVER register */
+#define MDIO_OATC14_PLCA_IDM 0xff00 /* PLCA MAP ID */
+#define MDIO_OATC14_PLCA_VER 0x00ff /* PLCA MAP version */
+
+/* Open Alliance TC14 PLCA CTRL0 register */
+#define MDIO_OATC14_PLCA_EN BIT(15) /* PLCA enable */
+#define MDIO_OATC14_PLCA_RST BIT(14) /* PLCA reset */
+
+/* Open Alliance TC14 PLCA CTRL1 register */
+#define MDIO_OATC14_PLCA_NCNT 0xff00 /* PLCA node count */
+#define MDIO_OATC14_PLCA_ID 0x00ff /* PLCA local node ID */
+
+/* Open Alliance TC14 PLCA STATUS register */
+#define MDIO_OATC14_PLCA_PST BIT(15) /* PLCA status indication */
+
+/* Open Alliance TC14 PLCA TOTMR register */
+#define MDIO_OATC14_PLCA_TOT 0x00ff
+
+/* Open Alliance TC14 PLCA BURST register */
+#define MDIO_OATC14_PLCA_MAXBC 0xff00
+#define MDIO_OATC14_PLCA_BTMR 0x00ff
+
+/* Version Identifiers */
+#define OATC14_IDM 0x0a00
+
+#endif /* __MDIO_OPEN_ALLIANCE__ */
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
new file mode 100644
index 000000000..25dcaa49a
--- /dev/null
+++ b/drivers/net/phy/mdio_bus.c
@@ -0,0 +1,1422 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* MDIO Bus interface
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/micrel_phy.h>
+#include <linux/mii.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/reset.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/unistd.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/mdio.h>
+
+#include "mdio-boardinfo.h"
+
+static int mdiobus_register_gpiod(struct mdio_device *mdiodev)
+{
+ /* Deassert the optional reset signal */
+ mdiodev->reset_gpio = gpiod_get_optional(&mdiodev->dev,
+ "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(mdiodev->reset_gpio))
+ return PTR_ERR(mdiodev->reset_gpio);
+
+ if (mdiodev->reset_gpio)
+ gpiod_set_consumer_name(mdiodev->reset_gpio, "PHY reset");
+
+ return 0;
+}
+
+static int mdiobus_register_reset(struct mdio_device *mdiodev)
+{
+ struct reset_control *reset;
+
+ reset = reset_control_get_optional_exclusive(&mdiodev->dev, "phy");
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+
+ mdiodev->reset_ctrl = reset;
+
+ 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;
+
+ err = mdiobus_register_reset(mdiodev);
+ if (err)
+ return err;
+
+ /* Assert the reset signal */
+ mdio_device_reset(mdiodev, 1);
+ }
+
+ 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;
+
+ reset_control_put(mdiodev->reset_ctrl);
+
+ mdiodev->bus->mdio_map[mdiodev->addr] = NULL;
+
+ return 0;
+}
+EXPORT_SYMBOL(mdiobus_unregister_device);
+
+static struct mdio_device *mdiobus_find_device(struct mii_bus *bus, int addr)
+{
+ bool addr_valid = addr >= 0 && addr < ARRAY_SIZE(bus->mdio_map);
+
+ if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr))
+ return NULL;
+
+ return bus->mdio_map[addr];
+}
+
+struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
+{
+ struct mdio_device *mdiodev;
+
+ mdiodev = mdiobus_find_device(bus, 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 mdiobus_find_device(bus, addr) != NULL;
+}
+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 and 64-bit seqcounts */
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ bus->irq[i] = PHY_POLL;
+ u64_stats_init(&bus->stats[i].syncp);
+ }
+
+ return bus;
+}
+EXPORT_SYMBOL(mdiobus_alloc_size);
+
+/**
+ * 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);
+
+ WARN(bus->state != MDIOBUS_RELEASED &&
+ /* for compatibility with error handling in drivers */
+ bus->state != MDIOBUS_ALLOCATED,
+ "%s: not in RELEASED or ALLOCATED state\n",
+ bus->id);
+ kfree(bus);
+}
+
+struct mdio_bus_stat_attr {
+ int addr;
+ unsigned int field_offset;
+};
+
+static u64 mdio_bus_get_stat(struct mdio_bus_stats *s, unsigned int offset)
+{
+ const char *p = (const char *)s + offset;
+ unsigned int start;
+ u64 val = 0;
+
+ do {
+ start = u64_stats_fetch_begin(&s->syncp);
+ val = u64_stats_read((const u64_stats_t *)p);
+ } while (u64_stats_fetch_retry(&s->syncp, start));
+
+ return val;
+}
+
+static u64 mdio_bus_get_global_stat(struct mii_bus *bus, unsigned int offset)
+{
+ unsigned int i;
+ u64 val = 0;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++)
+ val += mdio_bus_get_stat(&bus->stats[i], offset);
+
+ return val;
+}
+
+static ssize_t mdio_bus_stat_field_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct mii_bus *bus = to_mii_bus(dev);
+ struct mdio_bus_stat_attr *sattr;
+ struct dev_ext_attribute *eattr;
+ u64 val;
+
+ eattr = container_of(attr, struct dev_ext_attribute, attr);
+ sattr = eattr->var;
+
+ if (sattr->addr < 0)
+ val = mdio_bus_get_global_stat(bus, sattr->field_offset);
+ else
+ val = mdio_bus_get_stat(&bus->stats[sattr->addr],
+ sattr->field_offset);
+
+ return sysfs_emit(buf, "%llu\n", val);
+}
+
+static ssize_t mdio_bus_device_stat_field_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct mdio_device *mdiodev = to_mdio_device(dev);
+ struct mii_bus *bus = mdiodev->bus;
+ struct mdio_bus_stat_attr *sattr;
+ struct dev_ext_attribute *eattr;
+ int addr = mdiodev->addr;
+ u64 val;
+
+ eattr = container_of(attr, struct dev_ext_attribute, attr);
+ sattr = eattr->var;
+
+ val = mdio_bus_get_stat(&bus->stats[addr], sattr->field_offset);
+
+ return sysfs_emit(buf, "%llu\n", val);
+}
+
+#define MDIO_BUS_STATS_ATTR_DECL(field, file) \
+static struct dev_ext_attribute dev_attr_mdio_bus_##field = { \
+ .attr = { .attr = { .name = file, .mode = 0444 }, \
+ .show = mdio_bus_stat_field_show, \
+ }, \
+ .var = &((struct mdio_bus_stat_attr) { \
+ -1, offsetof(struct mdio_bus_stats, field) \
+ }), \
+}; \
+static struct dev_ext_attribute dev_attr_mdio_bus_device_##field = { \
+ .attr = { .attr = { .name = file, .mode = 0444 }, \
+ .show = mdio_bus_device_stat_field_show, \
+ }, \
+ .var = &((struct mdio_bus_stat_attr) { \
+ -1, offsetof(struct mdio_bus_stats, field) \
+ }), \
+};
+
+#define MDIO_BUS_STATS_ATTR(field) \
+ MDIO_BUS_STATS_ATTR_DECL(field, __stringify(field))
+
+MDIO_BUS_STATS_ATTR(transfers);
+MDIO_BUS_STATS_ATTR(errors);
+MDIO_BUS_STATS_ATTR(writes);
+MDIO_BUS_STATS_ATTR(reads);
+
+#define MDIO_BUS_STATS_ADDR_ATTR_DECL(field, addr, file) \
+static struct dev_ext_attribute dev_attr_mdio_bus_addr_##field##_##addr = { \
+ .attr = { .attr = { .name = file, .mode = 0444 }, \
+ .show = mdio_bus_stat_field_show, \
+ }, \
+ .var = &((struct mdio_bus_stat_attr) { \
+ addr, offsetof(struct mdio_bus_stats, field) \
+ }), \
+}
+
+#define MDIO_BUS_STATS_ADDR_ATTR(field, addr) \
+ MDIO_BUS_STATS_ADDR_ATTR_DECL(field, addr, \
+ __stringify(field) "_" __stringify(addr))
+
+#define MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(addr) \
+ MDIO_BUS_STATS_ADDR_ATTR(transfers, addr); \
+ MDIO_BUS_STATS_ADDR_ATTR(errors, addr); \
+ MDIO_BUS_STATS_ADDR_ATTR(writes, addr); \
+ MDIO_BUS_STATS_ADDR_ATTR(reads, addr) \
+
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(0);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(1);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(2);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(3);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(4);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(5);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(6);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(7);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(8);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(9);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(10);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(11);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(12);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(13);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(14);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(15);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(16);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(17);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(18);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(19);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(20);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(21);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(22);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(23);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(24);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(25);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(26);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(27);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(28);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(29);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(30);
+MDIO_BUS_STATS_ADDR_ATTR_GROUP_DECL(31);
+
+#define MDIO_BUS_STATS_ADDR_ATTR_GROUP(addr) \
+ &dev_attr_mdio_bus_addr_transfers_##addr.attr.attr, \
+ &dev_attr_mdio_bus_addr_errors_##addr.attr.attr, \
+ &dev_attr_mdio_bus_addr_writes_##addr.attr.attr, \
+ &dev_attr_mdio_bus_addr_reads_##addr.attr.attr \
+
+static struct attribute *mdio_bus_statistics_attrs[] = {
+ &dev_attr_mdio_bus_transfers.attr.attr,
+ &dev_attr_mdio_bus_errors.attr.attr,
+ &dev_attr_mdio_bus_writes.attr.attr,
+ &dev_attr_mdio_bus_reads.attr.attr,
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(0),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(1),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(2),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(3),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(4),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(5),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(6),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(7),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(8),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(9),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(10),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(11),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(12),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(13),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(14),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(15),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(16),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(17),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(18),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(19),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(20),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(21),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(22),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(23),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(24),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(25),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(26),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(27),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(28),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(29),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(30),
+ MDIO_BUS_STATS_ADDR_ATTR_GROUP(31),
+ NULL,
+};
+
+static const struct attribute_group mdio_bus_statistics_group = {
+ .name = "statistics",
+ .attrs = mdio_bus_statistics_attrs,
+};
+
+static const struct attribute_group *mdio_bus_groups[] = {
+ &mdio_bus_statistics_group,
+ NULL,
+};
+
+static struct class mdio_bus_class = {
+ .name = "mdio_bus",
+ .dev_release = mdiobus_release,
+ .dev_groups = mdio_bus_groups,
+};
+
+/**
+ * mdio_find_bus - Given the name of a mdiobus, find the mii_bus.
+ * @mdio_name: The name of a mdiobus.
+ *
+ * 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_deviced'ed once the bus is finished with.
+ */
+struct mii_bus *mdio_find_bus(const char *mdio_name)
+{
+ struct device *d;
+
+ d = class_find_device_by_name(&mdio_bus_class, mdio_name);
+ return d ? to_mii_bus(d) : NULL;
+}
+EXPORT_SYMBOL(mdio_find_bus);
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+/**
+ * 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_by_of_node(&mdio_bus_class, mdio_bus_np);
+ 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) {
+ device_set_node(dev, of_fwnode_handle(child));
+ /* The refcount on "child" is passed to the mdio
+ * device. Do _not_ use of_node_put(child) here.
+ */
+ 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 - 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;
+}
+
+static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)
+{
+ struct phy_device *phydev = ERR_PTR(-ENODEV);
+ int err;
+
+ phydev = get_phy_device(bus, addr, c45);
+ if (IS_ERR(phydev))
+ return phydev;
+
+ /* For DT, see if the auto-probed phy has a corresponding 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;
+}
+
+/**
+ * mdiobus_scan_c22 - scan one address on a bus for C22 MDIO devices.
+ * @bus: mii_bus to scan
+ * @addr: address on bus to scan
+ *
+ * This function scans one address on 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_c22(struct mii_bus *bus, int addr)
+{
+ return mdiobus_scan(bus, addr, false);
+}
+EXPORT_SYMBOL(mdiobus_scan_c22);
+
+/**
+ * mdiobus_scan_c45 - scan one address on a bus for C45 MDIO devices.
+ * @bus: mii_bus to scan
+ * @addr: address on bus to scan
+ *
+ * This function scans one address on 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.
+ */
+static struct phy_device *mdiobus_scan_c45(struct mii_bus *bus, int addr)
+{
+ return mdiobus_scan(bus, addr, true);
+}
+
+static int mdiobus_scan_bus_c22(struct mii_bus *bus)
+{
+ int i;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ if ((bus->phy_mask & BIT(i)) == 0) {
+ struct phy_device *phydev;
+
+ phydev = mdiobus_scan_c22(bus, i);
+ if (IS_ERR(phydev) && (PTR_ERR(phydev) != -ENODEV))
+ return PTR_ERR(phydev);
+ }
+ }
+ return 0;
+}
+
+static int mdiobus_scan_bus_c45(struct mii_bus *bus)
+{
+ int i;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ if ((bus->phy_mask & BIT(i)) == 0) {
+ struct phy_device *phydev;
+
+ /* Don't scan C45 if we already have a C22 device */
+ if (bus->mdio_map[i])
+ continue;
+
+ phydev = mdiobus_scan_c45(bus, i);
+ if (IS_ERR(phydev) && (PTR_ERR(phydev) != -ENODEV))
+ return PTR_ERR(phydev);
+ }
+ }
+ return 0;
+}
+
+/* There are some C22 PHYs which do bad things when where is a C45
+ * transaction on the bus, like accepting a read themselves, and
+ * stomping over the true devices reply, to performing a write to
+ * themselves which was intended for another device. Now that C22
+ * devices have been found, see if any of them are bad for C45, and if we
+ * should skip the C45 scan.
+ */
+static bool mdiobus_prevent_c45_scan(struct mii_bus *bus)
+{
+ int i;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ struct phy_device *phydev;
+ u32 oui;
+
+ phydev = mdiobus_get_phy(bus, i);
+ if (!phydev)
+ continue;
+ oui = phydev->phy_id >> 10;
+
+ if (oui == MICREL_OUI)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * __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 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;
+ struct gpio_desc *gpiod;
+ bool prevent_c45_scan;
+ int i, err;
+
+ if (!bus || !bus->name)
+ return -EINVAL;
+
+ /* An access method always needs both read and write operations */
+ if (!!bus->read != !!bus->write || !!bus->read_c45 != !!bus->write_c45)
+ return -EINVAL;
+
+ /* At least one method is mandatory */
+ if (!bus->read && !bus->read_c45)
+ return -EINVAL;
+
+ if (bus->parent && bus->parent->of_node)
+ bus->parent->of_node->fwnode.flags |=
+ FWNODE_FLAG_NEEDS_CHILD_BOUND_ON_ADD;
+
+ WARN(bus->state != MDIOBUS_ALLOCATED &&
+ bus->state != MDIOBUS_UNREGISTERED,
+ "%s: not in ALLOCATED or UNREGISTERED state\n", bus->id);
+
+ 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);
+ mutex_init(&bus->shared_lock);
+
+ /* assert bus level PHY GPIO reset */
+ gpiod = devm_gpiod_get_optional(&bus->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpiod)) {
+ err = dev_err_probe(&bus->dev, PTR_ERR(gpiod),
+ "mii_bus %s couldn't get reset GPIO\n",
+ bus->id);
+ device_del(&bus->dev);
+ return err;
+ } else if (gpiod) {
+ bus->reset_gpiod = gpiod;
+ fsleep(bus->reset_delay_us);
+ gpiod_set_value_cansleep(gpiod, 0);
+ if (bus->reset_post_delay_us > 0)
+ fsleep(bus->reset_post_delay_us);
+ }
+
+ if (bus->reset) {
+ err = bus->reset(bus);
+ if (err)
+ goto error_reset_gpiod;
+ }
+
+ if (bus->read) {
+ err = mdiobus_scan_bus_c22(bus);
+ if (err)
+ goto error;
+ }
+
+ prevent_c45_scan = mdiobus_prevent_c45_scan(bus);
+
+ if (!prevent_c45_scan && bus->read_c45) {
+ err = mdiobus_scan_bus_c45(bus);
+ if (err)
+ 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:
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ mdiodev = bus->mdio_map[i];
+ if (!mdiodev)
+ continue;
+
+ mdiodev->device_remove(mdiodev);
+ mdiodev->device_free(mdiodev);
+ }
+error_reset_gpiod:
+ /* 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_gpio)
+ gpiod_put(mdiodev->reset_gpio);
+
+ 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;
+ }
+
+ WARN(bus->state != MDIOBUS_UNREGISTERED,
+ "%s: not in UNREGISTERED state\n", bus->id);
+ bus->state = MDIOBUS_RELEASED;
+
+ put_device(&bus->dev);
+}
+EXPORT_SYMBOL(mdiobus_free);
+
+static void mdiobus_stats_acct(struct mdio_bus_stats *stats, bool op, int ret)
+{
+ preempt_disable();
+ u64_stats_update_begin(&stats->syncp);
+
+ u64_stats_inc(&stats->transfers);
+ if (ret < 0) {
+ u64_stats_inc(&stats->errors);
+ goto out;
+ }
+
+ if (op)
+ u64_stats_inc(&stats->reads);
+ else
+ u64_stats_inc(&stats->writes);
+out:
+ u64_stats_update_end(&stats->syncp);
+ preempt_enable();
+}
+
+/**
+ * __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;
+
+ lockdep_assert_held_once(&bus->mdio_lock);
+
+ if (bus->read)
+ retval = bus->read(bus, addr, regnum);
+ else
+ retval = -EOPNOTSUPP;
+
+ trace_mdio_access(bus, 1, addr, regnum, retval, retval);
+ mdiobus_stats_acct(&bus->stats[addr], true, 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;
+
+ lockdep_assert_held_once(&bus->mdio_lock);
+
+ if (bus->write)
+ err = bus->write(bus, addr, regnum, val);
+ else
+ err = -EOPNOTSUPP;
+
+ trace_mdio_access(bus, 0, addr, regnum, val, err);
+ mdiobus_stats_acct(&bus->stats[addr], false, err);
+
+ return err;
+}
+EXPORT_SYMBOL(__mdiobus_write);
+
+/**
+ * __mdiobus_modify_changed - Unlocked version of the mdiobus_modify function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * Read, modify, and if any change, write the register value back to the
+ * device. Any error returns a negative number.
+ *
+ * NOTE: MUST NOT be called from interrupt context.
+ */
+int __mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
+ u16 mask, u16 set)
+{
+ int new, ret;
+
+ ret = __mdiobus_read(bus, addr, regnum);
+ if (ret < 0)
+ return ret;
+
+ new = (ret & ~mask) | set;
+ if (new == ret)
+ return 0;
+
+ ret = __mdiobus_write(bus, addr, regnum, new);
+
+ return ret < 0 ? ret : 1;
+}
+EXPORT_SYMBOL_GPL(__mdiobus_modify_changed);
+
+/**
+ * __mdiobus_c45_read - Unlocked version of the mdiobus_c45_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @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_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
+{
+ int retval;
+
+ lockdep_assert_held_once(&bus->mdio_lock);
+
+ if (bus->read_c45)
+ retval = bus->read_c45(bus, addr, devad, regnum);
+ else
+ retval = -EOPNOTSUPP;
+
+ trace_mdio_access(bus, 1, addr, regnum, retval, retval);
+ mdiobus_stats_acct(&bus->stats[addr], true, retval);
+
+ return retval;
+}
+EXPORT_SYMBOL(__mdiobus_c45_read);
+
+/**
+ * __mdiobus_c45_write - Unlocked version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @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_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
+ u16 val)
+{
+ int err;
+
+ lockdep_assert_held_once(&bus->mdio_lock);
+
+ if (bus->write_c45)
+ err = bus->write_c45(bus, addr, devad, regnum, val);
+ else
+ err = -EOPNOTSUPP;
+
+ trace_mdio_access(bus, 0, addr, regnum, val, err);
+ mdiobus_stats_acct(&bus->stats[addr], false, err);
+
+ return err;
+}
+EXPORT_SYMBOL(__mdiobus_c45_write);
+
+/**
+ * __mdiobus_c45_modify_changed - Unlocked version of the mdiobus_modify function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * Read, modify, and if any change, write the register value back to the
+ * device. Any error returns a negative number.
+ *
+ * NOTE: MUST NOT be called from interrupt context.
+ */
+static int __mdiobus_c45_modify_changed(struct mii_bus *bus, int addr,
+ int devad, u32 regnum, u16 mask,
+ u16 set)
+{
+ int new, ret;
+
+ ret = __mdiobus_c45_read(bus, addr, devad, regnum);
+ if (ret < 0)
+ return ret;
+
+ new = (ret & ~mask) | set;
+ if (new == ret)
+ return 0;
+
+ ret = __mdiobus_c45_write(bus, addr, devad, regnum, new);
+
+ return ret < 0 ? ret : 1;
+}
+
+/**
+ * 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;
+
+ 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;
+
+ mutex_lock(&bus->mdio_lock);
+ retval = __mdiobus_read(bus, addr, regnum);
+ mutex_unlock(&bus->mdio_lock);
+
+ return retval;
+}
+EXPORT_SYMBOL(mdiobus_read);
+
+/**
+ * mdiobus_c45_read - Convenience function for reading a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @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_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
+{
+ int retval;
+
+ mutex_lock(&bus->mdio_lock);
+ retval = __mdiobus_c45_read(bus, addr, devad, regnum);
+ mutex_unlock(&bus->mdio_lock);
+
+ return retval;
+}
+EXPORT_SYMBOL(mdiobus_c45_read);
+
+/**
+ * mdiobus_c45_read_nested - Nested version of the mdiobus_c45_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @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_c45_read_nested(struct mii_bus *bus, int addr, int devad,
+ u32 regnum)
+{
+ int retval;
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+ retval = __mdiobus_c45_read(bus, addr, devad, regnum);
+ mutex_unlock(&bus->mdio_lock);
+
+ return retval;
+}
+EXPORT_SYMBOL(mdiobus_c45_read_nested);
+
+/**
+ * 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;
+
+ 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;
+
+ mutex_lock(&bus->mdio_lock);
+ err = __mdiobus_write(bus, addr, regnum, val);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err;
+}
+EXPORT_SYMBOL(mdiobus_write);
+
+/**
+ * mdiobus_c45_write - Convenience function for writing a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @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_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
+ u16 val)
+{
+ int err;
+
+ mutex_lock(&bus->mdio_lock);
+ err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err;
+}
+EXPORT_SYMBOL(mdiobus_c45_write);
+
+/**
+ * mdiobus_c45_write_nested - Nested version of the mdiobus_c45_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @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_c45_write_nested(struct mii_bus *bus, int addr, int devad,
+ u32 regnum, u16 val)
+{
+ int err;
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+ err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err;
+}
+EXPORT_SYMBOL(mdiobus_c45_write_nested);
+
+/*
+ * __mdiobus_modify - Convenience function for modifying a given mdio device
+ * register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ */
+int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
+ u16 set)
+{
+ int err;
+
+ err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
+
+ return err < 0 ? err : 0;
+}
+EXPORT_SYMBOL_GPL(__mdiobus_modify);
+
+/**
+ * mdiobus_modify - Convenience function for modifying a given mdio device
+ * register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ */
+int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set)
+{
+ int err;
+
+ mutex_lock(&bus->mdio_lock);
+ err = __mdiobus_modify(bus, addr, regnum, mask, set);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(mdiobus_modify);
+
+/**
+ * mdiobus_c45_modify - Convenience function for modifying a given mdio device
+ * register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ */
+int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
+ u16 mask, u16 set)
+{
+ int err;
+
+ mutex_lock(&bus->mdio_lock);
+ err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum,
+ mask, set);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err < 0 ? err : 0;
+}
+EXPORT_SYMBOL_GPL(mdiobus_c45_modify);
+
+/**
+ * mdiobus_modify_changed - Convenience function for modifying a given mdio
+ * device register and returning if it changed
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ */
+int mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
+ u16 mask, u16 set)
+{
+ int err;
+
+ mutex_lock(&bus->mdio_lock);
+ err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(mdiobus_modify_changed);
+
+/**
+ * mdiobus_c45_modify_changed - Convenience function for modifying a given mdio
+ * device register and returning if it changed
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @devad: device address to read
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ */
+int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad,
+ u32 regnum, u16 mask, u16 set)
+{
+ int err;
+
+ mutex_lock(&bus->mdio_lock);
+ err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum, mask, set);
+ mutex_unlock(&bus->mdio_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(mdiobus_c45_modify_changed);
+
+/**
+ * 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_driver *mdiodrv = to_mdio_driver(drv);
+ struct mdio_device *mdio = to_mdio_device(dev);
+
+ /* Both the driver and device must type-match */
+ if (!(mdiodrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY) !=
+ !(mdio->flags & MDIO_DEVICE_FLAG_PHY))
+ return 0;
+
+ 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(const 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;
+}
+
+static struct attribute *mdio_bus_device_statistics_attrs[] = {
+ &dev_attr_mdio_bus_device_transfers.attr.attr,
+ &dev_attr_mdio_bus_device_errors.attr.attr,
+ &dev_attr_mdio_bus_device_writes.attr.attr,
+ &dev_attr_mdio_bus_device_reads.attr.attr,
+ NULL,
+};
+
+static const struct attribute_group mdio_bus_device_statistics_group = {
+ .name = "statistics",
+ .attrs = mdio_bus_device_statistics_attrs,
+};
+
+static const struct attribute_group *mdio_bus_dev_groups[] = {
+ &mdio_bus_device_statistics_group,
+ NULL,
+};
+
+struct bus_type mdio_bus_type = {
+ .name = "mdio_bus",
+ .dev_groups = mdio_bus_dev_groups,
+ .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..044828d08
--- /dev/null
+++ b/drivers/net/phy/mdio_device.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Framework for MDIO devices, other than PHYs.
+ *
+ * Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#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/reset.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/unistd.h>
+#include <linux/property.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)
+{
+ fwnode_handle_put(dev->fwnode);
+ 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, "%s\n", __func__);
+
+ 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_gpio && !mdiodev->reset_ctrl)
+ return;
+
+ if (mdiodev->reset_gpio)
+ gpiod_set_value_cansleep(mdiodev->reset_gpio, value);
+
+ if (mdiodev->reset_ctrl) {
+ if (value)
+ reset_control_assert(mdiodev->reset_ctrl);
+ else
+ reset_control_deassert(mdiodev->reset_ctrl);
+ }
+
+ d = value ? mdiodev->reset_assert_delay : mdiodev->reset_deassert_delay;
+ if (d)
+ fsleep(d);
+}
+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;
+
+ /* Deassert the reset signal */
+ mdio_device_reset(mdiodev, 0);
+
+ if (mdiodrv->probe) {
+ 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
+ * @drv: new mdio_driver to register
+ */
+int mdio_driver_register(struct mdio_driver *drv)
+{
+ struct mdio_driver_common *mdiodrv = &drv->mdiodrv;
+ int retval;
+
+ pr_debug("%s: %s\n", __func__, 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/mdio_devres.c b/drivers/net/phy/mdio_devres.c
new file mode 100644
index 000000000..69b829e6a
--- /dev/null
+++ b/drivers/net/phy/mdio_devres.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/device.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/stddef.h>
+
+struct mdiobus_devres {
+ struct mii_bus *mii;
+};
+
+static void devm_mdiobus_free(struct device *dev, void *this)
+{
+ struct mdiobus_devres *dr = this;
+
+ mdiobus_free(dr->mii);
+}
+
+/**
+ * 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.
+ *
+ * RETURNS:
+ * Pointer to allocated mii_bus on success, NULL on out-of-memory error.
+ */
+struct mii_bus *devm_mdiobus_alloc_size(struct device *dev, int sizeof_priv)
+{
+ struct mdiobus_devres *dr;
+
+ dr = devres_alloc(devm_mdiobus_free, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return NULL;
+
+ dr->mii = mdiobus_alloc_size(sizeof_priv);
+ if (!dr->mii) {
+ devres_free(dr);
+ return NULL;
+ }
+
+ devres_add(dev, dr);
+ return dr->mii;
+}
+EXPORT_SYMBOL(devm_mdiobus_alloc_size);
+
+static void devm_mdiobus_unregister(struct device *dev, void *this)
+{
+ struct mdiobus_devres *dr = this;
+
+ mdiobus_unregister(dr->mii);
+}
+
+static int mdiobus_devres_match(struct device *dev,
+ void *this, void *match_data)
+{
+ struct mdiobus_devres *res = this;
+ struct mii_bus *mii = match_data;
+
+ return mii == res->mii;
+}
+
+/**
+ * __devm_mdiobus_register - Resource-managed variant of mdiobus_register()
+ * @dev: Device to register mii_bus for
+ * @bus: MII bus structure to register
+ * @owner: Owning module
+ *
+ * Returns 0 on success, negative error number on failure.
+ */
+int __devm_mdiobus_register(struct device *dev, struct mii_bus *bus,
+ struct module *owner)
+{
+ struct mdiobus_devres *dr;
+ int ret;
+
+ if (WARN_ON(!devres_find(dev, devm_mdiobus_free,
+ mdiobus_devres_match, bus)))
+ return -EINVAL;
+
+ dr = devres_alloc(devm_mdiobus_unregister, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ ret = __mdiobus_register(bus, owner);
+ if (ret) {
+ devres_free(dr);
+ return ret;
+ }
+
+ dr->mii = bus;
+ devres_add(dev, dr);
+ return 0;
+}
+EXPORT_SYMBOL(__devm_mdiobus_register);
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+/**
+ * __devm_of_mdiobus_register - Resource managed variant of of_mdiobus_register()
+ * @dev: Device to register mii_bus for
+ * @mdio: MII bus structure to register
+ * @np: Device node to parse
+ * @owner: Owning module
+ */
+int __devm_of_mdiobus_register(struct device *dev, struct mii_bus *mdio,
+ struct device_node *np, struct module *owner)
+{
+ struct mdiobus_devres *dr;
+ int ret;
+
+ if (WARN_ON(!devres_find(dev, devm_mdiobus_free,
+ mdiobus_devres_match, mdio)))
+ return -EINVAL;
+
+ dr = devres_alloc(devm_mdiobus_unregister, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ ret = __of_mdiobus_register(mdio, np, owner);
+ if (ret) {
+ devres_free(dr);
+ return ret;
+ }
+
+ dr->mii = mdio;
+ devres_add(dev, dr);
+ return 0;
+}
+EXPORT_SYMBOL(__devm_of_mdiobus_register);
+#endif /* CONFIG_OF_MDIO */
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/mediatek-ge-soc.c b/drivers/net/phy/mediatek-ge-soc.c
new file mode 100644
index 000000000..8a20d9889
--- /dev/null
+++ b/drivers/net/phy/mediatek-ge-soc.c
@@ -0,0 +1,1531 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+
+#define MTK_GPHY_ID_MT7981 0x03a29461
+#define MTK_GPHY_ID_MT7988 0x03a29481
+
+#define MTK_EXT_PAGE_ACCESS 0x1f
+#define MTK_PHY_PAGE_STANDARD 0x0000
+#define MTK_PHY_PAGE_EXTENDED_3 0x0003
+
+#define MTK_PHY_LPI_REG_14 0x14
+#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK GENMASK(8, 0)
+
+#define MTK_PHY_LPI_REG_1c 0x1c
+#define MTK_PHY_SMI_DET_ON_THRESH_MASK GENMASK(13, 8)
+
+#define MTK_PHY_PAGE_EXTENDED_2A30 0x2a30
+#define MTK_PHY_PAGE_EXTENDED_52B5 0x52b5
+
+#define ANALOG_INTERNAL_OPERATION_MAX_US 20
+#define TXRESERVE_MIN 0
+#define TXRESERVE_MAX 7
+
+#define MTK_PHY_ANARG_RG 0x10
+#define MTK_PHY_TCLKOFFSET_MASK GENMASK(12, 8)
+
+/* Registers on MDIO_MMD_VEND1 */
+#define MTK_PHY_TXVLD_DA_RG 0x12
+#define MTK_PHY_DA_TX_I2MPB_A_GBE_MASK GENMASK(15, 10)
+#define MTK_PHY_DA_TX_I2MPB_A_TBT_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_A2 0x16
+#define MTK_PHY_DA_TX_I2MPB_A_HBT_MASK GENMASK(15, 10)
+#define MTK_PHY_DA_TX_I2MPB_A_TST_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_B1 0x17
+#define MTK_PHY_DA_TX_I2MPB_B_GBE_MASK GENMASK(13, 8)
+#define MTK_PHY_DA_TX_I2MPB_B_TBT_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_B2 0x18
+#define MTK_PHY_DA_TX_I2MPB_B_HBT_MASK GENMASK(13, 8)
+#define MTK_PHY_DA_TX_I2MPB_B_TST_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_C1 0x19
+#define MTK_PHY_DA_TX_I2MPB_C_GBE_MASK GENMASK(13, 8)
+#define MTK_PHY_DA_TX_I2MPB_C_TBT_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_C2 0x20
+#define MTK_PHY_DA_TX_I2MPB_C_HBT_MASK GENMASK(13, 8)
+#define MTK_PHY_DA_TX_I2MPB_C_TST_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_D1 0x21
+#define MTK_PHY_DA_TX_I2MPB_D_GBE_MASK GENMASK(13, 8)
+#define MTK_PHY_DA_TX_I2MPB_D_TBT_MASK GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_D2 0x22
+#define MTK_PHY_DA_TX_I2MPB_D_HBT_MASK GENMASK(13, 8)
+#define MTK_PHY_DA_TX_I2MPB_D_TST_MASK GENMASK(5, 0)
+
+#define MTK_PHY_RXADC_CTRL_RG7 0xc6
+#define MTK_PHY_DA_AD_BUF_BIAS_LP_MASK GENMASK(9, 8)
+
+#define MTK_PHY_RXADC_CTRL_RG9 0xc8
+#define MTK_PHY_DA_RX_PSBN_TBT_MASK GENMASK(14, 12)
+#define MTK_PHY_DA_RX_PSBN_HBT_MASK GENMASK(10, 8)
+#define MTK_PHY_DA_RX_PSBN_GBE_MASK GENMASK(6, 4)
+#define MTK_PHY_DA_RX_PSBN_LP_MASK GENMASK(2, 0)
+
+#define MTK_PHY_LDO_OUTPUT_V 0xd7
+
+#define MTK_PHY_RG_ANA_CAL_RG0 0xdb
+#define MTK_PHY_RG_CAL_CKINV BIT(12)
+#define MTK_PHY_RG_ANA_CALEN BIT(8)
+#define MTK_PHY_RG_ZCALEN_A BIT(0)
+
+#define MTK_PHY_RG_ANA_CAL_RG1 0xdc
+#define MTK_PHY_RG_ZCALEN_B BIT(12)
+#define MTK_PHY_RG_ZCALEN_C BIT(8)
+#define MTK_PHY_RG_ZCALEN_D BIT(4)
+#define MTK_PHY_RG_TXVOS_CALEN BIT(0)
+
+#define MTK_PHY_RG_ANA_CAL_RG5 0xe0
+#define MTK_PHY_RG_REXT_TRIM_MASK GENMASK(13, 8)
+
+#define MTK_PHY_RG_TX_FILTER 0xfe
+
+#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120 0x120
+#define MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK GENMASK(12, 8)
+#define MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK GENMASK(4, 0)
+
+#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122 0x122
+#define MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK GENMASK(7, 0)
+
+#define MTK_PHY_RG_TESTMUX_ADC_CTRL 0x144
+#define MTK_PHY_RG_TXEN_DIG_MASK GENMASK(5, 5)
+
+#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B 0x172
+#define MTK_PHY_CR_TX_AMP_OFFSET_A_MASK GENMASK(13, 8)
+#define MTK_PHY_CR_TX_AMP_OFFSET_B_MASK GENMASK(6, 0)
+
+#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D 0x173
+#define MTK_PHY_CR_TX_AMP_OFFSET_C_MASK GENMASK(13, 8)
+#define MTK_PHY_CR_TX_AMP_OFFSET_D_MASK GENMASK(6, 0)
+
+#define MTK_PHY_RG_AD_CAL_COMP 0x17a
+#define MTK_PHY_AD_CAL_COMP_OUT_SHIFT (8)
+
+#define MTK_PHY_RG_AD_CAL_CLK 0x17b
+#define MTK_PHY_DA_CAL_CLK BIT(0)
+
+#define MTK_PHY_RG_AD_CALIN 0x17c
+#define MTK_PHY_DA_CALIN_FLAG BIT(0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_A 0x17d
+#define MTK_PHY_DASN_DAC_IN0_A_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_B 0x17e
+#define MTK_PHY_DASN_DAC_IN0_B_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_C 0x17f
+#define MTK_PHY_DASN_DAC_IN0_C_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_D 0x180
+#define MTK_PHY_DASN_DAC_IN0_D_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_A 0x181
+#define MTK_PHY_DASN_DAC_IN1_A_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_B 0x182
+#define MTK_PHY_DASN_DAC_IN1_B_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_C 0x183
+#define MTK_PHY_DASN_DAC_IN1_C_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_D 0x184
+#define MTK_PHY_DASN_DAC_IN1_D_MASK GENMASK(9, 0)
+
+#define MTK_PHY_RG_DEV1E_REG19b 0x19b
+#define MTK_PHY_BYPASS_DSP_LPI_READY BIT(8)
+
+#define MTK_PHY_RG_LP_IIR2_K1_L 0x22a
+#define MTK_PHY_RG_LP_IIR2_K1_U 0x22b
+#define MTK_PHY_RG_LP_IIR2_K2_L 0x22c
+#define MTK_PHY_RG_LP_IIR2_K2_U 0x22d
+#define MTK_PHY_RG_LP_IIR2_K3_L 0x22e
+#define MTK_PHY_RG_LP_IIR2_K3_U 0x22f
+#define MTK_PHY_RG_LP_IIR2_K4_L 0x230
+#define MTK_PHY_RG_LP_IIR2_K4_U 0x231
+#define MTK_PHY_RG_LP_IIR2_K5_L 0x232
+#define MTK_PHY_RG_LP_IIR2_K5_U 0x233
+
+#define MTK_PHY_RG_DEV1E_REG234 0x234
+#define MTK_PHY_TR_OPEN_LOOP_EN_MASK GENMASK(0, 0)
+#define MTK_PHY_LPF_X_AVERAGE_MASK GENMASK(7, 4)
+#define MTK_PHY_TR_LP_IIR_EEE_EN BIT(12)
+
+#define MTK_PHY_RG_LPF_CNT_VAL 0x235
+
+#define MTK_PHY_RG_DEV1E_REG238 0x238
+#define MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK GENMASK(8, 0)
+#define MTK_PHY_LPI_SLV_SEND_TX_EN BIT(12)
+
+#define MTK_PHY_RG_DEV1E_REG239 0x239
+#define MTK_PHY_LPI_SEND_LOC_TIMER_MASK GENMASK(8, 0)
+#define MTK_PHY_LPI_TXPCS_LOC_RCV BIT(12)
+
+#define MTK_PHY_RG_DEV1E_REG27C 0x27c
+#define MTK_PHY_VGASTATE_FFE_THR_ST1_MASK GENMASK(12, 8)
+#define MTK_PHY_RG_DEV1E_REG27D 0x27d
+#define MTK_PHY_VGASTATE_FFE_THR_ST2_MASK GENMASK(4, 0)
+
+#define MTK_PHY_RG_DEV1E_REG2C7 0x2c7
+#define MTK_PHY_MAX_GAIN_MASK GENMASK(4, 0)
+#define MTK_PHY_MIN_GAIN_MASK GENMASK(12, 8)
+
+#define MTK_PHY_RG_DEV1E_REG2D1 0x2d1
+#define MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK GENMASK(7, 0)
+#define MTK_PHY_LPI_SKIP_SD_SLV_TR BIT(8)
+#define MTK_PHY_LPI_TR_READY BIT(9)
+#define MTK_PHY_LPI_VCO_EEE_STG0_EN BIT(10)
+
+#define MTK_PHY_RG_DEV1E_REG323 0x323
+#define MTK_PHY_EEE_WAKE_MAS_INT_DC BIT(0)
+#define MTK_PHY_EEE_WAKE_SLV_INT_DC BIT(4)
+
+#define MTK_PHY_RG_DEV1E_REG324 0x324
+#define MTK_PHY_SMI_DETCNT_MAX_MASK GENMASK(5, 0)
+#define MTK_PHY_SMI_DET_MAX_EN BIT(8)
+
+#define MTK_PHY_RG_DEV1E_REG326 0x326
+#define MTK_PHY_LPI_MODE_SD_ON BIT(0)
+#define MTK_PHY_RESET_RANDUPD_CNT BIT(1)
+#define MTK_PHY_TREC_UPDATE_ENAB_CLR BIT(2)
+#define MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF BIT(4)
+#define MTK_PHY_TR_READY_SKIP_AFE_WAKEUP BIT(5)
+
+#define MTK_PHY_LDO_PUMP_EN_PAIRAB 0x502
+#define MTK_PHY_LDO_PUMP_EN_PAIRCD 0x503
+
+#define MTK_PHY_DA_TX_R50_PAIR_A 0x53d
+#define MTK_PHY_DA_TX_R50_PAIR_B 0x53e
+#define MTK_PHY_DA_TX_R50_PAIR_C 0x53f
+#define MTK_PHY_DA_TX_R50_PAIR_D 0x540
+
+/* Registers on MDIO_MMD_VEND2 */
+#define MTK_PHY_LED0_ON_CTRL 0x24
+#define MTK_PHY_LED1_ON_CTRL 0x26
+#define MTK_PHY_LED_ON_MASK GENMASK(6, 0)
+#define MTK_PHY_LED_ON_LINK1000 BIT(0)
+#define MTK_PHY_LED_ON_LINK100 BIT(1)
+#define MTK_PHY_LED_ON_LINK10 BIT(2)
+#define MTK_PHY_LED_ON_LINKDOWN BIT(3)
+#define MTK_PHY_LED_ON_FDX BIT(4) /* Full duplex */
+#define MTK_PHY_LED_ON_HDX BIT(5) /* Half duplex */
+#define MTK_PHY_LED_ON_FORCE_ON BIT(6)
+#define MTK_PHY_LED_ON_POLARITY BIT(14)
+#define MTK_PHY_LED_ON_ENABLE BIT(15)
+
+#define MTK_PHY_LED0_BLINK_CTRL 0x25
+#define MTK_PHY_LED1_BLINK_CTRL 0x27
+#define MTK_PHY_LED_BLINK_1000TX BIT(0)
+#define MTK_PHY_LED_BLINK_1000RX BIT(1)
+#define MTK_PHY_LED_BLINK_100TX BIT(2)
+#define MTK_PHY_LED_BLINK_100RX BIT(3)
+#define MTK_PHY_LED_BLINK_10TX BIT(4)
+#define MTK_PHY_LED_BLINK_10RX BIT(5)
+#define MTK_PHY_LED_BLINK_COLLISION BIT(6)
+#define MTK_PHY_LED_BLINK_RX_CRC_ERR BIT(7)
+#define MTK_PHY_LED_BLINK_RX_IDLE_ERR BIT(8)
+#define MTK_PHY_LED_BLINK_FORCE_BLINK BIT(9)
+
+#define MTK_PHY_LED1_DEFAULT_POLARITIES BIT(1)
+
+#define MTK_PHY_RG_BG_RASEL 0x115
+#define MTK_PHY_RG_BG_RASEL_MASK GENMASK(2, 0)
+
+/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
+#define RG_GPIO_MISC_TPBANK0 0x6f0
+#define RG_GPIO_MISC_TPBANK0_BOOTMODE GENMASK(11, 8)
+
+/* These macro privides efuse parsing for internal phy. */
+#define EFS_DA_TX_I2MPB_A(x) (((x) >> 0) & GENMASK(5, 0))
+#define EFS_DA_TX_I2MPB_B(x) (((x) >> 6) & GENMASK(5, 0))
+#define EFS_DA_TX_I2MPB_C(x) (((x) >> 12) & GENMASK(5, 0))
+#define EFS_DA_TX_I2MPB_D(x) (((x) >> 18) & GENMASK(5, 0))
+#define EFS_DA_TX_AMP_OFFSET_A(x) (((x) >> 24) & GENMASK(5, 0))
+
+#define EFS_DA_TX_AMP_OFFSET_B(x) (((x) >> 0) & GENMASK(5, 0))
+#define EFS_DA_TX_AMP_OFFSET_C(x) (((x) >> 6) & GENMASK(5, 0))
+#define EFS_DA_TX_AMP_OFFSET_D(x) (((x) >> 12) & GENMASK(5, 0))
+#define EFS_DA_TX_R50_A(x) (((x) >> 18) & GENMASK(5, 0))
+#define EFS_DA_TX_R50_B(x) (((x) >> 24) & GENMASK(5, 0))
+
+#define EFS_DA_TX_R50_C(x) (((x) >> 0) & GENMASK(5, 0))
+#define EFS_DA_TX_R50_D(x) (((x) >> 6) & GENMASK(5, 0))
+
+#define EFS_RG_BG_RASEL(x) (((x) >> 4) & GENMASK(2, 0))
+#define EFS_RG_REXT_TRIM(x) (((x) >> 7) & GENMASK(5, 0))
+
+enum {
+ NO_PAIR,
+ PAIR_A,
+ PAIR_B,
+ PAIR_C,
+ PAIR_D,
+};
+
+enum calibration_mode {
+ EFUSE_K,
+ SW_K
+};
+
+enum CAL_ITEM {
+ REXT,
+ TX_OFFSET,
+ TX_AMP,
+ TX_R50,
+ TX_VCM
+};
+
+enum CAL_MODE {
+ EFUSE_M,
+ SW_M
+};
+
+#define MTK_PHY_LED_STATE_FORCE_ON 0
+#define MTK_PHY_LED_STATE_FORCE_BLINK 1
+#define MTK_PHY_LED_STATE_NETDEV 2
+
+struct mtk_socphy_priv {
+ unsigned long led_state;
+};
+
+struct mtk_socphy_shared {
+ u32 boottrap;
+ struct mtk_socphy_priv priv[4];
+};
+
+static int mtk_socphy_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+}
+
+static int mtk_socphy_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+}
+
+/* One calibration cycle consists of:
+ * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
+ * until AD_CAL_COMP is ready to output calibration result.
+ * 2.Wait until DA_CAL_CLK is available.
+ * 3.Fetch AD_CAL_COMP_OUT.
+ */
+static int cal_cycle(struct phy_device *phydev, int devad,
+ u32 regnum, u16 mask, u16 cal_val)
+{
+ int reg_val;
+ int ret;
+
+ phy_modify_mmd(phydev, devad, regnum,
+ mask, cal_val);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
+ MTK_PHY_DA_CALIN_FLAG);
+
+ ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_AD_CAL_CLK, reg_val,
+ reg_val & MTK_PHY_DA_CAL_CLK, 500,
+ ANALOG_INTERNAL_OPERATION_MAX_US, false);
+ if (ret) {
+ phydev_err(phydev, "Calibration cycle timeout\n");
+ return ret;
+ }
+
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
+ MTK_PHY_DA_CALIN_FLAG);
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP) >>
+ MTK_PHY_AD_CAL_COMP_OUT_SHIFT;
+ phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
+
+ return ret;
+}
+
+static int rext_fill_result(struct phy_device *phydev, u16 *buf)
+{
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+ MTK_PHY_RG_REXT_TRIM_MASK, buf[0] << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL,
+ MTK_PHY_RG_BG_RASEL_MASK, buf[1]);
+
+ return 0;
+}
+
+static int rext_cal_efuse(struct phy_device *phydev, u32 *buf)
+{
+ u16 rext_cal_val[2];
+
+ rext_cal_val[0] = EFS_RG_REXT_TRIM(buf[3]);
+ rext_cal_val[1] = EFS_RG_BG_RASEL(buf[3]);
+ rext_fill_result(phydev, rext_cal_val);
+
+ return 0;
+}
+
+static int tx_offset_fill_result(struct phy_device *phydev, u16 *buf)
+{
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B,
+ MTK_PHY_CR_TX_AMP_OFFSET_A_MASK, buf[0] << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B,
+ MTK_PHY_CR_TX_AMP_OFFSET_B_MASK, buf[1]);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D,
+ MTK_PHY_CR_TX_AMP_OFFSET_C_MASK, buf[2] << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D,
+ MTK_PHY_CR_TX_AMP_OFFSET_D_MASK, buf[3]);
+
+ return 0;
+}
+
+static int tx_offset_cal_efuse(struct phy_device *phydev, u32 *buf)
+{
+ u16 tx_offset_cal_val[4];
+
+ tx_offset_cal_val[0] = EFS_DA_TX_AMP_OFFSET_A(buf[0]);
+ tx_offset_cal_val[1] = EFS_DA_TX_AMP_OFFSET_B(buf[1]);
+ tx_offset_cal_val[2] = EFS_DA_TX_AMP_OFFSET_C(buf[1]);
+ tx_offset_cal_val[3] = EFS_DA_TX_AMP_OFFSET_D(buf[1]);
+
+ tx_offset_fill_result(phydev, tx_offset_cal_val);
+
+ return 0;
+}
+
+static int tx_amp_fill_result(struct phy_device *phydev, u16 *buf)
+{
+ int i;
+ int bias[16] = {};
+ const int vals_9461[16] = { 7, 1, 4, 7,
+ 7, 1, 4, 7,
+ 7, 1, 4, 7,
+ 7, 1, 4, 7 };
+ const int vals_9481[16] = { 10, 6, 6, 10,
+ 10, 6, 6, 10,
+ 10, 6, 6, 10,
+ 10, 6, 6, 10 };
+ switch (phydev->drv->phy_id) {
+ case MTK_GPHY_ID_MT7981:
+ /* We add some calibration to efuse values
+ * due to board level influence.
+ * GBE: +7, TBT: +1, HBT: +4, TST: +7
+ */
+ memcpy(bias, (const void *)vals_9461, sizeof(bias));
+ break;
+ case MTK_GPHY_ID_MT7988:
+ memcpy(bias, (const void *)vals_9481, sizeof(bias));
+ break;
+ }
+
+ /* Prevent overflow */
+ for (i = 0; i < 12; i++) {
+ if (buf[i >> 2] + bias[i] > 63) {
+ buf[i >> 2] = 63;
+ bias[i] = 0;
+ }
+ }
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
+ MTK_PHY_DA_TX_I2MPB_A_GBE_MASK, (buf[0] + bias[0]) << 10);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG,
+ MTK_PHY_DA_TX_I2MPB_A_TBT_MASK, buf[0] + bias[1]);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
+ MTK_PHY_DA_TX_I2MPB_A_HBT_MASK, (buf[0] + bias[2]) << 10);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2,
+ MTK_PHY_DA_TX_I2MPB_A_TST_MASK, buf[0] + bias[3]);
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
+ MTK_PHY_DA_TX_I2MPB_B_GBE_MASK, (buf[1] + bias[4]) << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1,
+ MTK_PHY_DA_TX_I2MPB_B_TBT_MASK, buf[1] + bias[5]);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
+ MTK_PHY_DA_TX_I2MPB_B_HBT_MASK, (buf[1] + bias[6]) << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2,
+ MTK_PHY_DA_TX_I2MPB_B_TST_MASK, buf[1] + bias[7]);
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
+ MTK_PHY_DA_TX_I2MPB_C_GBE_MASK, (buf[2] + bias[8]) << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1,
+ MTK_PHY_DA_TX_I2MPB_C_TBT_MASK, buf[2] + bias[9]);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
+ MTK_PHY_DA_TX_I2MPB_C_HBT_MASK, (buf[2] + bias[10]) << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2,
+ MTK_PHY_DA_TX_I2MPB_C_TST_MASK, buf[2] + bias[11]);
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
+ MTK_PHY_DA_TX_I2MPB_D_GBE_MASK, (buf[3] + bias[12]) << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1,
+ MTK_PHY_DA_TX_I2MPB_D_TBT_MASK, buf[3] + bias[13]);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
+ MTK_PHY_DA_TX_I2MPB_D_HBT_MASK, (buf[3] + bias[14]) << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2,
+ MTK_PHY_DA_TX_I2MPB_D_TST_MASK, buf[3] + bias[15]);
+
+ return 0;
+}
+
+static int tx_amp_cal_efuse(struct phy_device *phydev, u32 *buf)
+{
+ u16 tx_amp_cal_val[4];
+
+ tx_amp_cal_val[0] = EFS_DA_TX_I2MPB_A(buf[0]);
+ tx_amp_cal_val[1] = EFS_DA_TX_I2MPB_B(buf[0]);
+ tx_amp_cal_val[2] = EFS_DA_TX_I2MPB_C(buf[0]);
+ tx_amp_cal_val[3] = EFS_DA_TX_I2MPB_D(buf[0]);
+ tx_amp_fill_result(phydev, tx_amp_cal_val);
+
+ return 0;
+}
+
+static int tx_r50_fill_result(struct phy_device *phydev, u16 tx_r50_cal_val,
+ u8 txg_calen_x)
+{
+ int bias = 0;
+ u16 reg, val;
+
+ if (phydev->drv->phy_id == MTK_GPHY_ID_MT7988)
+ bias = -2;
+
+ val = clamp_val(bias + tx_r50_cal_val, 0, 63);
+
+ switch (txg_calen_x) {
+ case PAIR_A:
+ reg = MTK_PHY_DA_TX_R50_PAIR_A;
+ break;
+ case PAIR_B:
+ reg = MTK_PHY_DA_TX_R50_PAIR_B;
+ break;
+ case PAIR_C:
+ reg = MTK_PHY_DA_TX_R50_PAIR_C;
+ break;
+ case PAIR_D:
+ reg = MTK_PHY_DA_TX_R50_PAIR_D;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, val | val << 8);
+
+ return 0;
+}
+
+static int tx_r50_cal_efuse(struct phy_device *phydev, u32 *buf,
+ u8 txg_calen_x)
+{
+ u16 tx_r50_cal_val;
+
+ switch (txg_calen_x) {
+ case PAIR_A:
+ tx_r50_cal_val = EFS_DA_TX_R50_A(buf[1]);
+ break;
+ case PAIR_B:
+ tx_r50_cal_val = EFS_DA_TX_R50_B(buf[1]);
+ break;
+ case PAIR_C:
+ tx_r50_cal_val = EFS_DA_TX_R50_C(buf[2]);
+ break;
+ case PAIR_D:
+ tx_r50_cal_val = EFS_DA_TX_R50_D(buf[2]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ tx_r50_fill_result(phydev, tx_r50_cal_val, txg_calen_x);
+
+ return 0;
+}
+
+static int tx_vcm_cal_sw(struct phy_device *phydev, u8 rg_txreserve_x)
+{
+ u8 lower_idx, upper_idx, txreserve_val;
+ u8 lower_ret, upper_ret;
+ int ret;
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+ MTK_PHY_RG_ANA_CALEN);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+ MTK_PHY_RG_CAL_CKINV);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+ MTK_PHY_RG_TXVOS_CALEN);
+
+ switch (rg_txreserve_x) {
+ case PAIR_A:
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN0_A,
+ MTK_PHY_DASN_DAC_IN0_A_MASK);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN1_A,
+ MTK_PHY_DASN_DAC_IN1_A_MASK);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_ANA_CAL_RG0,
+ MTK_PHY_RG_ZCALEN_A);
+ break;
+ case PAIR_B:
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN0_B,
+ MTK_PHY_DASN_DAC_IN0_B_MASK);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN1_B,
+ MTK_PHY_DASN_DAC_IN1_B_MASK);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_ANA_CAL_RG1,
+ MTK_PHY_RG_ZCALEN_B);
+ break;
+ case PAIR_C:
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN0_C,
+ MTK_PHY_DASN_DAC_IN0_C_MASK);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN1_C,
+ MTK_PHY_DASN_DAC_IN1_C_MASK);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_ANA_CAL_RG1,
+ MTK_PHY_RG_ZCALEN_C);
+ break;
+ case PAIR_D:
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN0_D,
+ MTK_PHY_DASN_DAC_IN0_D_MASK);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DASN_DAC_IN1_D,
+ MTK_PHY_DASN_DAC_IN1_D_MASK);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_ANA_CAL_RG1,
+ MTK_PHY_RG_ZCALEN_D);
+ break;
+ default:
+ ret = -EINVAL;
+ goto restore;
+ }
+
+ lower_idx = TXRESERVE_MIN;
+ upper_idx = TXRESERVE_MAX;
+
+ phydev_dbg(phydev, "Start TX-VCM SW cal.\n");
+ while ((upper_idx - lower_idx) > 1) {
+ txreserve_val = DIV_ROUND_CLOSEST(lower_idx + upper_idx, 2);
+ ret = cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+ MTK_PHY_DA_RX_PSBN_TBT_MASK |
+ MTK_PHY_DA_RX_PSBN_HBT_MASK |
+ MTK_PHY_DA_RX_PSBN_GBE_MASK |
+ MTK_PHY_DA_RX_PSBN_LP_MASK,
+ txreserve_val << 12 | txreserve_val << 8 |
+ txreserve_val << 4 | txreserve_val);
+ if (ret == 1) {
+ upper_idx = txreserve_val;
+ upper_ret = ret;
+ } else if (ret == 0) {
+ lower_idx = txreserve_val;
+ lower_ret = ret;
+ } else {
+ goto restore;
+ }
+ }
+
+ if (lower_idx == TXRESERVE_MIN) {
+ lower_ret = cal_cycle(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RXADC_CTRL_RG9,
+ MTK_PHY_DA_RX_PSBN_TBT_MASK |
+ MTK_PHY_DA_RX_PSBN_HBT_MASK |
+ MTK_PHY_DA_RX_PSBN_GBE_MASK |
+ MTK_PHY_DA_RX_PSBN_LP_MASK,
+ lower_idx << 12 | lower_idx << 8 |
+ lower_idx << 4 | lower_idx);
+ ret = lower_ret;
+ } else if (upper_idx == TXRESERVE_MAX) {
+ upper_ret = cal_cycle(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RXADC_CTRL_RG9,
+ MTK_PHY_DA_RX_PSBN_TBT_MASK |
+ MTK_PHY_DA_RX_PSBN_HBT_MASK |
+ MTK_PHY_DA_RX_PSBN_GBE_MASK |
+ MTK_PHY_DA_RX_PSBN_LP_MASK,
+ upper_idx << 12 | upper_idx << 8 |
+ upper_idx << 4 | upper_idx);
+ ret = upper_ret;
+ }
+ if (ret < 0)
+ goto restore;
+
+ /* We calibrate TX-VCM in different logic. Check upper index and then
+ * lower index. If this calibration is valid, apply lower index's result.
+ */
+ ret = upper_ret - lower_ret;
+ if (ret == 1) {
+ ret = 0;
+ /* Make sure we use upper_idx in our calibration system */
+ cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+ MTK_PHY_DA_RX_PSBN_TBT_MASK |
+ MTK_PHY_DA_RX_PSBN_HBT_MASK |
+ MTK_PHY_DA_RX_PSBN_GBE_MASK |
+ MTK_PHY_DA_RX_PSBN_LP_MASK,
+ upper_idx << 12 | upper_idx << 8 |
+ upper_idx << 4 | upper_idx);
+ phydev_dbg(phydev, "TX-VCM SW cal result: 0x%x\n", upper_idx);
+ } else if (lower_idx == TXRESERVE_MIN && upper_ret == 1 &&
+ lower_ret == 1) {
+ ret = 0;
+ cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+ MTK_PHY_DA_RX_PSBN_TBT_MASK |
+ MTK_PHY_DA_RX_PSBN_HBT_MASK |
+ MTK_PHY_DA_RX_PSBN_GBE_MASK |
+ MTK_PHY_DA_RX_PSBN_LP_MASK,
+ lower_idx << 12 | lower_idx << 8 |
+ lower_idx << 4 | lower_idx);
+ phydev_warn(phydev, "TX-VCM SW cal result at low margin 0x%x\n",
+ lower_idx);
+ } else if (upper_idx == TXRESERVE_MAX && upper_ret == 0 &&
+ lower_ret == 0) {
+ ret = 0;
+ phydev_warn(phydev, "TX-VCM SW cal result at high margin 0x%x\n",
+ upper_idx);
+ } else {
+ ret = -EINVAL;
+ }
+
+restore:
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+ MTK_PHY_RG_ANA_CALEN);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+ MTK_PHY_RG_TXVOS_CALEN);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+ MTK_PHY_RG_ZCALEN_A);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+ MTK_PHY_RG_ZCALEN_B | MTK_PHY_RG_ZCALEN_C |
+ MTK_PHY_RG_ZCALEN_D);
+
+ return ret;
+}
+
+static void mt798x_phy_common_finetune(struct phy_device *phydev)
+{
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+ /* EnabRandUpdTrig = 1 */
+ __phy_write(phydev, 0x11, 0x2f00);
+ __phy_write(phydev, 0x12, 0xe);
+ __phy_write(phydev, 0x10, 0x8fb0);
+
+ /* NormMseLoThresh = 85 */
+ __phy_write(phydev, 0x11, 0x55a0);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x83aa);
+
+ /* TrFreeze = 0 */
+ __phy_write(phydev, 0x11, 0x0);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x9686);
+
+ /* SSTrKp1000Slv = 5 */
+ __phy_write(phydev, 0x11, 0xbaef);
+ __phy_write(phydev, 0x12, 0x2e);
+ __phy_write(phydev, 0x10, 0x968c);
+
+ /* MrvlTrFix100Kp = 3, MrvlTrFix100Kf = 2,
+ * MrvlTrFix1000Kp = 3, MrvlTrFix1000Kf = 2
+ */
+ __phy_write(phydev, 0x11, 0xd10a);
+ __phy_write(phydev, 0x12, 0x34);
+ __phy_write(phydev, 0x10, 0x8f82);
+
+ /* VcoSlicerThreshBitsHigh */
+ __phy_write(phydev, 0x11, 0x5555);
+ __phy_write(phydev, 0x12, 0x55);
+ __phy_write(phydev, 0x10, 0x8ec0);
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+ /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 9*/
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234,
+ MTK_PHY_TR_OPEN_LOOP_EN_MASK | MTK_PHY_LPF_X_AVERAGE_MASK,
+ BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0x9));
+
+ /* rg_tr_lpf_cnt_val = 512 */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x200);
+
+ /* IIR2 related */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_L, 0x82);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_U, 0x0);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_L, 0x103);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_U, 0x0);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_L, 0x82);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_U, 0x0);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_L, 0xd177);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_U, 0x3);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_L, 0x2c82);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_U, 0xe);
+
+ /* FFE peaking */
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27C,
+ MTK_PHY_VGASTATE_FFE_THR_ST1_MASK, 0x1b << 8);
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27D,
+ MTK_PHY_VGASTATE_FFE_THR_ST2_MASK, 0x1e);
+
+ /* Disable LDO pump */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRAB, 0x0);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRCD, 0x0);
+ /* Adjust LDO output voltage */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_OUTPUT_V, 0x2222);
+}
+
+static void mt7981_phy_finetune(struct phy_device *phydev)
+{
+ u16 val[8] = { 0x01ce, 0x01c1,
+ 0x020f, 0x0202,
+ 0x03d0, 0x03c0,
+ 0x0013, 0x0005 };
+ int i, k;
+
+ /* 100M eye finetune:
+ * Keep middle level of TX MLT3 shapper as default.
+ * Only change TX MLT3 overshoot level here.
+ */
+ for (k = 0, i = 1; i < 12; i++) {
+ if (i % 3 == 0)
+ continue;
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[k++]);
+ }
+
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+ /* SlvDSPreadyTime = 24, MasDSPreadyTime = 24 */
+ __phy_write(phydev, 0x11, 0xc71);
+ __phy_write(phydev, 0x12, 0xc);
+ __phy_write(phydev, 0x10, 0x8fae);
+
+ /* ResetSyncOffset = 6 */
+ __phy_write(phydev, 0x11, 0x600);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x8fc0);
+
+ /* VgaDecRate = 1 */
+ __phy_write(phydev, 0x11, 0x4c2a);
+ __phy_write(phydev, 0x12, 0x3e);
+ __phy_write(phydev, 0x10, 0x8fa4);
+
+ /* FfeUpdGainForce = 4 */
+ __phy_write(phydev, 0x11, 0x240);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x9680);
+
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+}
+
+static void mt7988_phy_finetune(struct phy_device *phydev)
+{
+ u16 val[12] = { 0x0187, 0x01cd, 0x01c8, 0x0182,
+ 0x020d, 0x0206, 0x0384, 0x03d0,
+ 0x03c6, 0x030a, 0x0011, 0x0005 };
+ int i;
+
+ /* Set default MLT3 shaper first */
+ for (i = 0; i < 12; i++)
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[i]);
+
+ /* TCT finetune */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_TX_FILTER, 0x5);
+
+ /* Disable TX power saving */
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7,
+ MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3 << 8);
+
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+
+ /* SlvDSPreadyTime = 24, MasDSPreadyTime = 12 */
+ __phy_write(phydev, 0x11, 0x671);
+ __phy_write(phydev, 0x12, 0xc);
+ __phy_write(phydev, 0x10, 0x8fae);
+
+ /* ResetSyncOffset = 5 */
+ __phy_write(phydev, 0x11, 0x500);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x8fc0);
+
+ /* VgaDecRate is 1 at default on mt7988 */
+
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_2A30);
+ /* TxClkOffset = 2 */
+ __phy_modify(phydev, MTK_PHY_ANARG_RG, MTK_PHY_TCLKOFFSET_MASK,
+ FIELD_PREP(MTK_PHY_TCLKOFFSET_MASK, 0x2));
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+}
+
+static void mt798x_phy_eee(struct phy_device *phydev)
+{
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120,
+ MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK |
+ MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK,
+ FIELD_PREP(MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK, 0x0) |
+ FIELD_PREP(MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK, 0x14));
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
+ MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+ FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+ 0xff));
+
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_TESTMUX_ADC_CTRL,
+ MTK_PHY_RG_TXEN_DIG_MASK);
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DEV1E_REG19b, MTK_PHY_BYPASS_DSP_LPI_READY);
+
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_DEV1E_REG234, MTK_PHY_TR_LP_IIR_EEE_EN);
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG238,
+ MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK |
+ MTK_PHY_LPI_SLV_SEND_TX_EN,
+ FIELD_PREP(MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK, 0x120));
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG239,
+ MTK_PHY_LPI_SEND_LOC_TIMER_MASK |
+ MTK_PHY_LPI_TXPCS_LOC_RCV,
+ FIELD_PREP(MTK_PHY_LPI_SEND_LOC_TIMER_MASK, 0x117));
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2C7,
+ MTK_PHY_MAX_GAIN_MASK | MTK_PHY_MIN_GAIN_MASK,
+ FIELD_PREP(MTK_PHY_MAX_GAIN_MASK, 0x8) |
+ FIELD_PREP(MTK_PHY_MIN_GAIN_MASK, 0x13));
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2D1,
+ MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK,
+ FIELD_PREP(MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK,
+ 0x33) |
+ MTK_PHY_LPI_SKIP_SD_SLV_TR | MTK_PHY_LPI_TR_READY |
+ MTK_PHY_LPI_VCO_EEE_STG0_EN);
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG323,
+ MTK_PHY_EEE_WAKE_MAS_INT_DC |
+ MTK_PHY_EEE_WAKE_SLV_INT_DC);
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG324,
+ MTK_PHY_SMI_DETCNT_MAX_MASK,
+ FIELD_PREP(MTK_PHY_SMI_DETCNT_MAX_MASK, 0x3f) |
+ MTK_PHY_SMI_DET_MAX_EN);
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG326,
+ MTK_PHY_LPI_MODE_SD_ON | MTK_PHY_RESET_RANDUPD_CNT |
+ MTK_PHY_TREC_UPDATE_ENAB_CLR |
+ MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF |
+ MTK_PHY_TR_READY_SKIP_AFE_WAKEUP);
+
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+ /* Regsigdet_sel_1000 = 0 */
+ __phy_write(phydev, 0x11, 0xb);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x9690);
+
+ /* REG_EEE_st2TrKf1000 = 3 */
+ __phy_write(phydev, 0x11, 0x114f);
+ __phy_write(phydev, 0x12, 0x2);
+ __phy_write(phydev, 0x10, 0x969a);
+
+ /* RegEEE_slv_wake_tr_timer_tar = 6, RegEEE_slv_remtx_timer_tar = 20 */
+ __phy_write(phydev, 0x11, 0x3028);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x969e);
+
+ /* RegEEE_slv_wake_int_timer_tar = 8 */
+ __phy_write(phydev, 0x11, 0x5010);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x96a0);
+
+ /* RegEEE_trfreeze_timer2 = 586 */
+ __phy_write(phydev, 0x11, 0x24a);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x96a8);
+
+ /* RegEEE100Stg1_tar = 16 */
+ __phy_write(phydev, 0x11, 0x3210);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x96b8);
+
+ /* REGEEE_wake_slv_tr_wait_dfesigdet_en = 1 */
+ __phy_write(phydev, 0x11, 0x1463);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x96ca);
+
+ /* DfeTailEnableVgaThresh1000 = 27 */
+ __phy_write(phydev, 0x11, 0x36);
+ __phy_write(phydev, 0x12, 0x0);
+ __phy_write(phydev, 0x10, 0x8f80);
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
+ __phy_modify(phydev, MTK_PHY_LPI_REG_14, MTK_PHY_LPI_WAKE_TIMER_1000_MASK,
+ FIELD_PREP(MTK_PHY_LPI_WAKE_TIMER_1000_MASK, 0x19c));
+
+ __phy_modify(phydev, MTK_PHY_LPI_REG_1c, MTK_PHY_SMI_DET_ON_THRESH_MASK,
+ FIELD_PREP(MTK_PHY_SMI_DET_ON_THRESH_MASK, 0xc));
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122,
+ MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK,
+ FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK, 0xff));
+}
+
+static int cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item,
+ u8 start_pair, u8 end_pair)
+{
+ u8 pair_n;
+ int ret;
+
+ for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
+ /* TX_OFFSET & TX_AMP have no SW calibration. */
+ switch (cal_item) {
+ case TX_VCM:
+ ret = tx_vcm_cal_sw(phydev, pair_n);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int cal_efuse(struct phy_device *phydev, enum CAL_ITEM cal_item,
+ u8 start_pair, u8 end_pair, u32 *buf)
+{
+ u8 pair_n;
+ int ret;
+
+ for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
+ /* TX_VCM has no efuse calibration. */
+ switch (cal_item) {
+ case REXT:
+ ret = rext_cal_efuse(phydev, buf);
+ break;
+ case TX_OFFSET:
+ ret = tx_offset_cal_efuse(phydev, buf);
+ break;
+ case TX_AMP:
+ ret = tx_amp_cal_efuse(phydev, buf);
+ break;
+ case TX_R50:
+ ret = tx_r50_cal_efuse(phydev, buf, pair_n);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item,
+ enum CAL_MODE cal_mode, u8 start_pair,
+ u8 end_pair, u32 *buf)
+{
+ int ret;
+
+ switch (cal_mode) {
+ case EFUSE_M:
+ ret = cal_efuse(phydev, cal_item, start_pair,
+ end_pair, buf);
+ break;
+ case SW_M:
+ ret = cal_sw(phydev, cal_item, start_pair, end_pair);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret) {
+ phydev_err(phydev, "cal %d failed\n", cal_item);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mt798x_phy_calibration(struct phy_device *phydev)
+{
+ int ret = 0;
+ u32 *buf;
+ size_t len;
+ struct nvmem_cell *cell;
+
+ cell = nvmem_cell_get(&phydev->mdio.dev, "phy-cal-data");
+ if (IS_ERR(cell)) {
+ if (PTR_ERR(cell) == -EPROBE_DEFER)
+ return PTR_ERR(cell);
+ return 0;
+ }
+
+ buf = (u32 *)nvmem_cell_read(cell, &len);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ nvmem_cell_put(cell);
+
+ if (!buf[0] || !buf[1] || !buf[2] || !buf[3] || len < 4 * sizeof(u32)) {
+ phydev_err(phydev, "invalid efuse data\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = start_cal(phydev, REXT, EFUSE_M, NO_PAIR, NO_PAIR, buf);
+ if (ret)
+ goto out;
+ ret = start_cal(phydev, TX_OFFSET, EFUSE_M, NO_PAIR, NO_PAIR, buf);
+ if (ret)
+ goto out;
+ ret = start_cal(phydev, TX_AMP, EFUSE_M, NO_PAIR, NO_PAIR, buf);
+ if (ret)
+ goto out;
+ ret = start_cal(phydev, TX_R50, EFUSE_M, PAIR_A, PAIR_D, buf);
+ if (ret)
+ goto out;
+ ret = start_cal(phydev, TX_VCM, SW_M, PAIR_A, PAIR_A, buf);
+ if (ret)
+ goto out;
+
+out:
+ kfree(buf);
+ return ret;
+}
+
+static int mt798x_phy_config_init(struct phy_device *phydev)
+{
+ switch (phydev->drv->phy_id) {
+ case MTK_GPHY_ID_MT7981:
+ mt7981_phy_finetune(phydev);
+ break;
+ case MTK_GPHY_ID_MT7988:
+ mt7988_phy_finetune(phydev);
+ break;
+ }
+
+ mt798x_phy_common_finetune(phydev);
+ mt798x_phy_eee(phydev);
+
+ return mt798x_phy_calibration(phydev);
+}
+
+static int mt798x_phy_hw_led_on_set(struct phy_device *phydev, u8 index,
+ bool on)
+{
+ unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+ struct mtk_socphy_priv *priv = phydev->priv;
+ bool changed;
+
+ if (on)
+ changed = !test_and_set_bit(bit_on, &priv->led_state);
+ else
+ changed = !!test_and_clear_bit(bit_on, &priv->led_state);
+
+ changed |= !!test_and_clear_bit(MTK_PHY_LED_STATE_NETDEV +
+ (index ? 16 : 0), &priv->led_state);
+ if (changed)
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+ MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
+ MTK_PHY_LED_ON_MASK,
+ on ? MTK_PHY_LED_ON_FORCE_ON : 0);
+ else
+ return 0;
+}
+
+static int mt798x_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
+ bool blinking)
+{
+ unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK + (index ? 16 : 0);
+ struct mtk_socphy_priv *priv = phydev->priv;
+ bool changed;
+
+ if (blinking)
+ changed = !test_and_set_bit(bit_blink, &priv->led_state);
+ else
+ changed = !!test_and_clear_bit(bit_blink, &priv->led_state);
+
+ changed |= !!test_bit(MTK_PHY_LED_STATE_NETDEV +
+ (index ? 16 : 0), &priv->led_state);
+ if (changed)
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+ MTK_PHY_LED1_BLINK_CTRL : MTK_PHY_LED0_BLINK_CTRL,
+ blinking ? MTK_PHY_LED_BLINK_FORCE_BLINK : 0);
+ else
+ return 0;
+}
+
+static int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ bool blinking = false;
+ int err = 0;
+
+ if (index > 1)
+ return -EINVAL;
+
+ if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) {
+ blinking = true;
+ *delay_on = 50;
+ *delay_off = 50;
+ }
+
+ err = mt798x_phy_hw_led_blink_set(phydev, index, blinking);
+ if (err)
+ return err;
+
+ return mt798x_phy_hw_led_on_set(phydev, index, false);
+}
+
+static int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ int err;
+
+ err = mt798x_phy_hw_led_blink_set(phydev, index, false);
+ if (err)
+ return err;
+
+ return mt798x_phy_hw_led_on_set(phydev, index, (value != LED_OFF));
+}
+
+static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
+ BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
+ BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX));
+
+static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ if (index > 1)
+ return -EINVAL;
+
+ /* All combinations of the supported triggers are allowed */
+ if (rules & ~supported_triggers)
+ return -EOPNOTSUPP;
+
+ return 0;
+};
+
+static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ unsigned int bit_blink = MTK_PHY_LED_STATE_FORCE_BLINK + (index ? 16 : 0);
+ unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+ unsigned int bit_on = MTK_PHY_LED_STATE_FORCE_ON + (index ? 16 : 0);
+ struct mtk_socphy_priv *priv = phydev->priv;
+ int on, blink;
+
+ if (index > 1)
+ return -EINVAL;
+
+ on = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ index ? MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL);
+
+ if (on < 0)
+ return -EIO;
+
+ blink = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ index ? MTK_PHY_LED1_BLINK_CTRL :
+ MTK_PHY_LED0_BLINK_CTRL);
+ if (blink < 0)
+ return -EIO;
+
+ if ((on & (MTK_PHY_LED_ON_LINK1000 | MTK_PHY_LED_ON_LINK100 |
+ MTK_PHY_LED_ON_LINK10)) ||
+ (blink & (MTK_PHY_LED_BLINK_1000RX | MTK_PHY_LED_BLINK_100RX |
+ MTK_PHY_LED_BLINK_10RX | MTK_PHY_LED_BLINK_1000TX |
+ MTK_PHY_LED_BLINK_100TX | MTK_PHY_LED_BLINK_10TX)))
+ set_bit(bit_netdev, &priv->led_state);
+ else
+ clear_bit(bit_netdev, &priv->led_state);
+
+ if (on & MTK_PHY_LED_ON_FORCE_ON)
+ set_bit(bit_on, &priv->led_state);
+ else
+ clear_bit(bit_on, &priv->led_state);
+
+ if (blink & MTK_PHY_LED_BLINK_FORCE_BLINK)
+ set_bit(bit_blink, &priv->led_state);
+ else
+ clear_bit(bit_blink, &priv->led_state);
+
+ if (!rules)
+ return 0;
+
+ if (on & (MTK_PHY_LED_ON_LINK1000 | MTK_PHY_LED_ON_LINK100 | MTK_PHY_LED_ON_LINK10))
+ *rules |= BIT(TRIGGER_NETDEV_LINK);
+
+ if (on & MTK_PHY_LED_ON_LINK10)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_10);
+
+ if (on & MTK_PHY_LED_ON_LINK100)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_100);
+
+ if (on & MTK_PHY_LED_ON_LINK1000)
+ *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
+
+ if (on & MTK_PHY_LED_ON_FDX)
+ *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX);
+
+ if (on & MTK_PHY_LED_ON_HDX)
+ *rules |= BIT(TRIGGER_NETDEV_HALF_DUPLEX);
+
+ if (blink & (MTK_PHY_LED_BLINK_1000RX | MTK_PHY_LED_BLINK_100RX | MTK_PHY_LED_BLINK_10RX))
+ *rules |= BIT(TRIGGER_NETDEV_RX);
+
+ if (blink & (MTK_PHY_LED_BLINK_1000TX | MTK_PHY_LED_BLINK_100TX | MTK_PHY_LED_BLINK_10TX))
+ *rules |= BIT(TRIGGER_NETDEV_TX);
+
+ return 0;
+};
+
+static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ unsigned int bit_netdev = MTK_PHY_LED_STATE_NETDEV + (index ? 16 : 0);
+ struct mtk_socphy_priv *priv = phydev->priv;
+ u16 on = 0, blink = 0;
+ int ret;
+
+ if (index > 1)
+ return -EINVAL;
+
+ if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX))
+ on |= MTK_PHY_LED_ON_FDX;
+
+ if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX))
+ on |= MTK_PHY_LED_ON_HDX;
+
+ if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK)))
+ on |= MTK_PHY_LED_ON_LINK10;
+
+ if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK)))
+ on |= MTK_PHY_LED_ON_LINK100;
+
+ if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK)))
+ on |= MTK_PHY_LED_ON_LINK1000;
+
+ if (rules & BIT(TRIGGER_NETDEV_RX)) {
+ blink |= MTK_PHY_LED_BLINK_10RX |
+ MTK_PHY_LED_BLINK_100RX |
+ MTK_PHY_LED_BLINK_1000RX;
+ }
+
+ if (rules & BIT(TRIGGER_NETDEV_TX)) {
+ blink |= MTK_PHY_LED_BLINK_10TX |
+ MTK_PHY_LED_BLINK_100TX |
+ MTK_PHY_LED_BLINK_1000TX;
+ }
+
+ if (blink || on)
+ set_bit(bit_netdev, &priv->led_state);
+ else
+ clear_bit(bit_netdev, &priv->led_state);
+
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+ MTK_PHY_LED1_ON_CTRL :
+ MTK_PHY_LED0_ON_CTRL,
+ MTK_PHY_LED_ON_FDX |
+ MTK_PHY_LED_ON_HDX |
+ MTK_PHY_LED_ON_LINK10 |
+ MTK_PHY_LED_ON_LINK100 |
+ MTK_PHY_LED_ON_LINK1000,
+ on);
+
+ if (ret)
+ return ret;
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, index ?
+ MTK_PHY_LED1_BLINK_CTRL :
+ MTK_PHY_LED0_BLINK_CTRL, blink);
+};
+
+static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num)
+{
+ struct mtk_socphy_shared *priv = phydev->shared->priv;
+ u32 polarities;
+
+ if (led_num == 0)
+ polarities = ~(priv->boottrap);
+ else
+ polarities = MTK_PHY_LED1_DEFAULT_POLARITIES;
+
+ if (polarities & BIT(phydev->mdio.addr))
+ return true;
+
+ return false;
+}
+
+static int mt7988_phy_fix_leds_polarities(struct phy_device *phydev)
+{
+ struct pinctrl *pinctrl;
+ int index;
+
+ /* Setup LED polarity according to bootstrap use of LED pins */
+ for (index = 0; index < 2; ++index)
+ phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+ MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
+ MTK_PHY_LED_ON_POLARITY,
+ mt7988_phy_led_get_polarity(phydev, index) ?
+ MTK_PHY_LED_ON_POLARITY : 0);
+
+ /* Only now setup pinctrl to avoid bogus blinking */
+ pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
+ if (IS_ERR(pinctrl))
+ dev_err(&phydev->mdio.bus->dev, "Failed to setup PHY LED pinctrl\n");
+
+ return 0;
+}
+
+static int mt7988_phy_probe_shared(struct phy_device *phydev)
+{
+ struct device_node *np = dev_of_node(&phydev->mdio.bus->dev);
+ struct mtk_socphy_shared *shared = phydev->shared->priv;
+ struct regmap *regmap;
+ u32 reg;
+ int ret;
+
+ /* The LED0 of the 4 PHYs in MT7988 are wired to SoC pins LED_A, LED_B,
+ * LED_C and LED_D respectively. At the same time those pins are used to
+ * bootstrap configuration of the reference clock source (LED_A),
+ * DRAM DDRx16b x2/x1 (LED_B) and boot device (LED_C, LED_D).
+ * In practise this is done using a LED and a resistor pulling the pin
+ * either to GND or to VIO.
+ * The detected value at boot time is accessible at run-time using the
+ * TPBANK0 register located in the gpio base of the pinctrl, in order
+ * to read it here it needs to be referenced by a phandle called
+ * 'mediatek,pio' in the MDIO bus hosting the PHY.
+ * The 4 bits in TPBANK0 are kept as package shared data and are used to
+ * set LED polarity for each of the LED0.
+ */
+ regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,pio");
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ ret = regmap_read(regmap, RG_GPIO_MISC_TPBANK0, &reg);
+ if (ret)
+ return ret;
+
+ shared->boottrap = FIELD_GET(RG_GPIO_MISC_TPBANK0_BOOTMODE, reg);
+
+ return 0;
+}
+
+static void mt798x_phy_leds_state_init(struct phy_device *phydev)
+{
+ int i;
+
+ for (i = 0; i < 2; ++i)
+ mt798x_phy_led_hw_control_get(phydev, i, NULL);
+}
+
+static int mt7988_phy_probe(struct phy_device *phydev)
+{
+ struct mtk_socphy_shared *shared;
+ struct mtk_socphy_priv *priv;
+ int err;
+
+ if (phydev->mdio.addr > 3)
+ return -EINVAL;
+
+ err = devm_phy_package_join(&phydev->mdio.dev, phydev, 0,
+ sizeof(struct mtk_socphy_shared));
+ if (err)
+ return err;
+
+ if (phy_package_probe_once(phydev)) {
+ err = mt7988_phy_probe_shared(phydev);
+ if (err)
+ return err;
+ }
+
+ shared = phydev->shared->priv;
+ priv = &shared->priv[phydev->mdio.addr];
+
+ phydev->priv = priv;
+
+ mt798x_phy_leds_state_init(phydev);
+
+ err = mt7988_phy_fix_leds_polarities(phydev);
+ if (err)
+ return err;
+
+ return mt798x_phy_calibration(phydev);
+}
+
+static int mt7981_phy_probe(struct phy_device *phydev)
+{
+ struct mtk_socphy_priv *priv;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct mtk_socphy_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ mt798x_phy_leds_state_init(phydev);
+
+ return mt798x_phy_calibration(phydev);
+}
+
+static struct phy_driver mtk_socphy_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981),
+ .name = "MediaTek MT7981 PHY",
+ .config_init = mt798x_phy_config_init,
+ .config_intr = genphy_no_config_intr,
+ .handle_interrupt = genphy_handle_interrupt_no_ack,
+ .probe = mt7981_phy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = mtk_socphy_read_page,
+ .write_page = mtk_socphy_write_page,
+ .led_blink_set = mt798x_phy_led_blink_set,
+ .led_brightness_set = mt798x_phy_led_brightness_set,
+ .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+ .led_hw_control_set = mt798x_phy_led_hw_control_set,
+ .led_hw_control_get = mt798x_phy_led_hw_control_get,
+ },
+ {
+ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988),
+ .name = "MediaTek MT7988 PHY",
+ .config_init = mt798x_phy_config_init,
+ .config_intr = genphy_no_config_intr,
+ .handle_interrupt = genphy_handle_interrupt_no_ack,
+ .probe = mt7988_phy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = mtk_socphy_read_page,
+ .write_page = mtk_socphy_write_page,
+ .led_blink_set = mt798x_phy_led_blink_set,
+ .led_brightness_set = mt798x_phy_led_brightness_set,
+ .led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+ .led_hw_control_set = mt798x_phy_led_hw_control_set,
+ .led_hw_control_get = mt798x_phy_led_hw_control_get,
+ },
+};
+
+module_phy_driver(mtk_socphy_driver);
+
+static struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
+ { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
+ { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
+ { }
+};
+
+MODULE_DESCRIPTION("MediaTek SoC Gigabit Ethernet PHY driver");
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+MODULE_AUTHOR("SkyLake Huang <SkyLake.Huang@mediatek.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, mtk_socphy_tbl);
diff --git a/drivers/net/phy/mediatek-ge.c b/drivers/net/phy/mediatek-ge.c
new file mode 100644
index 000000000..a493ae01b
--- /dev/null
+++ b/drivers/net/phy/mediatek-ge.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define MTK_EXT_PAGE_ACCESS 0x1f
+#define MTK_PHY_PAGE_STANDARD 0x0000
+#define MTK_PHY_PAGE_EXTENDED 0x0001
+#define MTK_PHY_PAGE_EXTENDED_2 0x0002
+#define MTK_PHY_PAGE_EXTENDED_3 0x0003
+#define MTK_PHY_PAGE_EXTENDED_2A30 0x2a30
+#define MTK_PHY_PAGE_EXTENDED_52B5 0x52b5
+
+static int mtk_gephy_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+}
+
+static int mtk_gephy_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+}
+
+static void mtk_gephy_config_init(struct phy_device *phydev)
+{
+ /* Disable EEE */
+ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0);
+
+ /* Enable HW auto downshift */
+ phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED, 0x14, 0, BIT(4));
+
+ /* Increase SlvDPSready time */
+ phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+ __phy_write(phydev, 0x10, 0xafae);
+ __phy_write(phydev, 0x12, 0x2f);
+ __phy_write(phydev, 0x10, 0x8fae);
+ phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+ /* Adjust 100_mse_threshold */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x123, 0xffff);
+
+ /* Disable mcc */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xa6, 0x300);
+}
+
+static int mt7530_phy_config_init(struct phy_device *phydev)
+{
+ mtk_gephy_config_init(phydev);
+
+ /* Increase post_update_timer */
+ phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, 0x11, 0x4b);
+
+ return 0;
+}
+
+static int mt7531_phy_config_init(struct phy_device *phydev)
+{
+ mtk_gephy_config_init(phydev);
+
+ /* PHY link down power saving enable */
+ phy_set_bits(phydev, 0x17, BIT(4));
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0xc6, 0x300);
+
+ /* Set TX Pair delay selection */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x13, 0x404);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14, 0x404);
+
+ return 0;
+}
+
+static struct phy_driver mtk_gephy_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(0x03a29412),
+ .name = "MediaTek MT7530 PHY",
+ .config_init = mt7530_phy_config_init,
+ /* Interrupts are handled by the switch, not the PHY
+ * itself.
+ */
+ .config_intr = genphy_no_config_intr,
+ .handle_interrupt = genphy_handle_interrupt_no_ack,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = mtk_gephy_read_page,
+ .write_page = mtk_gephy_write_page,
+ },
+ {
+ PHY_ID_MATCH_EXACT(0x03a29441),
+ .name = "MediaTek MT7531 PHY",
+ .config_init = mt7531_phy_config_init,
+ /* Interrupts are handled by the switch, not the PHY
+ * itself.
+ */
+ .config_intr = genphy_no_config_intr,
+ .handle_interrupt = genphy_handle_interrupt_no_ack,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = mtk_gephy_read_page,
+ .write_page = mtk_gephy_write_page,
+ },
+};
+
+module_phy_driver(mtk_gephy_driver);
+
+static struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = {
+ { PHY_ID_MATCH_EXACT(0x03a29441) },
+ { PHY_ID_MATCH_EXACT(0x03a29412) },
+ { }
+};
+
+MODULE_DESCRIPTION("MediaTek Gigabit Ethernet PHY driver");
+MODULE_AUTHOR("DENG, Qingfang <dqfext@gmail.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, mtk_gephy_tbl);
diff --git a/drivers/net/phy/meson-gxl.c b/drivers/net/phy/meson-gxl.c
new file mode 100644
index 000000000..bb9b33b6b
--- /dev/null
+++ b/drivers/net/phy/meson-gxl.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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>
+ */
+#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>
+#include <linux/smscphy.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 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 0;
+}
+
+/* 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 struct phy_driver meson_gxl_phy[] = {
+ {
+ PHY_ID_MATCH_EXACT(0x01814400),
+ .name = "Meson GXL Internal PHY",
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_IS_INTERNAL,
+ .soft_reset = genphy_soft_reset,
+ .config_init = meson_gxl_config_init,
+ .read_status = meson_gxl_read_status,
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_mmd = genphy_read_mmd_unsupported,
+ .write_mmd = genphy_write_mmd_unsupported,
+ }, {
+ PHY_ID_MATCH_EXACT(0x01803301),
+ .name = "Meson G12A Internal PHY",
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_IS_INTERNAL,
+ .probe = smsc_phy_probe,
+ .config_init = smsc_phy_config_init,
+ .soft_reset = genphy_soft_reset,
+ .read_status = lan87xx_read_status,
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ .get_tunable = smsc_phy_get_tunable,
+ .set_tunable = smsc_phy_set_tunable,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_mmd = genphy_read_mmd_unsupported,
+ .write_mmd = genphy_write_mmd_unsupported,
+ },
+};
+
+static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
+ { PHY_ID_MATCH_VENDOR(0x01814400) },
+ { PHY_ID_MATCH_VENDOR(0x01803301) },
+ { }
+};
+
+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..27ca25bbd
--- /dev/null
+++ b/drivers/net/phy/micrel.c
@@ -0,0 +1,4926 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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>
+ *
+ * Support : Micrel Phys:
+ * Giga phys: ksz9021, ksz9031, ksz9131, lan8841, lan8814
+ * 100/10 Phys : ksz8001, ksz8721, ksz8737, ksz8041
+ * ksz8021, ksz8031, ksz8051,
+ * ksz8081, ksz8091,
+ * ksz8061,
+ * Switch : ksz8873, ksz886x
+ * ksz9477, lan8804
+ */
+
+#include <linux/bitfield.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/micrel_phy.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/ptp_clock.h>
+#include <linux/ptp_classify.h>
+#include <linux/net_tstamp.h>
+#include <linux/gpio/consumer.h>
+
+/* Operation Mode Strap Override */
+#define MII_KSZPHY_OMSO 0x16
+#define KSZPHY_OMSO_FACTORY_TEST BIT(15)
+#define KSZPHY_OMSO_B_CAST_OFF BIT(9)
+#define KSZPHY_OMSO_NAND_TREE_ON BIT(5)
+#define KSZPHY_OMSO_RMII_OVERRIDE BIT(1)
+#define KSZPHY_OMSO_MII_OVERRIDE BIT(0)
+
+/* general Interrupt control/status reg in vendor specific block. */
+#define MII_KSZPHY_INTCS 0x1B
+#define KSZPHY_INTCS_JABBER BIT(15)
+#define KSZPHY_INTCS_RECEIVE_ERR BIT(14)
+#define KSZPHY_INTCS_PAGE_RECEIVE BIT(13)
+#define KSZPHY_INTCS_PARELLEL BIT(12)
+#define KSZPHY_INTCS_LINK_PARTNER_ACK BIT(11)
+#define KSZPHY_INTCS_LINK_DOWN BIT(10)
+#define KSZPHY_INTCS_REMOTE_FAULT BIT(9)
+#define KSZPHY_INTCS_LINK_UP BIT(8)
+#define KSZPHY_INTCS_ALL (KSZPHY_INTCS_LINK_UP |\
+ KSZPHY_INTCS_LINK_DOWN)
+#define KSZPHY_INTCS_LINK_DOWN_STATUS BIT(2)
+#define KSZPHY_INTCS_LINK_UP_STATUS BIT(0)
+#define KSZPHY_INTCS_STATUS (KSZPHY_INTCS_LINK_DOWN_STATUS |\
+ KSZPHY_INTCS_LINK_UP_STATUS)
+
+/* LinkMD Control/Status */
+#define KSZ8081_LMD 0x1d
+#define KSZ8081_LMD_ENABLE_TEST BIT(15)
+#define KSZ8081_LMD_STAT_NORMAL 0
+#define KSZ8081_LMD_STAT_OPEN 1
+#define KSZ8081_LMD_STAT_SHORT 2
+#define KSZ8081_LMD_STAT_FAIL 3
+#define KSZ8081_LMD_STAT_MASK GENMASK(14, 13)
+/* Short cable (<10 meter) has been detected by LinkMD */
+#define KSZ8081_LMD_SHORT_INDICATOR BIT(12)
+#define KSZ8081_LMD_DELTA_TIME_MASK GENMASK(8, 0)
+
+#define KSZ9x31_LMD 0x12
+#define KSZ9x31_LMD_VCT_EN BIT(15)
+#define KSZ9x31_LMD_VCT_DIS_TX BIT(14)
+#define KSZ9x31_LMD_VCT_PAIR(n) (((n) & 0x3) << 12)
+#define KSZ9x31_LMD_VCT_SEL_RESULT 0
+#define KSZ9x31_LMD_VCT_SEL_THRES_HI BIT(10)
+#define KSZ9x31_LMD_VCT_SEL_THRES_LO BIT(11)
+#define KSZ9x31_LMD_VCT_SEL_MASK GENMASK(11, 10)
+#define KSZ9x31_LMD_VCT_ST_NORMAL 0
+#define KSZ9x31_LMD_VCT_ST_OPEN 1
+#define KSZ9x31_LMD_VCT_ST_SHORT 2
+#define KSZ9x31_LMD_VCT_ST_FAIL 3
+#define KSZ9x31_LMD_VCT_ST_MASK GENMASK(9, 8)
+#define KSZ9x31_LMD_VCT_DATA_REFLECTED_INVALID BIT(7)
+#define KSZ9x31_LMD_VCT_DATA_SIG_WAIT_TOO_LONG BIT(6)
+#define KSZ9x31_LMD_VCT_DATA_MASK100 BIT(5)
+#define KSZ9x31_LMD_VCT_DATA_NLP_FLP BIT(4)
+#define KSZ9x31_LMD_VCT_DATA_LO_PULSE_MASK GENMASK(3, 2)
+#define KSZ9x31_LMD_VCT_DATA_HI_PULSE_MASK GENMASK(1, 0)
+#define KSZ9x31_LMD_VCT_DATA_MASK GENMASK(7, 0)
+
+#define KSZPHY_WIRE_PAIR_MASK 0x3
+
+#define LAN8814_CABLE_DIAG 0x12
+#define LAN8814_CABLE_DIAG_STAT_MASK GENMASK(9, 8)
+#define LAN8814_CABLE_DIAG_VCT_DATA_MASK GENMASK(7, 0)
+#define LAN8814_PAIR_BIT_SHIFT 12
+
+#define LAN8814_WIRE_PAIR_MASK 0xF
+
+/* Lan8814 general Interrupt control/status reg in GPHY specific block. */
+#define LAN8814_INTC 0x18
+#define LAN8814_INTS 0x1B
+
+#define LAN8814_INT_LINK_DOWN BIT(2)
+#define LAN8814_INT_LINK_UP BIT(0)
+#define LAN8814_INT_LINK (LAN8814_INT_LINK_UP |\
+ LAN8814_INT_LINK_DOWN)
+
+#define LAN8814_INTR_CTRL_REG 0x34
+#define LAN8814_INTR_CTRL_REG_POLARITY BIT(1)
+#define LAN8814_INTR_CTRL_REG_INTR_ENABLE BIT(0)
+
+/* Represents 1ppm adjustment in 2^32 format with
+ * each nsec contains 4 clock cycles.
+ * The value is calculated as following: (1/1000000)/((2^-32)/4)
+ */
+#define LAN8814_1PPM_FORMAT 17179
+
+#define PTP_RX_VERSION 0x0248
+#define PTP_TX_VERSION 0x0288
+#define PTP_MAX_VERSION(x) (((x) & GENMASK(7, 0)) << 8)
+#define PTP_MIN_VERSION(x) ((x) & GENMASK(7, 0))
+
+#define PTP_RX_MOD 0x024F
+#define PTP_RX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_ BIT(3)
+#define PTP_RX_TIMESTAMP_EN 0x024D
+#define PTP_TX_TIMESTAMP_EN 0x028D
+
+#define PTP_TIMESTAMP_EN_SYNC_ BIT(0)
+#define PTP_TIMESTAMP_EN_DREQ_ BIT(1)
+#define PTP_TIMESTAMP_EN_PDREQ_ BIT(2)
+#define PTP_TIMESTAMP_EN_PDRES_ BIT(3)
+
+#define PTP_TX_PARSE_L2_ADDR_EN 0x0284
+#define PTP_RX_PARSE_L2_ADDR_EN 0x0244
+
+#define PTP_TX_PARSE_IP_ADDR_EN 0x0285
+#define PTP_RX_PARSE_IP_ADDR_EN 0x0245
+#define LTC_HARD_RESET 0x023F
+#define LTC_HARD_RESET_ BIT(0)
+
+#define TSU_HARD_RESET 0x02C1
+#define TSU_HARD_RESET_ BIT(0)
+
+#define PTP_CMD_CTL 0x0200
+#define PTP_CMD_CTL_PTP_DISABLE_ BIT(0)
+#define PTP_CMD_CTL_PTP_ENABLE_ BIT(1)
+#define PTP_CMD_CTL_PTP_CLOCK_READ_ BIT(3)
+#define PTP_CMD_CTL_PTP_CLOCK_LOAD_ BIT(4)
+#define PTP_CMD_CTL_PTP_LTC_STEP_SEC_ BIT(5)
+#define PTP_CMD_CTL_PTP_LTC_STEP_NSEC_ BIT(6)
+
+#define PTP_CLOCK_SET_SEC_MID 0x0206
+#define PTP_CLOCK_SET_SEC_LO 0x0207
+#define PTP_CLOCK_SET_NS_HI 0x0208
+#define PTP_CLOCK_SET_NS_LO 0x0209
+
+#define PTP_CLOCK_READ_SEC_MID 0x022A
+#define PTP_CLOCK_READ_SEC_LO 0x022B
+#define PTP_CLOCK_READ_NS_HI 0x022C
+#define PTP_CLOCK_READ_NS_LO 0x022D
+
+#define PTP_OPERATING_MODE 0x0241
+#define PTP_OPERATING_MODE_STANDALONE_ BIT(0)
+
+#define PTP_TX_MOD 0x028F
+#define PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_ BIT(12)
+#define PTP_TX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_ BIT(3)
+
+#define PTP_RX_PARSE_CONFIG 0x0242
+#define PTP_RX_PARSE_CONFIG_LAYER2_EN_ BIT(0)
+#define PTP_RX_PARSE_CONFIG_IPV4_EN_ BIT(1)
+#define PTP_RX_PARSE_CONFIG_IPV6_EN_ BIT(2)
+
+#define PTP_TX_PARSE_CONFIG 0x0282
+#define PTP_TX_PARSE_CONFIG_LAYER2_EN_ BIT(0)
+#define PTP_TX_PARSE_CONFIG_IPV4_EN_ BIT(1)
+#define PTP_TX_PARSE_CONFIG_IPV6_EN_ BIT(2)
+
+#define PTP_CLOCK_RATE_ADJ_HI 0x020C
+#define PTP_CLOCK_RATE_ADJ_LO 0x020D
+#define PTP_CLOCK_RATE_ADJ_DIR_ BIT(15)
+
+#define PTP_LTC_STEP_ADJ_HI 0x0212
+#define PTP_LTC_STEP_ADJ_LO 0x0213
+#define PTP_LTC_STEP_ADJ_DIR_ BIT(15)
+
+#define LAN8814_INTR_STS_REG 0x0033
+#define LAN8814_INTR_STS_REG_1588_TSU0_ BIT(0)
+#define LAN8814_INTR_STS_REG_1588_TSU1_ BIT(1)
+#define LAN8814_INTR_STS_REG_1588_TSU2_ BIT(2)
+#define LAN8814_INTR_STS_REG_1588_TSU3_ BIT(3)
+
+#define PTP_CAP_INFO 0x022A
+#define PTP_CAP_INFO_TX_TS_CNT_GET_(reg_val) (((reg_val) & 0x0f00) >> 8)
+#define PTP_CAP_INFO_RX_TS_CNT_GET_(reg_val) ((reg_val) & 0x000f)
+
+#define PTP_TX_EGRESS_SEC_HI 0x0296
+#define PTP_TX_EGRESS_SEC_LO 0x0297
+#define PTP_TX_EGRESS_NS_HI 0x0294
+#define PTP_TX_EGRESS_NS_LO 0x0295
+#define PTP_TX_MSG_HEADER2 0x0299
+
+#define PTP_RX_INGRESS_SEC_HI 0x0256
+#define PTP_RX_INGRESS_SEC_LO 0x0257
+#define PTP_RX_INGRESS_NS_HI 0x0254
+#define PTP_RX_INGRESS_NS_LO 0x0255
+#define PTP_RX_MSG_HEADER2 0x0259
+
+#define PTP_TSU_INT_EN 0x0200
+#define PTP_TSU_INT_EN_PTP_TX_TS_OVRFL_EN_ BIT(3)
+#define PTP_TSU_INT_EN_PTP_TX_TS_EN_ BIT(2)
+#define PTP_TSU_INT_EN_PTP_RX_TS_OVRFL_EN_ BIT(1)
+#define PTP_TSU_INT_EN_PTP_RX_TS_EN_ BIT(0)
+
+#define PTP_TSU_INT_STS 0x0201
+#define PTP_TSU_INT_STS_PTP_TX_TS_OVRFL_INT_ BIT(3)
+#define PTP_TSU_INT_STS_PTP_TX_TS_EN_ BIT(2)
+#define PTP_TSU_INT_STS_PTP_RX_TS_OVRFL_INT_ BIT(1)
+#define PTP_TSU_INT_STS_PTP_RX_TS_EN_ BIT(0)
+
+#define LAN8814_LED_CTRL_1 0x0
+#define LAN8814_LED_CTRL_1_KSZ9031_LED_MODE_ BIT(6)
+
+/* PHY Control 1 */
+#define MII_KSZPHY_CTRL_1 0x1e
+#define KSZ8081_CTRL1_MDIX_STAT BIT(4)
+
+/* 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 KSZ8081_CTRL2_HP_MDIX BIT(15)
+#define KSZ8081_CTRL2_MDI_MDI_X_SELECT BIT(14)
+#define KSZ8081_CTRL2_DISABLE_AUTO_MDIX BIT(13)
+#define KSZ8081_CTRL2_FORCE_LINK BIT(11)
+#define KSZ8081_CTRL2_POWER_SAVING BIT(10)
+#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
+#define FIFO_SIZE 8
+
+/* Delay used to get the second part from the LTC */
+#define LAN8841_GET_SEC_LTC_DELAY (500 * NSEC_PER_MSEC)
+
+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;
+ u16 cable_diag_reg;
+ unsigned long pair_mask;
+ u16 disable_dll_tx_bit;
+ u16 disable_dll_rx_bit;
+ u16 disable_dll_mask;
+ bool has_broadcast_disable;
+ bool has_nand_tree_disable;
+ bool has_rmii_ref_clk_sel;
+};
+
+/* Shared structure between the PHYs of the same package. */
+struct lan8814_shared_priv {
+ struct phy_device *phydev;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+
+ /* Reference counter to how many ports in the package are enabling the
+ * timestamping
+ */
+ u8 ref;
+
+ /* Lock for ptp_clock and ref */
+ struct mutex shared_lock;
+};
+
+struct lan8814_ptp_rx_ts {
+ struct list_head list;
+ u32 seconds;
+ u32 nsec;
+ u16 seq_id;
+};
+
+struct kszphy_ptp_priv {
+ struct mii_timestamper mii_ts;
+ struct phy_device *phydev;
+
+ struct sk_buff_head tx_queue;
+ struct sk_buff_head rx_queue;
+
+ struct list_head rx_ts_list;
+ /* Lock for Rx ts fifo */
+ spinlock_t rx_ts_lock;
+
+ int hwts_tx_type;
+ enum hwtstamp_rx_filters rx_filter;
+ int layer;
+ int version;
+
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+ /* Lock for ptp_clock */
+ struct mutex ptp_lock;
+ struct ptp_pin_desc *pin_config;
+
+ s64 seconds;
+ /* Lock for accessing seconds */
+ spinlock_t seconds_lock;
+};
+
+struct kszphy_priv {
+ struct kszphy_ptp_priv ptp_priv;
+ const struct kszphy_type *type;
+ int led_mode;
+ u16 vct_ctrl1000;
+ bool rmii_ref_clk_sel;
+ bool rmii_ref_clk_sel_val;
+ u64 stats[ARRAY_SIZE(kszphy_hw_stats)];
+};
+
+static const struct kszphy_type lan8814_type = {
+ .led_mode_reg = ~LAN8814_LED_CTRL_1,
+ .cable_diag_reg = LAN8814_CABLE_DIAG,
+ .pair_mask = LAN8814_WIRE_PAIR_MASK,
+};
+
+static const struct kszphy_type ksz886x_type = {
+ .cable_diag_reg = KSZ8081_LMD,
+ .pair_mask = KSZPHY_WIRE_PAIR_MASK,
+};
+
+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 const struct kszphy_type ksz9131_type = {
+ .interrupt_level_mask = BIT(14),
+ .disable_dll_tx_bit = BIT(12),
+ .disable_dll_rx_bit = BIT(12),
+ .disable_dll_mask = BIT_MASK(12),
+};
+
+static const struct kszphy_type lan8841_type = {
+ .disable_dll_tx_bit = BIT(14),
+ .disable_dll_rx_bit = BIT(14),
+ .disable_dll_mask = BIT_MASK(14),
+ .cable_diag_reg = LAN8814_CABLE_DIAG,
+ .pair_mask = LAN8814_WIRE_PAIR_MASK,
+};
+
+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, err;
+ 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) {
+ err = kszphy_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_KSZPHY_INTCS, KSZPHY_INTCS_ALL);
+ } else {
+ err = phy_write(phydev, MII_KSZPHY_INTCS, 0);
+ if (err)
+ return err;
+
+ err = kszphy_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t kszphy_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_KSZPHY_INTCS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & KSZPHY_INTCS_STATUS))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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_fiber_mode(struct phy_device *phydev)
+{
+ struct device_node *of_node = phydev->mdio.dev.of_node;
+
+ return of_property_read_bool(of_node, "micrel,fiber-mode");
+}
+
+static int ksz8041_config_init(struct phy_device *phydev)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+
+ /* Limit supported and advertised modes in fiber mode */
+ if (ksz8041_fiber_mode(phydev)) {
+ phydev->dev_flags |= MICREL_PHY_FXEN;
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, mask);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, mask);
+
+ linkmode_and(phydev->supported, phydev->supported, mask);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->supported);
+ linkmode_and(phydev->advertising, phydev->advertising, mask);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ phydev->advertising);
+ 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 ksz8051_ksz8795_match_phy_device(struct phy_device *phydev,
+ const bool ksz_8051)
+{
+ int ret;
+
+ if (!phy_id_compare(phydev->phy_id, PHY_ID_KSZ8051, MICREL_PHY_ID_MASK))
+ return 0;
+
+ ret = phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ return ret;
+
+ /* KSZ8051 PHY and KSZ8794/KSZ8795/KSZ8765 switch share the same
+ * exact PHY ID. However, they can be told apart by the extended
+ * capability registers presence. The KSZ8051 PHY has them while
+ * the switch does not.
+ */
+ ret &= BMSR_ERCAP;
+ if (ksz_8051)
+ return ret;
+ else
+ return !ret;
+}
+
+static int ksz8051_match_phy_device(struct phy_device *phydev)
+{
+ return ksz8051_ksz8795_match_phy_device(phydev, true);
+}
+
+static int ksz8081_config_init(struct phy_device *phydev)
+{
+ /* KSZPHY_OMSO_FACTORY_TEST is set at de-assertion of the reset line
+ * based on the RXER (KSZ8081RNA/RND) or TXC (KSZ8081MNX/RNB) pin. If a
+ * pull-down is missing, the factory test mode should be cleared by
+ * manually writing a 0.
+ */
+ phy_clear_bits(phydev, MII_KSZPHY_OMSO, KSZPHY_OMSO_FACTORY_TEST);
+
+ return kszphy_config_init(phydev);
+}
+
+static int ksz8081_config_mdix(struct phy_device *phydev, u8 ctrl)
+{
+ u16 val;
+
+ switch (ctrl) {
+ case ETH_TP_MDI:
+ val = KSZ8081_CTRL2_DISABLE_AUTO_MDIX;
+ break;
+ case ETH_TP_MDI_X:
+ val = KSZ8081_CTRL2_DISABLE_AUTO_MDIX |
+ KSZ8081_CTRL2_MDI_MDI_X_SELECT;
+ break;
+ case ETH_TP_MDI_AUTO:
+ val = 0;
+ break;
+ default:
+ return 0;
+ }
+
+ return phy_modify(phydev, MII_KSZPHY_CTRL_2,
+ KSZ8081_CTRL2_HP_MDIX |
+ KSZ8081_CTRL2_MDI_MDI_X_SELECT |
+ KSZ8081_CTRL2_DISABLE_AUTO_MDIX,
+ KSZ8081_CTRL2_HP_MDIX | val);
+}
+
+static int ksz8081_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_config_aneg(phydev);
+ if (ret)
+ return ret;
+
+ /* The MDI-X configuration is automatically changed by the PHY after
+ * switching from autoneg off to on. So, take MDI-X configuration under
+ * own control and set it after autoneg configuration was done.
+ */
+ return ksz8081_config_mdix(phydev, phydev->mdix_ctrl);
+}
+
+static int ksz8081_mdix_update(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_KSZPHY_CTRL_2);
+ if (ret < 0)
+ return ret;
+
+ if (ret & KSZ8081_CTRL2_DISABLE_AUTO_MDIX) {
+ if (ret & KSZ8081_CTRL2_MDI_MDI_X_SELECT)
+ phydev->mdix_ctrl = ETH_TP_MDI_X;
+ else
+ phydev->mdix_ctrl = ETH_TP_MDI;
+ } else {
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ }
+
+ ret = phy_read(phydev, MII_KSZPHY_CTRL_1);
+ if (ret < 0)
+ return ret;
+
+ if (ret & KSZ8081_CTRL1_MDIX_STAT)
+ phydev->mdix = ETH_TP_MDI;
+ else
+ phydev->mdix = ETH_TP_MDI_X;
+
+ return 0;
+}
+
+static int ksz8081_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = ksz8081_mdix_update(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_read_status(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 ksz8795_match_phy_device(struct phy_device *phydev)
+{
+ return ksz8051_ksz8795_match_phy_device(phydev, false);
+}
+
+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_node *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 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_CTL_M GENMASK(7, 4)
+#define MII_KSZ9031RN_TX_CTL_M GENMASK(3, 0)
+
+#define MII_KSZ9031RN_RX_DATA_PAD_SKEW 5
+#define MII_KSZ9031RN_RXD3 GENMASK(15, 12)
+#define MII_KSZ9031RN_RXD2 GENMASK(11, 8)
+#define MII_KSZ9031RN_RXD1 GENMASK(7, 4)
+#define MII_KSZ9031RN_RXD0 GENMASK(3, 0)
+
+#define MII_KSZ9031RN_TX_DATA_PAD_SKEW 6
+#define MII_KSZ9031RN_TXD3 GENMASK(15, 12)
+#define MII_KSZ9031RN_TXD2 GENMASK(11, 8)
+#define MII_KSZ9031RN_TXD1 GENMASK(7, 4)
+#define MII_KSZ9031RN_TXD0 GENMASK(3, 0)
+
+#define MII_KSZ9031RN_CLK_PAD_SKEW 8
+#define MII_KSZ9031RN_GTX_CLK GENMASK(9, 5)
+#define MII_KSZ9031RN_RX_CLK GENMASK(4, 0)
+
+/* KSZ9031 has internal RGMII_IDRX = 1.2ns and RGMII_IDTX = 0ns. To
+ * provide different RGMII options we need to configure delay offset
+ * for each pad relative to build in delay.
+ */
+/* keep rx as "No delay adjustment" and set rx_clk to +0.60ns to get delays of
+ * 1.80ns
+ */
+#define RX_ID 0x7
+#define RX_CLK_ID 0x19
+
+/* set rx to +0.30ns and rx_clk to -0.90ns to compensate the
+ * internal 1.2ns delay.
+ */
+#define RX_ND 0xc
+#define RX_CLK_ND 0x0
+
+/* set tx to -0.42ns and tx_clk to +0.96ns to get 1.38ns delay */
+#define TX_ID 0x0
+#define TX_CLK_ID 0x1f
+
+/* set tx and tx_clk to "No delay adjustment" to keep 0ns
+ * dealy
+ */
+#define TX_ND 0x7
+#define TX_CLK_ND 0xf
+
+/* MMD Address 0x1C */
+#define MII_KSZ9031RN_EDPD 0x23
+#define MII_KSZ9031RN_EDPD_ENABLE BIT(0)
+
+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,
+ bool *update)
+{
+ 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;
+
+ *update |= true;
+
+ if (matches < numfields)
+ newval = phy_read_mmd(phydev, 2, reg);
+ else
+ newval = 0;
+
+ maxval = (field_sz == 4) ? 0xf : 0x1f;
+ for (i = 0; i < numfields; i++)
+ if (val[i] != -(i + 1)) {
+ mask = 0xffff;
+ mask ^= maxval << (field_sz * i);
+ newval = (newval & mask) |
+ (((val[i] / KSZ9031_PS_TO_REG) & maxval)
+ << (field_sz * i));
+ }
+
+ return phy_write_mmd(phydev, 2, reg, newval);
+}
+
+/* Center KSZ9031RNX FLP timing at 16ms. */
+static int ksz9031_center_flp_timing(struct phy_device *phydev)
+{
+ int result;
+
+ result = phy_write_mmd(phydev, 0, MII_KSZ9031RN_FLP_BURST_TX_HI,
+ 0x0006);
+ if (result)
+ return result;
+
+ result = phy_write_mmd(phydev, 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 = phy_read_mmd(phydev, 0x1C, MII_KSZ9031RN_EDPD);
+ if (reg < 0)
+ return reg;
+ return phy_write_mmd(phydev, 0x1C, MII_KSZ9031RN_EDPD,
+ reg | MII_KSZ9031RN_EDPD_ENABLE);
+}
+
+static int ksz9031_config_rgmii_delay(struct phy_device *phydev)
+{
+ u16 rx, tx, rx_clk, tx_clk;
+ int ret;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ tx = TX_ND;
+ tx_clk = TX_CLK_ND;
+ rx = RX_ND;
+ rx_clk = RX_CLK_ND;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ tx = TX_ID;
+ tx_clk = TX_CLK_ID;
+ rx = RX_ID;
+ rx_clk = RX_CLK_ID;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ tx = TX_ND;
+ tx_clk = TX_CLK_ND;
+ rx = RX_ID;
+ rx_clk = RX_CLK_ID;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ tx = TX_ID;
+ tx_clk = TX_CLK_ID;
+ rx = RX_ND;
+ rx_clk = RX_CLK_ND;
+ break;
+ default:
+ return 0;
+ }
+
+ ret = phy_write_mmd(phydev, 2, MII_KSZ9031RN_CONTROL_PAD_SKEW,
+ FIELD_PREP(MII_KSZ9031RN_RX_CTL_M, rx) |
+ FIELD_PREP(MII_KSZ9031RN_TX_CTL_M, tx));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, 2, MII_KSZ9031RN_RX_DATA_PAD_SKEW,
+ FIELD_PREP(MII_KSZ9031RN_RXD3, rx) |
+ FIELD_PREP(MII_KSZ9031RN_RXD2, rx) |
+ FIELD_PREP(MII_KSZ9031RN_RXD1, rx) |
+ FIELD_PREP(MII_KSZ9031RN_RXD0, rx));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, 2, MII_KSZ9031RN_TX_DATA_PAD_SKEW,
+ FIELD_PREP(MII_KSZ9031RN_TXD3, tx) |
+ FIELD_PREP(MII_KSZ9031RN_TXD2, tx) |
+ FIELD_PREP(MII_KSZ9031RN_TXD1, tx) |
+ FIELD_PREP(MII_KSZ9031RN_TXD0, tx));
+ if (ret < 0)
+ return ret;
+
+ return phy_write_mmd(phydev, 2, MII_KSZ9031RN_CLK_PAD_SKEW,
+ FIELD_PREP(MII_KSZ9031RN_GTX_CLK, tx_clk) |
+ FIELD_PREP(MII_KSZ9031RN_RX_CLK, rx_clk));
+}
+
+static int ksz9031_config_init(struct phy_device *phydev)
+{
+ const struct device_node *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) {
+ bool update = false;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ result = ksz9031_config_rgmii_delay(phydev);
+ if (result < 0)
+ return result;
+ }
+
+ ksz9031_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_CLK_PAD_SKEW, 5,
+ clk_skews, 2, &update);
+
+ ksz9031_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_CONTROL_PAD_SKEW, 4,
+ control_skews, 2, &update);
+
+ ksz9031_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_RX_DATA_PAD_SKEW, 4,
+ rx_data_skews, 4, &update);
+
+ ksz9031_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4,
+ tx_data_skews, 4, &update);
+
+ if (update && !phy_interface_is_rgmii(phydev))
+ phydev_warn(phydev,
+ "*-skew-ps values should be used only with RGMII PHY modes\n");
+
+ /* Silicon Errata Sheet (DS80000691D or DS80000692D):
+ * When the device links in the 1000BASE-T slave mode only,
+ * the optional 125MHz reference output clock (CLK125_NDO)
+ * has wide duty cycle variation.
+ *
+ * The optional CLK125_NDO clock does not meet the RGMII
+ * 45/55 percent (min/max) duty cycle requirement and therefore
+ * cannot be used directly by the MAC side for clocking
+ * applications that have setup/hold time requirements on
+ * rising and falling clock edges.
+ *
+ * Workaround:
+ * Force the phy to be the master to receive a stable clock
+ * which meets the duty cycle requirement.
+ */
+ if (of_property_read_bool(of_node, "micrel,force-master")) {
+ result = phy_read(phydev, MII_CTRL1000);
+ if (result < 0)
+ goto err_force_master;
+
+ /* enable master mode, config & prefer master */
+ result |= CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER;
+ result = phy_write(phydev, MII_CTRL1000, result);
+ if (result < 0)
+ goto err_force_master;
+ }
+ }
+
+ return ksz9031_center_flp_timing(phydev);
+
+err_force_master:
+ phydev_err(phydev, "failed to force the phy to master mode\n");
+ return result;
+}
+
+#define KSZ9131_SKEW_5BIT_MAX 2400
+#define KSZ9131_SKEW_4BIT_MAX 800
+#define KSZ9131_OFFSET 700
+#define KSZ9131_STEP 100
+
+static int ksz9131_of_load_skew_values(struct phy_device *phydev,
+ struct device_node *of_node,
+ u16 reg, size_t field_sz,
+ char *field[], u8 numfields)
+{
+ int val[4] = {-(1 + KSZ9131_OFFSET), -(2 + KSZ9131_OFFSET),
+ -(3 + KSZ9131_OFFSET), -(4 + KSZ9131_OFFSET)};
+ int skewval, skewmax = 0;
+ int matches = 0;
+ u16 maxval;
+ u16 newval;
+ u16 mask;
+ int i;
+
+ /* psec properties in dts should mean x pico seconds */
+ if (field_sz == 5)
+ skewmax = KSZ9131_SKEW_5BIT_MAX;
+ else
+ skewmax = KSZ9131_SKEW_4BIT_MAX;
+
+ for (i = 0; i < numfields; i++)
+ if (!of_property_read_s32(of_node, field[i], &skewval)) {
+ if (skewval < -KSZ9131_OFFSET)
+ skewval = -KSZ9131_OFFSET;
+ else if (skewval > skewmax)
+ skewval = skewmax;
+
+ val[i] = skewval + KSZ9131_OFFSET;
+ matches++;
+ }
+
+ if (!matches)
+ return 0;
+
+ if (matches < numfields)
+ newval = phy_read_mmd(phydev, 2, reg);
+ else
+ newval = 0;
+
+ maxval = (field_sz == 4) ? 0xf : 0x1f;
+ for (i = 0; i < numfields; i++)
+ if (val[i] != -(i + 1 + KSZ9131_OFFSET)) {
+ mask = 0xffff;
+ mask ^= maxval << (field_sz * i);
+ newval = (newval & mask) |
+ (((val[i] / KSZ9131_STEP) & maxval)
+ << (field_sz * i));
+ }
+
+ return phy_write_mmd(phydev, 2, reg, newval);
+}
+
+#define KSZ9131RN_MMD_COMMON_CTRL_REG 2
+#define KSZ9131RN_RXC_DLL_CTRL 76
+#define KSZ9131RN_TXC_DLL_CTRL 77
+#define KSZ9131RN_DLL_ENABLE_DELAY 0
+
+static int ksz9131_config_rgmii_delay(struct phy_device *phydev)
+{
+ const struct kszphy_type *type = phydev->drv->driver_data;
+ u16 rxcdll_val, txcdll_val;
+ int ret;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ rxcdll_val = type->disable_dll_rx_bit;
+ txcdll_val = type->disable_dll_tx_bit;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ rxcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+ txcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ rxcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+ txcdll_val = type->disable_dll_tx_bit;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ rxcdll_val = type->disable_dll_rx_bit;
+ txcdll_val = KSZ9131RN_DLL_ENABLE_DELAY;
+ break;
+ default:
+ return 0;
+ }
+
+ ret = phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ KSZ9131RN_RXC_DLL_CTRL, type->disable_dll_mask,
+ rxcdll_val);
+ if (ret < 0)
+ return ret;
+
+ return phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ KSZ9131RN_TXC_DLL_CTRL, type->disable_dll_mask,
+ txcdll_val);
+}
+
+/* Silicon Errata DS80000693B
+ *
+ * When LEDs are configured in Individual Mode, LED1 is ON in a no-link
+ * condition. Workaround is to set register 0x1e, bit 9, this way LED1 behaves
+ * according to the datasheet (off if there is no link).
+ */
+static int ksz9131_led_errata(struct phy_device *phydev)
+{
+ int reg;
+
+ reg = phy_read_mmd(phydev, 2, 0);
+ if (reg < 0)
+ return reg;
+
+ if (!(reg & BIT(4)))
+ return 0;
+
+ return phy_set_bits(phydev, 0x1e, BIT(9));
+}
+
+static int ksz9131_config_init(struct phy_device *phydev)
+{
+ struct device_node *of_node;
+ char *clk_skews[2] = {"rxc-skew-psec", "txc-skew-psec"};
+ char *rx_data_skews[4] = {
+ "rxd0-skew-psec", "rxd1-skew-psec",
+ "rxd2-skew-psec", "rxd3-skew-psec"
+ };
+ char *tx_data_skews[4] = {
+ "txd0-skew-psec", "txd1-skew-psec",
+ "txd2-skew-psec", "txd3-skew-psec"
+ };
+ char *control_skews[2] = {"txen-skew-psec", "rxdv-skew-psec"};
+ const struct device *dev_walker;
+ int ret;
+
+ dev_walker = &phydev->mdio.dev;
+ do {
+ of_node = dev_walker->of_node;
+ dev_walker = dev_walker->parent;
+ } while (!of_node && dev_walker);
+
+ if (!of_node)
+ return 0;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ ret = ksz9131_config_rgmii_delay(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = ksz9131_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_CLK_PAD_SKEW, 5,
+ clk_skews, 2);
+ if (ret < 0)
+ return ret;
+
+ ret = ksz9131_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_CONTROL_PAD_SKEW, 4,
+ control_skews, 2);
+ if (ret < 0)
+ return ret;
+
+ ret = ksz9131_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_RX_DATA_PAD_SKEW, 4,
+ rx_data_skews, 4);
+ if (ret < 0)
+ return ret;
+
+ ret = ksz9131_of_load_skew_values(phydev, of_node,
+ MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4,
+ tx_data_skews, 4);
+ if (ret < 0)
+ return ret;
+
+ ret = ksz9131_led_errata(phydev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+#define MII_KSZ9131_AUTO_MDIX 0x1C
+#define MII_KSZ9131_AUTO_MDI_SET BIT(7)
+#define MII_KSZ9131_AUTO_MDIX_SWAP_OFF BIT(6)
+
+static int ksz9131_mdix_update(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_KSZ9131_AUTO_MDIX);
+ if (ret < 0)
+ return ret;
+
+ if (ret & MII_KSZ9131_AUTO_MDIX_SWAP_OFF) {
+ if (ret & MII_KSZ9131_AUTO_MDI_SET)
+ phydev->mdix_ctrl = ETH_TP_MDI;
+ else
+ phydev->mdix_ctrl = ETH_TP_MDI_X;
+ } else {
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ }
+
+ if (ret & MII_KSZ9131_AUTO_MDI_SET)
+ phydev->mdix = ETH_TP_MDI;
+ else
+ phydev->mdix = ETH_TP_MDI_X;
+
+ return 0;
+}
+
+static int ksz9131_config_mdix(struct phy_device *phydev, u8 ctrl)
+{
+ u16 val;
+
+ switch (ctrl) {
+ case ETH_TP_MDI:
+ val = MII_KSZ9131_AUTO_MDIX_SWAP_OFF |
+ MII_KSZ9131_AUTO_MDI_SET;
+ break;
+ case ETH_TP_MDI_X:
+ val = MII_KSZ9131_AUTO_MDIX_SWAP_OFF;
+ break;
+ case ETH_TP_MDI_AUTO:
+ val = 0;
+ break;
+ default:
+ return 0;
+ }
+
+ return phy_modify(phydev, MII_KSZ9131_AUTO_MDIX,
+ MII_KSZ9131_AUTO_MDIX_SWAP_OFF |
+ MII_KSZ9131_AUTO_MDI_SET, val);
+}
+
+static int ksz9131_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = ksz9131_mdix_update(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_read_status(phydev);
+}
+
+static int ksz9131_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = ksz9131_config_mdix(phydev, phydev->mdix_ctrl);
+ if (ret)
+ return ret;
+
+ return genphy_config_aneg(phydev);
+}
+
+static int ksz9477_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* The "EEE control and capability 1" (Register 3.20) seems to be
+ * influenced by the "EEE advertisement 1" (Register 7.60). Changes
+ * on the 7.60 will affect 3.20. So, we need to construct our own list
+ * of caps.
+ * KSZ8563R should have 100BaseTX/Full only.
+ */
+ linkmode_and(phydev->supported_eee, phydev->supported,
+ PHY_EEE_CAP1_FEATURES);
+
+ return 0;
+}
+
+#define KSZ8873MLL_GLOBAL_CONTROL_4 0x06
+#define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX BIT(6)
+#define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED BIT(4)
+static int ksz8873mll_read_status(struct phy_device *phydev)
+{
+ int regval;
+
+ /* dummy read */
+ regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4);
+
+ regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4);
+
+ if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX)
+ phydev->duplex = DUPLEX_HALF;
+ else
+ phydev->duplex = DUPLEX_FULL;
+
+ if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_SPEED)
+ phydev->speed = SPEED_10;
+ else
+ phydev->speed = SPEED_100;
+
+ phydev->link = 1;
+ phydev->pause = phydev->asym_pause = 0;
+
+ return 0;
+}
+
+static int ksz9031_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_read_abilities(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* Silicon Errata Sheet (DS80000691D or DS80000692D):
+ * Whenever the device's Asymmetric Pause capability is set to 1,
+ * link-up may fail after a link-up to link-down transition.
+ *
+ * The Errata Sheet is for ksz9031, but ksz9021 has the same issue
+ *
+ * Workaround:
+ * Do not enable the Asymmetric Pause capability bit.
+ */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);
+
+ /* We force setting the Pause capability as the core will force the
+ * Asymmetric Pause capability to 1 otherwise.
+ */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+
+ 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 ksz9x31_cable_test_start(struct phy_device *phydev)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ int ret;
+
+ /* KSZ9131RNX, DS00002841B-page 38, 4.14 LinkMD (R) Cable Diagnostic
+ * Prior to running the cable diagnostics, Auto-negotiation should
+ * be disabled, full duplex set and the link speed set to 1000Mbps
+ * via the Basic Control Register.
+ */
+ ret = phy_modify(phydev, MII_BMCR,
+ BMCR_SPEED1000 | BMCR_FULLDPLX |
+ BMCR_ANENABLE | BMCR_SPEED100,
+ BMCR_SPEED1000 | BMCR_FULLDPLX);
+ if (ret)
+ return ret;
+
+ /* KSZ9131RNX, DS00002841B-page 38, 4.14 LinkMD (R) Cable Diagnostic
+ * The Master-Slave configuration should be set to Slave by writing
+ * a value of 0x1000 to the Auto-Negotiation Master Slave Control
+ * Register.
+ */
+ ret = phy_read(phydev, MII_CTRL1000);
+ if (ret < 0)
+ return ret;
+
+ /* Cache these bits, they need to be restored once LinkMD finishes. */
+ priv->vct_ctrl1000 = ret & (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
+ ret &= ~(CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
+ ret |= CTL1000_ENABLE_MASTER;
+
+ return phy_write(phydev, MII_CTRL1000, ret);
+}
+
+static int ksz9x31_cable_test_result_trans(u16 status)
+{
+ switch (FIELD_GET(KSZ9x31_LMD_VCT_ST_MASK, status)) {
+ case KSZ9x31_LMD_VCT_ST_NORMAL:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case KSZ9x31_LMD_VCT_ST_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case KSZ9x31_LMD_VCT_ST_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ case KSZ9x31_LMD_VCT_ST_FAIL:
+ fallthrough;
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static bool ksz9x31_cable_test_failed(u16 status)
+{
+ int stat = FIELD_GET(KSZ9x31_LMD_VCT_ST_MASK, status);
+
+ return stat == KSZ9x31_LMD_VCT_ST_FAIL;
+}
+
+static bool ksz9x31_cable_test_fault_length_valid(u16 status)
+{
+ switch (FIELD_GET(KSZ9x31_LMD_VCT_ST_MASK, status)) {
+ case KSZ9x31_LMD_VCT_ST_OPEN:
+ fallthrough;
+ case KSZ9x31_LMD_VCT_ST_SHORT:
+ return true;
+ }
+ return false;
+}
+
+static int ksz9x31_cable_test_fault_length(struct phy_device *phydev, u16 stat)
+{
+ int dt = FIELD_GET(KSZ9x31_LMD_VCT_DATA_MASK, stat);
+
+ /* KSZ9131RNX, DS00002841B-page 38, 4.14 LinkMD (R) Cable Diagnostic
+ *
+ * distance to fault = (VCT_DATA - 22) * 4 / cable propagation velocity
+ */
+ if (phydev_id_compare(phydev, PHY_ID_KSZ9131))
+ dt = clamp(dt - 22, 0, 255);
+
+ return (dt * 400) / 10;
+}
+
+static int ksz9x31_cable_test_wait_for_completion(struct phy_device *phydev)
+{
+ int val, ret;
+
+ ret = phy_read_poll_timeout(phydev, KSZ9x31_LMD, val,
+ !(val & KSZ9x31_LMD_VCT_EN),
+ 30000, 100000, true);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int ksz9x31_cable_test_get_pair(int pair)
+{
+ static const int ethtool_pair[] = {
+ ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_PAIR_B,
+ ETHTOOL_A_CABLE_PAIR_C,
+ ETHTOOL_A_CABLE_PAIR_D,
+ };
+
+ return ethtool_pair[pair];
+}
+
+static int ksz9x31_cable_test_one_pair(struct phy_device *phydev, int pair)
+{
+ int ret, val;
+
+ /* KSZ9131RNX, DS00002841B-page 38, 4.14 LinkMD (R) Cable Diagnostic
+ * To test each individual cable pair, set the cable pair in the Cable
+ * Diagnostics Test Pair (VCT_PAIR[1:0]) field of the LinkMD Cable
+ * Diagnostic Register, along with setting the Cable Diagnostics Test
+ * Enable (VCT_EN) bit. The Cable Diagnostics Test Enable (VCT_EN) bit
+ * will self clear when the test is concluded.
+ */
+ ret = phy_write(phydev, KSZ9x31_LMD,
+ KSZ9x31_LMD_VCT_EN | KSZ9x31_LMD_VCT_PAIR(pair));
+ if (ret)
+ return ret;
+
+ ret = ksz9x31_cable_test_wait_for_completion(phydev);
+ if (ret)
+ return ret;
+
+ val = phy_read(phydev, KSZ9x31_LMD);
+ if (val < 0)
+ return val;
+
+ if (ksz9x31_cable_test_failed(val))
+ return -EAGAIN;
+
+ ret = ethnl_cable_test_result(phydev,
+ ksz9x31_cable_test_get_pair(pair),
+ ksz9x31_cable_test_result_trans(val));
+ if (ret)
+ return ret;
+
+ if (!ksz9x31_cable_test_fault_length_valid(val))
+ return 0;
+
+ return ethnl_cable_test_fault_length(phydev,
+ ksz9x31_cable_test_get_pair(pair),
+ ksz9x31_cable_test_fault_length(phydev, val));
+}
+
+static int ksz9x31_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ unsigned long pair_mask = 0xf;
+ int retries = 20;
+ int pair, ret, rv;
+
+ *finished = false;
+
+ /* Try harder if link partner is active */
+ while (pair_mask && retries--) {
+ for_each_set_bit(pair, &pair_mask, 4) {
+ ret = ksz9x31_cable_test_one_pair(phydev, pair);
+ if (ret == -EAGAIN)
+ continue;
+ if (ret < 0)
+ return ret;
+ clear_bit(pair, &pair_mask);
+ }
+ /* If link partner is in autonegotiation mode it will send 2ms
+ * of FLPs with at least 6ms of silence.
+ * Add 2ms sleep to have better chances to hit this silence.
+ */
+ if (pair_mask)
+ usleep_range(2000, 3000);
+ }
+
+ /* Report remaining unfinished pair result as unknown. */
+ for_each_set_bit(pair, &pair_mask, 4) {
+ ret = ethnl_cable_test_result(phydev,
+ ksz9x31_cable_test_get_pair(pair),
+ ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC);
+ }
+
+ *finished = true;
+
+ /* Restore cached bits from before LinkMD got started. */
+ rv = phy_modify(phydev, MII_CTRL1000,
+ CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER,
+ priv->vct_ctrl1000);
+ if (rv)
+ return rv;
+
+ return ret;
+}
+
+static int ksz8873mll_config_aneg(struct phy_device *phydev)
+{
+ return 0;
+}
+
+static int ksz886x_config_mdix(struct phy_device *phydev, u8 ctrl)
+{
+ u16 val;
+
+ switch (ctrl) {
+ case ETH_TP_MDI:
+ val = KSZ886X_BMCR_DISABLE_AUTO_MDIX;
+ break;
+ case ETH_TP_MDI_X:
+ /* Note: The naming of the bit KSZ886X_BMCR_FORCE_MDI is bit
+ * counter intuitive, the "-X" in "1 = Force MDI" in the data
+ * sheet seems to be missing:
+ * 1 = Force MDI (sic!) (transmit on RX+/RX- pins)
+ * 0 = Normal operation (transmit on TX+/TX- pins)
+ */
+ val = KSZ886X_BMCR_DISABLE_AUTO_MDIX | KSZ886X_BMCR_FORCE_MDI;
+ break;
+ case ETH_TP_MDI_AUTO:
+ val = 0;
+ break;
+ default:
+ return 0;
+ }
+
+ return phy_modify(phydev, MII_BMCR,
+ KSZ886X_BMCR_HP_MDIX | KSZ886X_BMCR_FORCE_MDI |
+ KSZ886X_BMCR_DISABLE_AUTO_MDIX,
+ KSZ886X_BMCR_HP_MDIX | val);
+}
+
+static int ksz886x_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_config_aneg(phydev);
+ if (ret)
+ return ret;
+
+ /* The MDI-X configuration is automatically changed by the PHY after
+ * switching from autoneg off to on. So, take MDI-X configuration under
+ * own control and set it after autoneg configuration was done.
+ */
+ return ksz886x_config_mdix(phydev, phydev->mdix_ctrl);
+}
+
+static int ksz886x_mdix_update(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ return ret;
+
+ if (ret & KSZ886X_BMCR_DISABLE_AUTO_MDIX) {
+ if (ret & KSZ886X_BMCR_FORCE_MDI)
+ phydev->mdix_ctrl = ETH_TP_MDI_X;
+ else
+ phydev->mdix_ctrl = ETH_TP_MDI;
+ } else {
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ }
+
+ ret = phy_read(phydev, MII_KSZPHY_CTRL);
+ if (ret < 0)
+ return ret;
+
+ /* Same reverse logic as KSZ886X_BMCR_FORCE_MDI */
+ if (ret & KSZ886X_CTRL_MDIX_STAT)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+
+ return 0;
+}
+
+static int ksz886x_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = ksz886x_mdix_update(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_read_status(phydev);
+}
+
+struct ksz9477_errata_write {
+ u8 dev_addr;
+ u8 reg_addr;
+ u16 val;
+};
+
+static const struct ksz9477_errata_write ksz9477_errata_writes[] = {
+ /* Register settings are needed to improve PHY receive performance */
+ {0x01, 0x6f, 0xdd0b},
+ {0x01, 0x8f, 0x6032},
+ {0x01, 0x9d, 0x248c},
+ {0x01, 0x75, 0x0060},
+ {0x01, 0xd3, 0x7777},
+ {0x1c, 0x06, 0x3008},
+ {0x1c, 0x08, 0x2000},
+
+ /* Transmit waveform amplitude can be improved (1000BASE-T, 100BASE-TX, 10BASE-Te) */
+ {0x1c, 0x04, 0x00d0},
+
+ /* Register settings are required to meet data sheet supply current specifications */
+ {0x1c, 0x13, 0x6eff},
+ {0x1c, 0x14, 0xe6ff},
+ {0x1c, 0x15, 0x6eff},
+ {0x1c, 0x16, 0xe6ff},
+ {0x1c, 0x17, 0x00ff},
+ {0x1c, 0x18, 0x43ff},
+ {0x1c, 0x19, 0xc3ff},
+ {0x1c, 0x1a, 0x6fff},
+ {0x1c, 0x1b, 0x07ff},
+ {0x1c, 0x1c, 0x0fff},
+ {0x1c, 0x1d, 0xe7ff},
+ {0x1c, 0x1e, 0xefff},
+ {0x1c, 0x20, 0xeeee},
+};
+
+static int ksz9477_config_init(struct phy_device *phydev)
+{
+ int err;
+ int i;
+
+ /* Apply PHY settings to address errata listed in
+ * KSZ9477, KSZ9897, KSZ9896, KSZ9567, KSZ8565
+ * Silicon Errata and Data Sheet Clarification documents.
+ *
+ * Document notes: Before configuring the PHY MMD registers, it is
+ * necessary to set the PHY to 100 Mbps speed with auto-negotiation
+ * disabled by writing to register 0xN100-0xN101. After writing the
+ * MMD registers, and after all errata workarounds that involve PHY
+ * register settings, write register 0xN100-0xN101 again to enable
+ * and restart auto-negotiation.
+ */
+ err = phy_write(phydev, MII_BMCR, BMCR_SPEED100 | BMCR_FULLDPLX);
+ if (err)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(ksz9477_errata_writes); ++i) {
+ const struct ksz9477_errata_write *errata = &ksz9477_errata_writes[i];
+
+ err = phy_write_mmd(phydev, errata->dev_addr, errata->reg_addr, errata->val);
+ if (err)
+ return err;
+ }
+
+ /* According to KSZ9477 Errata DS80000754C (Module 4) all EEE modes
+ * in this switch shall be regarded as broken.
+ */
+ if (phydev->dev_flags & MICREL_NO_EEE)
+ phydev->eee_broken_modes = -1;
+
+ err = genphy_restart_aneg(phydev);
+ if (err)
+ return err;
+
+ return kszphy_config_init(phydev);
+}
+
+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++) {
+ strscpy(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 void kszphy_parse_led_mode(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 = phydev->priv;
+ int ret;
+
+ 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;
+ }
+}
+
+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;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ priv->type = type;
+
+ kszphy_parse_led_mode(phydev);
+
+ 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;
+ }
+ }
+
+ if (ksz8041_fiber_mode(phydev))
+ phydev->port = PORT_FIBRE;
+
+ /* Support legacy board-file configuration */
+ if (phydev->dev_flags & MICREL_PHY_50MHZ_CLK) {
+ priv->rmii_ref_clk_sel = true;
+ priv->rmii_ref_clk_sel_val = true;
+ }
+
+ return 0;
+}
+
+static int lan8814_cable_test_start(struct phy_device *phydev)
+{
+ /* If autoneg is enabled, we won't be able to test cross pair
+ * short. In this case, the PHY will "detect" a link and
+ * confuse the internal state machine - disable auto neg here.
+ * Set the speed to 1000mbit and full duplex.
+ */
+ return phy_modify(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_SPEED100,
+ BMCR_SPEED1000 | BMCR_FULLDPLX);
+}
+
+static int ksz886x_cable_test_start(struct phy_device *phydev)
+{
+ if (phydev->dev_flags & MICREL_KSZ8_P1_ERRATA)
+ return -EOPNOTSUPP;
+
+ /* If autoneg is enabled, we won't be able to test cross pair
+ * short. In this case, the PHY will "detect" a link and
+ * confuse the internal state machine - disable auto neg here.
+ * If autoneg is disabled, we should set the speed to 10mbit.
+ */
+ return phy_clear_bits(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_SPEED100);
+}
+
+static __always_inline int ksz886x_cable_test_result_trans(u16 status, u16 mask)
+{
+ switch (FIELD_GET(mask, status)) {
+ case KSZ8081_LMD_STAT_NORMAL:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case KSZ8081_LMD_STAT_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ case KSZ8081_LMD_STAT_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case KSZ8081_LMD_STAT_FAIL:
+ fallthrough;
+ default:
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static __always_inline bool ksz886x_cable_test_failed(u16 status, u16 mask)
+{
+ return FIELD_GET(mask, status) ==
+ KSZ8081_LMD_STAT_FAIL;
+}
+
+static __always_inline bool ksz886x_cable_test_fault_length_valid(u16 status, u16 mask)
+{
+ switch (FIELD_GET(mask, status)) {
+ case KSZ8081_LMD_STAT_OPEN:
+ fallthrough;
+ case KSZ8081_LMD_STAT_SHORT:
+ return true;
+ }
+ return false;
+}
+
+static __always_inline int ksz886x_cable_test_fault_length(struct phy_device *phydev,
+ u16 status, u16 data_mask)
+{
+ int dt;
+
+ /* According to the data sheet the distance to the fault is
+ * DELTA_TIME * 0.4 meters for ksz phys.
+ * (DELTA_TIME - 22) * 0.8 for lan8814 phy.
+ */
+ dt = FIELD_GET(data_mask, status);
+
+ if (phydev_id_compare(phydev, PHY_ID_LAN8814))
+ return ((dt - 22) * 800) / 10;
+ else
+ return (dt * 400) / 10;
+}
+
+static int ksz886x_cable_test_wait_for_completion(struct phy_device *phydev)
+{
+ const struct kszphy_type *type = phydev->drv->driver_data;
+ int val, ret;
+
+ ret = phy_read_poll_timeout(phydev, type->cable_diag_reg, val,
+ !(val & KSZ8081_LMD_ENABLE_TEST),
+ 30000, 100000, true);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int lan8814_cable_test_one_pair(struct phy_device *phydev, int pair)
+{
+ static const int ethtool_pair[] = { ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_PAIR_B,
+ ETHTOOL_A_CABLE_PAIR_C,
+ ETHTOOL_A_CABLE_PAIR_D,
+ };
+ u32 fault_length;
+ int ret;
+ int val;
+
+ val = KSZ8081_LMD_ENABLE_TEST;
+ val = val | (pair << LAN8814_PAIR_BIT_SHIFT);
+
+ ret = phy_write(phydev, LAN8814_CABLE_DIAG, val);
+ if (ret < 0)
+ return ret;
+
+ ret = ksz886x_cable_test_wait_for_completion(phydev);
+ if (ret)
+ return ret;
+
+ val = phy_read(phydev, LAN8814_CABLE_DIAG);
+ if (val < 0)
+ return val;
+
+ if (ksz886x_cable_test_failed(val, LAN8814_CABLE_DIAG_STAT_MASK))
+ return -EAGAIN;
+
+ ret = ethnl_cable_test_result(phydev, ethtool_pair[pair],
+ ksz886x_cable_test_result_trans(val,
+ LAN8814_CABLE_DIAG_STAT_MASK
+ ));
+ if (ret)
+ return ret;
+
+ if (!ksz886x_cable_test_fault_length_valid(val, LAN8814_CABLE_DIAG_STAT_MASK))
+ return 0;
+
+ fault_length = ksz886x_cable_test_fault_length(phydev, val,
+ LAN8814_CABLE_DIAG_VCT_DATA_MASK);
+
+ return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair], fault_length);
+}
+
+static int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair)
+{
+ static const int ethtool_pair[] = {
+ ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_PAIR_B,
+ };
+ int ret, val, mdix;
+ u32 fault_length;
+
+ /* There is no way to choice the pair, like we do one ksz9031.
+ * We can workaround this limitation by using the MDI-X functionality.
+ */
+ if (pair == 0)
+ mdix = ETH_TP_MDI;
+ else
+ mdix = ETH_TP_MDI_X;
+
+ switch (phydev->phy_id & MICREL_PHY_ID_MASK) {
+ case PHY_ID_KSZ8081:
+ ret = ksz8081_config_mdix(phydev, mdix);
+ break;
+ case PHY_ID_KSZ886X:
+ ret = ksz886x_config_mdix(phydev, mdix);
+ break;
+ default:
+ ret = -ENODEV;
+ }
+
+ if (ret)
+ return ret;
+
+ /* Now we are ready to fire. This command will send a 100ns pulse
+ * to the pair.
+ */
+ ret = phy_write(phydev, KSZ8081_LMD, KSZ8081_LMD_ENABLE_TEST);
+ if (ret)
+ return ret;
+
+ ret = ksz886x_cable_test_wait_for_completion(phydev);
+ if (ret)
+ return ret;
+
+ val = phy_read(phydev, KSZ8081_LMD);
+ if (val < 0)
+ return val;
+
+ if (ksz886x_cable_test_failed(val, KSZ8081_LMD_STAT_MASK))
+ return -EAGAIN;
+
+ ret = ethnl_cable_test_result(phydev, ethtool_pair[pair],
+ ksz886x_cable_test_result_trans(val, KSZ8081_LMD_STAT_MASK));
+ if (ret)
+ return ret;
+
+ if (!ksz886x_cable_test_fault_length_valid(val, KSZ8081_LMD_STAT_MASK))
+ return 0;
+
+ fault_length = ksz886x_cable_test_fault_length(phydev, val, KSZ8081_LMD_DELTA_TIME_MASK);
+
+ return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair], fault_length);
+}
+
+static int ksz886x_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ const struct kszphy_type *type = phydev->drv->driver_data;
+ unsigned long pair_mask = type->pair_mask;
+ int retries = 20;
+ int ret = 0;
+ int pair;
+
+ *finished = false;
+
+ /* Try harder if link partner is active */
+ while (pair_mask && retries--) {
+ for_each_set_bit(pair, &pair_mask, 4) {
+ if (type->cable_diag_reg == LAN8814_CABLE_DIAG)
+ ret = lan8814_cable_test_one_pair(phydev, pair);
+ else
+ ret = ksz886x_cable_test_one_pair(phydev, pair);
+ if (ret == -EAGAIN)
+ continue;
+ if (ret < 0)
+ return ret;
+ clear_bit(pair, &pair_mask);
+ }
+ /* If link partner is in autonegotiation mode it will send 2ms
+ * of FLPs with at least 6ms of silence.
+ * Add 2ms sleep to have better chances to hit this silence.
+ */
+ if (pair_mask)
+ msleep(2);
+ }
+
+ *finished = true;
+
+ return ret;
+}
+
+#define LAN_EXT_PAGE_ACCESS_CONTROL 0x16
+#define LAN_EXT_PAGE_ACCESS_ADDRESS_DATA 0x17
+#define LAN_EXT_PAGE_ACCESS_CTRL_EP_FUNC 0x4000
+
+#define LAN8814_QSGMII_SOFT_RESET 0x43
+#define LAN8814_QSGMII_SOFT_RESET_BIT BIT(0)
+#define LAN8814_QSGMII_PCS1G_ANEG_CONFIG 0x13
+#define LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA BIT(3)
+#define LAN8814_ALIGN_SWAP 0x4a
+#define LAN8814_ALIGN_TX_A_B_SWAP 0x1
+#define LAN8814_ALIGN_TX_A_B_SWAP_MASK GENMASK(2, 0)
+
+#define LAN8804_ALIGN_SWAP 0x4a
+#define LAN8804_ALIGN_TX_A_B_SWAP 0x1
+#define LAN8804_ALIGN_TX_A_B_SWAP_MASK GENMASK(2, 0)
+#define LAN8814_CLOCK_MANAGEMENT 0xd
+#define LAN8814_LINK_QUALITY 0x8e
+
+static int lanphy_read_page_reg(struct phy_device *phydev, int page, u32 addr)
+{
+ int data;
+
+ phy_lock_mdio_bus(phydev);
+ __phy_write(phydev, LAN_EXT_PAGE_ACCESS_CONTROL, page);
+ __phy_write(phydev, LAN_EXT_PAGE_ACCESS_ADDRESS_DATA, addr);
+ __phy_write(phydev, LAN_EXT_PAGE_ACCESS_CONTROL,
+ (page | LAN_EXT_PAGE_ACCESS_CTRL_EP_FUNC));
+ data = __phy_read(phydev, LAN_EXT_PAGE_ACCESS_ADDRESS_DATA);
+ phy_unlock_mdio_bus(phydev);
+
+ return data;
+}
+
+static int lanphy_write_page_reg(struct phy_device *phydev, int page, u16 addr,
+ u16 val)
+{
+ phy_lock_mdio_bus(phydev);
+ __phy_write(phydev, LAN_EXT_PAGE_ACCESS_CONTROL, page);
+ __phy_write(phydev, LAN_EXT_PAGE_ACCESS_ADDRESS_DATA, addr);
+ __phy_write(phydev, LAN_EXT_PAGE_ACCESS_CONTROL,
+ page | LAN_EXT_PAGE_ACCESS_CTRL_EP_FUNC);
+
+ val = __phy_write(phydev, LAN_EXT_PAGE_ACCESS_ADDRESS_DATA, val);
+ if (val != 0)
+ phydev_err(phydev, "Error: phy_write has returned error %d\n",
+ val);
+ phy_unlock_mdio_bus(phydev);
+ return val;
+}
+
+static int lan8814_config_ts_intr(struct phy_device *phydev, bool enable)
+{
+ u16 val = 0;
+
+ if (enable)
+ val = PTP_TSU_INT_EN_PTP_TX_TS_EN_ |
+ PTP_TSU_INT_EN_PTP_TX_TS_OVRFL_EN_ |
+ PTP_TSU_INT_EN_PTP_RX_TS_EN_ |
+ PTP_TSU_INT_EN_PTP_RX_TS_OVRFL_EN_;
+
+ return lanphy_write_page_reg(phydev, 5, PTP_TSU_INT_EN, val);
+}
+
+static void lan8814_ptp_rx_ts_get(struct phy_device *phydev,
+ u32 *seconds, u32 *nano_seconds, u16 *seq_id)
+{
+ *seconds = lanphy_read_page_reg(phydev, 5, PTP_RX_INGRESS_SEC_HI);
+ *seconds = (*seconds << 16) |
+ lanphy_read_page_reg(phydev, 5, PTP_RX_INGRESS_SEC_LO);
+
+ *nano_seconds = lanphy_read_page_reg(phydev, 5, PTP_RX_INGRESS_NS_HI);
+ *nano_seconds = ((*nano_seconds & 0x3fff) << 16) |
+ lanphy_read_page_reg(phydev, 5, PTP_RX_INGRESS_NS_LO);
+
+ *seq_id = lanphy_read_page_reg(phydev, 5, PTP_RX_MSG_HEADER2);
+}
+
+static void lan8814_ptp_tx_ts_get(struct phy_device *phydev,
+ u32 *seconds, u32 *nano_seconds, u16 *seq_id)
+{
+ *seconds = lanphy_read_page_reg(phydev, 5, PTP_TX_EGRESS_SEC_HI);
+ *seconds = *seconds << 16 |
+ lanphy_read_page_reg(phydev, 5, PTP_TX_EGRESS_SEC_LO);
+
+ *nano_seconds = lanphy_read_page_reg(phydev, 5, PTP_TX_EGRESS_NS_HI);
+ *nano_seconds = ((*nano_seconds & 0x3fff) << 16) |
+ lanphy_read_page_reg(phydev, 5, PTP_TX_EGRESS_NS_LO);
+
+ *seq_id = lanphy_read_page_reg(phydev, 5, PTP_TX_MSG_HEADER2);
+}
+
+static int lan8814_ts_info(struct mii_timestamper *mii_ts, struct ethtool_ts_info *info)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct lan8814_shared_priv *shared = phydev->shared->priv;
+
+ info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ info->phc_index = ptp_clock_index(shared->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 void lan8814_flush_fifo(struct phy_device *phydev, bool egress)
+{
+ int i;
+
+ for (i = 0; i < FIFO_SIZE; ++i)
+ lanphy_read_page_reg(phydev, 5,
+ egress ? PTP_TX_MSG_HEADER2 : PTP_RX_MSG_HEADER2);
+
+ /* Read to clear overflow status bit */
+ lanphy_read_page_reg(phydev, 5, PTP_TSU_INT_STS);
+}
+
+static int lan8814_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
+{
+ struct kszphy_ptp_priv *ptp_priv =
+ container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct lan8814_shared_priv *shared = phydev->shared->priv;
+ struct lan8814_ptp_rx_ts *rx_ts, *tmp;
+ struct hwtstamp_config config;
+ int txcfg = 0, rxcfg = 0;
+ int pkt_ts_enable;
+
+ if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
+ return -EFAULT;
+
+ ptp_priv->hwts_tx_type = config.tx_type;
+ ptp_priv->rx_filter = config.rx_filter;
+
+ switch (config.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ ptp_priv->layer = 0;
+ ptp_priv->version = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ ptp_priv->layer = PTP_CLASS_L4;
+ ptp_priv->version = PTP_CLASS_V2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ ptp_priv->layer = PTP_CLASS_L2;
+ ptp_priv->version = PTP_CLASS_V2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ ptp_priv->layer = PTP_CLASS_L4 | PTP_CLASS_L2;
+ ptp_priv->version = PTP_CLASS_V2;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ if (ptp_priv->layer & PTP_CLASS_L2) {
+ rxcfg = PTP_RX_PARSE_CONFIG_LAYER2_EN_;
+ txcfg = PTP_TX_PARSE_CONFIG_LAYER2_EN_;
+ } else if (ptp_priv->layer & PTP_CLASS_L4) {
+ rxcfg |= PTP_RX_PARSE_CONFIG_IPV4_EN_ | PTP_RX_PARSE_CONFIG_IPV6_EN_;
+ txcfg |= PTP_TX_PARSE_CONFIG_IPV4_EN_ | PTP_TX_PARSE_CONFIG_IPV6_EN_;
+ }
+ lanphy_write_page_reg(ptp_priv->phydev, 5, PTP_RX_PARSE_CONFIG, rxcfg);
+ lanphy_write_page_reg(ptp_priv->phydev, 5, PTP_TX_PARSE_CONFIG, txcfg);
+
+ pkt_ts_enable = PTP_TIMESTAMP_EN_SYNC_ | PTP_TIMESTAMP_EN_DREQ_ |
+ PTP_TIMESTAMP_EN_PDREQ_ | PTP_TIMESTAMP_EN_PDRES_;
+ lanphy_write_page_reg(ptp_priv->phydev, 5, PTP_RX_TIMESTAMP_EN, pkt_ts_enable);
+ lanphy_write_page_reg(ptp_priv->phydev, 5, PTP_TX_TIMESTAMP_EN, pkt_ts_enable);
+
+ if (ptp_priv->hwts_tx_type == HWTSTAMP_TX_ONESTEP_SYNC)
+ lanphy_write_page_reg(ptp_priv->phydev, 5, PTP_TX_MOD,
+ PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_);
+
+ if (config.rx_filter != HWTSTAMP_FILTER_NONE)
+ lan8814_config_ts_intr(ptp_priv->phydev, true);
+ else
+ lan8814_config_ts_intr(ptp_priv->phydev, false);
+
+ mutex_lock(&shared->shared_lock);
+ if (config.rx_filter != HWTSTAMP_FILTER_NONE)
+ shared->ref++;
+ else
+ shared->ref--;
+
+ if (shared->ref)
+ lanphy_write_page_reg(ptp_priv->phydev, 4, PTP_CMD_CTL,
+ PTP_CMD_CTL_PTP_ENABLE_);
+ else
+ lanphy_write_page_reg(ptp_priv->phydev, 4, PTP_CMD_CTL,
+ PTP_CMD_CTL_PTP_DISABLE_);
+ mutex_unlock(&shared->shared_lock);
+
+ /* In case of multiple starts and stops, these needs to be cleared */
+ list_for_each_entry_safe(rx_ts, tmp, &ptp_priv->rx_ts_list, list) {
+ list_del(&rx_ts->list);
+ kfree(rx_ts);
+ }
+ skb_queue_purge(&ptp_priv->rx_queue);
+ skb_queue_purge(&ptp_priv->tx_queue);
+
+ lan8814_flush_fifo(ptp_priv->phydev, false);
+ lan8814_flush_fifo(ptp_priv->phydev, true);
+
+ return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
+}
+
+static void lan8814_txtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+
+ switch (ptp_priv->hwts_tx_type) {
+ case HWTSTAMP_TX_ONESTEP_SYNC:
+ if (ptp_msg_is_sync(skb, type)) {
+ kfree_skb(skb);
+ return;
+ }
+ fallthrough;
+ case HWTSTAMP_TX_ON:
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ skb_queue_tail(&ptp_priv->tx_queue, skb);
+ break;
+ case HWTSTAMP_TX_OFF:
+ default:
+ kfree_skb(skb);
+ break;
+ }
+}
+
+static void lan8814_get_sig_rx(struct sk_buff *skb, u16 *sig)
+{
+ struct ptp_header *ptp_header;
+ u32 type;
+
+ skb_push(skb, ETH_HLEN);
+ type = ptp_classify_raw(skb);
+ ptp_header = ptp_parse_header(skb, type);
+ skb_pull_inline(skb, ETH_HLEN);
+
+ *sig = (__force u16)(ntohs(ptp_header->sequence_id));
+}
+
+static bool lan8814_match_rx_skb(struct kszphy_ptp_priv *ptp_priv,
+ struct sk_buff *skb)
+{
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct lan8814_ptp_rx_ts *rx_ts, *tmp;
+ unsigned long flags;
+ bool ret = false;
+ u16 skb_sig;
+
+ lan8814_get_sig_rx(skb, &skb_sig);
+
+ /* Iterate over all RX timestamps and match it with the received skbs */
+ spin_lock_irqsave(&ptp_priv->rx_ts_lock, flags);
+ list_for_each_entry_safe(rx_ts, tmp, &ptp_priv->rx_ts_list, list) {
+ /* Check if we found the signature we were looking for. */
+ if (memcmp(&skb_sig, &rx_ts->seq_id, sizeof(rx_ts->seq_id)))
+ continue;
+
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ktime_set(rx_ts->seconds,
+ rx_ts->nsec);
+ list_del(&rx_ts->list);
+ kfree(rx_ts);
+
+ ret = true;
+ break;
+ }
+ spin_unlock_irqrestore(&ptp_priv->rx_ts_lock, flags);
+
+ if (ret)
+ netif_rx(skb);
+ return ret;
+}
+
+static bool lan8814_rxtstamp(struct mii_timestamper *mii_ts, struct sk_buff *skb, int type)
+{
+ struct kszphy_ptp_priv *ptp_priv =
+ container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+
+ if (ptp_priv->rx_filter == HWTSTAMP_FILTER_NONE ||
+ type == PTP_CLASS_NONE)
+ return false;
+
+ if ((type & ptp_priv->version) == 0 || (type & ptp_priv->layer) == 0)
+ return false;
+
+ /* If we failed to match then add it to the queue for when the timestamp
+ * will come
+ */
+ if (!lan8814_match_rx_skb(ptp_priv, skb))
+ skb_queue_tail(&ptp_priv->rx_queue, skb);
+
+ return true;
+}
+
+static void lan8814_ptp_clock_set(struct phy_device *phydev,
+ u32 seconds, u32 nano_seconds)
+{
+ u32 sec_low, sec_high, nsec_low, nsec_high;
+
+ sec_low = seconds & 0xffff;
+ sec_high = (seconds >> 16) & 0xffff;
+ nsec_low = nano_seconds & 0xffff;
+ nsec_high = (nano_seconds >> 16) & 0x3fff;
+
+ lanphy_write_page_reg(phydev, 4, PTP_CLOCK_SET_SEC_LO, sec_low);
+ lanphy_write_page_reg(phydev, 4, PTP_CLOCK_SET_SEC_MID, sec_high);
+ lanphy_write_page_reg(phydev, 4, PTP_CLOCK_SET_NS_LO, nsec_low);
+ lanphy_write_page_reg(phydev, 4, PTP_CLOCK_SET_NS_HI, nsec_high);
+
+ lanphy_write_page_reg(phydev, 4, PTP_CMD_CTL, PTP_CMD_CTL_PTP_CLOCK_LOAD_);
+}
+
+static void lan8814_ptp_clock_get(struct phy_device *phydev,
+ u32 *seconds, u32 *nano_seconds)
+{
+ lanphy_write_page_reg(phydev, 4, PTP_CMD_CTL, PTP_CMD_CTL_PTP_CLOCK_READ_);
+
+ *seconds = lanphy_read_page_reg(phydev, 4, PTP_CLOCK_READ_SEC_MID);
+ *seconds = (*seconds << 16) |
+ lanphy_read_page_reg(phydev, 4, PTP_CLOCK_READ_SEC_LO);
+
+ *nano_seconds = lanphy_read_page_reg(phydev, 4, PTP_CLOCK_READ_NS_HI);
+ *nano_seconds = ((*nano_seconds & 0x3fff) << 16) |
+ lanphy_read_page_reg(phydev, 4, PTP_CLOCK_READ_NS_LO);
+}
+
+static int lan8814_ptpci_gettime64(struct ptp_clock_info *ptpci,
+ struct timespec64 *ts)
+{
+ struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = shared->phydev;
+ u32 nano_seconds;
+ u32 seconds;
+
+ mutex_lock(&shared->shared_lock);
+ lan8814_ptp_clock_get(phydev, &seconds, &nano_seconds);
+ mutex_unlock(&shared->shared_lock);
+ ts->tv_sec = seconds;
+ ts->tv_nsec = nano_seconds;
+
+ return 0;
+}
+
+static int lan8814_ptpci_settime64(struct ptp_clock_info *ptpci,
+ const struct timespec64 *ts)
+{
+ struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = shared->phydev;
+
+ mutex_lock(&shared->shared_lock);
+ lan8814_ptp_clock_set(phydev, ts->tv_sec, ts->tv_nsec);
+ mutex_unlock(&shared->shared_lock);
+
+ return 0;
+}
+
+static void lan8814_ptp_clock_step(struct phy_device *phydev,
+ s64 time_step_ns)
+{
+ u32 nano_seconds_step;
+ u64 abs_time_step_ns;
+ u32 unsigned_seconds;
+ u32 nano_seconds;
+ u32 remainder;
+ s32 seconds;
+
+ if (time_step_ns > 15000000000LL) {
+ /* convert to clock set */
+ lan8814_ptp_clock_get(phydev, &unsigned_seconds, &nano_seconds);
+ unsigned_seconds += div_u64_rem(time_step_ns, 1000000000LL,
+ &remainder);
+ nano_seconds += remainder;
+ if (nano_seconds >= 1000000000) {
+ unsigned_seconds++;
+ nano_seconds -= 1000000000;
+ }
+ lan8814_ptp_clock_set(phydev, unsigned_seconds, nano_seconds);
+ return;
+ } else if (time_step_ns < -15000000000LL) {
+ /* convert to clock set */
+ time_step_ns = -time_step_ns;
+
+ lan8814_ptp_clock_get(phydev, &unsigned_seconds, &nano_seconds);
+ unsigned_seconds -= div_u64_rem(time_step_ns, 1000000000LL,
+ &remainder);
+ nano_seconds_step = remainder;
+ if (nano_seconds < nano_seconds_step) {
+ unsigned_seconds--;
+ nano_seconds += 1000000000;
+ }
+ nano_seconds -= nano_seconds_step;
+ lan8814_ptp_clock_set(phydev, unsigned_seconds,
+ nano_seconds);
+ return;
+ }
+
+ /* do clock step */
+ if (time_step_ns >= 0) {
+ abs_time_step_ns = (u64)time_step_ns;
+ seconds = (s32)div_u64_rem(abs_time_step_ns, 1000000000,
+ &remainder);
+ nano_seconds = remainder;
+ } else {
+ abs_time_step_ns = (u64)(-time_step_ns);
+ seconds = -((s32)div_u64_rem(abs_time_step_ns, 1000000000,
+ &remainder));
+ nano_seconds = remainder;
+ if (nano_seconds > 0) {
+ /* subtracting nano seconds is not allowed
+ * convert to subtracting from seconds,
+ * and adding to nanoseconds
+ */
+ seconds--;
+ nano_seconds = (1000000000 - nano_seconds);
+ }
+ }
+
+ if (nano_seconds > 0) {
+ /* add 8 ns to cover the likely normal increment */
+ nano_seconds += 8;
+ }
+
+ if (nano_seconds >= 1000000000) {
+ /* carry into seconds */
+ seconds++;
+ nano_seconds -= 1000000000;
+ }
+
+ while (seconds) {
+ if (seconds > 0) {
+ u32 adjustment_value = (u32)seconds;
+ u16 adjustment_value_lo, adjustment_value_hi;
+
+ if (adjustment_value > 0xF)
+ adjustment_value = 0xF;
+
+ adjustment_value_lo = adjustment_value & 0xffff;
+ adjustment_value_hi = (adjustment_value >> 16) & 0x3fff;
+
+ lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_LO,
+ adjustment_value_lo);
+ lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_HI,
+ PTP_LTC_STEP_ADJ_DIR_ |
+ adjustment_value_hi);
+ seconds -= ((s32)adjustment_value);
+ } else {
+ u32 adjustment_value = (u32)(-seconds);
+ u16 adjustment_value_lo, adjustment_value_hi;
+
+ if (adjustment_value > 0xF)
+ adjustment_value = 0xF;
+
+ adjustment_value_lo = adjustment_value & 0xffff;
+ adjustment_value_hi = (adjustment_value >> 16) & 0x3fff;
+
+ lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_LO,
+ adjustment_value_lo);
+ lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_HI,
+ adjustment_value_hi);
+ seconds += ((s32)adjustment_value);
+ }
+ lanphy_write_page_reg(phydev, 4, PTP_CMD_CTL,
+ PTP_CMD_CTL_PTP_LTC_STEP_SEC_);
+ }
+ if (nano_seconds) {
+ u16 nano_seconds_lo;
+ u16 nano_seconds_hi;
+
+ nano_seconds_lo = nano_seconds & 0xffff;
+ nano_seconds_hi = (nano_seconds >> 16) & 0x3fff;
+
+ lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_LO,
+ nano_seconds_lo);
+ lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_HI,
+ PTP_LTC_STEP_ADJ_DIR_ |
+ nano_seconds_hi);
+ lanphy_write_page_reg(phydev, 4, PTP_CMD_CTL,
+ PTP_CMD_CTL_PTP_LTC_STEP_NSEC_);
+ }
+}
+
+static int lan8814_ptpci_adjtime(struct ptp_clock_info *ptpci, s64 delta)
+{
+ struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = shared->phydev;
+
+ mutex_lock(&shared->shared_lock);
+ lan8814_ptp_clock_step(phydev, delta);
+ mutex_unlock(&shared->shared_lock);
+
+ return 0;
+}
+
+static int lan8814_ptpci_adjfine(struct ptp_clock_info *ptpci, long scaled_ppm)
+{
+ struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = shared->phydev;
+ u16 kszphy_rate_adj_lo, kszphy_rate_adj_hi;
+ bool positive = true;
+ u32 kszphy_rate_adj;
+
+ if (scaled_ppm < 0) {
+ scaled_ppm = -scaled_ppm;
+ positive = false;
+ }
+
+ kszphy_rate_adj = LAN8814_1PPM_FORMAT * (scaled_ppm >> 16);
+ kszphy_rate_adj += (LAN8814_1PPM_FORMAT * (0xffff & scaled_ppm)) >> 16;
+
+ kszphy_rate_adj_lo = kszphy_rate_adj & 0xffff;
+ kszphy_rate_adj_hi = (kszphy_rate_adj >> 16) & 0x3fff;
+
+ if (positive)
+ kszphy_rate_adj_hi |= PTP_CLOCK_RATE_ADJ_DIR_;
+
+ mutex_lock(&shared->shared_lock);
+ lanphy_write_page_reg(phydev, 4, PTP_CLOCK_RATE_ADJ_HI, kszphy_rate_adj_hi);
+ lanphy_write_page_reg(phydev, 4, PTP_CLOCK_RATE_ADJ_LO, kszphy_rate_adj_lo);
+ mutex_unlock(&shared->shared_lock);
+
+ return 0;
+}
+
+static void lan8814_get_sig_tx(struct sk_buff *skb, u16 *sig)
+{
+ struct ptp_header *ptp_header;
+ u32 type;
+
+ type = ptp_classify_raw(skb);
+ ptp_header = ptp_parse_header(skb, type);
+
+ *sig = (__force u16)(ntohs(ptp_header->sequence_id));
+}
+
+static void lan8814_match_tx_skb(struct kszphy_ptp_priv *ptp_priv,
+ u32 seconds, u32 nsec, u16 seq_id)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb, *skb_tmp;
+ unsigned long flags;
+ bool ret = false;
+ u16 skb_sig;
+
+ spin_lock_irqsave(&ptp_priv->tx_queue.lock, flags);
+ skb_queue_walk_safe(&ptp_priv->tx_queue, skb, skb_tmp) {
+ lan8814_get_sig_tx(skb, &skb_sig);
+
+ if (memcmp(&skb_sig, &seq_id, sizeof(seq_id)))
+ continue;
+
+ __skb_unlink(skb, &ptp_priv->tx_queue);
+ ret = true;
+ break;
+ }
+ spin_unlock_irqrestore(&ptp_priv->tx_queue.lock, flags);
+
+ if (ret) {
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ktime_set(seconds, nsec);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+ }
+}
+
+static void lan8814_dequeue_tx_skb(struct kszphy_ptp_priv *ptp_priv)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ u32 seconds, nsec;
+ u16 seq_id;
+
+ lan8814_ptp_tx_ts_get(phydev, &seconds, &nsec, &seq_id);
+ lan8814_match_tx_skb(ptp_priv, seconds, nsec, seq_id);
+}
+
+static void lan8814_get_tx_ts(struct kszphy_ptp_priv *ptp_priv)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ u32 reg;
+
+ do {
+ lan8814_dequeue_tx_skb(ptp_priv);
+
+ /* If other timestamps are available in the FIFO,
+ * process them.
+ */
+ reg = lanphy_read_page_reg(phydev, 5, PTP_CAP_INFO);
+ } while (PTP_CAP_INFO_TX_TS_CNT_GET_(reg) > 0);
+}
+
+static bool lan8814_match_skb(struct kszphy_ptp_priv *ptp_priv,
+ struct lan8814_ptp_rx_ts *rx_ts)
+{
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct sk_buff *skb, *skb_tmp;
+ unsigned long flags;
+ bool ret = false;
+ u16 skb_sig;
+
+ spin_lock_irqsave(&ptp_priv->rx_queue.lock, flags);
+ skb_queue_walk_safe(&ptp_priv->rx_queue, skb, skb_tmp) {
+ lan8814_get_sig_rx(skb, &skb_sig);
+
+ if (memcmp(&skb_sig, &rx_ts->seq_id, sizeof(rx_ts->seq_id)))
+ continue;
+
+ __skb_unlink(skb, &ptp_priv->rx_queue);
+
+ ret = true;
+ break;
+ }
+ spin_unlock_irqrestore(&ptp_priv->rx_queue.lock, flags);
+
+ if (ret) {
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ktime_set(rx_ts->seconds, rx_ts->nsec);
+ netif_rx(skb);
+ }
+
+ return ret;
+}
+
+static void lan8814_match_rx_ts(struct kszphy_ptp_priv *ptp_priv,
+ struct lan8814_ptp_rx_ts *rx_ts)
+{
+ unsigned long flags;
+
+ /* If we failed to match the skb add it to the queue for when
+ * the frame will come
+ */
+ if (!lan8814_match_skb(ptp_priv, rx_ts)) {
+ spin_lock_irqsave(&ptp_priv->rx_ts_lock, flags);
+ list_add(&rx_ts->list, &ptp_priv->rx_ts_list);
+ spin_unlock_irqrestore(&ptp_priv->rx_ts_lock, flags);
+ } else {
+ kfree(rx_ts);
+ }
+}
+
+static void lan8814_get_rx_ts(struct kszphy_ptp_priv *ptp_priv)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct lan8814_ptp_rx_ts *rx_ts;
+ u32 reg;
+
+ do {
+ rx_ts = kzalloc(sizeof(*rx_ts), GFP_KERNEL);
+ if (!rx_ts)
+ return;
+
+ lan8814_ptp_rx_ts_get(phydev, &rx_ts->seconds, &rx_ts->nsec,
+ &rx_ts->seq_id);
+ lan8814_match_rx_ts(ptp_priv, rx_ts);
+
+ /* If other timestamps are available in the FIFO,
+ * process them.
+ */
+ reg = lanphy_read_page_reg(phydev, 5, PTP_CAP_INFO);
+ } while (PTP_CAP_INFO_RX_TS_CNT_GET_(reg) > 0);
+}
+
+static void lan8814_handle_ptp_interrupt(struct phy_device *phydev, u16 status)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv;
+
+ if (status & PTP_TSU_INT_STS_PTP_TX_TS_EN_)
+ lan8814_get_tx_ts(ptp_priv);
+
+ if (status & PTP_TSU_INT_STS_PTP_RX_TS_EN_)
+ lan8814_get_rx_ts(ptp_priv);
+
+ if (status & PTP_TSU_INT_STS_PTP_TX_TS_OVRFL_INT_) {
+ lan8814_flush_fifo(phydev, true);
+ skb_queue_purge(&ptp_priv->tx_queue);
+ }
+
+ if (status & PTP_TSU_INT_STS_PTP_RX_TS_OVRFL_INT_) {
+ lan8814_flush_fifo(phydev, false);
+ skb_queue_purge(&ptp_priv->rx_queue);
+ }
+}
+
+static int lan8804_config_init(struct phy_device *phydev)
+{
+ int val;
+
+ /* MDI-X setting for swap A,B transmit */
+ val = lanphy_read_page_reg(phydev, 2, LAN8804_ALIGN_SWAP);
+ val &= ~LAN8804_ALIGN_TX_A_B_SWAP_MASK;
+ val |= LAN8804_ALIGN_TX_A_B_SWAP;
+ lanphy_write_page_reg(phydev, 2, LAN8804_ALIGN_SWAP, val);
+
+ /* Make sure that the PHY will not stop generating the clock when the
+ * link partner goes down
+ */
+ lanphy_write_page_reg(phydev, 31, LAN8814_CLOCK_MANAGEMENT, 0x27e);
+ lanphy_read_page_reg(phydev, 1, LAN8814_LINK_QUALITY);
+
+ return 0;
+}
+
+static irqreturn_t lan8804_handle_interrupt(struct phy_device *phydev)
+{
+ int status;
+
+ status = phy_read(phydev, LAN8814_INTS);
+ if (status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (status > 0)
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+#define LAN8804_OUTPUT_CONTROL 25
+#define LAN8804_OUTPUT_CONTROL_INTR_BUFFER BIT(14)
+#define LAN8804_CONTROL 31
+#define LAN8804_CONTROL_INTR_POLARITY BIT(14)
+
+static int lan8804_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ /* This is an internal PHY of lan966x and is not possible to change the
+ * polarity on the GIC found in lan966x, therefore change the polarity
+ * of the interrupt in the PHY from being active low instead of active
+ * high.
+ */
+ phy_write(phydev, LAN8804_CONTROL, LAN8804_CONTROL_INTR_POLARITY);
+
+ /* By default interrupt buffer is open-drain in which case the interrupt
+ * can be active only low. Therefore change the interrupt buffer to be
+ * push-pull to be able to change interrupt polarity
+ */
+ phy_write(phydev, LAN8804_OUTPUT_CONTROL,
+ LAN8804_OUTPUT_CONTROL_INTR_BUFFER);
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = phy_read(phydev, LAN8814_INTS);
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, LAN8814_INTC, LAN8814_INT_LINK);
+ if (err)
+ return err;
+ } else {
+ err = phy_write(phydev, LAN8814_INTC, 0);
+ if (err)
+ return err;
+
+ err = phy_read(phydev, LAN8814_INTS);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static irqreturn_t lan8814_handle_interrupt(struct phy_device *phydev)
+{
+ int ret = IRQ_NONE;
+ int irq_status;
+
+ irq_status = phy_read(phydev, LAN8814_INTS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (irq_status & LAN8814_INT_LINK) {
+ phy_trigger_machine(phydev);
+ ret = IRQ_HANDLED;
+ }
+
+ while (true) {
+ irq_status = lanphy_read_page_reg(phydev, 5, PTP_TSU_INT_STS);
+ if (!irq_status)
+ break;
+
+ lan8814_handle_ptp_interrupt(phydev, irq_status);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static int lan8814_ack_interrupt(struct phy_device *phydev)
+{
+ /* bit[12..0] int status, which is a read and clear register. */
+ int rc;
+
+ rc = phy_read(phydev, LAN8814_INTS);
+
+ return (rc < 0) ? rc : 0;
+}
+
+static int lan8814_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ lanphy_write_page_reg(phydev, 4, LAN8814_INTR_CTRL_REG,
+ LAN8814_INTR_CTRL_REG_POLARITY |
+ LAN8814_INTR_CTRL_REG_INTR_ENABLE);
+
+ /* enable / disable interrupts */
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = lan8814_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, LAN8814_INTC, LAN8814_INT_LINK);
+ } else {
+ err = phy_write(phydev, LAN8814_INTC, 0);
+ if (err)
+ return err;
+
+ err = lan8814_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static void lan8814_ptp_init(struct phy_device *phydev)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv;
+ u32 temp;
+
+ if (!IS_ENABLED(CONFIG_PTP_1588_CLOCK) ||
+ !IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING))
+ return;
+
+ lanphy_write_page_reg(phydev, 5, TSU_HARD_RESET, TSU_HARD_RESET_);
+
+ temp = lanphy_read_page_reg(phydev, 5, PTP_TX_MOD);
+ temp |= PTP_TX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_;
+ lanphy_write_page_reg(phydev, 5, PTP_TX_MOD, temp);
+
+ temp = lanphy_read_page_reg(phydev, 5, PTP_RX_MOD);
+ temp |= PTP_RX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_;
+ lanphy_write_page_reg(phydev, 5, PTP_RX_MOD, temp);
+
+ lanphy_write_page_reg(phydev, 5, PTP_RX_PARSE_CONFIG, 0);
+ lanphy_write_page_reg(phydev, 5, PTP_TX_PARSE_CONFIG, 0);
+
+ /* Removing default registers configs related to L2 and IP */
+ lanphy_write_page_reg(phydev, 5, PTP_TX_PARSE_L2_ADDR_EN, 0);
+ lanphy_write_page_reg(phydev, 5, PTP_RX_PARSE_L2_ADDR_EN, 0);
+ lanphy_write_page_reg(phydev, 5, PTP_TX_PARSE_IP_ADDR_EN, 0);
+ lanphy_write_page_reg(phydev, 5, PTP_RX_PARSE_IP_ADDR_EN, 0);
+
+ /* Disable checking for minorVersionPTP field */
+ lanphy_write_page_reg(phydev, 5, PTP_RX_VERSION,
+ PTP_MAX_VERSION(0xff) | PTP_MIN_VERSION(0x0));
+ lanphy_write_page_reg(phydev, 5, PTP_TX_VERSION,
+ PTP_MAX_VERSION(0xff) | PTP_MIN_VERSION(0x0));
+
+ skb_queue_head_init(&ptp_priv->tx_queue);
+ skb_queue_head_init(&ptp_priv->rx_queue);
+ INIT_LIST_HEAD(&ptp_priv->rx_ts_list);
+ spin_lock_init(&ptp_priv->rx_ts_lock);
+
+ ptp_priv->phydev = phydev;
+
+ ptp_priv->mii_ts.rxtstamp = lan8814_rxtstamp;
+ ptp_priv->mii_ts.txtstamp = lan8814_txtstamp;
+ ptp_priv->mii_ts.hwtstamp = lan8814_hwtstamp;
+ ptp_priv->mii_ts.ts_info = lan8814_ts_info;
+
+ phydev->mii_ts = &ptp_priv->mii_ts;
+}
+
+static int lan8814_ptp_probe_once(struct phy_device *phydev)
+{
+ struct lan8814_shared_priv *shared = phydev->shared->priv;
+
+ /* Initialise shared lock for clock*/
+ mutex_init(&shared->shared_lock);
+
+ shared->ptp_clock_info.owner = THIS_MODULE;
+ snprintf(shared->ptp_clock_info.name, 30, "%s", phydev->drv->name);
+ shared->ptp_clock_info.max_adj = 31249999;
+ shared->ptp_clock_info.n_alarm = 0;
+ shared->ptp_clock_info.n_ext_ts = 0;
+ shared->ptp_clock_info.n_pins = 0;
+ shared->ptp_clock_info.pps = 0;
+ shared->ptp_clock_info.pin_config = NULL;
+ shared->ptp_clock_info.adjfine = lan8814_ptpci_adjfine;
+ shared->ptp_clock_info.adjtime = lan8814_ptpci_adjtime;
+ shared->ptp_clock_info.gettime64 = lan8814_ptpci_gettime64;
+ shared->ptp_clock_info.settime64 = lan8814_ptpci_settime64;
+ shared->ptp_clock_info.getcrosststamp = NULL;
+
+ shared->ptp_clock = ptp_clock_register(&shared->ptp_clock_info,
+ &phydev->mdio.dev);
+ if (IS_ERR(shared->ptp_clock)) {
+ phydev_err(phydev, "ptp_clock_register failed %lu\n",
+ PTR_ERR(shared->ptp_clock));
+ return -EINVAL;
+ }
+
+ /* Check if PHC support is missing at the configuration level */
+ if (!shared->ptp_clock)
+ return 0;
+
+ phydev_dbg(phydev, "successfully registered ptp clock\n");
+
+ shared->phydev = phydev;
+
+ /* The EP.4 is shared between all the PHYs in the package and also it
+ * can be accessed by any of the PHYs
+ */
+ lanphy_write_page_reg(phydev, 4, LTC_HARD_RESET, LTC_HARD_RESET_);
+ lanphy_write_page_reg(phydev, 4, PTP_OPERATING_MODE,
+ PTP_OPERATING_MODE_STANDALONE_);
+
+ return 0;
+}
+
+static void lan8814_setup_led(struct phy_device *phydev, int val)
+{
+ int temp;
+
+ temp = lanphy_read_page_reg(phydev, 5, LAN8814_LED_CTRL_1);
+
+ if (val)
+ temp |= LAN8814_LED_CTRL_1_KSZ9031_LED_MODE_;
+ else
+ temp &= ~LAN8814_LED_CTRL_1_KSZ9031_LED_MODE_;
+
+ lanphy_write_page_reg(phydev, 5, LAN8814_LED_CTRL_1, temp);
+}
+
+static int lan8814_config_init(struct phy_device *phydev)
+{
+ struct kszphy_priv *lan8814 = phydev->priv;
+ int val;
+
+ /* Reset the PHY */
+ val = lanphy_read_page_reg(phydev, 4, LAN8814_QSGMII_SOFT_RESET);
+ val |= LAN8814_QSGMII_SOFT_RESET_BIT;
+ lanphy_write_page_reg(phydev, 4, LAN8814_QSGMII_SOFT_RESET, val);
+
+ /* Disable ANEG with QSGMII PCS Host side */
+ val = lanphy_read_page_reg(phydev, 5, LAN8814_QSGMII_PCS1G_ANEG_CONFIG);
+ val &= ~LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA;
+ lanphy_write_page_reg(phydev, 5, LAN8814_QSGMII_PCS1G_ANEG_CONFIG, val);
+
+ /* MDI-X setting for swap A,B transmit */
+ val = lanphy_read_page_reg(phydev, 2, LAN8814_ALIGN_SWAP);
+ val &= ~LAN8814_ALIGN_TX_A_B_SWAP_MASK;
+ val |= LAN8814_ALIGN_TX_A_B_SWAP;
+ lanphy_write_page_reg(phydev, 2, LAN8814_ALIGN_SWAP, val);
+
+ if (lan8814->led_mode >= 0)
+ lan8814_setup_led(phydev, lan8814->led_mode);
+
+ return 0;
+}
+
+/* It is expected that there will not be any 'lan8814_take_coma_mode'
+ * function called in suspend. Because the GPIO line can be shared, so if one of
+ * the phys goes back in coma mode, then all the other PHYs will go, which is
+ * wrong.
+ */
+static int lan8814_release_coma_mode(struct phy_device *phydev)
+{
+ struct gpio_desc *gpiod;
+
+ gpiod = devm_gpiod_get_optional(&phydev->mdio.dev, "coma-mode",
+ GPIOD_OUT_HIGH_OPEN_DRAIN |
+ GPIOD_FLAGS_BIT_NONEXCLUSIVE);
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ gpiod_set_consumer_name(gpiod, "LAN8814 coma mode");
+ gpiod_set_value_cansleep(gpiod, 0);
+
+ return 0;
+}
+
+static int lan8814_probe(struct phy_device *phydev)
+{
+ const struct kszphy_type *type = phydev->drv->driver_data;
+ struct kszphy_priv *priv;
+ u16 addr;
+ int err;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ priv->type = type;
+
+ kszphy_parse_led_mode(phydev);
+
+ /* Strap-in value for PHY address, below register read gives starting
+ * phy address value
+ */
+ addr = lanphy_read_page_reg(phydev, 4, 0) & 0x1F;
+ devm_phy_package_join(&phydev->mdio.dev, phydev,
+ addr, sizeof(struct lan8814_shared_priv));
+
+ if (phy_package_init_once(phydev)) {
+ err = lan8814_release_coma_mode(phydev);
+ if (err)
+ return err;
+
+ err = lan8814_ptp_probe_once(phydev);
+ if (err)
+ return err;
+ }
+
+ lan8814_ptp_init(phydev);
+
+ return 0;
+}
+
+#define LAN8841_MMD_TIMER_REG 0
+#define LAN8841_MMD0_REGISTER_17 17
+#define LAN8841_MMD0_REGISTER_17_DROP_OPT(x) ((x) & 0x3)
+#define LAN8841_MMD0_REGISTER_17_XMIT_TOG_TX_DIS BIT(3)
+#define LAN8841_OPERATION_MODE_STRAP_OVERRIDE_LOW_REG 2
+#define LAN8841_OPERATION_MODE_STRAP_OVERRIDE_LOW_REG_MAGJACK BIT(14)
+#define LAN8841_MMD_ANALOG_REG 28
+#define LAN8841_ANALOG_CONTROL_1 1
+#define LAN8841_ANALOG_CONTROL_1_PLL_TRIM(x) (((x) & 0x3) << 5)
+#define LAN8841_ANALOG_CONTROL_10 13
+#define LAN8841_ANALOG_CONTROL_10_PLL_DIV(x) ((x) & 0x3)
+#define LAN8841_ANALOG_CONTROL_11 14
+#define LAN8841_ANALOG_CONTROL_11_LDO_REF(x) (((x) & 0x7) << 12)
+#define LAN8841_TX_LOW_I_CH_C_D_POWER_MANAGMENT 69
+#define LAN8841_TX_LOW_I_CH_C_D_POWER_MANAGMENT_VAL 0xbffc
+#define LAN8841_BTRX_POWER_DOWN 70
+#define LAN8841_BTRX_POWER_DOWN_QBIAS_CH_A BIT(0)
+#define LAN8841_BTRX_POWER_DOWN_BTRX_CH_A BIT(1)
+#define LAN8841_BTRX_POWER_DOWN_QBIAS_CH_B BIT(2)
+#define LAN8841_BTRX_POWER_DOWN_BTRX_CH_B BIT(3)
+#define LAN8841_BTRX_POWER_DOWN_BTRX_CH_C BIT(5)
+#define LAN8841_BTRX_POWER_DOWN_BTRX_CH_D BIT(7)
+#define LAN8841_ADC_CHANNEL_MASK 198
+#define LAN8841_PTP_RX_PARSE_L2_ADDR_EN 370
+#define LAN8841_PTP_RX_PARSE_IP_ADDR_EN 371
+#define LAN8841_PTP_RX_VERSION 374
+#define LAN8841_PTP_TX_PARSE_L2_ADDR_EN 434
+#define LAN8841_PTP_TX_PARSE_IP_ADDR_EN 435
+#define LAN8841_PTP_TX_VERSION 438
+#define LAN8841_PTP_CMD_CTL 256
+#define LAN8841_PTP_CMD_CTL_PTP_ENABLE BIT(2)
+#define LAN8841_PTP_CMD_CTL_PTP_DISABLE BIT(1)
+#define LAN8841_PTP_CMD_CTL_PTP_RESET BIT(0)
+#define LAN8841_PTP_RX_PARSE_CONFIG 368
+#define LAN8841_PTP_TX_PARSE_CONFIG 432
+#define LAN8841_PTP_RX_MODE 381
+#define LAN8841_PTP_INSERT_TS_EN BIT(0)
+#define LAN8841_PTP_INSERT_TS_32BIT BIT(1)
+
+static int lan8841_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = ksz9131_config_init(phydev);
+ if (ret)
+ return ret;
+
+ /* Initialize the HW by resetting everything */
+ phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_RESET,
+ LAN8841_PTP_CMD_CTL_PTP_RESET);
+
+ phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_ENABLE,
+ LAN8841_PTP_CMD_CTL_PTP_ENABLE);
+
+ /* Don't process any frames */
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_RX_PARSE_CONFIG, 0);
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_TX_PARSE_CONFIG, 0);
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_TX_PARSE_L2_ADDR_EN, 0);
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_RX_PARSE_L2_ADDR_EN, 0);
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_TX_PARSE_IP_ADDR_EN, 0);
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_RX_PARSE_IP_ADDR_EN, 0);
+
+ /* Disable checking for minorVersionPTP field */
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_RX_VERSION, 0xff00);
+ phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_TX_VERSION, 0xff00);
+
+ /* 100BT Clause 40 improvenent errata */
+ phy_write_mmd(phydev, LAN8841_MMD_ANALOG_REG,
+ LAN8841_ANALOG_CONTROL_1,
+ LAN8841_ANALOG_CONTROL_1_PLL_TRIM(0x2));
+ phy_write_mmd(phydev, LAN8841_MMD_ANALOG_REG,
+ LAN8841_ANALOG_CONTROL_10,
+ LAN8841_ANALOG_CONTROL_10_PLL_DIV(0x1));
+
+ /* 10M/100M Ethernet Signal Tuning Errata for Shorted-Center Tap
+ * Magnetics
+ */
+ ret = phy_read_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_OPERATION_MODE_STRAP_OVERRIDE_LOW_REG);
+ if (ret & LAN8841_OPERATION_MODE_STRAP_OVERRIDE_LOW_REG_MAGJACK) {
+ phy_write_mmd(phydev, LAN8841_MMD_ANALOG_REG,
+ LAN8841_TX_LOW_I_CH_C_D_POWER_MANAGMENT,
+ LAN8841_TX_LOW_I_CH_C_D_POWER_MANAGMENT_VAL);
+ phy_write_mmd(phydev, LAN8841_MMD_ANALOG_REG,
+ LAN8841_BTRX_POWER_DOWN,
+ LAN8841_BTRX_POWER_DOWN_QBIAS_CH_A |
+ LAN8841_BTRX_POWER_DOWN_BTRX_CH_A |
+ LAN8841_BTRX_POWER_DOWN_QBIAS_CH_B |
+ LAN8841_BTRX_POWER_DOWN_BTRX_CH_B |
+ LAN8841_BTRX_POWER_DOWN_BTRX_CH_C |
+ LAN8841_BTRX_POWER_DOWN_BTRX_CH_D);
+ }
+
+ /* LDO Adjustment errata */
+ phy_write_mmd(phydev, LAN8841_MMD_ANALOG_REG,
+ LAN8841_ANALOG_CONTROL_11,
+ LAN8841_ANALOG_CONTROL_11_LDO_REF(1));
+
+ /* 100BT RGMII latency tuning errata */
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD,
+ LAN8841_ADC_CHANNEL_MASK, 0x0);
+ phy_write_mmd(phydev, LAN8841_MMD_TIMER_REG,
+ LAN8841_MMD0_REGISTER_17,
+ LAN8841_MMD0_REGISTER_17_DROP_OPT(2) |
+ LAN8841_MMD0_REGISTER_17_XMIT_TOG_TX_DIS);
+
+ return 0;
+}
+
+#define LAN8841_OUTPUT_CTRL 25
+#define LAN8841_OUTPUT_CTRL_INT_BUFFER BIT(14)
+#define LAN8841_INT_PTP BIT(9)
+
+static int lan8841_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ phy_modify(phydev, LAN8841_OUTPUT_CTRL,
+ LAN8841_OUTPUT_CTRL_INT_BUFFER, 0);
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = phy_read(phydev, LAN8814_INTS);
+ if (err)
+ return err;
+
+ /* Enable / disable interrupts. It is OK to enable PTP interrupt
+ * even if it PTP is not enabled. Because the underneath blocks
+ * will not enable the PTP so we will never get the PTP
+ * interrupt.
+ */
+ err = phy_write(phydev, LAN8814_INTC,
+ LAN8814_INT_LINK | LAN8841_INT_PTP);
+ } else {
+ err = phy_write(phydev, LAN8814_INTC, 0);
+ if (err)
+ return err;
+
+ err = phy_read(phydev, LAN8814_INTS);
+ }
+
+ return err;
+}
+
+#define LAN8841_PTP_TX_EGRESS_SEC_LO 453
+#define LAN8841_PTP_TX_EGRESS_SEC_HI 452
+#define LAN8841_PTP_TX_EGRESS_NS_LO 451
+#define LAN8841_PTP_TX_EGRESS_NS_HI 450
+#define LAN8841_PTP_TX_EGRESS_NSEC_HI_VALID BIT(15)
+#define LAN8841_PTP_TX_MSG_HEADER2 455
+
+static bool lan8841_ptp_get_tx_ts(struct kszphy_ptp_priv *ptp_priv,
+ u32 *sec, u32 *nsec, u16 *seq)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+
+ *nsec = phy_read_mmd(phydev, 2, LAN8841_PTP_TX_EGRESS_NS_HI);
+ if (!(*nsec & LAN8841_PTP_TX_EGRESS_NSEC_HI_VALID))
+ return false;
+
+ *nsec = ((*nsec & 0x3fff) << 16);
+ *nsec = *nsec | phy_read_mmd(phydev, 2, LAN8841_PTP_TX_EGRESS_NS_LO);
+
+ *sec = phy_read_mmd(phydev, 2, LAN8841_PTP_TX_EGRESS_SEC_HI);
+ *sec = *sec << 16;
+ *sec = *sec | phy_read_mmd(phydev, 2, LAN8841_PTP_TX_EGRESS_SEC_LO);
+
+ *seq = phy_read_mmd(phydev, 2, LAN8841_PTP_TX_MSG_HEADER2);
+
+ return true;
+}
+
+static void lan8841_ptp_process_tx_ts(struct kszphy_ptp_priv *ptp_priv)
+{
+ u32 sec, nsec;
+ u16 seq;
+
+ while (lan8841_ptp_get_tx_ts(ptp_priv, &sec, &nsec, &seq))
+ lan8814_match_tx_skb(ptp_priv, sec, nsec, seq);
+}
+
+#define LAN8841_PTP_INT_STS 259
+#define LAN8841_PTP_INT_STS_PTP_TX_TS_OVRFL_INT BIT(13)
+#define LAN8841_PTP_INT_STS_PTP_TX_TS_INT BIT(12)
+#define LAN8841_PTP_INT_STS_PTP_GPIO_CAP_INT BIT(2)
+
+static void lan8841_ptp_flush_fifo(struct kszphy_ptp_priv *ptp_priv)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ int i;
+
+ for (i = 0; i < FIFO_SIZE; ++i)
+ phy_read_mmd(phydev, 2, LAN8841_PTP_TX_MSG_HEADER2);
+
+ phy_read_mmd(phydev, 2, LAN8841_PTP_INT_STS);
+}
+
+#define LAN8841_PTP_GPIO_CAP_STS 506
+#define LAN8841_PTP_GPIO_SEL 327
+#define LAN8841_PTP_GPIO_SEL_GPIO_SEL(gpio) ((gpio) << 8)
+#define LAN8841_PTP_GPIO_RE_LTC_SEC_HI_CAP 498
+#define LAN8841_PTP_GPIO_RE_LTC_SEC_LO_CAP 499
+#define LAN8841_PTP_GPIO_RE_LTC_NS_HI_CAP 500
+#define LAN8841_PTP_GPIO_RE_LTC_NS_LO_CAP 501
+#define LAN8841_PTP_GPIO_FE_LTC_SEC_HI_CAP 502
+#define LAN8841_PTP_GPIO_FE_LTC_SEC_LO_CAP 503
+#define LAN8841_PTP_GPIO_FE_LTC_NS_HI_CAP 504
+#define LAN8841_PTP_GPIO_FE_LTC_NS_LO_CAP 505
+
+static void lan8841_gpio_process_cap(struct kszphy_ptp_priv *ptp_priv)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct ptp_clock_event ptp_event = {0};
+ int pin, ret, tmp;
+ s32 sec, nsec;
+
+ pin = ptp_find_pin_unlocked(ptp_priv->ptp_clock, PTP_PF_EXTTS, 0);
+ if (pin == -1)
+ return;
+
+ tmp = phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_CAP_STS);
+ if (tmp < 0)
+ return;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_GPIO_SEL,
+ LAN8841_PTP_GPIO_SEL_GPIO_SEL(pin));
+ if (ret)
+ return;
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ if (tmp & BIT(pin)) {
+ sec = phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_RE_LTC_SEC_HI_CAP);
+ sec <<= 16;
+ sec |= phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_RE_LTC_SEC_LO_CAP);
+
+ nsec = phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_RE_LTC_NS_HI_CAP) & 0x3fff;
+ nsec <<= 16;
+ nsec |= phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_RE_LTC_NS_LO_CAP);
+ } else {
+ sec = phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_FE_LTC_SEC_HI_CAP);
+ sec <<= 16;
+ sec |= phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_FE_LTC_SEC_LO_CAP);
+
+ nsec = phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_FE_LTC_NS_HI_CAP) & 0x3fff;
+ nsec <<= 16;
+ nsec |= phy_read_mmd(phydev, 2, LAN8841_PTP_GPIO_FE_LTC_NS_LO_CAP);
+ }
+ mutex_unlock(&ptp_priv->ptp_lock);
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_GPIO_SEL, 0);
+ if (ret)
+ return;
+
+ ptp_event.index = 0;
+ ptp_event.timestamp = ktime_set(sec, nsec);
+ ptp_event.type = PTP_CLOCK_EXTTS;
+ ptp_clock_event(ptp_priv->ptp_clock, &ptp_event);
+}
+
+static void lan8841_handle_ptp_interrupt(struct phy_device *phydev)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv;
+ u16 status;
+
+ do {
+ status = phy_read_mmd(phydev, 2, LAN8841_PTP_INT_STS);
+
+ if (status & LAN8841_PTP_INT_STS_PTP_TX_TS_INT)
+ lan8841_ptp_process_tx_ts(ptp_priv);
+
+ if (status & LAN8841_PTP_INT_STS_PTP_GPIO_CAP_INT)
+ lan8841_gpio_process_cap(ptp_priv);
+
+ if (status & LAN8841_PTP_INT_STS_PTP_TX_TS_OVRFL_INT) {
+ lan8841_ptp_flush_fifo(ptp_priv);
+ skb_queue_purge(&ptp_priv->tx_queue);
+ }
+
+ } while (status & (LAN8841_PTP_INT_STS_PTP_TX_TS_INT |
+ LAN8841_PTP_INT_STS_PTP_GPIO_CAP_INT |
+ LAN8841_PTP_INT_STS_PTP_TX_TS_OVRFL_INT));
+}
+
+#define LAN8841_INTS_PTP BIT(9)
+
+static irqreturn_t lan8841_handle_interrupt(struct phy_device *phydev)
+{
+ irqreturn_t ret = IRQ_NONE;
+ int irq_status;
+
+ irq_status = phy_read(phydev, LAN8814_INTS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (irq_status & LAN8814_INT_LINK) {
+ phy_trigger_machine(phydev);
+ ret = IRQ_HANDLED;
+ }
+
+ if (irq_status & LAN8841_INTS_PTP) {
+ lan8841_handle_ptp_interrupt(phydev);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static int lan8841_ts_info(struct mii_timestamper *mii_ts,
+ struct ethtool_ts_info *info)
+{
+ struct kszphy_ptp_priv *ptp_priv;
+
+ ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+
+ info->phc_index = ptp_priv->ptp_clock ?
+ ptp_clock_index(ptp_priv->ptp_clock) : -1;
+ if (info->phc_index == -1) {
+ info->so_timestamping |= SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_RX_SOFTWARE |
+ SOF_TIMESTAMPING_SOFTWARE;
+ return 0;
+ }
+
+ info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ 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_V2_L4_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_EVENT);
+
+ return 0;
+}
+
+#define LAN8841_PTP_INT_EN 260
+#define LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN BIT(13)
+#define LAN8841_PTP_INT_EN_PTP_TX_TS_EN BIT(12)
+
+static void lan8841_ptp_enable_processing(struct kszphy_ptp_priv *ptp_priv,
+ bool enable)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+
+ if (enable) {
+ /* Enable interrupts on the TX side */
+ phy_modify_mmd(phydev, 2, LAN8841_PTP_INT_EN,
+ LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN |
+ LAN8841_PTP_INT_EN_PTP_TX_TS_EN,
+ LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN |
+ LAN8841_PTP_INT_EN_PTP_TX_TS_EN);
+
+ /* Enable the modification of the frame on RX side,
+ * this will add the ns and 2 bits of sec in the reserved field
+ * of the PTP header
+ */
+ phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_RX_MODE,
+ LAN8841_PTP_INSERT_TS_EN |
+ LAN8841_PTP_INSERT_TS_32BIT,
+ LAN8841_PTP_INSERT_TS_EN |
+ LAN8841_PTP_INSERT_TS_32BIT);
+
+ ptp_schedule_worker(ptp_priv->ptp_clock, 0);
+ } else {
+ /* Disable interrupts on the TX side */
+ phy_modify_mmd(phydev, 2, LAN8841_PTP_INT_EN,
+ LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN |
+ LAN8841_PTP_INT_EN_PTP_TX_TS_EN, 0);
+
+ /* Disable modification of the RX frames */
+ phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_PTP_RX_MODE,
+ LAN8841_PTP_INSERT_TS_EN |
+ LAN8841_PTP_INSERT_TS_32BIT, 0);
+
+ ptp_cancel_worker_sync(ptp_priv->ptp_clock);
+ }
+}
+
+#define LAN8841_PTP_RX_TIMESTAMP_EN 379
+#define LAN8841_PTP_TX_TIMESTAMP_EN 443
+#define LAN8841_PTP_TX_MOD 445
+
+static int lan8841_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct hwtstamp_config config;
+ int txcfg = 0, rxcfg = 0;
+ int pkt_ts_enable;
+
+ if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
+ return -EFAULT;
+
+ ptp_priv->hwts_tx_type = config.tx_type;
+ ptp_priv->rx_filter = config.rx_filter;
+
+ switch (config.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ ptp_priv->layer = 0;
+ ptp_priv->version = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ ptp_priv->layer = PTP_CLASS_L4;
+ ptp_priv->version = PTP_CLASS_V2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ ptp_priv->layer = PTP_CLASS_L2;
+ ptp_priv->version = PTP_CLASS_V2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ ptp_priv->layer = PTP_CLASS_L4 | PTP_CLASS_L2;
+ ptp_priv->version = PTP_CLASS_V2;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ /* Setup parsing of the frames and enable the timestamping for ptp
+ * frames
+ */
+ if (ptp_priv->layer & PTP_CLASS_L2) {
+ rxcfg |= PTP_RX_PARSE_CONFIG_LAYER2_EN_;
+ txcfg |= PTP_TX_PARSE_CONFIG_LAYER2_EN_;
+ } else if (ptp_priv->layer & PTP_CLASS_L4) {
+ rxcfg |= PTP_RX_PARSE_CONFIG_IPV4_EN_ | PTP_RX_PARSE_CONFIG_IPV6_EN_;
+ txcfg |= PTP_TX_PARSE_CONFIG_IPV4_EN_ | PTP_TX_PARSE_CONFIG_IPV6_EN_;
+ }
+
+ phy_write_mmd(phydev, 2, LAN8841_PTP_RX_PARSE_CONFIG, rxcfg);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_TX_PARSE_CONFIG, txcfg);
+
+ pkt_ts_enable = PTP_TIMESTAMP_EN_SYNC_ | PTP_TIMESTAMP_EN_DREQ_ |
+ PTP_TIMESTAMP_EN_PDREQ_ | PTP_TIMESTAMP_EN_PDRES_;
+ phy_write_mmd(phydev, 2, LAN8841_PTP_RX_TIMESTAMP_EN, pkt_ts_enable);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_TX_TIMESTAMP_EN, pkt_ts_enable);
+
+ /* Enable / disable of the TX timestamp in the SYNC frames */
+ phy_modify_mmd(phydev, 2, LAN8841_PTP_TX_MOD,
+ PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_,
+ ptp_priv->hwts_tx_type == HWTSTAMP_TX_ONESTEP_SYNC ?
+ PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_ : 0);
+
+ /* Now enable/disable the timestamping */
+ lan8841_ptp_enable_processing(ptp_priv,
+ config.rx_filter != HWTSTAMP_FILTER_NONE);
+
+ skb_queue_purge(&ptp_priv->tx_queue);
+
+ lan8841_ptp_flush_fifo(ptp_priv);
+
+ return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
+}
+
+static bool lan8841_rxtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct kszphy_ptp_priv *ptp_priv =
+ container_of(mii_ts, struct kszphy_ptp_priv, mii_ts);
+ struct ptp_header *header = ptp_parse_header(skb, type);
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct timespec64 ts;
+ unsigned long flags;
+ u32 ts_header;
+
+ if (!header)
+ return false;
+
+ if (ptp_priv->rx_filter == HWTSTAMP_FILTER_NONE ||
+ type == PTP_CLASS_NONE)
+ return false;
+
+ if ((type & ptp_priv->version) == 0 || (type & ptp_priv->layer) == 0)
+ return false;
+
+ spin_lock_irqsave(&ptp_priv->seconds_lock, flags);
+ ts.tv_sec = ptp_priv->seconds;
+ spin_unlock_irqrestore(&ptp_priv->seconds_lock, flags);
+ ts_header = __be32_to_cpu(header->reserved2);
+
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+
+ /* Check for any wrap arounds for the second part */
+ if ((ts.tv_sec & GENMASK(1, 0)) == 0 && (ts_header >> 30) == 3)
+ ts.tv_sec -= GENMASK(1, 0) + 1;
+ else if ((ts.tv_sec & GENMASK(1, 0)) == 3 && (ts_header >> 30) == 0)
+ ts.tv_sec += 1;
+
+ shhwtstamps->hwtstamp =
+ ktime_set((ts.tv_sec & ~(GENMASK(1, 0))) | ts_header >> 30,
+ ts_header & GENMASK(29, 0));
+ header->reserved2 = 0;
+
+ netif_rx(skb);
+
+ return true;
+}
+
+#define LAN8841_EVENT_A 0
+#define LAN8841_EVENT_B 1
+#define LAN8841_PTP_LTC_TARGET_SEC_HI(event) ((event) == LAN8841_EVENT_A ? 278 : 288)
+#define LAN8841_PTP_LTC_TARGET_SEC_LO(event) ((event) == LAN8841_EVENT_A ? 279 : 289)
+#define LAN8841_PTP_LTC_TARGET_NS_HI(event) ((event) == LAN8841_EVENT_A ? 280 : 290)
+#define LAN8841_PTP_LTC_TARGET_NS_LO(event) ((event) == LAN8841_EVENT_A ? 281 : 291)
+
+static int lan8841_ptp_set_target(struct kszphy_ptp_priv *ptp_priv, u8 event,
+ s64 sec, u32 nsec)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ int ret;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_SEC_HI(event),
+ upper_16_bits(sec));
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_SEC_LO(event),
+ lower_16_bits(sec));
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_NS_HI(event) & 0x3fff,
+ upper_16_bits(nsec));
+ if (ret)
+ return ret;
+
+ return phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_NS_LO(event),
+ lower_16_bits(nsec));
+}
+
+#define LAN8841_BUFFER_TIME 2
+
+static int lan8841_ptp_update_target(struct kszphy_ptp_priv *ptp_priv,
+ const struct timespec64 *ts)
+{
+ return lan8841_ptp_set_target(ptp_priv, LAN8841_EVENT_A,
+ ts->tv_sec + LAN8841_BUFFER_TIME, 0);
+}
+
+#define LAN8841_PTP_LTC_TARGET_RELOAD_SEC_HI(event) ((event) == LAN8841_EVENT_A ? 282 : 292)
+#define LAN8841_PTP_LTC_TARGET_RELOAD_SEC_LO(event) ((event) == LAN8841_EVENT_A ? 283 : 293)
+#define LAN8841_PTP_LTC_TARGET_RELOAD_NS_HI(event) ((event) == LAN8841_EVENT_A ? 284 : 294)
+#define LAN8841_PTP_LTC_TARGET_RELOAD_NS_LO(event) ((event) == LAN8841_EVENT_A ? 285 : 295)
+
+static int lan8841_ptp_set_reload(struct kszphy_ptp_priv *ptp_priv, u8 event,
+ s64 sec, u32 nsec)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ int ret;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_RELOAD_SEC_HI(event),
+ upper_16_bits(sec));
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_RELOAD_SEC_LO(event),
+ lower_16_bits(sec));
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_RELOAD_NS_HI(event) & 0x3fff,
+ upper_16_bits(nsec));
+ if (ret)
+ return ret;
+
+ return phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_TARGET_RELOAD_NS_LO(event),
+ lower_16_bits(nsec));
+}
+
+#define LAN8841_PTP_LTC_SET_SEC_HI 262
+#define LAN8841_PTP_LTC_SET_SEC_MID 263
+#define LAN8841_PTP_LTC_SET_SEC_LO 264
+#define LAN8841_PTP_LTC_SET_NS_HI 265
+#define LAN8841_PTP_LTC_SET_NS_LO 266
+#define LAN8841_PTP_CMD_CTL_PTP_LTC_LOAD BIT(4)
+
+static int lan8841_ptp_settime64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = ptp_priv->phydev;
+ unsigned long flags;
+ int ret;
+
+ /* Set the value to be stored */
+ mutex_lock(&ptp_priv->ptp_lock);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_SET_SEC_LO, lower_16_bits(ts->tv_sec));
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_SET_SEC_MID, upper_16_bits(ts->tv_sec));
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_SET_SEC_HI, upper_32_bits(ts->tv_sec) & 0xffff);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_SET_NS_LO, lower_16_bits(ts->tv_nsec));
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_SET_NS_HI, upper_16_bits(ts->tv_nsec) & 0x3fff);
+
+ /* Set the command to load the LTC */
+ phy_write_mmd(phydev, 2, LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_LTC_LOAD);
+ ret = lan8841_ptp_update_target(ptp_priv, ts);
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ spin_lock_irqsave(&ptp_priv->seconds_lock, flags);
+ ptp_priv->seconds = ts->tv_sec;
+ spin_unlock_irqrestore(&ptp_priv->seconds_lock, flags);
+
+ return ret;
+}
+
+#define LAN8841_PTP_LTC_RD_SEC_HI 358
+#define LAN8841_PTP_LTC_RD_SEC_MID 359
+#define LAN8841_PTP_LTC_RD_SEC_LO 360
+#define LAN8841_PTP_LTC_RD_NS_HI 361
+#define LAN8841_PTP_LTC_RD_NS_LO 362
+#define LAN8841_PTP_CMD_CTL_PTP_LTC_READ BIT(3)
+
+static int lan8841_ptp_gettime64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = ptp_priv->phydev;
+ time64_t s;
+ s64 ns;
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ /* Issue the command to read the LTC */
+ phy_write_mmd(phydev, 2, LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_LTC_READ);
+
+ /* Read the LTC */
+ s = phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_HI);
+ s <<= 16;
+ s |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_MID);
+ s <<= 16;
+ s |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_LO);
+
+ ns = phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_NS_HI) & 0x3fff;
+ ns <<= 16;
+ ns |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_NS_LO);
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ set_normalized_timespec64(ts, s, ns);
+ return 0;
+}
+
+static void lan8841_ptp_getseconds(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = ptp_priv->phydev;
+ time64_t s;
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ /* Issue the command to read the LTC */
+ phy_write_mmd(phydev, 2, LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_LTC_READ);
+
+ /* Read the LTC */
+ s = phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_HI);
+ s <<= 16;
+ s |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_MID);
+ s <<= 16;
+ s |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_LO);
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ set_normalized_timespec64(ts, s, 0);
+}
+
+#define LAN8841_PTP_LTC_STEP_ADJ_LO 276
+#define LAN8841_PTP_LTC_STEP_ADJ_HI 275
+#define LAN8841_PTP_LTC_STEP_ADJ_DIR BIT(15)
+#define LAN8841_PTP_CMD_CTL_PTP_LTC_STEP_SECONDS BIT(5)
+#define LAN8841_PTP_CMD_CTL_PTP_LTC_STEP_NANOSECONDS BIT(6)
+
+static int lan8841_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct timespec64 ts;
+ bool add = true;
+ u32 nsec;
+ s32 sec;
+ int ret;
+
+ /* The HW allows up to 15 sec to adjust the time, but here we limit to
+ * 10 sec the adjustment. The reason is, in case the adjustment is 14
+ * sec and 999999999 nsec, then we add 8ns to compansate the actual
+ * increment so the value can be bigger than 15 sec. Therefore limit the
+ * possible adjustments so we will not have these corner cases
+ */
+ if (delta > 10000000000LL || delta < -10000000000LL) {
+ /* The timeadjustment is too big, so fall back using set time */
+ u64 now;
+
+ ptp->gettime64(ptp, &ts);
+
+ now = ktime_to_ns(timespec64_to_ktime(ts));
+ ts = ns_to_timespec64(now + delta);
+
+ ptp->settime64(ptp, &ts);
+ return 0;
+ }
+
+ sec = div_u64_rem(delta < 0 ? -delta : delta, NSEC_PER_SEC, &nsec);
+ if (delta < 0 && nsec != 0) {
+ /* It is not allowed to adjust low the nsec part, therefore
+ * subtract more from second part and add to nanosecond such
+ * that would roll over, so the second part will increase
+ */
+ sec--;
+ nsec = NSEC_PER_SEC - nsec;
+ }
+
+ /* Calculate the adjustments and the direction */
+ if (delta < 0)
+ add = false;
+
+ if (nsec > 0)
+ /* add 8 ns to cover the likely normal increment */
+ nsec += 8;
+
+ if (nsec >= NSEC_PER_SEC) {
+ /* carry into seconds */
+ sec++;
+ nsec -= NSEC_PER_SEC;
+ }
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ if (sec) {
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_STEP_ADJ_LO, sec);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_STEP_ADJ_HI,
+ add ? LAN8841_PTP_LTC_STEP_ADJ_DIR : 0);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_LTC_STEP_SECONDS);
+ }
+
+ if (nsec) {
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_STEP_ADJ_LO,
+ nsec & 0xffff);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_STEP_ADJ_HI,
+ (nsec >> 16) & 0x3fff);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_CMD_CTL,
+ LAN8841_PTP_CMD_CTL_PTP_LTC_STEP_NANOSECONDS);
+ }
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ /* Update the target clock */
+ ptp->gettime64(ptp, &ts);
+ mutex_lock(&ptp_priv->ptp_lock);
+ ret = lan8841_ptp_update_target(ptp_priv, &ts);
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ return ret;
+}
+
+#define LAN8841_PTP_LTC_RATE_ADJ_HI 269
+#define LAN8841_PTP_LTC_RATE_ADJ_HI_DIR BIT(15)
+#define LAN8841_PTP_LTC_RATE_ADJ_LO 270
+
+static int lan8841_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = ptp_priv->phydev;
+ bool faster = true;
+ u32 rate;
+
+ if (!scaled_ppm)
+ return 0;
+
+ if (scaled_ppm < 0) {
+ scaled_ppm = -scaled_ppm;
+ faster = false;
+ }
+
+ rate = LAN8814_1PPM_FORMAT * (upper_16_bits(scaled_ppm));
+ rate += (LAN8814_1PPM_FORMAT * (lower_16_bits(scaled_ppm))) >> 16;
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_RATE_ADJ_HI,
+ faster ? LAN8841_PTP_LTC_RATE_ADJ_HI_DIR | (upper_16_bits(rate) & 0x3fff)
+ : upper_16_bits(rate) & 0x3fff);
+ phy_write_mmd(phydev, 2, LAN8841_PTP_LTC_RATE_ADJ_LO, lower_16_bits(rate));
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ return 0;
+}
+
+static int lan8841_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin,
+ enum ptp_pin_function func, unsigned int chan)
+{
+ switch (func) {
+ case PTP_PF_NONE:
+ case PTP_PF_PEROUT:
+ case PTP_PF_EXTTS:
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+#define LAN8841_PTP_GPIO_NUM 10
+#define LAN8841_GPIO_EN 128
+#define LAN8841_GPIO_DIR 129
+#define LAN8841_GPIO_BUF 130
+
+static int lan8841_ptp_perout_off(struct kszphy_ptp_priv *ptp_priv, int pin)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ int ret;
+
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_EN, BIT(pin));
+ if (ret)
+ return ret;
+
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_DIR, BIT(pin));
+ if (ret)
+ return ret;
+
+ return phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_BUF, BIT(pin));
+}
+
+static int lan8841_ptp_perout_on(struct kszphy_ptp_priv *ptp_priv, int pin)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ int ret;
+
+ ret = phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_EN, BIT(pin));
+ if (ret)
+ return ret;
+
+ ret = phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_DIR, BIT(pin));
+ if (ret)
+ return ret;
+
+ return phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_BUF, BIT(pin));
+}
+
+#define LAN8841_GPIO_DATA_SEL1 131
+#define LAN8841_GPIO_DATA_SEL2 132
+#define LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_MASK GENMASK(2, 0)
+#define LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_A 1
+#define LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_B 2
+#define LAN8841_PTP_GENERAL_CONFIG 257
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_A BIT(1)
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_B BIT(3)
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_A_MASK GENMASK(7, 4)
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_B_MASK GENMASK(11, 8)
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_A 4
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_B 7
+
+static int lan8841_ptp_remove_event(struct kszphy_ptp_priv *ptp_priv, int pin,
+ u8 event)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ u16 tmp;
+ int ret;
+
+ /* Now remove pin from the event. GPIO_DATA_SEL1 contains the GPIO
+ * pins 0-4 while GPIO_DATA_SEL2 contains GPIO pins 5-9, therefore
+ * depending on the pin, it requires to read a different register
+ */
+ if (pin < 5) {
+ tmp = LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_MASK << (3 * pin);
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_DATA_SEL1, tmp);
+ } else {
+ tmp = LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_MASK << (3 * (pin - 5));
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_DATA_SEL2, tmp);
+ }
+ if (ret)
+ return ret;
+
+ /* Disable the event */
+ if (event == LAN8841_EVENT_A)
+ tmp = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_A |
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_A_MASK;
+ else
+ tmp = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_B |
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_B_MASK;
+ return phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_EN, tmp);
+}
+
+static int lan8841_ptp_enable_event(struct kszphy_ptp_priv *ptp_priv, int pin,
+ u8 event, int pulse_width)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ u16 tmp;
+ int ret;
+
+ /* Enable the event */
+ if (event == LAN8841_EVENT_A)
+ ret = phy_modify_mmd(phydev, 2, LAN8841_PTP_GENERAL_CONFIG,
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_A |
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_A_MASK,
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_A |
+ pulse_width << LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_A);
+ else
+ ret = phy_modify_mmd(phydev, 2, LAN8841_PTP_GENERAL_CONFIG,
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_B |
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_B_MASK,
+ LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_POL_B |
+ pulse_width << LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_B);
+ if (ret)
+ return ret;
+
+ /* Now connect the pin to the event. GPIO_DATA_SEL1 contains the GPIO
+ * pins 0-4 while GPIO_DATA_SEL2 contains GPIO pins 5-9, therefore
+ * depending on the pin, it requires to read a different register
+ */
+ if (event == LAN8841_EVENT_A)
+ tmp = LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_A;
+ else
+ tmp = LAN8841_GPIO_DATA_SEL_GPIO_DATA_SEL_EVENT_B;
+
+ if (pin < 5)
+ ret = phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_DATA_SEL1,
+ tmp << (3 * pin));
+ else
+ ret = phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_DATA_SEL2,
+ tmp << (3 * (pin - 5)));
+
+ return ret;
+}
+
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_200MS 13
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100MS 12
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50MS 11
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10MS 10
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5MS 9
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1MS 8
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500US 7
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100US 6
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50US 5
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10US 4
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5US 3
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1US 2
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500NS 1
+#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100NS 0
+
+static int lan8841_ptp_perout(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct phy_device *phydev = ptp_priv->phydev;
+ struct timespec64 ts_on, ts_period;
+ s64 on_nsec, period_nsec;
+ int pulse_width;
+ int pin;
+ int ret;
+
+ if (rq->perout.flags & ~PTP_PEROUT_DUTY_CYCLE)
+ return -EOPNOTSUPP;
+
+ pin = ptp_find_pin(ptp_priv->ptp_clock, PTP_PF_PEROUT, rq->perout.index);
+ if (pin == -1 || pin >= LAN8841_PTP_GPIO_NUM)
+ return -EINVAL;
+
+ if (!on) {
+ ret = lan8841_ptp_perout_off(ptp_priv, pin);
+ if (ret)
+ return ret;
+
+ return lan8841_ptp_remove_event(ptp_priv, LAN8841_EVENT_A, pin);
+ }
+
+ ts_on.tv_sec = rq->perout.on.sec;
+ ts_on.tv_nsec = rq->perout.on.nsec;
+ on_nsec = timespec64_to_ns(&ts_on);
+
+ ts_period.tv_sec = rq->perout.period.sec;
+ ts_period.tv_nsec = rq->perout.period.nsec;
+ period_nsec = timespec64_to_ns(&ts_period);
+
+ if (period_nsec < 200) {
+ pr_warn_ratelimited("%s: perout period too small, minimum is 200 nsec\n",
+ phydev_name(phydev));
+ return -EOPNOTSUPP;
+ }
+
+ if (on_nsec >= period_nsec) {
+ pr_warn_ratelimited("%s: pulse width must be smaller than period\n",
+ phydev_name(phydev));
+ return -EINVAL;
+ }
+
+ switch (on_nsec) {
+ case 200000000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_200MS;
+ break;
+ case 100000000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100MS;
+ break;
+ case 50000000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50MS;
+ break;
+ case 10000000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10MS;
+ break;
+ case 5000000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5MS;
+ break;
+ case 1000000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1MS;
+ break;
+ case 500000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500US;
+ break;
+ case 100000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100US;
+ break;
+ case 50000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50US;
+ break;
+ case 10000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10US;
+ break;
+ case 5000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5US;
+ break;
+ case 1000:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1US;
+ break;
+ case 500:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500NS;
+ break;
+ case 100:
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100NS;
+ break;
+ default:
+ pr_warn_ratelimited("%s: Use default duty cycle of 100ns\n",
+ phydev_name(phydev));
+ pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100NS;
+ break;
+ }
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ ret = lan8841_ptp_set_target(ptp_priv, LAN8841_EVENT_A, rq->perout.start.sec,
+ rq->perout.start.nsec);
+ mutex_unlock(&ptp_priv->ptp_lock);
+ if (ret)
+ return ret;
+
+ ret = lan8841_ptp_set_reload(ptp_priv, LAN8841_EVENT_A, rq->perout.period.sec,
+ rq->perout.period.nsec);
+ if (ret)
+ return ret;
+
+ ret = lan8841_ptp_enable_event(ptp_priv, pin, LAN8841_EVENT_A,
+ pulse_width);
+ if (ret)
+ return ret;
+
+ ret = lan8841_ptp_perout_on(ptp_priv, pin);
+ if (ret)
+ lan8841_ptp_remove_event(ptp_priv, pin, LAN8841_EVENT_A);
+
+ return ret;
+}
+
+#define LAN8841_PTP_GPIO_CAP_EN 496
+#define LAN8841_PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(gpio) (BIT(gpio))
+#define LAN8841_PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(gpio) (BIT(gpio) << 8)
+#define LAN8841_PTP_INT_EN_PTP_GPIO_CAP_EN BIT(2)
+
+static int lan8841_ptp_extts_on(struct kszphy_ptp_priv *ptp_priv, int pin,
+ u32 flags)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ u16 tmp = 0;
+ int ret;
+
+ /* Set GPIO to be intput */
+ ret = phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_EN, BIT(pin));
+ if (ret)
+ return ret;
+
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_BUF, BIT(pin));
+ if (ret)
+ return ret;
+
+ /* Enable capture on the edges of the pin */
+ if (flags & PTP_RISING_EDGE)
+ tmp |= LAN8841_PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(pin);
+ if (flags & PTP_FALLING_EDGE)
+ tmp |= LAN8841_PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(pin);
+ ret = phy_write_mmd(phydev, 2, LAN8841_PTP_GPIO_CAP_EN, tmp);
+ if (ret)
+ return ret;
+
+ /* Enable interrupt */
+ return phy_modify_mmd(phydev, 2, LAN8841_PTP_INT_EN,
+ LAN8841_PTP_INT_EN_PTP_GPIO_CAP_EN,
+ LAN8841_PTP_INT_EN_PTP_GPIO_CAP_EN);
+}
+
+static int lan8841_ptp_extts_off(struct kszphy_ptp_priv *ptp_priv, int pin)
+{
+ struct phy_device *phydev = ptp_priv->phydev;
+ int ret;
+
+ /* Set GPIO to be output */
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_EN, BIT(pin));
+ if (ret)
+ return ret;
+
+ ret = phy_clear_bits_mmd(phydev, 2, LAN8841_GPIO_BUF, BIT(pin));
+ if (ret)
+ return ret;
+
+ /* Disable capture on both of the edges */
+ ret = phy_modify_mmd(phydev, 2, LAN8841_PTP_GPIO_CAP_EN,
+ LAN8841_PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(pin) |
+ LAN8841_PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(pin),
+ 0);
+ if (ret)
+ return ret;
+
+ /* Disable interrupt */
+ return phy_modify_mmd(phydev, 2, LAN8841_PTP_INT_EN,
+ LAN8841_PTP_INT_EN_PTP_GPIO_CAP_EN,
+ 0);
+}
+
+static int lan8841_ptp_extts(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ int pin;
+ int ret;
+
+ /* Reject requests with unsupported flags */
+ if (rq->extts.flags & ~(PTP_ENABLE_FEATURE |
+ PTP_EXTTS_EDGES |
+ PTP_STRICT_FLAGS))
+ return -EOPNOTSUPP;
+
+ pin = ptp_find_pin(ptp_priv->ptp_clock, PTP_PF_EXTTS, rq->extts.index);
+ if (pin == -1 || pin >= LAN8841_PTP_GPIO_NUM)
+ return -EINVAL;
+
+ mutex_lock(&ptp_priv->ptp_lock);
+ if (on)
+ ret = lan8841_ptp_extts_on(ptp_priv, pin, rq->extts.flags);
+ else
+ ret = lan8841_ptp_extts_off(ptp_priv, pin);
+ mutex_unlock(&ptp_priv->ptp_lock);
+
+ return ret;
+}
+
+static int lan8841_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ return lan8841_ptp_extts(ptp, rq, on);
+ case PTP_CLK_REQ_PEROUT:
+ return lan8841_ptp_perout(ptp, rq, on);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static long lan8841_ptp_do_aux_work(struct ptp_clock_info *ptp)
+{
+ struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv,
+ ptp_clock_info);
+ struct timespec64 ts;
+ unsigned long flags;
+
+ lan8841_ptp_getseconds(&ptp_priv->ptp_clock_info, &ts);
+
+ spin_lock_irqsave(&ptp_priv->seconds_lock, flags);
+ ptp_priv->seconds = ts.tv_sec;
+ spin_unlock_irqrestore(&ptp_priv->seconds_lock, flags);
+
+ return nsecs_to_jiffies(LAN8841_GET_SEC_LTC_DELAY);
+}
+
+static struct ptp_clock_info lan8841_ptp_clock_info = {
+ .owner = THIS_MODULE,
+ .name = "lan8841 ptp",
+ .max_adj = 31249999,
+ .gettime64 = lan8841_ptp_gettime64,
+ .settime64 = lan8841_ptp_settime64,
+ .adjtime = lan8841_ptp_adjtime,
+ .adjfine = lan8841_ptp_adjfine,
+ .verify = lan8841_ptp_verify,
+ .enable = lan8841_ptp_enable,
+ .do_aux_work = lan8841_ptp_do_aux_work,
+ .n_per_out = LAN8841_PTP_GPIO_NUM,
+ .n_ext_ts = LAN8841_PTP_GPIO_NUM,
+ .n_pins = LAN8841_PTP_GPIO_NUM,
+};
+
+#define LAN8841_OPERATION_MODE_STRAP_LOW_REGISTER 3
+#define LAN8841_OPERATION_MODE_STRAP_LOW_REGISTER_STRAP_RGMII_EN BIT(0)
+
+static int lan8841_probe(struct phy_device *phydev)
+{
+ struct kszphy_ptp_priv *ptp_priv;
+ struct kszphy_priv *priv;
+ int err;
+
+ err = kszphy_probe(phydev);
+ if (err)
+ return err;
+
+ if (phy_read_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG,
+ LAN8841_OPERATION_MODE_STRAP_LOW_REGISTER) &
+ LAN8841_OPERATION_MODE_STRAP_LOW_REGISTER_STRAP_RGMII_EN)
+ phydev->interface = PHY_INTERFACE_MODE_RGMII_RXID;
+
+ /* Register the clock */
+ if (!IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING))
+ return 0;
+
+ priv = phydev->priv;
+ ptp_priv = &priv->ptp_priv;
+
+ ptp_priv->pin_config = devm_kcalloc(&phydev->mdio.dev,
+ LAN8841_PTP_GPIO_NUM,
+ sizeof(*ptp_priv->pin_config),
+ GFP_KERNEL);
+ if (!ptp_priv->pin_config)
+ return -ENOMEM;
+
+ for (int i = 0; i < LAN8841_PTP_GPIO_NUM; ++i) {
+ struct ptp_pin_desc *p = &ptp_priv->pin_config[i];
+
+ snprintf(p->name, sizeof(p->name), "pin%d", i);
+ p->index = i;
+ p->func = PTP_PF_NONE;
+ }
+
+ ptp_priv->ptp_clock_info = lan8841_ptp_clock_info;
+ ptp_priv->ptp_clock_info.pin_config = ptp_priv->pin_config;
+ ptp_priv->ptp_clock = ptp_clock_register(&ptp_priv->ptp_clock_info,
+ &phydev->mdio.dev);
+ if (IS_ERR(ptp_priv->ptp_clock)) {
+ phydev_err(phydev, "ptp_clock_register failed: %lu\n",
+ PTR_ERR(ptp_priv->ptp_clock));
+ return -EINVAL;
+ }
+
+ if (!ptp_priv->ptp_clock)
+ return 0;
+
+ /* Initialize the SW */
+ skb_queue_head_init(&ptp_priv->tx_queue);
+ ptp_priv->phydev = phydev;
+ mutex_init(&ptp_priv->ptp_lock);
+ spin_lock_init(&ptp_priv->seconds_lock);
+
+ ptp_priv->mii_ts.rxtstamp = lan8841_rxtstamp;
+ ptp_priv->mii_ts.txtstamp = lan8814_txtstamp;
+ ptp_priv->mii_ts.hwtstamp = lan8841_hwtstamp;
+ ptp_priv->mii_ts.ts_info = lan8841_ts_info;
+
+ phydev->mii_ts = &ptp_priv->mii_ts;
+
+ return 0;
+}
+
+static int lan8841_suspend(struct phy_device *phydev)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv;
+
+ ptp_cancel_worker_sync(ptp_priv->ptp_clock);
+
+ return genphy_suspend(phydev);
+}
+
+static struct phy_driver ksphy_driver[] = {
+{
+ .phy_id = PHY_ID_KS8737,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Micrel KS8737",
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ks8737_type,
+ .probe = kszphy_probe,
+ .config_init = kszphy_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+}, {
+ .phy_id = PHY_ID_KSZ8021,
+ .phy_id_mask = 0x00ffffff,
+ .name = "Micrel KSZ8021 or KSZ8031",
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8021_type,
+ .probe = kszphy_probe,
+ .config_init = kszphy_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .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_KSZ8031,
+ .phy_id_mask = 0x00ffffff,
+ .name = "Micrel KSZ8031",
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8021_type,
+ .probe = kszphy_probe,
+ .config_init = kszphy_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .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_KSZ8041,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Micrel KSZ8041",
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8041_type,
+ .probe = kszphy_probe,
+ .config_init = ksz8041_config_init,
+ .config_aneg = ksz8041_config_aneg,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .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",
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8041_type,
+ .probe = kszphy_probe,
+ .config_init = kszphy_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+}, {
+ .name = "Micrel KSZ8051",
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8051_type,
+ .probe = kszphy_probe,
+ .config_init = kszphy_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .match_phy_device = ksz8051_match_phy_device,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+}, {
+ .phy_id = PHY_ID_KSZ8001,
+ .name = "Micrel KSZ8001 or KS8721",
+ .phy_id_mask = 0x00fffffc,
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8041_type,
+ .probe = kszphy_probe,
+ .config_init = kszphy_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .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_KSZ8081,
+ .name = "Micrel KSZ8081 or KSZ8091",
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .flags = PHY_POLL_CABLE_TEST,
+ /* PHY_BASIC_FEATURES */
+ .driver_data = &ksz8081_type,
+ .probe = kszphy_probe,
+ .config_init = ksz8081_config_init,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = ksz8081_config_aneg,
+ .read_status = ksz8081_read_status,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+ .cable_test_start = ksz886x_cable_test_start,
+ .cable_test_get_status = ksz886x_cable_test_get_status,
+}, {
+ .phy_id = PHY_ID_KSZ8061,
+ .name = "Micrel KSZ8061",
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ /* PHY_BASIC_FEATURES */
+ .probe = kszphy_probe,
+ .config_init = ksz8061_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+}, {
+ .phy_id = PHY_ID_KSZ9021,
+ .phy_id_mask = 0x000ffffe,
+ .name = "Micrel KSZ9021 Gigabit PHY",
+ /* PHY_GBIT_FEATURES */
+ .driver_data = &ksz9021_type,
+ .probe = kszphy_probe,
+ .get_features = ksz9031_get_features,
+ .config_init = ksz9021_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_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",
+ .flags = PHY_POLL_CABLE_TEST,
+ .driver_data = &ksz9021_type,
+ .probe = kszphy_probe,
+ .get_features = ksz9031_get_features,
+ .config_init = ksz9031_config_init,
+ .soft_reset = genphy_soft_reset,
+ .read_status = ksz9031_read_status,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+ .cable_test_start = ksz9x31_cable_test_start,
+ .cable_test_get_status = ksz9x31_cable_test_get_status,
+}, {
+ .phy_id = PHY_ID_LAN8814,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Microchip INDY Gigabit Quad PHY",
+ .flags = PHY_POLL_CABLE_TEST,
+ .config_init = lan8814_config_init,
+ .driver_data = &lan8814_type,
+ .probe = lan8814_probe,
+ .soft_reset = genphy_soft_reset,
+ .read_status = ksz9031_read_status,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = genphy_suspend,
+ .resume = kszphy_resume,
+ .config_intr = lan8814_config_intr,
+ .handle_interrupt = lan8814_handle_interrupt,
+ .cable_test_start = lan8814_cable_test_start,
+ .cable_test_get_status = ksz886x_cable_test_get_status,
+}, {
+ .phy_id = PHY_ID_LAN8804,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Microchip LAN966X Gigabit PHY",
+ .config_init = lan8804_config_init,
+ .driver_data = &ksz9021_type,
+ .probe = kszphy_probe,
+ .soft_reset = genphy_soft_reset,
+ .read_status = ksz9031_read_status,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = genphy_suspend,
+ .resume = kszphy_resume,
+ .config_intr = lan8804_config_intr,
+ .handle_interrupt = lan8804_handle_interrupt,
+}, {
+ .phy_id = PHY_ID_LAN8841,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Microchip LAN8841 Gigabit PHY",
+ .flags = PHY_POLL_CABLE_TEST,
+ .driver_data = &lan8841_type,
+ .config_init = lan8841_config_init,
+ .probe = lan8841_probe,
+ .soft_reset = genphy_soft_reset,
+ .config_intr = lan8841_config_intr,
+ .handle_interrupt = lan8841_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = lan8841_suspend,
+ .resume = genphy_resume,
+ .cable_test_start = lan8814_cable_test_start,
+ .cable_test_get_status = ksz886x_cable_test_get_status,
+}, {
+ .phy_id = PHY_ID_KSZ9131,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Microchip KSZ9131 Gigabit PHY",
+ /* PHY_GBIT_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .driver_data = &ksz9131_type,
+ .probe = kszphy_probe,
+ .soft_reset = genphy_soft_reset,
+ .config_init = ksz9131_config_init,
+ .config_intr = kszphy_config_intr,
+ .config_aneg = ksz9131_config_aneg,
+ .read_status = ksz9131_read_status,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .get_sset_count = kszphy_get_sset_count,
+ .get_strings = kszphy_get_strings,
+ .get_stats = kszphy_get_stats,
+ .suspend = kszphy_suspend,
+ .resume = kszphy_resume,
+ .cable_test_start = ksz9x31_cable_test_start,
+ .cable_test_get_status = ksz9x31_cable_test_get_status,
+ .get_features = ksz9477_get_features,
+}, {
+ .phy_id = PHY_ID_KSZ8873MLL,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Micrel KSZ8873MLL Switch",
+ /* PHY_BASIC_FEATURES */
+ .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 KSZ8851 Ethernet MAC or KSZ886X Switch",
+ .driver_data = &ksz886x_type,
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_POLL_CABLE_TEST,
+ .config_init = kszphy_config_init,
+ .config_aneg = ksz886x_config_aneg,
+ .read_status = ksz886x_read_status,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .cable_test_start = ksz886x_cable_test_start,
+ .cable_test_get_status = ksz886x_cable_test_get_status,
+}, {
+ .name = "Micrel KSZ87XX Switch",
+ /* PHY_BASIC_FEATURES */
+ .config_init = kszphy_config_init,
+ .match_phy_device = ksz8795_match_phy_device,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = PHY_ID_KSZ9477,
+ .phy_id_mask = MICREL_PHY_ID_MASK,
+ .name = "Microchip KSZ9477",
+ /* PHY_GBIT_FEATURES */
+ .config_init = ksz9477_config_init,
+ .config_intr = kszphy_config_intr,
+ .handle_interrupt = kszphy_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .get_features = ksz9477_get_features,
+} };
+
+module_phy_driver(ksphy_driver);
+
+MODULE_DESCRIPTION("Micrel PHY driver");
+MODULE_AUTHOR("David J. Choi");
+MODULE_LICENSE("GPL");
+
+static struct mdio_device_id __maybe_unused micrel_tbl[] = {
+ { PHY_ID_KSZ9021, 0x000ffffe },
+ { PHY_ID_KSZ9031, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ9131, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ8001, 0x00fffffc },
+ { PHY_ID_KS8737, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ8021, 0x00ffffff },
+ { PHY_ID_KSZ8031, 0x00ffffff },
+ { PHY_ID_KSZ8041, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ8051, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ8061, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ8081, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ8873MLL, MICREL_PHY_ID_MASK },
+ { PHY_ID_KSZ886X, MICREL_PHY_ID_MASK },
+ { PHY_ID_LAN8814, MICREL_PHY_ID_MASK },
+ { PHY_ID_LAN8804, MICREL_PHY_ID_MASK },
+ { PHY_ID_LAN8841, 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..0b88635f4
--- /dev/null
+++ b/drivers/net/phy/microchip.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015 Microchip Technology
+ */
+#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);
+ if (rc)
+ return rc;
+
+ /* Ack interrupts after they have been disabled */
+ rc = phy_read(phydev, LAN88XX_INT_STS);
+ }
+
+ return rc < 0 ? rc : 0;
+}
+
+static irqreturn_t lan88xx_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, LAN88XX_INT_STS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & LAN88XX_INT_STS_LINK_CHANGE_))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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) {
+ phydev_warn(phydev, "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) {
+ phydev_warn(phydev, "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) {
+ phydev_warn(phydev, "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) {
+ phydev_warn(phydev, "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))
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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)
+ phydev_warn(phydev, "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;
+
+ /*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 void lan88xx_link_change_notify(struct phy_device *phydev)
+{
+ int temp;
+
+ /* At forced 100 F/H mode, chip may fail to set mode correctly
+ * when cable is switched between long(~50+m) and short one.
+ * As workaround, set to 10 before setting to 100
+ * at forced 100 F/H mode.
+ */
+ if (!phydev->autoneg && phydev->speed == 100) {
+ /* disable phy interrupt */
+ temp = phy_read(phydev, LAN88XX_INT_MASK);
+ temp &= ~LAN88XX_INT_MASK_MDINTPIN_EN_;
+ phy_write(phydev, LAN88XX_INT_MASK, temp);
+
+ temp = phy_read(phydev, MII_BMCR);
+ temp &= ~(BMCR_SPEED100 | BMCR_SPEED1000);
+ phy_write(phydev, MII_BMCR, temp); /* set to 10 first */
+ temp |= BMCR_SPEED100;
+ phy_write(phydev, MII_BMCR, temp); /* set to 100 later */
+
+ /* clear pending interrupt generated while workaround */
+ temp = phy_read(phydev, LAN88XX_INT_STS);
+
+ /* enable phy interrupt back */
+ temp = phy_read(phydev, LAN88XX_INT_MASK);
+ temp |= LAN88XX_INT_MASK_MDINTPIN_EN_;
+ phy_write(phydev, LAN88XX_INT_MASK, temp);
+ }
+}
+
+static struct phy_driver microchip_phy_driver[] = {
+{
+ .phy_id = 0x0007c132,
+ /* This mask (0xfffffff2) is to differentiate from
+ * LAN8742 (phy_id 0x0007c130 and 0x0007c131)
+ * and allows future phy_id revisions.
+ */
+ .phy_id_mask = 0xfffffff2,
+ .name = "Microchip LAN88xx",
+
+ /* PHY_GBIT_FEATURES */
+
+ .probe = lan88xx_probe,
+ .remove = lan88xx_remove,
+
+ .config_init = lan88xx_config_init,
+ .config_aneg = lan88xx_config_aneg,
+ .link_change_notify = lan88xx_link_change_notify,
+
+ .config_intr = lan88xx_phy_config_intr,
+ .handle_interrupt = lan88xx_handle_interrupt,
+
+ .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[] = {
+ { 0x0007c132, 0xfffffff2 },
+ { }
+};
+
+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..a838b61cd
--- /dev/null
+++ b/drivers/net/phy/microchip_t1.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 Microchip Technology
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/bitfield.h>
+
+#define PHY_ID_LAN87XX 0x0007c150
+#define PHY_ID_LAN937X 0x0007c180
+
+/* External Register Control Register */
+#define LAN87XX_EXT_REG_CTL (0x14)
+#define LAN87XX_EXT_REG_CTL_RD_CTL (0x1000)
+#define LAN87XX_EXT_REG_CTL_WR_CTL (0x0800)
+#define LAN87XX_REG_BANK_SEL_MASK GENMASK(10, 8)
+#define LAN87XX_REG_ADDR_MASK GENMASK(7, 0)
+
+/* External Register Read Data Register */
+#define LAN87XX_EXT_REG_RD_DATA (0x15)
+
+/* External Register Write Data Register */
+#define LAN87XX_EXT_REG_WR_DATA (0x16)
+
+/* Interrupt Source Register */
+#define LAN87XX_INTERRUPT_SOURCE (0x18)
+#define LAN87XX_INTERRUPT_SOURCE_2 (0x08)
+
+/* Interrupt Mask Register */
+#define LAN87XX_INTERRUPT_MASK (0x19)
+#define LAN87XX_MASK_LINK_UP (0x0004)
+#define LAN87XX_MASK_LINK_DOWN (0x0002)
+
+#define LAN87XX_INTERRUPT_MASK_2 (0x09)
+#define LAN87XX_MASK_COMM_RDY BIT(10)
+
+/* MISC Control 1 Register */
+#define LAN87XX_CTRL_1 (0x11)
+#define LAN87XX_MASK_RGMII_TXC_DLY_EN (0x4000)
+#define LAN87XX_MASK_RGMII_RXC_DLY_EN (0x2000)
+
+/* phyaccess nested types */
+#define PHYACC_ATTR_MODE_READ 0
+#define PHYACC_ATTR_MODE_WRITE 1
+#define PHYACC_ATTR_MODE_MODIFY 2
+#define PHYACC_ATTR_MODE_POLL 3
+
+#define PHYACC_ATTR_BANK_SMI 0
+#define PHYACC_ATTR_BANK_MISC 1
+#define PHYACC_ATTR_BANK_PCS 2
+#define PHYACC_ATTR_BANK_AFE 3
+#define PHYACC_ATTR_BANK_DSP 4
+#define PHYACC_ATTR_BANK_MAX 7
+
+/* measurement defines */
+#define LAN87XX_CABLE_TEST_OK 0
+#define LAN87XX_CABLE_TEST_OPEN 1
+#define LAN87XX_CABLE_TEST_SAME_SHORT 2
+
+/* T1 Registers */
+#define T1_AFE_PORT_CFG1_REG 0x0B
+#define T1_POWER_DOWN_CONTROL_REG 0x1A
+#define T1_SLV_FD_MULT_CFG_REG 0x18
+#define T1_CDR_CFG_PRE_LOCK_REG 0x05
+#define T1_CDR_CFG_POST_LOCK_REG 0x06
+#define T1_LCK_STG2_MUFACT_CFG_REG 0x1A
+#define T1_LCK_STG3_MUFACT_CFG_REG 0x1B
+#define T1_POST_LCK_MUFACT_CFG_REG 0x1C
+#define T1_TX_RX_FIFO_CFG_REG 0x02
+#define T1_TX_LPF_FIR_CFG_REG 0x55
+#define T1_COEF_CLK_PWR_DN_CFG 0x04
+#define T1_COEF_RW_CTL_CFG 0x0D
+#define T1_SQI_CONFIG_REG 0x2E
+#define T1_SQI_CONFIG2_REG 0x4A
+#define T1_DCQ_SQI_REG 0xC3
+#define T1_DCQ_SQI_MSK GENMASK(3, 1)
+#define T1_MDIO_CONTROL2_REG 0x10
+#define T1_INTERRUPT_SOURCE_REG 0x18
+#define T1_INTERRUPT2_SOURCE_REG 0x08
+#define T1_EQ_FD_STG1_FRZ_CFG 0x69
+#define T1_EQ_FD_STG2_FRZ_CFG 0x6A
+#define T1_EQ_FD_STG3_FRZ_CFG 0x6B
+#define T1_EQ_FD_STG4_FRZ_CFG 0x6C
+#define T1_EQ_WT_FD_LCK_FRZ_CFG 0x6D
+#define T1_PST_EQ_LCK_STG1_FRZ_CFG 0x6E
+
+#define T1_MODE_STAT_REG 0x11
+#define T1_LINK_UP_MSK BIT(0)
+
+/* SQI defines */
+#define LAN87XX_MAX_SQI 0x07
+
+#define DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>"
+#define DRIVER_DESC "Microchip LAN87XX/LAN937x T1 PHY driver"
+
+struct access_ereg_val {
+ u8 mode;
+ u8 bank;
+ u8 offset;
+ u16 val;
+ u16 mask;
+};
+
+static int lan937x_dsp_workaround(struct phy_device *phydev, u16 ereg, u8 bank)
+{
+ u8 prev_bank;
+ int rc = 0;
+ u16 val;
+
+ mutex_lock(&phydev->lock);
+ /* Read previous selected bank */
+ rc = phy_read(phydev, LAN87XX_EXT_REG_CTL);
+ if (rc < 0)
+ goto out_unlock;
+
+ /* store the prev_bank */
+ prev_bank = FIELD_GET(LAN87XX_REG_BANK_SEL_MASK, rc);
+
+ if (bank != prev_bank && bank == PHYACC_ATTR_BANK_DSP) {
+ val = ereg & ~LAN87XX_REG_ADDR_MASK;
+
+ val &= ~LAN87XX_EXT_REG_CTL_WR_CTL;
+ val |= LAN87XX_EXT_REG_CTL_RD_CTL;
+
+ /* access twice for DSP bank change,dummy access */
+ rc = phy_write(phydev, LAN87XX_EXT_REG_CTL, val);
+ }
+
+out_unlock:
+ mutex_unlock(&phydev->lock);
+
+ return rc;
+}
+
+static int access_ereg(struct phy_device *phydev, u8 mode, u8 bank,
+ u8 offset, u16 val)
+{
+ u16 ereg = 0;
+ int rc = 0;
+
+ if (mode > PHYACC_ATTR_MODE_WRITE || bank > PHYACC_ATTR_BANK_MAX)
+ return -EINVAL;
+
+ if (bank == PHYACC_ATTR_BANK_SMI) {
+ if (mode == PHYACC_ATTR_MODE_WRITE)
+ rc = phy_write(phydev, offset, val);
+ else
+ rc = phy_read(phydev, offset);
+ return rc;
+ }
+
+ if (mode == PHYACC_ATTR_MODE_WRITE) {
+ ereg = LAN87XX_EXT_REG_CTL_WR_CTL;
+ rc = phy_write(phydev, LAN87XX_EXT_REG_WR_DATA, val);
+ if (rc < 0)
+ return rc;
+ } else {
+ ereg = LAN87XX_EXT_REG_CTL_RD_CTL;
+ }
+
+ ereg |= (bank << 8) | offset;
+
+ /* DSP bank access workaround for lan937x */
+ if (phydev->phy_id == PHY_ID_LAN937X) {
+ rc = lan937x_dsp_workaround(phydev, ereg, bank);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = phy_write(phydev, LAN87XX_EXT_REG_CTL, ereg);
+ if (rc < 0)
+ return rc;
+
+ if (mode == PHYACC_ATTR_MODE_READ)
+ rc = phy_read(phydev, LAN87XX_EXT_REG_RD_DATA);
+
+ return rc;
+}
+
+static int access_ereg_modify_changed(struct phy_device *phydev,
+ u8 bank, u8 offset, u16 val, u16 mask)
+{
+ int new = 0, rc = 0;
+
+ if (bank > PHYACC_ATTR_BANK_MAX)
+ return -EINVAL;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, bank, offset, val);
+ if (rc < 0)
+ return rc;
+
+ new = val | (rc & (mask ^ 0xFFFF));
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE, bank, offset, new);
+
+ return rc;
+}
+
+static int access_smi_poll_timeout(struct phy_device *phydev,
+ u8 offset, u16 mask, u16 clr)
+{
+ int val;
+
+ return phy_read_poll_timeout(phydev, offset, val, (val & mask) == clr,
+ 150, 30000, true);
+}
+
+static int lan87xx_config_rgmii_delay(struct phy_device *phydev)
+{
+ int rc;
+
+ if (!phy_interface_is_rgmii(phydev))
+ return 0;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_MISC, LAN87XX_CTRL_1, 0);
+ if (rc < 0)
+ return rc;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ rc &= ~LAN87XX_MASK_RGMII_TXC_DLY_EN;
+ rc &= ~LAN87XX_MASK_RGMII_RXC_DLY_EN;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ rc |= LAN87XX_MASK_RGMII_TXC_DLY_EN;
+ rc |= LAN87XX_MASK_RGMII_RXC_DLY_EN;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ rc &= ~LAN87XX_MASK_RGMII_TXC_DLY_EN;
+ rc |= LAN87XX_MASK_RGMII_RXC_DLY_EN;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ rc |= LAN87XX_MASK_RGMII_TXC_DLY_EN;
+ rc &= ~LAN87XX_MASK_RGMII_RXC_DLY_EN;
+ break;
+ default:
+ return 0;
+ }
+
+ return access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
+ PHYACC_ATTR_BANK_MISC, LAN87XX_CTRL_1, rc);
+}
+
+static int lan87xx_phy_init_cmd(struct phy_device *phydev,
+ const struct access_ereg_val *cmd_seq, int cnt)
+{
+ int ret, i;
+
+ for (i = 0; i < cnt; i++) {
+ if (cmd_seq[i].mode == PHYACC_ATTR_MODE_POLL &&
+ cmd_seq[i].bank == PHYACC_ATTR_BANK_SMI) {
+ ret = access_smi_poll_timeout(phydev,
+ cmd_seq[i].offset,
+ cmd_seq[i].val,
+ cmd_seq[i].mask);
+ } else {
+ ret = access_ereg(phydev, cmd_seq[i].mode,
+ cmd_seq[i].bank, cmd_seq[i].offset,
+ cmd_seq[i].val);
+ }
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int lan87xx_phy_init(struct phy_device *phydev)
+{
+ static const struct access_ereg_val hw_init[] = {
+ /* TXPD/TXAMP6 Configs */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_AFE,
+ T1_AFE_PORT_CFG1_REG, 0x002D, 0 },
+ /* HW_Init Hi and Force_ED */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
+ T1_POWER_DOWN_CONTROL_REG, 0x0308, 0 },
+ };
+
+ static const struct access_ereg_val slave_init[] = {
+ /* Equalizer Full Duplex Freeze - T1 Slave */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_EQ_FD_STG1_FRZ_CFG, 0x0002, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_EQ_FD_STG2_FRZ_CFG, 0x0002, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_EQ_FD_STG3_FRZ_CFG, 0x0002, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_EQ_FD_STG4_FRZ_CFG, 0x0002, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_EQ_WT_FD_LCK_FRZ_CFG, 0x0002, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_PST_EQ_LCK_STG1_FRZ_CFG, 0x0002, 0 },
+ };
+
+ static const struct access_ereg_val phy_init[] = {
+ /* Slave Full Duplex Multi Configs */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_SLV_FD_MULT_CFG_REG, 0x0D53, 0 },
+ /* CDR Pre and Post Lock Configs */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_CDR_CFG_PRE_LOCK_REG, 0x0AB2, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_CDR_CFG_POST_LOCK_REG, 0x0AB3, 0 },
+ /* Lock Stage 2-3 Multi Factor Config */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_LCK_STG2_MUFACT_CFG_REG, 0x0AEA, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_LCK_STG3_MUFACT_CFG_REG, 0x0AEB, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_POST_LCK_MUFACT_CFG_REG, 0x0AEB, 0 },
+ /* Pointer delay */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_RX_FIFO_CFG_REG, 0x1C00, 0 },
+ /* Tx iir edits */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1000, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1861, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1061, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1922, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1122, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1983, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1183, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1944, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1144, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x18c5, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x10c5, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1846, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1046, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1807, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1007, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1808, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1008, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1809, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1009, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x180A, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x100A, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x180B, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x100B, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x180C, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x100C, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x180D, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x100D, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x180E, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x100E, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x180F, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x100F, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1810, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1010, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1811, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1011, 0 },
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_TX_LPF_FIR_CFG_REG, 0x1000, 0 },
+ /* Setup SQI measurement */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_COEF_CLK_PWR_DN_CFG, 0x16d6, 0 },
+ /* SQI enable */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_SQI_CONFIG_REG, 0x9572, 0 },
+ /* SQI select mode 5 */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_SQI_CONFIG2_REG, 0x0001, 0 },
+ /* Throws the first SQI reading */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
+ T1_COEF_RW_CTL_CFG, 0x0301, 0 },
+ { PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_DSP,
+ T1_DCQ_SQI_REG, 0, 0 },
+ /* Flag LPS and WUR as idle errors */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
+ T1_MDIO_CONTROL2_REG, 0x0014, 0 },
+ /* HW_Init toggle, undo force ED, TXPD off */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
+ T1_POWER_DOWN_CONTROL_REG, 0x0200, 0 },
+ /* Reset PCS to trigger hardware initialization */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
+ T1_MDIO_CONTROL2_REG, 0x0094, 0 },
+ /* Poll till Hardware is initialized */
+ { PHYACC_ATTR_MODE_POLL, PHYACC_ATTR_BANK_SMI,
+ T1_MDIO_CONTROL2_REG, 0x0080, 0 },
+ /* Tx AMP - 0x06 */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_AFE,
+ T1_AFE_PORT_CFG1_REG, 0x000C, 0 },
+ /* Read INTERRUPT_SOURCE Register */
+ { PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI,
+ T1_INTERRUPT_SOURCE_REG, 0, 0 },
+ /* Read INTERRUPT_SOURCE Register */
+ { PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_MISC,
+ T1_INTERRUPT2_SOURCE_REG, 0, 0 },
+ /* HW_Init Hi */
+ { PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
+ T1_POWER_DOWN_CONTROL_REG, 0x0300, 0 },
+ };
+ int rc;
+
+ /* phy Soft reset */
+ rc = genphy_soft_reset(phydev);
+ if (rc < 0)
+ return rc;
+
+ /* PHY Initialization */
+ rc = lan87xx_phy_init_cmd(phydev, hw_init, ARRAY_SIZE(hw_init));
+ if (rc < 0)
+ return rc;
+
+ rc = genphy_read_master_slave(phydev);
+ if (rc)
+ return rc;
+
+ /* The following squence needs to run only if phydev is in
+ * slave mode.
+ */
+ if (phydev->master_slave_state == MASTER_SLAVE_STATE_SLAVE) {
+ rc = lan87xx_phy_init_cmd(phydev, slave_init,
+ ARRAY_SIZE(slave_init));
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = lan87xx_phy_init_cmd(phydev, phy_init, ARRAY_SIZE(phy_init));
+ if (rc < 0)
+ return rc;
+
+ return lan87xx_config_rgmii_delay(phydev);
+}
+
+static int lan87xx_phy_config_intr(struct phy_device *phydev)
+{
+ int rc, val = 0;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* clear all interrupt */
+ rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
+ if (rc < 0)
+ return rc;
+
+ rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
+ if (rc < 0)
+ return rc;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
+ PHYACC_ATTR_BANK_MISC,
+ LAN87XX_INTERRUPT_MASK_2, val);
+ if (rc < 0)
+ return rc;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_MISC,
+ LAN87XX_INTERRUPT_SOURCE_2, 0);
+ if (rc < 0)
+ return rc;
+
+ /* enable link down and comm ready interrupt */
+ val = LAN87XX_MASK_LINK_DOWN;
+ rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
+ if (rc < 0)
+ return rc;
+
+ val = LAN87XX_MASK_COMM_RDY;
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
+ PHYACC_ATTR_BANK_MISC,
+ LAN87XX_INTERRUPT_MASK_2, val);
+ } else {
+ rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
+ if (rc < 0)
+ return rc;
+
+ rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
+ if (rc < 0)
+ return rc;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
+ PHYACC_ATTR_BANK_MISC,
+ LAN87XX_INTERRUPT_MASK_2, val);
+ if (rc < 0)
+ return rc;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_MISC,
+ LAN87XX_INTERRUPT_SOURCE_2, 0);
+ }
+
+ return rc < 0 ? rc : 0;
+}
+
+static irqreturn_t lan87xx_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_MISC,
+ LAN87XX_INTERRUPT_SOURCE_2, 0);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_status = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (irq_status == 0)
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int lan87xx_config_init(struct phy_device *phydev)
+{
+ int rc = lan87xx_phy_init(phydev);
+
+ return rc < 0 ? rc : 0;
+}
+
+static int microchip_cable_test_start_common(struct phy_device *phydev)
+{
+ int bmcr, bmsr, ret;
+
+ /* If auto-negotiation is enabled, but not complete, the cable
+ * test never completes. So disable auto-neg.
+ */
+ bmcr = phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ bmsr = phy_read(phydev, MII_BMSR);
+
+ if (bmsr < 0)
+ return bmsr;
+
+ if (bmcr & BMCR_ANENABLE) {
+ ret = phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+ if (ret < 0)
+ return ret;
+ ret = genphy_soft_reset(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* If the link is up, allow it some time to go down */
+ if (bmsr & BMSR_LSTATUS)
+ msleep(1500);
+
+ return 0;
+}
+
+static int lan87xx_cable_test_start(struct phy_device *phydev)
+{
+ static const struct access_ereg_val cable_test[] = {
+ /* min wait */
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 93,
+ 0, 0},
+ /* max wait */
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 94,
+ 10, 0},
+ /* pulse cycle */
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 95,
+ 90, 0},
+ /* cable diag thresh */
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 92,
+ 60, 0},
+ /* max gain */
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 79,
+ 31, 0},
+ /* clock align for each iteration */
+ {PHYACC_ATTR_MODE_MODIFY, PHYACC_ATTR_BANK_DSP, 55,
+ 0, 0x0038},
+ /* max cycle wait config */
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 94,
+ 70, 0},
+ /* start cable diag*/
+ {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 90,
+ 1, 0},
+ };
+ int rc, i;
+
+ rc = microchip_cable_test_start_common(phydev);
+ if (rc < 0)
+ return rc;
+
+ /* start cable diag */
+ /* check if part is alive - if not, return diagnostic error */
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI,
+ 0x00, 0);
+ if (rc < 0)
+ return rc;
+
+ /* master/slave specific configs */
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI,
+ 0x0A, 0);
+ if (rc < 0)
+ return rc;
+
+ if ((rc & 0x4000) != 0x4000) {
+ /* DUT is Slave */
+ rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_AFE,
+ 0x0E, 0x5, 0x7);
+ if (rc < 0)
+ return rc;
+ rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_SMI,
+ 0x1A, 0x8, 0x8);
+ if (rc < 0)
+ return rc;
+ } else {
+ /* DUT is Master */
+ rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_SMI,
+ 0x10, 0x8, 0x40);
+ if (rc < 0)
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cable_test); i++) {
+ if (cable_test[i].mode == PHYACC_ATTR_MODE_MODIFY) {
+ rc = access_ereg_modify_changed(phydev,
+ cable_test[i].bank,
+ cable_test[i].offset,
+ cable_test[i].val,
+ cable_test[i].mask);
+ /* wait 50ms */
+ msleep(50);
+ } else {
+ rc = access_ereg(phydev, cable_test[i].mode,
+ cable_test[i].bank,
+ cable_test[i].offset,
+ cable_test[i].val);
+ }
+ if (rc < 0)
+ return rc;
+ }
+ /* cable diag started */
+
+ return 0;
+}
+
+static int lan87xx_cable_test_report_trans(u32 result)
+{
+ switch (result) {
+ case LAN87XX_CABLE_TEST_OK:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ case LAN87XX_CABLE_TEST_OPEN:
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ case LAN87XX_CABLE_TEST_SAME_SHORT:
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ default:
+ /* DIAGNOSTIC_ERROR */
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static int lan87xx_cable_test_report(struct phy_device *phydev)
+{
+ int pos_peak_cycle = 0, pos_peak_in_phases = 0, pos_peak_phase = 0;
+ int neg_peak_cycle = 0, neg_peak_in_phases = 0, neg_peak_phase = 0;
+ int noise_margin = 20, time_margin = 89, jitter_var = 30;
+ int min_time_diff = 96, max_time_diff = 96 + time_margin;
+ bool fault = false, check_a = false, check_b = false;
+ int gain_idx = 0, pos_peak = 0, neg_peak = 0;
+ int pos_peak_time = 0, neg_peak_time = 0;
+ int pos_peak_in_phases_hybrid = 0;
+ int detect = -1;
+
+ gain_idx = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_DSP, 151, 0);
+ /* read non-hybrid results */
+ pos_peak = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_DSP, 153, 0);
+ neg_peak = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_DSP, 154, 0);
+ pos_peak_time = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_DSP, 156, 0);
+ neg_peak_time = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_DSP, 157, 0);
+
+ pos_peak_cycle = (pos_peak_time >> 7) & 0x7F;
+ /* calculate non-hybrid values */
+ pos_peak_phase = pos_peak_time & 0x7F;
+ pos_peak_in_phases = (pos_peak_cycle * 96) + pos_peak_phase;
+ neg_peak_cycle = (neg_peak_time >> 7) & 0x7F;
+ neg_peak_phase = neg_peak_time & 0x7F;
+ neg_peak_in_phases = (neg_peak_cycle * 96) + neg_peak_phase;
+
+ /* process values */
+ check_a =
+ ((pos_peak_in_phases - neg_peak_in_phases) >= min_time_diff) &&
+ ((pos_peak_in_phases - neg_peak_in_phases) < max_time_diff) &&
+ pos_peak_in_phases_hybrid < pos_peak_in_phases &&
+ (pos_peak_in_phases_hybrid < (neg_peak_in_phases + jitter_var));
+ check_b =
+ ((neg_peak_in_phases - pos_peak_in_phases) >= min_time_diff) &&
+ ((neg_peak_in_phases - pos_peak_in_phases) < max_time_diff) &&
+ pos_peak_in_phases_hybrid < neg_peak_in_phases &&
+ (pos_peak_in_phases_hybrid < (pos_peak_in_phases + jitter_var));
+
+ if (pos_peak_in_phases > neg_peak_in_phases && check_a)
+ detect = 2;
+ else if ((neg_peak_in_phases > pos_peak_in_phases) && check_b)
+ detect = 1;
+
+ if (pos_peak > noise_margin && neg_peak > noise_margin &&
+ gain_idx >= 0) {
+ if (detect == 1 || detect == 2)
+ fault = true;
+ }
+
+ if (!fault)
+ detect = 0;
+
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ lan87xx_cable_test_report_trans(detect));
+
+ return 0;
+}
+
+static int lan87xx_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ int rc = 0;
+
+ *finished = false;
+
+ /* check if cable diag was finished */
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_DSP,
+ 90, 0);
+ if (rc < 0)
+ return rc;
+
+ if ((rc & 2) == 2) {
+ /* stop cable diag*/
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
+ PHYACC_ATTR_BANK_DSP,
+ 90, 0);
+ if (rc < 0)
+ return rc;
+
+ *finished = true;
+
+ return lan87xx_cable_test_report(phydev);
+ }
+
+ return 0;
+}
+
+static int lan87xx_read_status(struct phy_device *phydev)
+{
+ int rc = 0;
+
+ rc = phy_read(phydev, T1_MODE_STAT_REG);
+ if (rc < 0)
+ return rc;
+
+ if (rc & T1_LINK_UP_MSK)
+ phydev->link = 1;
+ else
+ phydev->link = 0;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ rc = genphy_read_master_slave(phydev);
+ if (rc < 0)
+ return rc;
+
+ rc = genphy_read_status_fixed(phydev);
+ if (rc < 0)
+ return rc;
+
+ return rc;
+}
+
+static int lan87xx_config_aneg(struct phy_device *phydev)
+{
+ u16 ctl = 0;
+ int ret;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl |= CTL1000_AS_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = phy_modify_changed(phydev, MII_CTRL1000, CTL1000_AS_MASTER, ctl);
+ if (ret == 1)
+ return phy_init_hw(phydev);
+
+ return ret;
+}
+
+static int lan87xx_get_sqi(struct phy_device *phydev)
+{
+ u8 sqi_value = 0;
+ int rc;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
+ PHYACC_ATTR_BANK_DSP, T1_COEF_RW_CTL_CFG, 0x0301);
+ if (rc < 0)
+ return rc;
+
+ rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
+ PHYACC_ATTR_BANK_DSP, T1_DCQ_SQI_REG, 0x0);
+ if (rc < 0)
+ return rc;
+
+ sqi_value = FIELD_GET(T1_DCQ_SQI_MSK, rc);
+
+ return sqi_value;
+}
+
+static int lan87xx_get_sqi_max(struct phy_device *phydev)
+{
+ return LAN87XX_MAX_SQI;
+}
+
+static struct phy_driver microchip_t1_phy_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX),
+ .name = "Microchip LAN87xx T1",
+ .flags = PHY_POLL_CABLE_TEST,
+ .features = PHY_BASIC_T1_FEATURES,
+ .config_init = lan87xx_config_init,
+ .config_intr = lan87xx_phy_config_intr,
+ .handle_interrupt = lan87xx_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = lan87xx_config_aneg,
+ .read_status = lan87xx_read_status,
+ .get_sqi = lan87xx_get_sqi,
+ .get_sqi_max = lan87xx_get_sqi_max,
+ .cable_test_start = lan87xx_cable_test_start,
+ .cable_test_get_status = lan87xx_cable_test_get_status,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_LAN937X),
+ .name = "Microchip LAN937x T1",
+ .flags = PHY_POLL_CABLE_TEST,
+ .features = PHY_BASIC_T1_FEATURES,
+ .config_init = lan87xx_config_init,
+ .config_intr = lan87xx_phy_config_intr,
+ .handle_interrupt = lan87xx_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = lan87xx_config_aneg,
+ .read_status = lan87xx_read_status,
+ .get_sqi = lan87xx_get_sqi,
+ .get_sqi_max = lan87xx_get_sqi_max,
+ .cable_test_start = lan87xx_cable_test_start,
+ .cable_test_get_status = lan87xx_cable_test_get_status,
+ }
+};
+
+module_phy_driver(microchip_t1_phy_driver);
+
+static struct mdio_device_id __maybe_unused microchip_t1_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_LAN937X) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, microchip_t1_tbl);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
new file mode 100644
index 000000000..534ca7d1b
--- /dev/null
+++ b/drivers/net/phy/microchip_t1s.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Microchip 10BASE-T1S PHYs
+ *
+ * Support: Microchip Phys:
+ * lan8670/1/2 Rev.B1
+ * lan8650/1 Rev.B0 Internal PHYs
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define PHY_ID_LAN867X_REVB1 0x0007C162
+#define PHY_ID_LAN865X_REVB0 0x0007C1B3
+
+#define LAN867X_REG_STS2 0x0019
+
+#define LAN867x_RESET_COMPLETE_STS BIT(11)
+
+#define LAN865X_REG_CFGPARAM_ADDR 0x00D8
+#define LAN865X_REG_CFGPARAM_DATA 0x00D9
+#define LAN865X_REG_CFGPARAM_CTRL 0x00DA
+#define LAN865X_REG_STS2 0x0019
+
+#define LAN865X_CFGPARAM_READ_ENABLE BIT(1)
+
+/* The arrays below are pulled from the following table from AN1699
+ * Access MMD Address Value Mask
+ * RMW 0x1F 0x00D0 0x0002 0x0E03
+ * RMW 0x1F 0x00D1 0x0000 0x0300
+ * RMW 0x1F 0x0084 0x3380 0xFFC0
+ * RMW 0x1F 0x0085 0x0006 0x000F
+ * RMW 0x1F 0x008A 0xC000 0xF800
+ * RMW 0x1F 0x0087 0x801C 0x801C
+ * RMW 0x1F 0x0088 0x033F 0x1FFF
+ * W 0x1F 0x008B 0x0404 ------
+ * RMW 0x1F 0x0080 0x0600 0x0600
+ * RMW 0x1F 0x00F1 0x2400 0x7F00
+ * RMW 0x1F 0x0096 0x2000 0x2000
+ * W 0x1F 0x0099 0x7F80 ------
+ */
+
+static const u32 lan867x_revb1_fixup_registers[12] = {
+ 0x00D0, 0x00D1, 0x0084, 0x0085,
+ 0x008A, 0x0087, 0x0088, 0x008B,
+ 0x0080, 0x00F1, 0x0096, 0x0099,
+};
+
+static const u16 lan867x_revb1_fixup_values[12] = {
+ 0x0002, 0x0000, 0x3380, 0x0006,
+ 0xC000, 0x801C, 0x033F, 0x0404,
+ 0x0600, 0x2400, 0x2000, 0x7F80,
+};
+
+static const u16 lan867x_revb1_fixup_masks[12] = {
+ 0x0E03, 0x0300, 0xFFC0, 0x000F,
+ 0xF800, 0x801C, 0x1FFF, 0xFFFF,
+ 0x0600, 0x7F00, 0x2000, 0xFFFF,
+};
+
+/* LAN865x Rev.B0 configuration parameters from AN1760 */
+static const u32 lan865x_revb0_fixup_registers[28] = {
+ 0x0091, 0x0081, 0x0043, 0x0044,
+ 0x0045, 0x0053, 0x0054, 0x0055,
+ 0x0040, 0x0050, 0x00D0, 0x00E9,
+ 0x00F5, 0x00F4, 0x00F8, 0x00F9,
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3,
+ 0x00B4, 0x00B5, 0x00B6, 0x00B7,
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB,
+};
+
+static const u16 lan865x_revb0_fixup_values[28] = {
+ 0x9660, 0x00C0, 0x00FF, 0xFFFF,
+ 0x0000, 0x00FF, 0xFFFF, 0x0000,
+ 0x0002, 0x0002, 0x5F21, 0x9E50,
+ 0x1CF8, 0xC020, 0x9B00, 0x4E53,
+ 0x0103, 0x0910, 0x1D26, 0x002A,
+ 0x0103, 0x070D, 0x1720, 0x0027,
+ 0x0509, 0x0E13, 0x1C25, 0x002B,
+};
+
+static const u16 lan865x_revb0_fixup_cfg_regs[5] = {
+ 0x0084, 0x008A, 0x00AD, 0x00AE, 0x00AF
+};
+
+/* Pulled from AN1760 describing 'indirect read'
+ *
+ * write_register(0x4, 0x00D8, addr)
+ * write_register(0x4, 0x00DA, 0x2)
+ * return (int8)(read_register(0x4, 0x00D9))
+ *
+ * 0x4 refers to memory map selector 4, which maps to MDIO_MMD_VEND2
+ */
+static int lan865x_revb0_indirect_read(struct phy_device *phydev, u16 addr)
+{
+ int ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_ADDR,
+ addr);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_CTRL,
+ LAN865X_CFGPARAM_READ_ENABLE);
+ if (ret)
+ return ret;
+
+ return phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_DATA);
+}
+
+/* This is pulled straight from AN1760 from 'calculation of offset 1' &
+ * 'calculation of offset 2'
+ */
+static int lan865x_generate_cfg_offsets(struct phy_device *phydev, s8 offsets[2])
+{
+ const u16 fixup_regs[2] = {0x0004, 0x0008};
+ int ret;
+
+ for (int i = 0; i < ARRAY_SIZE(fixup_regs); i++) {
+ ret = lan865x_revb0_indirect_read(phydev, fixup_regs[i]);
+ if (ret < 0)
+ return ret;
+ if (ret & BIT(4))
+ offsets[i] = ret | 0xE0;
+ else
+ offsets[i] = ret;
+ }
+
+ return 0;
+}
+
+static int lan865x_read_cfg_params(struct phy_device *phydev, u16 cfg_params[])
+{
+ int ret;
+
+ for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ lan865x_revb0_fixup_cfg_regs[i]);
+ if (ret < 0)
+ return ret;
+ cfg_params[i] = (u16)ret;
+ }
+
+ return 0;
+}
+
+static int lan865x_write_cfg_params(struct phy_device *phydev, u16 cfg_params[])
+{
+ int ret;
+
+ for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ lan865x_revb0_fixup_cfg_regs[i],
+ cfg_params[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lan865x_setup_cfgparam(struct phy_device *phydev)
+{
+ u16 cfg_params[ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs)];
+ u16 cfg_results[5];
+ s8 offsets[2];
+ int ret;
+
+ ret = lan865x_generate_cfg_offsets(phydev, offsets);
+ if (ret)
+ return ret;
+
+ ret = lan865x_read_cfg_params(phydev, cfg_params);
+ if (ret)
+ return ret;
+
+ cfg_results[0] = (cfg_params[0] & 0x000F) |
+ FIELD_PREP(GENMASK(15, 10), 9 + offsets[0]) |
+ FIELD_PREP(GENMASK(15, 4), 14 + offsets[0]);
+ cfg_results[1] = (cfg_params[1] & 0x03FF) |
+ FIELD_PREP(GENMASK(15, 10), 40 + offsets[1]);
+ cfg_results[2] = (cfg_params[2] & 0xC0C0) |
+ FIELD_PREP(GENMASK(15, 8), 5 + offsets[0]) |
+ (9 + offsets[0]);
+ cfg_results[3] = (cfg_params[3] & 0xC0C0) |
+ FIELD_PREP(GENMASK(15, 8), 9 + offsets[0]) |
+ (14 + offsets[0]);
+ cfg_results[4] = (cfg_params[4] & 0xC0C0) |
+ FIELD_PREP(GENMASK(15, 8), 17 + offsets[0]) |
+ (22 + offsets[0]);
+
+ return lan865x_write_cfg_params(phydev, cfg_results);
+}
+
+static int lan865x_revb0_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Reference to AN1760
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8650-1-Configuration-60001760.pdf
+ */
+ for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_registers); i++) {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ lan865x_revb0_fixup_registers[i],
+ lan865x_revb0_fixup_values[i]);
+ if (ret)
+ return ret;
+ }
+ /* Function to calculate and write the configuration parameters in the
+ * 0x0084, 0x008A, 0x00AD, 0x00AE and 0x00AF registers (from AN1760)
+ */
+ return lan865x_setup_cfgparam(phydev);
+}
+
+static int lan867x_revb1_config_init(struct phy_device *phydev)
+{
+ int err;
+
+ /* The chip completes a reset in 3us, we might get here earlier than
+ * that, as an added margin we'll conditionally sleep 5us.
+ */
+ err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2);
+ if (err < 0)
+ return err;
+
+ if (!(err & LAN867x_RESET_COMPLETE_STS)) {
+ udelay(5);
+ err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2);
+ if (err < 0)
+ return err;
+ if (!(err & LAN867x_RESET_COMPLETE_STS)) {
+ phydev_err(phydev, "PHY reset failed\n");
+ return -ENODEV;
+ }
+ }
+
+ /* Reference to AN1699
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8670-1-2-config-60001699.pdf
+ * AN1699 says Read, Modify, Write, but the Write is not required if the
+ * register already has the required value. So it is safe to use
+ * phy_modify_mmd here.
+ */
+ for (int i = 0; i < ARRAY_SIZE(lan867x_revb1_fixup_registers); i++) {
+ err = phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+ lan867x_revb1_fixup_registers[i],
+ lan867x_revb1_fixup_masks[i],
+ lan867x_revb1_fixup_values[i]);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int lan86xx_read_status(struct phy_device *phydev)
+{
+ /* The phy has some limitations, namely:
+ * - always reports link up
+ * - only supports 10MBit half duplex
+ * - does not support auto negotiate
+ */
+ phydev->link = 1;
+ phydev->duplex = DUPLEX_HALF;
+ phydev->speed = SPEED_10;
+ phydev->autoneg = AUTONEG_DISABLE;
+
+ return 0;
+}
+
+static struct phy_driver microchip_t1s_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),
+ .name = "LAN867X Rev.B1",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .config_init = lan867x_revb1_config_init,
+ .read_status = lan86xx_read_status,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0),
+ .name = "LAN865X Rev.B0 Internal Phy",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .config_init = lan865x_revb0_config_init,
+ .read_status = lan86xx_read_status,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ },
+};
+
+module_phy_driver(microchip_t1s_driver);
+
+static struct mdio_device_id __maybe_unused tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, tbl);
+
+MODULE_DESCRIPTION("Microchip 10BASE-T1S PHYs driver");
+MODULE_AUTHOR("Ramón Nordin Rodriguez");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/mii_timestamper.c b/drivers/net/phy/mii_timestamper.c
new file mode 100644
index 000000000..51ae0593a
--- /dev/null
+++ b/drivers/net/phy/mii_timestamper.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Support for generic time stamping devices on MII buses.
+// Copyright (C) 2018 Richard Cochran <richardcochran@gmail.com>
+//
+
+#include <linux/mii_timestamper.h>
+
+static LIST_HEAD(mii_timestamping_devices);
+static DEFINE_MUTEX(tstamping_devices_lock);
+
+struct mii_timestamping_desc {
+ struct list_head list;
+ struct mii_timestamping_ctrl *ctrl;
+ struct device *device;
+};
+
+/**
+ * register_mii_tstamp_controller() - registers an MII time stamping device.
+ *
+ * @device: The device to be registered.
+ * @ctrl: Pointer to device's control interface.
+ *
+ * Returns zero on success or non-zero on failure.
+ */
+int register_mii_tstamp_controller(struct device *device,
+ struct mii_timestamping_ctrl *ctrl)
+{
+ struct mii_timestamping_desc *desc;
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&desc->list);
+ desc->ctrl = ctrl;
+ desc->device = device;
+
+ mutex_lock(&tstamping_devices_lock);
+ list_add_tail(&mii_timestamping_devices, &desc->list);
+ mutex_unlock(&tstamping_devices_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(register_mii_tstamp_controller);
+
+/**
+ * unregister_mii_tstamp_controller() - unregisters an MII time stamping device.
+ *
+ * @device: A device previously passed to register_mii_tstamp_controller().
+ */
+void unregister_mii_tstamp_controller(struct device *device)
+{
+ struct mii_timestamping_desc *desc;
+ struct list_head *this, *next;
+
+ mutex_lock(&tstamping_devices_lock);
+ list_for_each_safe(this, next, &mii_timestamping_devices) {
+ desc = list_entry(this, struct mii_timestamping_desc, list);
+ if (desc->device == device) {
+ list_del_init(&desc->list);
+ kfree(desc);
+ break;
+ }
+ }
+ mutex_unlock(&tstamping_devices_lock);
+}
+EXPORT_SYMBOL(unregister_mii_tstamp_controller);
+
+/**
+ * register_mii_timestamper - Enables a given port of an MII time stamper.
+ *
+ * @node: The device tree node of the MII time stamp controller.
+ * @port: The index of the port to be enabled.
+ *
+ * Returns a valid interface on success or ERR_PTR otherwise.
+ */
+struct mii_timestamper *register_mii_timestamper(struct device_node *node,
+ unsigned int port)
+{
+ struct mii_timestamper *mii_ts = NULL;
+ struct mii_timestamping_desc *desc;
+ struct list_head *this;
+
+ mutex_lock(&tstamping_devices_lock);
+ list_for_each(this, &mii_timestamping_devices) {
+ desc = list_entry(this, struct mii_timestamping_desc, list);
+ if (desc->device->of_node == node) {
+ mii_ts = desc->ctrl->probe_channel(desc->device, port);
+ if (!IS_ERR(mii_ts)) {
+ mii_ts->device = desc->device;
+ get_device(desc->device);
+ }
+ break;
+ }
+ }
+ mutex_unlock(&tstamping_devices_lock);
+
+ return mii_ts ? mii_ts : ERR_PTR(-EPROBE_DEFER);
+}
+EXPORT_SYMBOL(register_mii_timestamper);
+
+/**
+ * unregister_mii_timestamper - Disables a given MII time stamper.
+ *
+ * @mii_ts: An interface obtained via register_mii_timestamper().
+ *
+ */
+void unregister_mii_timestamper(struct mii_timestamper *mii_ts)
+{
+ struct mii_timestamping_desc *desc;
+ struct list_head *this;
+
+ if (!mii_ts)
+ return;
+
+ /* mii_timestamper statically registered by the PHY driver won't use the
+ * register_mii_timestamper() and thus don't have ->device set. Don't
+ * try to unregister these.
+ */
+ if (!mii_ts->device)
+ return;
+
+ mutex_lock(&tstamping_devices_lock);
+ list_for_each(this, &mii_timestamping_devices) {
+ desc = list_entry(this, struct mii_timestamping_desc, list);
+ if (desc->device == mii_ts->device) {
+ desc->ctrl->release_channel(desc->device, mii_ts);
+ put_device(desc->device);
+ break;
+ }
+ }
+ mutex_unlock(&tstamping_devices_lock);
+}
+EXPORT_SYMBOL(unregister_mii_timestamper);
diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
new file mode 100644
index 000000000..7a11fdb68
--- /dev/null
+++ b/drivers/net/phy/motorcomm.c
@@ -0,0 +1,2327 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Motorcomm 8511/8521/8531/8531S PHY driver.
+ *
+ * Author: Peter Geis <pgwipeout@gmail.com>
+ * Author: Frank <Frank.Sae@motor-comm.com>
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#define PHY_ID_YT8511 0x0000010a
+#define PHY_ID_YT8521 0x0000011a
+#define PHY_ID_YT8531 0x4f51e91b
+#define PHY_ID_YT8531S 0x4f51e91a
+
+/* YT8521/YT8531S Register Overview
+ * UTP Register space | FIBER Register space
+ * ------------------------------------------------------------
+ * | UTP MII | FIBER MII |
+ * | UTP MMD | |
+ * | UTP Extended | FIBER Extended |
+ * ------------------------------------------------------------
+ * | Common Extended |
+ * ------------------------------------------------------------
+ */
+
+/* 0x10 ~ 0x15 , 0x1E and 0x1F are common MII registers of yt phy */
+
+/* Specific Function Control Register */
+#define YTPHY_SPECIFIC_FUNCTION_CONTROL_REG 0x10
+
+/* 2b00 Manual MDI configuration
+ * 2b01 Manual MDIX configuration
+ * 2b10 Reserved
+ * 2b11 Enable automatic crossover for all modes *default*
+ */
+#define YTPHY_SFCR_MDI_CROSSOVER_MODE_MASK (BIT(6) | BIT(5))
+#define YTPHY_SFCR_CROSSOVER_EN BIT(3)
+#define YTPHY_SFCR_SQE_TEST_EN BIT(2)
+#define YTPHY_SFCR_POLARITY_REVERSAL_EN BIT(1)
+#define YTPHY_SFCR_JABBER_DIS BIT(0)
+
+/* Specific Status Register */
+#define YTPHY_SPECIFIC_STATUS_REG 0x11
+#define YTPHY_SSR_SPEED_MODE_OFFSET 14
+
+#define YTPHY_SSR_SPEED_MODE_MASK (BIT(15) | BIT(14))
+#define YTPHY_SSR_SPEED_10M 0x0
+#define YTPHY_SSR_SPEED_100M 0x1
+#define YTPHY_SSR_SPEED_1000M 0x2
+#define YTPHY_SSR_DUPLEX_OFFSET 13
+#define YTPHY_SSR_DUPLEX BIT(13)
+#define YTPHY_SSR_PAGE_RECEIVED BIT(12)
+#define YTPHY_SSR_SPEED_DUPLEX_RESOLVED BIT(11)
+#define YTPHY_SSR_LINK BIT(10)
+#define YTPHY_SSR_MDIX_CROSSOVER BIT(6)
+#define YTPHY_SSR_DOWNGRADE BIT(5)
+#define YTPHY_SSR_TRANSMIT_PAUSE BIT(3)
+#define YTPHY_SSR_RECEIVE_PAUSE BIT(2)
+#define YTPHY_SSR_POLARITY BIT(1)
+#define YTPHY_SSR_JABBER BIT(0)
+
+/* Interrupt enable Register */
+#define YTPHY_INTERRUPT_ENABLE_REG 0x12
+#define YTPHY_IER_WOL BIT(6)
+
+/* Interrupt Status Register */
+#define YTPHY_INTERRUPT_STATUS_REG 0x13
+#define YTPHY_ISR_AUTONEG_ERR BIT(15)
+#define YTPHY_ISR_SPEED_CHANGED BIT(14)
+#define YTPHY_ISR_DUPLEX_CHANGED BIT(13)
+#define YTPHY_ISR_PAGE_RECEIVED BIT(12)
+#define YTPHY_ISR_LINK_FAILED BIT(11)
+#define YTPHY_ISR_LINK_SUCCESSED BIT(10)
+#define YTPHY_ISR_WOL BIT(6)
+#define YTPHY_ISR_WIRESPEED_DOWNGRADE BIT(5)
+#define YTPHY_ISR_SERDES_LINK_FAILED BIT(3)
+#define YTPHY_ISR_SERDES_LINK_SUCCESSED BIT(2)
+#define YTPHY_ISR_POLARITY_CHANGED BIT(1)
+#define YTPHY_ISR_JABBER_HAPPENED BIT(0)
+
+/* Speed Auto Downgrade Control Register */
+#define YTPHY_SPEED_AUTO_DOWNGRADE_CONTROL_REG 0x14
+#define YTPHY_SADCR_SPEED_DOWNGRADE_EN BIT(5)
+
+/* If these bits are set to 3, the PHY attempts five times ( 3(set value) +
+ * additional 2) before downgrading, default 0x3
+ */
+#define YTPHY_SADCR_SPEED_RETRY_LIMIT (0x3 << 2)
+
+/* Rx Error Counter Register */
+#define YTPHY_RX_ERROR_COUNTER_REG 0x15
+
+/* Extended Register's Address Offset Register */
+#define YTPHY_PAGE_SELECT 0x1E
+
+/* Extended Register's Data Register */
+#define YTPHY_PAGE_DATA 0x1F
+
+/* FIBER Auto-Negotiation link partner ability */
+#define YTPHY_FLPA_PAUSE (0x3 << 7)
+#define YTPHY_FLPA_ASYM_PAUSE (0x2 << 7)
+
+#define YT8511_PAGE_SELECT 0x1e
+#define YT8511_PAGE 0x1f
+#define YT8511_EXT_CLK_GATE 0x0c
+#define YT8511_EXT_DELAY_DRIVE 0x0d
+#define YT8511_EXT_SLEEP_CTRL 0x27
+
+/* 2b00 25m from pll
+ * 2b01 25m from xtl *default*
+ * 2b10 62.m from pll
+ * 2b11 125m from pll
+ */
+#define YT8511_CLK_125M (BIT(2) | BIT(1))
+#define YT8511_PLLON_SLP BIT(14)
+
+/* RX Delay enabled = 1.8ns 1000T, 8ns 10/100T */
+#define YT8511_DELAY_RX BIT(0)
+
+/* TX Gig-E Delay is bits 7:4, default 0x5
+ * TX Fast-E Delay is bits 15:12, default 0xf
+ * Delay = 150ps * N - 250ps
+ * On = 2000ps, off = 50ps
+ */
+#define YT8511_DELAY_GE_TX_EN (0xf << 4)
+#define YT8511_DELAY_GE_TX_DIS (0x2 << 4)
+#define YT8511_DELAY_FE_TX_EN (0xf << 12)
+#define YT8511_DELAY_FE_TX_DIS (0x2 << 12)
+
+/* Extended register is different from MMD Register and MII Register.
+ * We can use ytphy_read_ext/ytphy_write_ext/ytphy_modify_ext function to
+ * operate extended register.
+ * Extended Register start
+ */
+
+/* Phy gmii clock gating Register */
+#define YT8521_CLOCK_GATING_REG 0xC
+#define YT8521_CGR_RX_CLK_EN BIT(12)
+
+#define YT8521_EXTREG_SLEEP_CONTROL1_REG 0x27
+#define YT8521_ESC1R_SLEEP_SW BIT(15)
+#define YT8521_ESC1R_PLLON_SLP BIT(14)
+
+/* Phy fiber Link timer cfg2 Register */
+#define YT8521_LINK_TIMER_CFG2_REG 0xA5
+#define YT8521_LTCR_EN_AUTOSEN BIT(15)
+
+/* 0xA000, 0xA001, 0xA003, 0xA006 ~ 0xA00A and 0xA012 are common ext registers
+ * of yt8521 phy. There is no need to switch reg space when operating these
+ * registers.
+ */
+
+#define YT8521_REG_SPACE_SELECT_REG 0xA000
+#define YT8521_RSSR_SPACE_MASK BIT(1)
+#define YT8521_RSSR_FIBER_SPACE (0x1 << 1)
+#define YT8521_RSSR_UTP_SPACE (0x0 << 1)
+#define YT8521_RSSR_TO_BE_ARBITRATED (0xFF)
+
+#define YT8521_CHIP_CONFIG_REG 0xA001
+#define YT8521_CCR_SW_RST BIT(15)
+#define YT8531_RGMII_LDO_VOL_MASK GENMASK(5, 4)
+#define YT8531_LDO_VOL_3V3 0x0
+#define YT8531_LDO_VOL_1V8 0x2
+
+/* 1b0 disable 1.9ns rxc clock delay *default*
+ * 1b1 enable 1.9ns rxc clock delay
+ */
+#define YT8521_CCR_RXC_DLY_EN BIT(8)
+#define YT8521_CCR_RXC_DLY_1_900_NS 1900
+
+#define YT8521_CCR_MODE_SEL_MASK (BIT(2) | BIT(1) | BIT(0))
+#define YT8521_CCR_MODE_UTP_TO_RGMII 0
+#define YT8521_CCR_MODE_FIBER_TO_RGMII 1
+#define YT8521_CCR_MODE_UTP_FIBER_TO_RGMII 2
+#define YT8521_CCR_MODE_UTP_TO_SGMII 3
+#define YT8521_CCR_MODE_SGPHY_TO_RGMAC 4
+#define YT8521_CCR_MODE_SGMAC_TO_RGPHY 5
+#define YT8521_CCR_MODE_UTP_TO_FIBER_AUTO 6
+#define YT8521_CCR_MODE_UTP_TO_FIBER_FORCE 7
+
+/* 3 phy polling modes,poll mode combines utp and fiber mode*/
+#define YT8521_MODE_FIBER 0x1
+#define YT8521_MODE_UTP 0x2
+#define YT8521_MODE_POLL 0x3
+
+#define YT8521_RGMII_CONFIG1_REG 0xA003
+/* 1b0 use original tx_clk_rgmii *default*
+ * 1b1 use inverted tx_clk_rgmii.
+ */
+#define YT8521_RC1R_TX_CLK_SEL_INVERTED BIT(14)
+#define YT8521_RC1R_RX_DELAY_MASK GENMASK(13, 10)
+#define YT8521_RC1R_FE_TX_DELAY_MASK GENMASK(7, 4)
+#define YT8521_RC1R_GE_TX_DELAY_MASK GENMASK(3, 0)
+#define YT8521_RC1R_RGMII_0_000_NS 0
+#define YT8521_RC1R_RGMII_0_150_NS 1
+#define YT8521_RC1R_RGMII_0_300_NS 2
+#define YT8521_RC1R_RGMII_0_450_NS 3
+#define YT8521_RC1R_RGMII_0_600_NS 4
+#define YT8521_RC1R_RGMII_0_750_NS 5
+#define YT8521_RC1R_RGMII_0_900_NS 6
+#define YT8521_RC1R_RGMII_1_050_NS 7
+#define YT8521_RC1R_RGMII_1_200_NS 8
+#define YT8521_RC1R_RGMII_1_350_NS 9
+#define YT8521_RC1R_RGMII_1_500_NS 10
+#define YT8521_RC1R_RGMII_1_650_NS 11
+#define YT8521_RC1R_RGMII_1_800_NS 12
+#define YT8521_RC1R_RGMII_1_950_NS 13
+#define YT8521_RC1R_RGMII_2_100_NS 14
+#define YT8521_RC1R_RGMII_2_250_NS 15
+
+#define YTPHY_MISC_CONFIG_REG 0xA006
+#define YTPHY_MCR_FIBER_SPEED_MASK BIT(0)
+#define YTPHY_MCR_FIBER_1000BX (0x1 << 0)
+#define YTPHY_MCR_FIBER_100FX (0x0 << 0)
+
+/* WOL MAC ADDR: MACADDR2(highest), MACADDR1(middle), MACADDR0(lowest) */
+#define YTPHY_WOL_MACADDR2_REG 0xA007
+#define YTPHY_WOL_MACADDR1_REG 0xA008
+#define YTPHY_WOL_MACADDR0_REG 0xA009
+
+#define YTPHY_WOL_CONFIG_REG 0xA00A
+#define YTPHY_WCR_INTR_SEL BIT(6)
+#define YTPHY_WCR_ENABLE BIT(3)
+
+/* 2b00 84ms
+ * 2b01 168ms *default*
+ * 2b10 336ms
+ * 2b11 672ms
+ */
+#define YTPHY_WCR_PULSE_WIDTH_MASK (BIT(2) | BIT(1))
+#define YTPHY_WCR_PULSE_WIDTH_672MS (BIT(2) | BIT(1))
+
+/* 1b0 Interrupt and WOL events is level triggered and active LOW *default*
+ * 1b1 Interrupt and WOL events is pulse triggered and active LOW
+ */
+#define YTPHY_WCR_TYPE_PULSE BIT(0)
+
+#define YTPHY_PAD_DRIVE_STRENGTH_REG 0xA010
+#define YT8531_RGMII_RXC_DS_MASK GENMASK(15, 13)
+#define YT8531_RGMII_RXD_DS_HI_MASK BIT(12) /* Bit 2 of rxd_ds */
+#define YT8531_RGMII_RXD_DS_LOW_MASK GENMASK(5, 4) /* Bit 1/0 of rxd_ds */
+#define YT8531_RGMII_RX_DS_DEFAULT 0x3
+
+#define YTPHY_SYNCE_CFG_REG 0xA012
+#define YT8521_SCR_SYNCE_ENABLE BIT(5)
+/* 1b0 output 25m clock
+ * 1b1 output 125m clock *default*
+ */
+#define YT8521_SCR_CLK_FRE_SEL_125M BIT(3)
+#define YT8521_SCR_CLK_SRC_MASK GENMASK(2, 1)
+#define YT8521_SCR_CLK_SRC_PLL_125M 0
+#define YT8521_SCR_CLK_SRC_UTP_RX 1
+#define YT8521_SCR_CLK_SRC_SDS_RX 2
+#define YT8521_SCR_CLK_SRC_REF_25M 3
+#define YT8531_SCR_SYNCE_ENABLE BIT(6)
+/* 1b0 output 25m clock *default*
+ * 1b1 output 125m clock
+ */
+#define YT8531_SCR_CLK_FRE_SEL_125M BIT(4)
+#define YT8531_SCR_CLK_SRC_MASK GENMASK(3, 1)
+#define YT8531_SCR_CLK_SRC_PLL_125M 0
+#define YT8531_SCR_CLK_SRC_UTP_RX 1
+#define YT8531_SCR_CLK_SRC_SDS_RX 2
+#define YT8531_SCR_CLK_SRC_CLOCK_FROM_DIGITAL 3
+#define YT8531_SCR_CLK_SRC_REF_25M 4
+#define YT8531_SCR_CLK_SRC_SSC_25M 5
+
+/* Extended Register end */
+
+#define YTPHY_DTS_OUTPUT_CLK_DIS 0
+#define YTPHY_DTS_OUTPUT_CLK_25M 25000000
+#define YTPHY_DTS_OUTPUT_CLK_125M 125000000
+
+struct yt8521_priv {
+ /* combo_advertising is used for case of YT8521 in combo mode,
+ * this means that yt8521 may work in utp or fiber mode which depends
+ * on which media is connected (YT8521_RSSR_TO_BE_ARBITRATED).
+ */
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(combo_advertising);
+
+ /* YT8521_MODE_FIBER / YT8521_MODE_UTP / YT8521_MODE_POLL*/
+ u8 polling_mode;
+ u8 strap_mode; /* 8 working modes */
+ /* current reg page of yt8521 phy:
+ * YT8521_RSSR_UTP_SPACE
+ * YT8521_RSSR_FIBER_SPACE
+ * YT8521_RSSR_TO_BE_ARBITRATED
+ */
+ u8 reg_page;
+};
+
+/**
+ * ytphy_read_ext() - read a PHY's extended register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to read
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int ytphy_read_ext(struct phy_device *phydev, u16 regnum)
+{
+ int ret;
+
+ ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum);
+ if (ret < 0)
+ return ret;
+
+ return __phy_read(phydev, YTPHY_PAGE_DATA);
+}
+
+/**
+ * ytphy_read_ext_with_lock() - read a PHY's extended register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to read
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int ytphy_read_ext_with_lock(struct phy_device *phydev, u16 regnum)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = ytphy_read_ext(phydev, regnum);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * ytphy_write_ext() - write a PHY's extended register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative error code
+ */
+static int ytphy_write_ext(struct phy_device *phydev, u16 regnum, u16 val)
+{
+ int ret;
+
+ ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum);
+ if (ret < 0)
+ return ret;
+
+ return __phy_write(phydev, YTPHY_PAGE_DATA, val);
+}
+
+/**
+ * ytphy_write_ext_with_lock() - write a PHY's extended register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @val: value to write to @regnum
+ *
+ * returns 0 or negative error code
+ */
+static int ytphy_write_ext_with_lock(struct phy_device *phydev, u16 regnum,
+ u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = ytphy_write_ext(phydev, regnum, val);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * ytphy_modify_ext() - bits modify a PHY's extended register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: Convenience function which allows a PHY's extended register to be
+ * modified as new register value = (old register value & ~mask) | set.
+ * The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative error code
+ */
+static int ytphy_modify_ext(struct phy_device *phydev, u16 regnum, u16 mask,
+ u16 set)
+{
+ int ret;
+
+ ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum);
+ if (ret < 0)
+ return ret;
+
+ return __phy_modify(phydev, YTPHY_PAGE_DATA, mask, set);
+}
+
+/**
+ * ytphy_modify_ext_with_lock() - bits modify a PHY's extended register
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: Convenience function which allows a PHY's extended register to be
+ * modified as new register value = (old register value & ~mask) | set.
+ *
+ * returns 0 or negative error code
+ */
+static int ytphy_modify_ext_with_lock(struct phy_device *phydev, u16 regnum,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = ytphy_modify_ext(phydev, regnum, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * ytphy_get_wol() - report whether wake-on-lan is enabled
+ * @phydev: a pointer to a &struct phy_device
+ * @wol: a pointer to a &struct ethtool_wolinfo
+ *
+ * NOTE: YTPHY_WOL_CONFIG_REG is common ext reg.
+ */
+static void ytphy_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int wol_config;
+
+ wol->supported = WAKE_MAGIC;
+ wol->wolopts = 0;
+
+ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
+ if (wol_config < 0)
+ return;
+
+ if (wol_config & YTPHY_WCR_ENABLE)
+ wol->wolopts |= WAKE_MAGIC;
+}
+
+/**
+ * ytphy_set_wol() - turn wake-on-lan on or off
+ * @phydev: a pointer to a &struct phy_device
+ * @wol: a pointer to a &struct ethtool_wolinfo
+ *
+ * NOTE: YTPHY_WOL_CONFIG_REG, YTPHY_WOL_MACADDR2_REG, YTPHY_WOL_MACADDR1_REG
+ * and YTPHY_WOL_MACADDR0_REG are common ext reg. The
+ * YTPHY_INTERRUPT_ENABLE_REG of UTP is special, fiber also use this register.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ struct net_device *p_attached_dev;
+ const u16 mac_addr_reg[] = {
+ YTPHY_WOL_MACADDR2_REG,
+ YTPHY_WOL_MACADDR1_REG,
+ YTPHY_WOL_MACADDR0_REG,
+ };
+ const u8 *mac_addr;
+ int old_page;
+ int ret = 0;
+ u16 mask;
+ u16 val;
+ u8 i;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ p_attached_dev = phydev->attached_dev;
+ if (!p_attached_dev)
+ return -ENODEV;
+
+ mac_addr = (const u8 *)p_attached_dev->dev_addr;
+ if (!is_valid_ether_addr(mac_addr))
+ return -EINVAL;
+
+ /* lock mdio bus then switch to utp reg space */
+ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ /* Store the device address for the magic packet */
+ for (i = 0; i < 3; i++) {
+ ret = ytphy_write_ext(phydev, mac_addr_reg[i],
+ ((mac_addr[i * 2] << 8)) |
+ (mac_addr[i * 2 + 1]));
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+ /* Enable WOL feature */
+ mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL;
+ val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
+ val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS;
+ ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, val);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* Enable WOL interrupt */
+ ret = __phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG, 0,
+ YTPHY_IER_WOL);
+ if (ret < 0)
+ goto err_restore_page;
+
+ } else {
+ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ /* Disable WOL feature */
+ mask = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
+ ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, 0);
+
+ /* Disable WOL interrupt */
+ ret = __phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG,
+ YTPHY_IER_WOL, 0);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+static int yt8531_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ const u16 mac_addr_reg[] = {
+ YTPHY_WOL_MACADDR2_REG,
+ YTPHY_WOL_MACADDR1_REG,
+ YTPHY_WOL_MACADDR0_REG,
+ };
+ const u8 *mac_addr;
+ u16 mask, val;
+ int ret;
+ u8 i;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ mac_addr = phydev->attached_dev->dev_addr;
+
+ /* Store the device address for the magic packet */
+ for (i = 0; i < 3; i++) {
+ ret = ytphy_write_ext_with_lock(phydev, mac_addr_reg[i],
+ ((mac_addr[i * 2] << 8)) |
+ (mac_addr[i * 2 + 1]));
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Enable WOL feature */
+ mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL;
+ val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
+ val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS;
+ ret = ytphy_modify_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG,
+ mask, val);
+ if (ret < 0)
+ return ret;
+
+ /* Enable WOL interrupt */
+ ret = phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG, 0,
+ YTPHY_IER_WOL);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* Disable WOL feature */
+ mask = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
+ ret = ytphy_modify_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG,
+ mask, 0);
+
+ /* Disable WOL interrupt */
+ ret = phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG,
+ YTPHY_IER_WOL, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int yt8511_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, YT8511_PAGE_SELECT);
+};
+
+static int yt8511_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, YT8511_PAGE_SELECT, page);
+};
+
+static int yt8511_config_init(struct phy_device *phydev)
+{
+ int oldpage, ret = 0;
+ unsigned int ge, fe;
+
+ oldpage = phy_select_page(phydev, YT8511_EXT_CLK_GATE);
+ if (oldpage < 0)
+ goto err_restore_page;
+
+ /* set rgmii delay mode */
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ ge = YT8511_DELAY_GE_TX_DIS;
+ fe = YT8511_DELAY_FE_TX_DIS;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_DIS;
+ fe = YT8511_DELAY_FE_TX_DIS;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ ge = YT8511_DELAY_GE_TX_EN;
+ fe = YT8511_DELAY_FE_TX_EN;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN;
+ fe = YT8511_DELAY_FE_TX_EN;
+ break;
+ default: /* do not support other modes */
+ ret = -EOPNOTSUPP;
+ goto err_restore_page;
+ }
+
+ ret = __phy_modify(phydev, YT8511_PAGE, (YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN), ge);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* set clock mode to 125mhz */
+ ret = __phy_modify(phydev, YT8511_PAGE, 0, YT8511_CLK_125M);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* fast ethernet delay is in a separate page */
+ ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_DELAY_DRIVE);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = __phy_modify(phydev, YT8511_PAGE, YT8511_DELAY_FE_TX_EN, fe);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* leave pll enabled in sleep */
+ ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_SLEEP_CTRL);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = __phy_modify(phydev, YT8511_PAGE, 0, YT8511_PLLON_SLP);
+ if (ret < 0)
+ goto err_restore_page;
+
+err_restore_page:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+/**
+ * yt8521_read_page() - read reg page
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns current reg space of yt8521 (YT8521_RSSR_FIBER_SPACE/
+ * YT8521_RSSR_UTP_SPACE) or negative errno code
+ */
+static int yt8521_read_page(struct phy_device *phydev)
+{
+ int old_page;
+
+ old_page = ytphy_read_ext(phydev, YT8521_REG_SPACE_SELECT_REG);
+ if (old_page < 0)
+ return old_page;
+
+ if ((old_page & YT8521_RSSR_SPACE_MASK) == YT8521_RSSR_FIBER_SPACE)
+ return YT8521_RSSR_FIBER_SPACE;
+
+ return YT8521_RSSR_UTP_SPACE;
+};
+
+/**
+ * yt8521_write_page() - write reg page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to write.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_write_page(struct phy_device *phydev, int page)
+{
+ int mask = YT8521_RSSR_SPACE_MASK;
+ int set;
+
+ if ((page & YT8521_RSSR_SPACE_MASK) == YT8521_RSSR_FIBER_SPACE)
+ set = YT8521_RSSR_FIBER_SPACE;
+ else
+ set = YT8521_RSSR_UTP_SPACE;
+
+ return ytphy_modify_ext(phydev, YT8521_REG_SPACE_SELECT_REG, mask, set);
+};
+
+/**
+ * struct ytphy_cfg_reg_map - map a config value to a register value
+ * @cfg: value in device configuration
+ * @reg: value in the register
+ */
+struct ytphy_cfg_reg_map {
+ u32 cfg;
+ u32 reg;
+};
+
+static const struct ytphy_cfg_reg_map ytphy_rgmii_delays[] = {
+ /* for tx delay / rx delay with YT8521_CCR_RXC_DLY_EN is not set. */
+ { 0, YT8521_RC1R_RGMII_0_000_NS },
+ { 150, YT8521_RC1R_RGMII_0_150_NS },
+ { 300, YT8521_RC1R_RGMII_0_300_NS },
+ { 450, YT8521_RC1R_RGMII_0_450_NS },
+ { 600, YT8521_RC1R_RGMII_0_600_NS },
+ { 750, YT8521_RC1R_RGMII_0_750_NS },
+ { 900, YT8521_RC1R_RGMII_0_900_NS },
+ { 1050, YT8521_RC1R_RGMII_1_050_NS },
+ { 1200, YT8521_RC1R_RGMII_1_200_NS },
+ { 1350, YT8521_RC1R_RGMII_1_350_NS },
+ { 1500, YT8521_RC1R_RGMII_1_500_NS },
+ { 1650, YT8521_RC1R_RGMII_1_650_NS },
+ { 1800, YT8521_RC1R_RGMII_1_800_NS },
+ { 1950, YT8521_RC1R_RGMII_1_950_NS }, /* default tx/rx delay */
+ { 2100, YT8521_RC1R_RGMII_2_100_NS },
+ { 2250, YT8521_RC1R_RGMII_2_250_NS },
+
+ /* only for rx delay with YT8521_CCR_RXC_DLY_EN is set. */
+ { 0 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_000_NS },
+ { 150 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_150_NS },
+ { 300 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_300_NS },
+ { 450 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_450_NS },
+ { 600 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_600_NS },
+ { 750 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_750_NS },
+ { 900 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_0_900_NS },
+ { 1050 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_050_NS },
+ { 1200 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_200_NS },
+ { 1350 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_350_NS },
+ { 1500 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_500_NS },
+ { 1650 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_650_NS },
+ { 1800 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_800_NS },
+ { 1950 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_1_950_NS },
+ { 2100 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_2_100_NS },
+ { 2250 + YT8521_CCR_RXC_DLY_1_900_NS, YT8521_RC1R_RGMII_2_250_NS }
+};
+
+static u32 ytphy_get_delay_reg_value(struct phy_device *phydev,
+ const char *prop_name,
+ const struct ytphy_cfg_reg_map *tbl,
+ int tb_size,
+ u16 *rxc_dly_en,
+ u32 dflt)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ int tb_size_half = tb_size / 2;
+ u32 val;
+ int i;
+
+ if (of_property_read_u32(node, prop_name, &val))
+ goto err_dts_val;
+
+ /* when rxc_dly_en is NULL, it is get the delay for tx, only half of
+ * tb_size is valid.
+ */
+ if (!rxc_dly_en)
+ tb_size = tb_size_half;
+
+ for (i = 0; i < tb_size; i++) {
+ if (tbl[i].cfg == val) {
+ if (rxc_dly_en && i < tb_size_half)
+ *rxc_dly_en = 0;
+ return tbl[i].reg;
+ }
+ }
+
+ phydev_warn(phydev, "Unsupported value %d for %s using default (%u)\n",
+ val, prop_name, dflt);
+
+err_dts_val:
+ /* when rxc_dly_en is not NULL, it is get the delay for rx.
+ * The rx default in dts and ytphy_rgmii_clk_delay_config is 1950 ps,
+ * so YT8521_CCR_RXC_DLY_EN should not be set.
+ */
+ if (rxc_dly_en)
+ *rxc_dly_en = 0;
+
+ return dflt;
+}
+
+static int ytphy_rgmii_clk_delay_config(struct phy_device *phydev)
+{
+ int tb_size = ARRAY_SIZE(ytphy_rgmii_delays);
+ u16 rxc_dly_en = YT8521_CCR_RXC_DLY_EN;
+ u32 rx_reg, tx_reg;
+ u16 mask, val = 0;
+ int ret;
+
+ rx_reg = ytphy_get_delay_reg_value(phydev, "rx-internal-delay-ps",
+ ytphy_rgmii_delays, tb_size,
+ &rxc_dly_en,
+ YT8521_RC1R_RGMII_1_950_NS);
+ tx_reg = ytphy_get_delay_reg_value(phydev, "tx-internal-delay-ps",
+ ytphy_rgmii_delays, tb_size, NULL,
+ YT8521_RC1R_RGMII_1_950_NS);
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ rxc_dly_en = 0;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ val |= FIELD_PREP(YT8521_RC1R_RX_DELAY_MASK, rx_reg);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ rxc_dly_en = 0;
+ val |= FIELD_PREP(YT8521_RC1R_GE_TX_DELAY_MASK, tx_reg);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ val |= FIELD_PREP(YT8521_RC1R_RX_DELAY_MASK, rx_reg) |
+ FIELD_PREP(YT8521_RC1R_GE_TX_DELAY_MASK, tx_reg);
+ break;
+ default: /* do not support other modes */
+ return -EOPNOTSUPP;
+ }
+
+ ret = ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG,
+ YT8521_CCR_RXC_DLY_EN, rxc_dly_en);
+ if (ret < 0)
+ return ret;
+
+ /* Generally, it is not necessary to adjust YT8521_RC1R_FE_TX_DELAY */
+ mask = YT8521_RC1R_RX_DELAY_MASK | YT8521_RC1R_GE_TX_DELAY_MASK;
+ return ytphy_modify_ext(phydev, YT8521_RGMII_CONFIG1_REG, mask, val);
+}
+
+static int ytphy_rgmii_clk_delay_config_with_lock(struct phy_device *phydev)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = ytphy_rgmii_clk_delay_config(phydev);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * struct ytphy_ldo_vol_map - map a current value to a register value
+ * @vol: ldo voltage
+ * @ds: value in the register
+ * @cur: value in device configuration
+ */
+struct ytphy_ldo_vol_map {
+ u32 vol;
+ u32 ds;
+ u32 cur;
+};
+
+static const struct ytphy_ldo_vol_map yt8531_ldo_vol[] = {
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 0, .cur = 1200},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 1, .cur = 2100},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 2, .cur = 2700},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 3, .cur = 2910},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 4, .cur = 3110},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 5, .cur = 3600},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 6, .cur = 3970},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 7, .cur = 4350},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 0, .cur = 3070},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 1, .cur = 4080},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 2, .cur = 4370},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 3, .cur = 4680},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 4, .cur = 5020},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 5, .cur = 5450},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 6, .cur = 5740},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 7, .cur = 6140},
+};
+
+static u32 yt8531_get_ldo_vol(struct phy_device *phydev)
+{
+ u32 val;
+
+ val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
+ val = FIELD_GET(YT8531_RGMII_LDO_VOL_MASK, val);
+
+ return val <= YT8531_LDO_VOL_1V8 ? val : YT8531_LDO_VOL_1V8;
+}
+
+static int yt8531_get_ds_map(struct phy_device *phydev, u32 cur)
+{
+ u32 vol;
+ int i;
+
+ vol = yt8531_get_ldo_vol(phydev);
+ for (i = 0; i < ARRAY_SIZE(yt8531_ldo_vol); i++) {
+ if (yt8531_ldo_vol[i].vol == vol && yt8531_ldo_vol[i].cur == cur)
+ return yt8531_ldo_vol[i].ds;
+ }
+
+ return -EINVAL;
+}
+
+static int yt8531_set_ds(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ u32 ds_field_low, ds_field_hi, val;
+ int ret, ds;
+
+ /* set rgmii rx clk driver strength */
+ if (!of_property_read_u32(node, "motorcomm,rx-clk-drv-microamp", &val)) {
+ ds = yt8531_get_ds_map(phydev, val);
+ if (ds < 0)
+ return dev_err_probe(&phydev->mdio.dev, ds,
+ "No matching current value was found.\n");
+ } else {
+ ds = YT8531_RGMII_RX_DS_DEFAULT;
+ }
+
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YTPHY_PAD_DRIVE_STRENGTH_REG,
+ YT8531_RGMII_RXC_DS_MASK,
+ FIELD_PREP(YT8531_RGMII_RXC_DS_MASK, ds));
+ if (ret < 0)
+ return ret;
+
+ /* set rgmii rx data driver strength */
+ if (!of_property_read_u32(node, "motorcomm,rx-data-drv-microamp", &val)) {
+ ds = yt8531_get_ds_map(phydev, val);
+ if (ds < 0)
+ return dev_err_probe(&phydev->mdio.dev, ds,
+ "No matching current value was found.\n");
+ } else {
+ ds = YT8531_RGMII_RX_DS_DEFAULT;
+ }
+
+ ds_field_hi = FIELD_GET(BIT(2), ds);
+ ds_field_hi = FIELD_PREP(YT8531_RGMII_RXD_DS_HI_MASK, ds_field_hi);
+
+ ds_field_low = FIELD_GET(GENMASK(1, 0), ds);
+ ds_field_low = FIELD_PREP(YT8531_RGMII_RXD_DS_LOW_MASK, ds_field_low);
+
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YTPHY_PAD_DRIVE_STRENGTH_REG,
+ YT8531_RGMII_RXD_DS_LOW_MASK | YT8531_RGMII_RXD_DS_HI_MASK,
+ ds_field_low | ds_field_hi);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * yt8521_probe() - read chip config then set suitable polling_mode
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_probe(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ struct device *dev = &phydev->mdio.dev;
+ struct yt8521_priv *priv;
+ int chip_config;
+ u16 mask, val;
+ u32 freq;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ chip_config = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
+ if (chip_config < 0)
+ return chip_config;
+
+ priv->strap_mode = chip_config & YT8521_CCR_MODE_SEL_MASK;
+ switch (priv->strap_mode) {
+ case YT8521_CCR_MODE_FIBER_TO_RGMII:
+ case YT8521_CCR_MODE_SGPHY_TO_RGMAC:
+ case YT8521_CCR_MODE_SGMAC_TO_RGPHY:
+ priv->polling_mode = YT8521_MODE_FIBER;
+ priv->reg_page = YT8521_RSSR_FIBER_SPACE;
+ phydev->port = PORT_FIBRE;
+ break;
+ case YT8521_CCR_MODE_UTP_FIBER_TO_RGMII:
+ case YT8521_CCR_MODE_UTP_TO_FIBER_AUTO:
+ case YT8521_CCR_MODE_UTP_TO_FIBER_FORCE:
+ priv->polling_mode = YT8521_MODE_POLL;
+ priv->reg_page = YT8521_RSSR_TO_BE_ARBITRATED;
+ phydev->port = PORT_NONE;
+ break;
+ case YT8521_CCR_MODE_UTP_TO_SGMII:
+ case YT8521_CCR_MODE_UTP_TO_RGMII:
+ priv->polling_mode = YT8521_MODE_UTP;
+ priv->reg_page = YT8521_RSSR_UTP_SPACE;
+ phydev->port = PORT_TP;
+ break;
+ }
+ /* set default reg space */
+ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
+ ret = ytphy_write_ext_with_lock(phydev,
+ YT8521_REG_SPACE_SELECT_REG,
+ priv->reg_page);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (of_property_read_u32(node, "motorcomm,clk-out-frequency-hz", &freq))
+ freq = YTPHY_DTS_OUTPUT_CLK_DIS;
+
+ if (phydev->drv->phy_id == PHY_ID_YT8521) {
+ switch (freq) {
+ case YTPHY_DTS_OUTPUT_CLK_DIS:
+ mask = YT8521_SCR_SYNCE_ENABLE;
+ val = 0;
+ break;
+ case YTPHY_DTS_OUTPUT_CLK_25M:
+ mask = YT8521_SCR_SYNCE_ENABLE |
+ YT8521_SCR_CLK_SRC_MASK |
+ YT8521_SCR_CLK_FRE_SEL_125M;
+ val = YT8521_SCR_SYNCE_ENABLE |
+ FIELD_PREP(YT8521_SCR_CLK_SRC_MASK,
+ YT8521_SCR_CLK_SRC_REF_25M);
+ break;
+ case YTPHY_DTS_OUTPUT_CLK_125M:
+ mask = YT8521_SCR_SYNCE_ENABLE |
+ YT8521_SCR_CLK_SRC_MASK |
+ YT8521_SCR_CLK_FRE_SEL_125M;
+ val = YT8521_SCR_SYNCE_ENABLE |
+ YT8521_SCR_CLK_FRE_SEL_125M |
+ FIELD_PREP(YT8521_SCR_CLK_SRC_MASK,
+ YT8521_SCR_CLK_SRC_PLL_125M);
+ break;
+ default:
+ phydev_warn(phydev, "Freq err:%u\n", freq);
+ return -EINVAL;
+ }
+ } else if (phydev->drv->phy_id == PHY_ID_YT8531S) {
+ switch (freq) {
+ case YTPHY_DTS_OUTPUT_CLK_DIS:
+ mask = YT8531_SCR_SYNCE_ENABLE;
+ val = 0;
+ break;
+ case YTPHY_DTS_OUTPUT_CLK_25M:
+ mask = YT8531_SCR_SYNCE_ENABLE |
+ YT8531_SCR_CLK_SRC_MASK |
+ YT8531_SCR_CLK_FRE_SEL_125M;
+ val = YT8531_SCR_SYNCE_ENABLE |
+ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
+ YT8531_SCR_CLK_SRC_REF_25M);
+ break;
+ case YTPHY_DTS_OUTPUT_CLK_125M:
+ mask = YT8531_SCR_SYNCE_ENABLE |
+ YT8531_SCR_CLK_SRC_MASK |
+ YT8531_SCR_CLK_FRE_SEL_125M;
+ val = YT8531_SCR_SYNCE_ENABLE |
+ YT8531_SCR_CLK_FRE_SEL_125M |
+ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
+ YT8531_SCR_CLK_SRC_PLL_125M);
+ break;
+ default:
+ phydev_warn(phydev, "Freq err:%u\n", freq);
+ return -EINVAL;
+ }
+ } else {
+ phydev_warn(phydev, "PHY id err\n");
+ return -EINVAL;
+ }
+
+ return ytphy_modify_ext_with_lock(phydev, YTPHY_SYNCE_CFG_REG, mask,
+ val);
+}
+
+static int yt8531_probe(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ u16 mask, val;
+ u32 freq;
+
+ if (of_property_read_u32(node, "motorcomm,clk-out-frequency-hz", &freq))
+ freq = YTPHY_DTS_OUTPUT_CLK_DIS;
+
+ switch (freq) {
+ case YTPHY_DTS_OUTPUT_CLK_DIS:
+ mask = YT8531_SCR_SYNCE_ENABLE;
+ val = 0;
+ break;
+ case YTPHY_DTS_OUTPUT_CLK_25M:
+ mask = YT8531_SCR_SYNCE_ENABLE | YT8531_SCR_CLK_SRC_MASK |
+ YT8531_SCR_CLK_FRE_SEL_125M;
+ val = YT8531_SCR_SYNCE_ENABLE |
+ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
+ YT8531_SCR_CLK_SRC_REF_25M);
+ break;
+ case YTPHY_DTS_OUTPUT_CLK_125M:
+ mask = YT8531_SCR_SYNCE_ENABLE | YT8531_SCR_CLK_SRC_MASK |
+ YT8531_SCR_CLK_FRE_SEL_125M;
+ val = YT8531_SCR_SYNCE_ENABLE | YT8531_SCR_CLK_FRE_SEL_125M |
+ FIELD_PREP(YT8531_SCR_CLK_SRC_MASK,
+ YT8531_SCR_CLK_SRC_PLL_125M);
+ break;
+ default:
+ phydev_warn(phydev, "Freq err:%u\n", freq);
+ return -EINVAL;
+ }
+
+ return ytphy_modify_ext_with_lock(phydev, YTPHY_SYNCE_CFG_REG, mask,
+ val);
+}
+
+/**
+ * ytphy_utp_read_lpa() - read LPA then setup lp_advertising for utp
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_utp_read_lpa(struct phy_device *phydev)
+{
+ int lpa, lpagb;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ if (!phydev->autoneg_complete) {
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
+ 0);
+ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+ return 0;
+ }
+
+ if (phydev->is_gigabit_capable) {
+ lpagb = __phy_read(phydev, MII_STAT1000);
+ if (lpagb < 0)
+ return lpagb;
+
+ if (lpagb & LPA_1000MSFAIL) {
+ int adv = __phy_read(phydev, MII_CTRL1000);
+
+ if (adv < 0)
+ return adv;
+
+ 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;
+ }
+
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
+ lpagb);
+ }
+
+ lpa = __phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa);
+ } else {
+ linkmode_zero(phydev->lp_advertising);
+ }
+
+ return 0;
+}
+
+/**
+ * yt8521_adjust_status() - update speed and duplex to phydev. when in fiber
+ * mode, adjust speed and duplex.
+ * @phydev: a pointer to a &struct phy_device
+ * @status: yt8521 status read from YTPHY_SPECIFIC_STATUS_REG
+ * @is_utp: false(yt8521 work in fiber mode) or true(yt8521 work in utp mode)
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns 0
+ */
+static int yt8521_adjust_status(struct phy_device *phydev, int status,
+ bool is_utp)
+{
+ int speed_mode, duplex;
+ int speed;
+ int err;
+ int lpa;
+
+ if (is_utp)
+ duplex = (status & YTPHY_SSR_DUPLEX) >> YTPHY_SSR_DUPLEX_OFFSET;
+ else
+ duplex = DUPLEX_FULL; /* for fiber, it always DUPLEX_FULL */
+
+ speed_mode = (status & YTPHY_SSR_SPEED_MODE_MASK) >>
+ YTPHY_SSR_SPEED_MODE_OFFSET;
+
+ switch (speed_mode) {
+ case YTPHY_SSR_SPEED_10M:
+ if (is_utp)
+ speed = SPEED_10;
+ else
+ /* for fiber, it will never run here, default to
+ * SPEED_UNKNOWN
+ */
+ speed = SPEED_UNKNOWN;
+ break;
+ case YTPHY_SSR_SPEED_100M:
+ speed = SPEED_100;
+ break;
+ case YTPHY_SSR_SPEED_1000M:
+ speed = SPEED_1000;
+ break;
+ default:
+ speed = SPEED_UNKNOWN;
+ break;
+ }
+
+ phydev->speed = speed;
+ phydev->duplex = duplex;
+
+ if (is_utp) {
+ err = ytphy_utp_read_lpa(phydev);
+ if (err < 0)
+ return err;
+
+ phy_resolve_aneg_pause(phydev);
+ } else {
+ lpa = __phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ /* only support 1000baseX Full */
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->lp_advertising, lpa & LPA_1000XFULL);
+
+ if (!(lpa & YTPHY_FLPA_PAUSE)) {
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ } else if ((lpa & YTPHY_FLPA_ASYM_PAUSE)) {
+ phydev->pause = 1;
+ phydev->asym_pause = 1;
+ } else {
+ phydev->pause = 1;
+ phydev->asym_pause = 0;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * yt8521_read_status_paged() - determines the speed and duplex of one page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 1 (utp or fiber link),0 (no link) or negative errno code
+ */
+static int yt8521_read_status_paged(struct phy_device *phydev, int page)
+{
+ int fiber_latch_val;
+ int fiber_curr_val;
+ int old_page;
+ int ret = 0;
+ int status;
+ int link;
+
+ linkmode_zero(phydev->lp_advertising);
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->asym_pause = 0;
+ phydev->pause = 0;
+
+ /* YT8521 has two reg space (utp/fiber) for linkup with utp/fiber
+ * respectively. but for utp/fiber combo mode, reg space should be
+ * arbitrated based on media priority. by default, utp takes
+ * priority. reg space should be properly set before read
+ * YTPHY_SPECIFIC_STATUS_REG.
+ */
+
+ page &= YT8521_RSSR_SPACE_MASK;
+ old_page = phy_select_page(phydev, page);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ /* Read YTPHY_SPECIFIC_STATUS_REG, which indicates the speed and duplex
+ * of the PHY is actually using.
+ */
+ ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
+ if (ret < 0)
+ goto err_restore_page;
+
+ status = ret;
+ link = !!(status & YTPHY_SSR_LINK);
+
+ /* When PHY is in fiber mode, speed transferred from 1000Mbps to
+ * 100Mbps,there is not link down from YTPHY_SPECIFIC_STATUS_REG, so
+ * we need check MII_BMSR to identify such case.
+ */
+ if (page == YT8521_RSSR_FIBER_SPACE) {
+ ret = __phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ fiber_latch_val = ret;
+ ret = __phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ fiber_curr_val = ret;
+ if (link && fiber_latch_val != fiber_curr_val) {
+ link = 0;
+ phydev_info(phydev,
+ "%s, fiber link down detect, latch = %04x, curr = %04x\n",
+ __func__, fiber_latch_val, fiber_curr_val);
+ }
+ } else {
+ /* Read autonegotiation status */
+ ret = __phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ phydev->autoneg_complete = ret & BMSR_ANEGCOMPLETE ? 1 : 0;
+ }
+
+ if (link) {
+ if (page == YT8521_RSSR_UTP_SPACE)
+ yt8521_adjust_status(phydev, status, true);
+ else
+ yt8521_adjust_status(phydev, status, false);
+ }
+ return phy_restore_page(phydev, old_page, link);
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8521_read_status() - determines the negotiated speed and duplex
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_read_status(struct phy_device *phydev)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int link_fiber = 0;
+ int link_utp;
+ int link;
+ int ret;
+
+ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
+ link = yt8521_read_status_paged(phydev, priv->reg_page);
+ if (link < 0)
+ return link;
+ } else {
+ /* when page is YT8521_RSSR_TO_BE_ARBITRATED, arbitration is
+ * needed. by default, utp is higher priority.
+ */
+
+ link_utp = yt8521_read_status_paged(phydev,
+ YT8521_RSSR_UTP_SPACE);
+ if (link_utp < 0)
+ return link_utp;
+
+ if (!link_utp) {
+ link_fiber = yt8521_read_status_paged(phydev,
+ YT8521_RSSR_FIBER_SPACE);
+ if (link_fiber < 0)
+ return link_fiber;
+ }
+
+ link = link_utp || link_fiber;
+ }
+
+ if (link) {
+ if (phydev->link == 0) {
+ /* arbitrate reg space based on linkup media type. */
+ if (priv->polling_mode == YT8521_MODE_POLL &&
+ priv->reg_page == YT8521_RSSR_TO_BE_ARBITRATED) {
+ if (link_fiber)
+ priv->reg_page =
+ YT8521_RSSR_FIBER_SPACE;
+ else
+ priv->reg_page = YT8521_RSSR_UTP_SPACE;
+
+ ret = ytphy_write_ext_with_lock(phydev,
+ YT8521_REG_SPACE_SELECT_REG,
+ priv->reg_page);
+ if (ret < 0)
+ return ret;
+
+ phydev->port = link_fiber ? PORT_FIBRE : PORT_TP;
+
+ phydev_info(phydev, "%s, link up, media: %s\n",
+ __func__,
+ (phydev->port == PORT_TP) ?
+ "UTP" : "Fiber");
+ }
+ }
+ phydev->link = 1;
+ } else {
+ if (phydev->link == 1) {
+ phydev_info(phydev, "%s, link down, media: %s\n",
+ __func__, (phydev->port == PORT_TP) ?
+ "UTP" : "Fiber");
+
+ /* When in YT8521_MODE_POLL mode, need prepare for next
+ * arbitration.
+ */
+ if (priv->polling_mode == YT8521_MODE_POLL) {
+ priv->reg_page = YT8521_RSSR_TO_BE_ARBITRATED;
+ phydev->port = PORT_NONE;
+ }
+ }
+
+ phydev->link = 0;
+ }
+
+ return 0;
+}
+
+/**
+ * yt8521_modify_bmcr_paged - bits modify a PHY's BMCR register of one page
+ * @phydev: the phy_device struct
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to operate
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: Convenience function which allows a PHY's BMCR register to be
+ * modified as new register value = (old register value & ~mask) | set.
+ * YT8521 has two space (utp/fiber) and three mode (utp/fiber/poll), each space
+ * has MII_BMCR. poll mode combines utp and faber,so need do both.
+ * If it is reset, it will wait for completion.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_modify_bmcr_paged(struct phy_device *phydev, int page,
+ u16 mask, u16 set)
+{
+ int max_cnt = 500; /* the max wait time of reset ~ 500 ms */
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ ret = __phy_modify(phydev, MII_BMCR, mask, set);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* If it is reset, need to wait for the reset to complete */
+ if (set == BMCR_RESET) {
+ while (max_cnt--) {
+ usleep_range(1000, 1100);
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ if (!(ret & BMCR_RESET))
+ return phy_restore_page(phydev, old_page, 0);
+ }
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8521_modify_utp_fiber_bmcr - bits modify a PHY's BMCR register
+ * @phydev: the phy_device struct
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: Convenience function which allows a PHY's BMCR register to be
+ * modified as new register value = (old register value & ~mask) | set.
+ * YT8521 has two space (utp/fiber) and three mode (utp/fiber/poll), each space
+ * has MII_BMCR. poll mode combines utp and faber,so need do both.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_modify_utp_fiber_bmcr(struct phy_device *phydev, u16 mask,
+ u16 set)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
+ ret = yt8521_modify_bmcr_paged(phydev, priv->reg_page, mask,
+ set);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_UTP_SPACE,
+ mask, set);
+ if (ret < 0)
+ return ret;
+
+ ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_FIBER_SPACE,
+ mask, set);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * yt8521_soft_reset() - called to issue a PHY software reset
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_soft_reset(struct phy_device *phydev)
+{
+ return yt8521_modify_utp_fiber_bmcr(phydev, 0, BMCR_RESET);
+}
+
+/**
+ * yt8521_suspend() - suspend the hardware
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_suspend(struct phy_device *phydev)
+{
+ int wol_config;
+
+ /* YTPHY_WOL_CONFIG_REG is common ext reg */
+ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
+ if (wol_config < 0)
+ return wol_config;
+
+ /* if wol enable, do nothing */
+ if (wol_config & YTPHY_WCR_ENABLE)
+ return 0;
+
+ return yt8521_modify_utp_fiber_bmcr(phydev, 0, BMCR_PDOWN);
+}
+
+/**
+ * yt8521_resume() - resume the hardware
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_resume(struct phy_device *phydev)
+{
+ int ret;
+ int wol_config;
+
+ /* disable auto sleep */
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YT8521_EXTREG_SLEEP_CONTROL1_REG,
+ YT8521_ESC1R_SLEEP_SW, 0);
+ if (ret < 0)
+ return ret;
+
+ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
+ if (wol_config < 0)
+ return wol_config;
+
+ /* if wol enable, do nothing */
+ if (wol_config & YTPHY_WCR_ENABLE)
+ return 0;
+
+ return yt8521_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0);
+}
+
+/**
+ * yt8521_config_init() - called to initialize the PHY
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_config_init(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ /* set rgmii delay mode */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII) {
+ ret = ytphy_rgmii_clk_delay_config(phydev);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+ if (of_property_read_bool(node, "motorcomm,auto-sleep-disabled")) {
+ /* disable auto sleep */
+ ret = ytphy_modify_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1_REG,
+ YT8521_ESC1R_SLEEP_SW, 0);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+ if (of_property_read_bool(node, "motorcomm,keep-pll-enabled")) {
+ /* enable RXC clock when no wire plug */
+ ret = ytphy_modify_ext(phydev, YT8521_CLOCK_GATING_REG,
+ YT8521_CGR_RX_CLK_EN, 0);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+static int yt8531_config_init(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ int ret;
+
+ ret = ytphy_rgmii_clk_delay_config_with_lock(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (of_property_read_bool(node, "motorcomm,auto-sleep-disabled")) {
+ /* disable auto sleep */
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YT8521_EXTREG_SLEEP_CONTROL1_REG,
+ YT8521_ESC1R_SLEEP_SW, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (of_property_read_bool(node, "motorcomm,keep-pll-enabled")) {
+ /* enable RXC clock when no wire plug */
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YT8521_CLOCK_GATING_REG,
+ YT8521_CGR_RX_CLK_EN, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = yt8531_set_ds(phydev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * yt8531_link_change_notify() - Adjust the tx clock direction according to
+ * the current speed and dts config.
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * NOTE: This function is only used to adapt to VF2 with JH7110 SoC. Please
+ * keep "motorcomm,tx-clk-adj-enabled" not exist in dts when the soc is not
+ * JH7110.
+ */
+static void yt8531_link_change_notify(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ bool tx_clk_1000_inverted = false;
+ bool tx_clk_100_inverted = false;
+ bool tx_clk_10_inverted = false;
+ bool tx_clk_adj_enabled = false;
+ u16 val = 0;
+ int ret;
+
+ if (of_property_read_bool(node, "motorcomm,tx-clk-adj-enabled"))
+ tx_clk_adj_enabled = true;
+
+ if (!tx_clk_adj_enabled)
+ return;
+
+ if (of_property_read_bool(node, "motorcomm,tx-clk-10-inverted"))
+ tx_clk_10_inverted = true;
+ if (of_property_read_bool(node, "motorcomm,tx-clk-100-inverted"))
+ tx_clk_100_inverted = true;
+ if (of_property_read_bool(node, "motorcomm,tx-clk-1000-inverted"))
+ tx_clk_1000_inverted = true;
+
+ if (phydev->speed < 0)
+ return;
+
+ switch (phydev->speed) {
+ case SPEED_1000:
+ if (tx_clk_1000_inverted)
+ val = YT8521_RC1R_TX_CLK_SEL_INVERTED;
+ break;
+ case SPEED_100:
+ if (tx_clk_100_inverted)
+ val = YT8521_RC1R_TX_CLK_SEL_INVERTED;
+ break;
+ case SPEED_10:
+ if (tx_clk_10_inverted)
+ val = YT8521_RC1R_TX_CLK_SEL_INVERTED;
+ break;
+ default:
+ return;
+ }
+
+ ret = ytphy_modify_ext_with_lock(phydev, YT8521_RGMII_CONFIG1_REG,
+ YT8521_RC1R_TX_CLK_SEL_INVERTED, val);
+ if (ret < 0)
+ phydev_warn(phydev, "Modify TX_CLK_SEL err:%d\n", ret);
+}
+
+/**
+ * yt8521_prepare_fiber_features() - A small helper function that setup
+ * fiber's features.
+ * @phydev: a pointer to a &struct phy_device
+ * @dst: a pointer to store fiber's features
+ */
+static void yt8521_prepare_fiber_features(struct phy_device *phydev,
+ unsigned long *dst)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, dst);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, dst);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, dst);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, dst);
+}
+
+/**
+ * yt8521_fiber_setup_forced - configures/forces speed from @phydev
+ * @phydev: target phy_device struct
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_fiber_setup_forced(struct phy_device *phydev)
+{
+ u16 val;
+ int ret;
+
+ if (phydev->speed == SPEED_1000)
+ val = YTPHY_MCR_FIBER_1000BX;
+ else if (phydev->speed == SPEED_100)
+ val = YTPHY_MCR_FIBER_100FX;
+ else
+ return -EINVAL;
+
+ ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+ if (ret < 0)
+ return ret;
+
+ /* disable Fiber auto sensing */
+ ret = ytphy_modify_ext(phydev, YT8521_LINK_TIMER_CFG2_REG,
+ YT8521_LTCR_EN_AUTOSEN, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = ytphy_modify_ext(phydev, YTPHY_MISC_CONFIG_REG,
+ YTPHY_MCR_FIBER_SPEED_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ return ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG,
+ YT8521_CCR_SW_RST, 0);
+}
+
+/**
+ * ytphy_check_and_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: target phy_device struct
+ * @restart: whether aneg restart is requested
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_check_and_restart_aneg(struct phy_device *phydev, bool restart)
+{
+ int ret;
+
+ if (!restart) {
+ /* Advertisement hasn't changed, but maybe aneg was never on to
+ * begin with? Or maybe phy was isolated?
+ */
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ return ret;
+
+ if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE))
+ restart = true;
+ }
+ /* Enable and Restart Autonegotiation
+ * Don't isolate the PHY if we're negotiating
+ */
+ if (restart)
+ return __phy_modify(phydev, MII_BMCR, BMCR_ISOLATE,
+ BMCR_ANENABLE | BMCR_ANRESTART);
+
+ return 0;
+}
+
+/**
+ * yt8521_fiber_config_aneg - restart auto-negotiation or write
+ * YTPHY_MISC_CONFIG_REG.
+ * @phydev: target phy_device struct
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_fiber_config_aneg(struct phy_device *phydev)
+{
+ int err, changed = 0;
+ int bmcr;
+ u16 adv;
+
+ if (phydev->autoneg != AUTONEG_ENABLE)
+ return yt8521_fiber_setup_forced(phydev);
+
+ /* enable Fiber auto sensing */
+ err = ytphy_modify_ext(phydev, YT8521_LINK_TIMER_CFG2_REG,
+ 0, YT8521_LTCR_EN_AUTOSEN);
+ if (err < 0)
+ return err;
+
+ err = ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG,
+ YT8521_CCR_SW_RST, 0);
+ if (err < 0)
+ return err;
+
+ bmcr = __phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ /* When it is coming from fiber forced mode, add bmcr power down
+ * and power up to let aneg work fine.
+ */
+ if (!(bmcr & BMCR_ANENABLE)) {
+ __phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN);
+ usleep_range(1000, 1100);
+ __phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0);
+ }
+
+ adv = linkmode_adv_to_mii_adv_x(phydev->advertising,
+ ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
+
+ /* Setup fiber advertisement */
+ err = __phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_1000XHALF | ADVERTISE_1000XFULL |
+ ADVERTISE_1000XPAUSE |
+ ADVERTISE_1000XPSE_ASYM,
+ adv);
+ if (err < 0)
+ return err;
+
+ if (err > 0)
+ changed = 1;
+
+ return ytphy_check_and_restart_aneg(phydev, changed);
+}
+
+/**
+ * ytphy_setup_master_slave
+ * @phydev: target phy_device struct
+ *
+ * NOTE: The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_setup_master_slave(struct phy_device *phydev)
+{
+ u16 ctl = 0;
+
+ if (!phydev->is_gigabit_capable)
+ return 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ ctl |= CTL1000_PREFER_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ break;
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl |= CTL1000_AS_MASTER;
+ fallthrough;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ ctl |= CTL1000_ENABLE_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ return __phy_modify_changed(phydev, MII_CTRL1000,
+ (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER |
+ CTL1000_PREFER_MASTER), ctl);
+}
+
+/**
+ * ytphy_utp_config_advert - sanitize and advertise auto-negotiation parameters
+ * @phydev: target phy_device struct
+ *
+ * NOTE: 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.
+ * The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_utp_config_advert(struct phy_device *phydev)
+{
+ int err, bmsr, changed = 0;
+ u32 adv;
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
+
+ /* Setup standard advertisement */
+ err = __phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_100BASE4 |
+ ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ 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;
+
+ adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+
+ err = __phy_modify_changed(phydev, MII_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ changed = 1;
+
+ return changed;
+}
+
+/**
+ * ytphy_utp_config_aneg - restart auto-negotiation or write BMCR
+ * @phydev: target phy_device struct
+ * @changed: whether autoneg is requested
+ *
+ * NOTE: If auto-negotiation is enabled, we configure the
+ * advertising, and then restart auto-negotiation. If it is not
+ * enabled, then we write the BMCR.
+ * The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_utp_config_aneg(struct phy_device *phydev, bool changed)
+{
+ int err;
+ u16 ctl;
+
+ err = ytphy_setup_master_slave(phydev);
+ if (err < 0)
+ return err;
+ else if (err)
+ changed = true;
+
+ if (phydev->autoneg != AUTONEG_ENABLE) {
+ /* configures/forces speed/duplex from @phydev */
+
+ ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
+
+ return __phy_modify(phydev, MII_BMCR, ~(BMCR_LOOPBACK |
+ BMCR_ISOLATE | BMCR_PDOWN), ctl);
+ }
+
+ err = ytphy_utp_config_advert(phydev);
+ if (err < 0) /* error */
+ return err;
+ else if (err)
+ changed = true;
+
+ return ytphy_check_and_restart_aneg(phydev, changed);
+}
+
+/**
+ * yt8521_config_aneg_paged() - switch reg space then call genphy_config_aneg
+ * of one page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_config_aneg_paged(struct phy_device *phydev, int page)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(fiber_supported);
+ struct yt8521_priv *priv = phydev->priv;
+ int old_page;
+ int ret = 0;
+
+ page &= YT8521_RSSR_SPACE_MASK;
+
+ old_page = phy_select_page(phydev, page);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ /* If reg_page is YT8521_RSSR_TO_BE_ARBITRATED,
+ * phydev->advertising should be updated.
+ */
+ if (priv->reg_page == YT8521_RSSR_TO_BE_ARBITRATED) {
+ linkmode_zero(fiber_supported);
+ yt8521_prepare_fiber_features(phydev, fiber_supported);
+
+ /* prepare fiber_supported, then setup advertising. */
+ if (page == YT8521_RSSR_FIBER_SPACE) {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ fiber_supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ fiber_supported);
+ linkmode_and(phydev->advertising,
+ priv->combo_advertising, fiber_supported);
+ } else {
+ /* ETHTOOL_LINK_MODE_Autoneg_BIT is also used in utp */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ fiber_supported);
+ linkmode_andnot(phydev->advertising,
+ priv->combo_advertising,
+ fiber_supported);
+ }
+ }
+
+ if (page == YT8521_RSSR_FIBER_SPACE)
+ ret = yt8521_fiber_config_aneg(phydev);
+ else
+ ret = ytphy_utp_config_aneg(phydev, false);
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8521_config_aneg() - change reg space then call yt8521_config_aneg_paged
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_config_aneg(struct phy_device *phydev)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
+ ret = yt8521_config_aneg_paged(phydev, priv->reg_page);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* If reg_page is YT8521_RSSR_TO_BE_ARBITRATED,
+ * phydev->advertising need to be saved at first run.
+ * Because it contains the advertising which supported by both
+ * mac and yt8521(utp and fiber).
+ */
+ if (linkmode_empty(priv->combo_advertising)) {
+ linkmode_copy(priv->combo_advertising,
+ phydev->advertising);
+ }
+
+ ret = yt8521_config_aneg_paged(phydev, YT8521_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ ret = yt8521_config_aneg_paged(phydev, YT8521_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* we don't known which will be link, so restore
+ * phydev->advertising as default value.
+ */
+ linkmode_copy(phydev->advertising, priv->combo_advertising);
+ }
+ return 0;
+}
+
+/**
+ * yt8521_aneg_done_paged() - determines the auto negotiation result of one
+ * page.
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 0(no link)or 1(fiber or utp link) or negative errno code
+ */
+static int yt8521_aneg_done_paged(struct phy_device *phydev, int page)
+{
+ int old_page;
+ int ret = 0;
+ int link;
+
+ old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
+ if (ret < 0)
+ goto err_restore_page;
+
+ link = !!(ret & YTPHY_SSR_LINK);
+ ret = link;
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8521_aneg_done() - determines the auto negotiation result
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0(no link)or 1(fiber or utp link) or negative errno code
+ */
+static int yt8521_aneg_done(struct phy_device *phydev)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int link_fiber = 0;
+ int link_utp;
+ int link;
+
+ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
+ link = yt8521_aneg_done_paged(phydev, priv->reg_page);
+ } else {
+ link_utp = yt8521_aneg_done_paged(phydev,
+ YT8521_RSSR_UTP_SPACE);
+ if (link_utp < 0)
+ return link_utp;
+
+ if (!link_utp) {
+ link_fiber = yt8521_aneg_done_paged(phydev,
+ YT8521_RSSR_FIBER_SPACE);
+ if (link_fiber < 0)
+ return link_fiber;
+ }
+ link = link_fiber || link_utp;
+ phydev_info(phydev, "%s, link_fiber: %d, link_utp: %d\n",
+ __func__, link_fiber, link_utp);
+ }
+
+ return link;
+}
+
+/**
+ * ytphy_utp_read_abilities - read PHY abilities from Clause 22 registers
+ * @phydev: target phy_device struct
+ *
+ * NOTE: Reads the PHY's abilities and populates
+ * phydev->supported accordingly.
+ * The caller must have taken the MDIO bus lock.
+ *
+ * returns 0 or negative errno code
+ */
+static int ytphy_utp_read_abilities(struct phy_device *phydev)
+{
+ int val;
+
+ linkmode_set_bit_array(phy_basic_ports_array,
+ ARRAY_SIZE(phy_basic_ports_array),
+ phydev->supported);
+
+ val = __phy_read(phydev, MII_BMSR);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported,
+ val & BMSR_ANEGCAPABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported,
+ val & BMSR_100FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported,
+ val & BMSR_100HALF);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported,
+ val & BMSR_10FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported,
+ val & BMSR_10HALF);
+
+ if (val & BMSR_ESTATEN) {
+ val = __phy_read(phydev, MII_ESTATUS);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported, val & ESTATUS_1000_TFULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->supported, val & ESTATUS_1000_THALF);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->supported, val & ESTATUS_1000_XFULL);
+ }
+
+ return 0;
+}
+
+/**
+ * yt8521_get_features_paged() - read supported link modes for one page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_get_features_paged(struct phy_device *phydev, int page)
+{
+ int old_page;
+ int ret = 0;
+
+ page &= YT8521_RSSR_SPACE_MASK;
+ old_page = phy_select_page(phydev, page);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ if (page == YT8521_RSSR_FIBER_SPACE) {
+ linkmode_zero(phydev->supported);
+ yt8521_prepare_fiber_features(phydev, phydev->supported);
+ } else {
+ ret = ytphy_utp_read_abilities(phydev);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8521_get_features - switch reg space then call yt8521_get_features_paged
+ * @phydev: target phy_device struct
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8521_get_features(struct phy_device *phydev)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) {
+ ret = yt8521_get_features_paged(phydev, priv->reg_page);
+ } else {
+ ret = yt8521_get_features_paged(phydev,
+ YT8521_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* add fiber's features to phydev->supported */
+ yt8521_prepare_fiber_features(phydev, phydev->supported);
+ }
+ return ret;
+}
+
+static struct phy_driver motorcomm_phy_drvs[] = {
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
+ .name = "YT8511 Gigabit Ethernet",
+ .config_init = yt8511_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = yt8511_read_page,
+ .write_page = yt8511_write_page,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8521),
+ .name = "YT8521 Gigabit Ethernet",
+ .get_features = yt8521_get_features,
+ .probe = yt8521_probe,
+ .read_page = yt8521_read_page,
+ .write_page = yt8521_write_page,
+ .get_wol = ytphy_get_wol,
+ .set_wol = ytphy_set_wol,
+ .config_aneg = yt8521_config_aneg,
+ .aneg_done = yt8521_aneg_done,
+ .config_init = yt8521_config_init,
+ .read_status = yt8521_read_status,
+ .soft_reset = yt8521_soft_reset,
+ .suspend = yt8521_suspend,
+ .resume = yt8521_resume,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8531),
+ .name = "YT8531 Gigabit Ethernet",
+ .probe = yt8531_probe,
+ .config_init = yt8531_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .get_wol = ytphy_get_wol,
+ .set_wol = yt8531_set_wol,
+ .link_change_notify = yt8531_link_change_notify,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
+ .name = "YT8531S Gigabit Ethernet",
+ .get_features = yt8521_get_features,
+ .probe = yt8521_probe,
+ .read_page = yt8521_read_page,
+ .write_page = yt8521_write_page,
+ .get_wol = ytphy_get_wol,
+ .set_wol = ytphy_set_wol,
+ .config_aneg = yt8521_config_aneg,
+ .aneg_done = yt8521_aneg_done,
+ .config_init = yt8521_config_init,
+ .read_status = yt8521_read_status,
+ .soft_reset = yt8521_soft_reset,
+ .suspend = yt8521_suspend,
+ .resume = yt8521_resume,
+ },
+};
+
+module_phy_driver(motorcomm_phy_drvs);
+
+MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S PHY driver");
+MODULE_AUTHOR("Peter Geis");
+MODULE_AUTHOR("Frank");
+MODULE_LICENSE("GPL");
+
+static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8531) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);
diff --git a/drivers/net/phy/mscc/Makefile b/drivers/net/phy/mscc/Makefile
new file mode 100644
index 000000000..78d84194f
--- /dev/null
+++ b/drivers/net/phy/mscc/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for MSCC networking PHY driver
+
+obj-$(CONFIG_MICROSEMI_PHY) := mscc.o
+mscc-objs := mscc_main.o
+mscc-objs += mscc_serdes.o
+
+ifdef CONFIG_MACSEC
+mscc-objs += mscc_macsec.o
+endif
+
+ifdef CONFIG_NETWORK_PHY_TIMESTAMPING
+mscc-objs += mscc_ptp.o
+endif
diff --git a/drivers/net/phy/mscc/mscc.h b/drivers/net/phy/mscc/mscc.h
new file mode 100644
index 000000000..7a962050a
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc.h
@@ -0,0 +1,495 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Copyright (c) 2016 Microsemi Corporation
+ */
+
+#ifndef _MSCC_PHY_H_
+#define _MSCC_PHY_H_
+
+#if IS_ENABLED(CONFIG_MACSEC)
+#include "mscc_macsec.h"
+#endif
+
+enum rgmii_clock_delay {
+ RGMII_CLK_DELAY_0_2_NS = 0,
+ RGMII_CLK_DELAY_0_8_NS = 1,
+ RGMII_CLK_DELAY_1_1_NS = 2,
+ RGMII_CLK_DELAY_1_7_NS = 3,
+ RGMII_CLK_DELAY_2_0_NS = 4,
+ RGMII_CLK_DELAY_2_3_NS = 5,
+ RGMII_CLK_DELAY_2_6_NS = 6,
+ RGMII_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 PARALLEL_DET_IGNORE_ADVERTISED 0x0008
+
+#define MSCC_PHY_EXT_CNTL_STATUS 22
+#define SMI_BROADCAST_WR_EN 0x0001
+
+#define MSCC_PHY_ERR_RX_CNT 19
+#define MSCC_PHY_ERR_FALSE_CARRIER_CNT 20
+#define MSCC_PHY_ERR_LINK_DISCONNECT_CNT 21
+#define ERR_CNT_MASK GENMASK(7, 0)
+
+#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 VSC8584_MAC_IF_SELECTION_MASK 0x1000
+#define VSC8584_MAC_IF_SELECTION_SGMII 0
+#define VSC8584_MAC_IF_SELECTION_1000BASEX 1
+#define VSC8584_MAC_IF_SELECTION_POS 12
+#define FAR_END_LOOPBACK_MODE_MASK 0x0008
+#define MEDIA_OP_MODE_MASK 0x0700
+#define MEDIA_OP_MODE_COPPER 0
+#define MEDIA_OP_MODE_SERDES 1
+#define MEDIA_OP_MODE_1000BASEX 2
+#define MEDIA_OP_MODE_100BASEFX 3
+#define MEDIA_OP_MODE_AMS_COPPER_SERDES 5
+#define MEDIA_OP_MODE_AMS_COPPER_1000BASEX 6
+#define MEDIA_OP_MODE_AMS_COPPER_100BASEFX 7
+#define MEDIA_OP_MODE_POS 8
+
+#define MSCC_PHY_EXT_PHY_CNTL_2 24
+
+#define MII_VSC85XX_INT_MASK 25
+#define MII_VSC85XX_INT_MASK_MDINT BIT(15)
+#define MII_VSC85XX_INT_MASK_LINK_CHG BIT(13)
+#define MII_VSC85XX_INT_MASK_WOL BIT(6)
+#define MII_VSC85XX_INT_MASK_EXT BIT(5)
+#define MII_VSC85XX_INT_STATUS 26
+
+#define MII_VSC85XX_INT_MASK_MASK (MII_VSC85XX_INT_MASK_MDINT | \
+ MII_VSC85XX_INT_MASK_LINK_CHG | \
+ MII_VSC85XX_INT_MASK_EXT)
+
+#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_MODE_SEL_POS(x) ((x) * 4)
+#define LED_MODE_SEL_MASK(x) (GENMASK(3, 0) << LED_MODE_SEL_POS(x))
+#define LED_MODE_SEL(x, mode) (((mode) << LED_MODE_SEL_POS(x)) & LED_MODE_SEL_MASK(x))
+
+#define MSCC_EXT_PAGE_CSR_CNTL_17 17
+#define MSCC_EXT_PAGE_CSR_CNTL_18 18
+
+#define MSCC_EXT_PAGE_CSR_CNTL_19 19
+#define MSCC_PHY_CSR_CNTL_19_REG_ADDR(x) (x)
+#define MSCC_PHY_CSR_CNTL_19_TARGET(x) ((x) << 12)
+#define MSCC_PHY_CSR_CNTL_19_READ BIT(14)
+#define MSCC_PHY_CSR_CNTL_19_CMD BIT(15)
+
+#define MSCC_EXT_PAGE_CSR_CNTL_20 20
+#define MSCC_PHY_CSR_CNTL_20_TARGET(x) (x)
+
+#define PHY_MCB_TARGET 0x07
+#define PHY_MCB_S6G_WRITE BIT(31)
+#define PHY_MCB_S6G_READ BIT(30)
+
+#define PHY_S6G_PLL5G_CFG0 0x06
+#define PHY_S6G_PLL5G_CFG2 0x08
+#define PHY_S6G_LCPLL_CFG 0x11
+#define PHY_S6G_PLL_CFG 0x2b
+#define PHY_S6G_COMMON_CFG 0x2c
+#define PHY_S6G_GPC_CFG 0x2e
+#define PHY_S6G_MISC_CFG 0x3b
+#define PHY_MCB_S6G_CFG 0x3f
+#define PHY_S6G_DFT_CFG2 0x3e
+#define PHY_S6G_PLL_STATUS 0x31
+#define PHY_S6G_IB_STATUS0 0x2f
+
+#define PHY_S6G_SYS_RST_POS 31
+#define PHY_S6G_ENA_LANE_POS 18
+#define PHY_S6G_ENA_LOOP_POS 8
+#define PHY_S6G_QRATE_POS 6
+#define PHY_S6G_IF_MODE_POS 4
+#define PHY_S6G_PLL_ENA_OFFS_POS 21
+#define PHY_S6G_PLL_FSM_CTRL_DATA_POS 8
+#define PHY_S6G_PLL_FSM_ENA_POS 7
+
+#define PHY_S6G_CFG2_FSM_DIS 1
+#define PHY_S6G_CFG2_FSM_CLK_BP 23
+
+#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 */
+#define MSCC_PHY_PAGE_EXTENDED_3 0x0003 /* Extended reg - page 3 */
+#define MSCC_PHY_PAGE_EXTENDED_4 0x0004 /* Extended reg - page 4 */
+#define MSCC_PHY_PAGE_CSR_CNTL MSCC_PHY_PAGE_EXTENDED_4
+#define MSCC_PHY_PAGE_MACSEC MSCC_PHY_PAGE_EXTENDED_4
+/* Extended reg - GPIO; this is a bank of registers that are shared for all PHYs
+ * in the same package.
+ */
+#define MSCC_PHY_PAGE_EXTENDED_GPIO 0x0010 /* Extended reg - GPIO */
+#define MSCC_PHY_PAGE_1588 0x1588 /* PTP (1588) */
+#define MSCC_PHY_PAGE_TEST 0x2a30 /* Test reg */
+#define MSCC_PHY_PAGE_TR 0x52b5 /* Token ring registers */
+#define MSCC_PHY_GPIO_CONTROL_2 14
+
+#define MSCC_PHY_COMA_MODE 0x2000 /* input(1) / output(0) */
+#define MSCC_PHY_COMA_OUTPUT 0x1000 /* value to output */
+
+/* Extended Page 1 Registers */
+#define MSCC_PHY_CU_MEDIA_CRC_VALID_CNT 18
+#define VALID_CRC_CNT_CRC_MASK GENMASK(13, 0)
+
+#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 PHY_ADDR_REVERSED 0x0200
+#define DOWNSHIFT_CNTL_MASK 0x001C
+#define DOWNSHIFT_EN 0x0010
+#define DOWNSHIFT_CNTL_POS 2
+
+#define MSCC_PHY_EXT_PHY_CNTL_4 23
+#define PHY_CNTL_4_ADDR_POS 11
+
+#define MSCC_PHY_VERIPHY_CNTL_2 25
+
+#define MSCC_PHY_VERIPHY_CNTL_3 26
+
+/* Extended Page 2 Registers */
+#define MSCC_PHY_CU_PMD_TX_CNTL 16
+
+/* RGMII setting controls at address 18E2, for VSC8572 and similar */
+#define VSC8572_RGMII_CNTL 18
+#define VSC8572_RGMII_RX_DELAY_MASK 0x000E
+#define VSC8572_RGMII_TX_DELAY_MASK 0x0070
+
+/* RGMII controls at address 20E2, for VSC8502 and similar */
+#define VSC8502_RGMII_CNTL 20
+#define VSC8502_RGMII_RX_DELAY_MASK 0x0070
+#define VSC8502_RGMII_TX_DELAY_MASK 0x0007
+#define VSC8502_RGMII_RX_CLK_DISABLE 0x0800
+
+#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
+
+#define MSCC_PHY_EXTENDED_INT 28
+#define MSCC_PHY_EXTENDED_INT_MS_EGR BIT(9)
+
+/* Extended Page 3 Registers */
+#define MSCC_PHY_SERDES_TX_VALID_CNT 21
+#define MSCC_PHY_SERDES_TX_CRC_ERR_CNT 22
+#define MSCC_PHY_SERDES_RX_VALID_CNT 28
+#define MSCC_PHY_SERDES_RX_CRC_ERR_CNT 29
+
+/* Extended page GPIO Registers */
+#define MSCC_DW8051_CNTL_STATUS 0
+#define MICRO_NSOFT_RESET 0x8000
+#define RUN_FROM_INT_ROM 0x4000
+#define AUTOINC_ADDR 0x2000
+#define PATCH_RAM_CLK 0x1000
+#define MICRO_PATCH_EN 0x0080
+#define DW8051_CLK_EN 0x0010
+#define MICRO_CLK_EN 0x0008
+#define MICRO_CLK_DIVIDE(x) ((x) >> 1)
+#define MSCC_DW8051_VLD_MASK 0xf1ff
+
+/* x Address in range 1-4 */
+#define MSCC_TRAP_ROM_ADDR(x) ((x) * 2 + 1)
+#define MSCC_PATCH_RAM_ADDR(x) (((x) + 1) * 2)
+#define MSCC_INT_MEM_ADDR 11
+
+#define MSCC_INT_MEM_CNTL 12
+#define READ_SFR 0x6000
+#define READ_PRAM 0x4000
+#define READ_ROM 0x2000
+#define READ_RAM 0x0000
+#define INT_MEM_WRITE_EN 0x1000
+#define EN_PATCH_RAM_TRAP_ADDR(x) (0x0100 << ((x) - 1))
+#define INT_MEM_DATA_M 0x00ff
+#define INT_MEM_DATA(x) (INT_MEM_DATA_M & (x))
+
+#define MSCC_PHY_PROC_CMD 18
+#define PROC_CMD_NCOMPLETED 0x8000
+#define PROC_CMD_FAILED 0x4000
+#define PROC_CMD_SGMII_PORT(x) ((x) << 8)
+#define PROC_CMD_FIBER_PORT(x) (0x0100 << (x) % 4)
+#define PROC_CMD_QSGMII_PORT 0x0c00
+#define PROC_CMD_RST_CONF_PORT 0x0080
+#define PROC_CMD_RECONF_PORT 0x0000
+#define PROC_CMD_READ_MOD_WRITE_PORT 0x0040
+#define PROC_CMD_WRITE 0x0040
+#define PROC_CMD_READ 0x0000
+#define PROC_CMD_FIBER_DISABLE 0x0020
+#define PROC_CMD_FIBER_100BASE_FX 0x0010
+#define PROC_CMD_FIBER_1000BASE_X 0x0000
+#define PROC_CMD_SGMII_MAC 0x0030
+#define PROC_CMD_QSGMII_MAC 0x0020
+#define PROC_CMD_NO_MAC_CONF 0x0000
+#define PROC_CMD_1588_DEFAULT_INIT 0x0010
+#define PROC_CMD_NOP 0x000f
+#define PROC_CMD_PHY_INIT 0x000a
+#define PROC_CMD_CRC16 0x0008
+#define PROC_CMD_FIBER_MEDIA_CONF 0x0001
+#define PROC_CMD_MCB_ACCESS_MAC_CONF 0x0000
+#define PROC_CMD_NCOMPLETED_TIMEOUT_MS 500
+
+#define MSCC_PHY_MAC_CFG_FASTLINK 19
+#define MAC_CFG_MASK 0xc000
+#define MAC_CFG_SGMII 0x0000
+#define MAC_CFG_QSGMII 0x4000
+#define MAC_CFG_RGMII 0x8000
+
+/* Test page Registers */
+#define MSCC_PHY_TEST_PAGE_5 5
+#define MSCC_PHY_TEST_PAGE_8 8
+#define TR_CLK_DISABLE 0x8000
+#define MSCC_PHY_TEST_PAGE_9 9
+#define MSCC_PHY_TEST_PAGE_20 20
+#define MSCC_PHY_TEST_PAGE_24 24
+
+/* Token ring page Registers */
+#define MSCC_PHY_TR_CNTL 16
+#define TR_WRITE 0x8000
+#define TR_ADDR(x) (0x7fff & (x))
+#define MSCC_PHY_TR_LSB 17
+#define MSCC_PHY_TR_MSB 18
+
+/* Microsemi PHY ID's
+ * Code assumes lowest nibble is 0
+ */
+#define PHY_ID_VSC8501 0x00070530
+#define PHY_ID_VSC8502 0x00070630
+#define PHY_ID_VSC8504 0x000704c0
+#define PHY_ID_VSC8514 0x00070670
+#define PHY_ID_VSC8530 0x00070560
+#define PHY_ID_VSC8531 0x00070570
+#define PHY_ID_VSC8540 0x00070760
+#define PHY_ID_VSC8541 0x00070770
+#define PHY_ID_VSC8552 0x000704e0
+#define PHY_ID_VSC856X 0x000707e0
+#define PHY_ID_VSC8572 0x000704d0
+#define PHY_ID_VSC8574 0x000704a0
+#define PHY_ID_VSC8575 0x000707d0
+#define PHY_ID_VSC8582 0x000707b0
+#define PHY_ID_VSC8584 0x000707c0
+#define PHY_VENDOR_MSCC 0x00070400
+
+#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
+
+#define MAX_LEDS 4
+
+#define VSC8584_SUPP_LED_MODES (BIT(VSC8531_LINK_ACTIVITY) | \
+ BIT(VSC8531_LINK_1000_ACTIVITY) | \
+ BIT(VSC8531_LINK_100_ACTIVITY) | \
+ BIT(VSC8531_LINK_10_ACTIVITY) | \
+ BIT(VSC8531_LINK_100_1000_ACTIVITY) | \
+ BIT(VSC8531_LINK_10_1000_ACTIVITY) | \
+ BIT(VSC8531_LINK_10_100_ACTIVITY) | \
+ BIT(VSC8584_LINK_100FX_1000X_ACTIVITY) | \
+ BIT(VSC8531_DUPLEX_COLLISION) | \
+ BIT(VSC8531_COLLISION) | \
+ BIT(VSC8531_ACTIVITY) | \
+ BIT(VSC8584_100FX_1000X_ACTIVITY) | \
+ BIT(VSC8531_AUTONEG_FAULT) | \
+ BIT(VSC8531_SERIAL_MODE) | \
+ BIT(VSC8531_FORCE_LED_OFF) | \
+ BIT(VSC8531_FORCE_LED_ON))
+
+#define VSC85XX_SUPP_LED_MODES (BIT(VSC8531_LINK_ACTIVITY) | \
+ BIT(VSC8531_LINK_1000_ACTIVITY) | \
+ BIT(VSC8531_LINK_100_ACTIVITY) | \
+ BIT(VSC8531_LINK_10_ACTIVITY) | \
+ BIT(VSC8531_LINK_100_1000_ACTIVITY) | \
+ BIT(VSC8531_LINK_10_1000_ACTIVITY) | \
+ BIT(VSC8531_LINK_10_100_ACTIVITY) | \
+ BIT(VSC8531_DUPLEX_COLLISION) | \
+ BIT(VSC8531_COLLISION) | \
+ BIT(VSC8531_ACTIVITY) | \
+ BIT(VSC8531_AUTONEG_FAULT) | \
+ BIT(VSC8531_SERIAL_MODE) | \
+ BIT(VSC8531_FORCE_LED_OFF) | \
+ BIT(VSC8531_FORCE_LED_ON))
+
+#define MSCC_VSC8584_REVB_INT8051_FW "microchip/mscc_vsc8584_revb_int8051_fb48.bin"
+#define MSCC_VSC8584_REVB_INT8051_FW_START_ADDR 0xe800
+#define MSCC_VSC8584_REVB_INT8051_FW_CRC 0xfb48
+
+#define MSCC_VSC8574_REVB_INT8051_FW "microchip/mscc_vsc8574_revb_int8051_29e8.bin"
+#define MSCC_VSC8574_REVB_INT8051_FW_START_ADDR 0x4000
+#define MSCC_VSC8574_REVB_INT8051_FW_CRC 0x29e8
+
+#define VSC8584_REVB 0x0001
+#define MSCC_DEV_REV_MASK GENMASK(3, 0)
+
+#define MSCC_ROM_TRAP_SERDES_6G_CFG 0x1E48
+#define MSCC_RAM_TRAP_SERDES_6G_CFG 0x1E4F
+#define PATCH_VEC_ZERO_EN 0x0100
+
+struct reg_val {
+ u16 reg;
+ u32 val;
+};
+
+struct vsc85xx_hw_stat {
+ const char *string;
+ u8 reg;
+ u16 page;
+ u16 mask;
+};
+
+struct vsc8531_private {
+ int rate_magic;
+ u16 supp_led_modes;
+ u32 leds_mode[MAX_LEDS];
+ u8 nleds;
+ const struct vsc85xx_hw_stat *hw_stats;
+ u64 *stats;
+ int nstats;
+ /* PHY address within the package. */
+ u8 addr;
+ /* For multiple port PHYs; the MDIO address of the base PHY in the
+ * package.
+ */
+ unsigned int base_addr;
+
+#if IS_ENABLED(CONFIG_MACSEC)
+ /* MACsec fields:
+ * - One SecY per device (enforced at the s/w implementation level)
+ * - macsec_flows: list of h/w flows
+ * - ingr_flows: bitmap of ingress flows
+ * - egr_flows: bitmap of egress flows
+ */
+ struct macsec_secy *secy;
+ struct list_head macsec_flows;
+ unsigned long ingr_flows;
+ unsigned long egr_flows;
+#endif
+
+ struct mii_timestamper mii_ts;
+
+ bool input_clk_init;
+ struct vsc85xx_ptp *ptp;
+ /* LOAD/SAVE GPIO pin, used for retrieving or setting time to the PHC. */
+ struct gpio_desc *load_save;
+
+ /* For multiple port PHYs; the MDIO address of the base PHY in the
+ * pair of two PHYs that share a 1588 engine. PHY0 and PHY2 are coupled.
+ * PHY1 and PHY3 as well. PHY0 and PHY1 are base PHYs for their
+ * respective pair.
+ */
+ unsigned int ts_base_addr;
+ u8 ts_base_phy;
+
+ /* ts_lock: used for per-PHY timestamping operations.
+ * phc_lock: used for per-PHY PHC opertations.
+ */
+ struct mutex ts_lock;
+ struct mutex phc_lock;
+};
+
+/* Shared structure between the PHYs of the same package.
+ * gpio_lock: used for PHC operations. Common for all PHYs as the load/save GPIO
+ * is shared.
+ */
+struct vsc85xx_shared_private {
+ struct mutex gpio_lock;
+};
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+struct vsc8531_edge_rate_table {
+ u32 vddmac;
+ u32 slowdown[8];
+};
+#endif /* CONFIG_OF_MDIO */
+
+enum csr_target {
+ MACRO_CTRL = 0x07,
+};
+
+u32 vsc85xx_csr_read(struct phy_device *phydev,
+ enum csr_target target, u32 reg);
+
+int vsc85xx_csr_write(struct phy_device *phydev,
+ enum csr_target target, u32 reg, u32 val);
+
+int phy_base_write(struct phy_device *phydev, u32 regnum, u16 val);
+int phy_base_read(struct phy_device *phydev, u32 regnum);
+int phy_update_mcb_s6g(struct phy_device *phydev, u32 reg, u8 mcb);
+int phy_commit_mcb_s6g(struct phy_device *phydev, u32 reg, u8 mcb);
+int vsc8584_cmd(struct phy_device *phydev, u16 val);
+
+#if IS_ENABLED(CONFIG_MACSEC)
+int vsc8584_macsec_init(struct phy_device *phydev);
+void vsc8584_handle_macsec_interrupt(struct phy_device *phydev);
+void vsc8584_config_macsec_intr(struct phy_device *phydev);
+#else
+static inline int vsc8584_macsec_init(struct phy_device *phydev)
+{
+ return 0;
+}
+static inline void vsc8584_handle_macsec_interrupt(struct phy_device *phydev)
+{
+}
+static inline void vsc8584_config_macsec_intr(struct phy_device *phydev)
+{
+}
+#endif
+
+#if IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING)
+void vsc85xx_link_change_notify(struct phy_device *phydev);
+void vsc8584_config_ts_intr(struct phy_device *phydev);
+int vsc8584_ptp_init(struct phy_device *phydev);
+int vsc8584_ptp_probe_once(struct phy_device *phydev);
+int vsc8584_ptp_probe(struct phy_device *phydev);
+irqreturn_t vsc8584_handle_ts_interrupt(struct phy_device *phydev);
+#else
+static inline void vsc85xx_link_change_notify(struct phy_device *phydev)
+{
+}
+static inline void vsc8584_config_ts_intr(struct phy_device *phydev)
+{
+}
+static inline int vsc8584_ptp_init(struct phy_device *phydev)
+{
+ return 0;
+}
+static inline int vsc8584_ptp_probe_once(struct phy_device *phydev)
+{
+ return 0;
+}
+static inline int vsc8584_ptp_probe(struct phy_device *phydev)
+{
+ return 0;
+}
+static inline irqreturn_t vsc8584_handle_ts_interrupt(struct phy_device *phydev)
+{
+ return IRQ_NONE;
+}
+#endif
+
+#endif /* _MSCC_PHY_H_ */
diff --git a/drivers/net/phy/mscc/mscc_fc_buffer.h b/drivers/net/phy/mscc/mscc_fc_buffer.h
new file mode 100644
index 000000000..399e80339
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_fc_buffer.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Copyright (C) 2020 Microsemi Corporation
+ */
+
+#ifndef _MSCC_PHY_FC_BUFFER_H_
+#define _MSCC_PHY_FC_BUFFER_H_
+
+#define MSCC_FCBUF_ENA_CFG 0x00
+#define MSCC_FCBUF_MODE_CFG 0x01
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG 0x02
+#define MSCC_FCBUF_TX_CTRL_QUEUE_CFG 0x03
+#define MSCC_FCBUF_TX_DATA_QUEUE_CFG 0x04
+#define MSCC_FCBUF_RX_DATA_QUEUE_CFG 0x05
+#define MSCC_FCBUF_TX_BUFF_XON_XOFF_THRESH_CFG 0x06
+#define MSCC_FCBUF_FC_READ_THRESH_CFG 0x07
+#define MSCC_FCBUF_TX_FRM_GAP_COMP 0x08
+
+#define MSCC_FCBUF_ENA_CFG_TX_ENA BIT(0)
+#define MSCC_FCBUF_ENA_CFG_RX_ENA BIT(4)
+
+#define MSCC_FCBUF_MODE_CFG_DROP_BEHAVIOUR BIT(4)
+#define MSCC_FCBUF_MODE_CFG_PAUSE_REACT_ENA BIT(8)
+#define MSCC_FCBUF_MODE_CFG_RX_PPM_RATE_ADAPT_ENA BIT(12)
+#define MSCC_FCBUF_MODE_CFG_TX_PPM_RATE_ADAPT_ENA BIT(16)
+#define MSCC_FCBUF_MODE_CFG_TX_CTRL_QUEUE_ENA BIT(20)
+#define MSCC_FCBUF_MODE_CFG_PAUSE_GEN_ENA BIT(24)
+#define MSCC_FCBUF_MODE_CFG_INCLUDE_PAUSE_RCVD_IN_PAUSE_GEN BIT(28)
+
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_TX_THRESH(x) (x)
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_TX_THRESH_M GENMASK(15, 0)
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_TX_OFFSET(x) ((x) << 16)
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_TX_OFFSET_M GENMASK(19, 16)
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_RX_THRESH(x) ((x) << 20)
+#define MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_RX_THRESH_M GENMASK(31, 20)
+
+#define MSCC_FCBUF_TX_CTRL_QUEUE_CFG_START(x) (x)
+#define MSCC_FCBUF_TX_CTRL_QUEUE_CFG_START_M GENMASK(15, 0)
+#define MSCC_FCBUF_TX_CTRL_QUEUE_CFG_END(x) ((x) << 16)
+#define MSCC_FCBUF_TX_CTRL_QUEUE_CFG_END_M GENMASK(31, 16)
+
+#define MSCC_FCBUF_TX_DATA_QUEUE_CFG_START(x) (x)
+#define MSCC_FCBUF_TX_DATA_QUEUE_CFG_START_M GENMASK(15, 0)
+#define MSCC_FCBUF_TX_DATA_QUEUE_CFG_END(x) ((x) << 16)
+#define MSCC_FCBUF_TX_DATA_QUEUE_CFG_END_M GENMASK(31, 16)
+
+#define MSCC_FCBUF_RX_DATA_QUEUE_CFG_START(x) (x)
+#define MSCC_FCBUF_RX_DATA_QUEUE_CFG_START_M GENMASK(15, 0)
+#define MSCC_FCBUF_RX_DATA_QUEUE_CFG_END(x) ((x) << 16)
+#define MSCC_FCBUF_RX_DATA_QUEUE_CFG_END_M GENMASK(31, 16)
+
+#define MSCC_FCBUF_TX_BUFF_XON_XOFF_THRESH_CFG_XOFF_THRESH(x) (x)
+#define MSCC_FCBUF_TX_BUFF_XON_XOFF_THRESH_CFG_XOFF_THRESH_M GENMASK(15, 0)
+#define MSCC_FCBUF_TX_BUFF_XON_XOFF_THRESH_CFG_XON_THRESH(x) ((x) << 16)
+#define MSCC_FCBUF_TX_BUFF_XON_XOFF_THRESH_CFG_XON_THRESH_M GENMASK(31, 16)
+
+#define MSCC_FCBUF_FC_READ_THRESH_CFG_TX_THRESH(x) (x)
+#define MSCC_FCBUF_FC_READ_THRESH_CFG_TX_THRESH_M GENMASK(15, 0)
+#define MSCC_FCBUF_FC_READ_THRESH_CFG_RX_THRESH(x) ((x) << 16)
+#define MSCC_FCBUF_FC_READ_THRESH_CFG_RX_THRESH_M GENMASK(31, 16)
+
+#endif /* _MSCC_PHY_FC_BUFFER_H_ */
diff --git a/drivers/net/phy/mscc/mscc_mac.h b/drivers/net/phy/mscc/mscc_mac.h
new file mode 100644
index 000000000..8dd38dc6e
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_mac.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Copyright (c) 2020 Microsemi Corporation
+ */
+
+#ifndef _MSCC_PHY_LINE_MAC_H_
+#define _MSCC_PHY_LINE_MAC_H_
+
+#define MSCC_MAC_CFG_ENA_CFG 0x00
+#define MSCC_MAC_CFG_MODE_CFG 0x01
+#define MSCC_MAC_CFG_MAXLEN_CFG 0x02
+#define MSCC_MAC_CFG_NUM_TAGS_CFG 0x03
+#define MSCC_MAC_CFG_TAGS_CFG 0x04
+#define MSCC_MAC_CFG_ADV_CHK_CFG 0x07
+#define MSCC_MAC_CFG_LFS_CFG 0x08
+#define MSCC_MAC_CFG_LB_CFG 0x09
+#define MSCC_MAC_CFG_PKTINF_CFG 0x0a
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL 0x0b
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_2 0x0c
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL 0x0d
+#define MSCC_MAC_PAUSE_CFG_STATE 0x0e
+#define MSCC_MAC_PAUSE_CFG_MAC_ADDRESS_LSB 0x0f
+#define MSCC_MAC_PAUSE_CFG_MAC_ADDRESS_MSB 0x10
+#define MSCC_MAC_STATUS_RX_LANE_STICKY_0 0x11
+#define MSCC_MAC_STATUS_RX_LANE_STICKY_1 0x12
+#define MSCC_MAC_STATUS_TX_MONITOR_STICKY 0x13
+#define MSCC_MAC_STATUS_TX_MONITOR_STICKY_MASK 0x14
+#define MSCC_MAC_STATUS_STICKY 0x15
+#define MSCC_MAC_STATUS_STICKY_MASK 0x16
+#define MSCC_MAC_STATS_32BIT_RX_HIH_CKSM_ERR_CNT 0x17
+#define MSCC_MAC_STATS_32BIT_RX_XGMII_PROT_ERR_CNT 0x18
+#define MSCC_MAC_STATS_32BIT_RX_SYMBOL_ERR_CNT 0x19
+#define MSCC_MAC_STATS_32BIT_RX_PAUSE_CNT 0x1a
+#define MSCC_MAC_STATS_32BIT_RX_UNSUP_OPCODE_CNT 0x1b
+#define MSCC_MAC_STATS_32BIT_RX_UC_CNT 0x1c
+#define MSCC_MAC_STATS_32BIT_RX_MC_CNT 0x1d
+#define MSCC_MAC_STATS_32BIT_RX_BC_CNT 0x1e
+#define MSCC_MAC_STATS_32BIT_RX_CRC_ERR_CNT 0x1f
+#define MSCC_MAC_STATS_32BIT_RX_UNDERSIZE_CNT 0x20
+#define MSCC_MAC_STATS_32BIT_RX_FRAGMENTS_CNT 0x21
+#define MSCC_MAC_STATS_32BIT_RX_IN_RANGE_LEN_ERR_CNT 0x22
+#define MSCC_MAC_STATS_32BIT_RX_OUT_OF_RANGE_LEN_ERR_CNT 0x23
+#define MSCC_MAC_STATS_32BIT_RX_OVERSIZE_CNT 0x24
+#define MSCC_MAC_STATS_32BIT_RX_JABBERS_CNT 0x25
+#define MSCC_MAC_STATS_32BIT_RX_SIZE64_CNT 0x26
+#define MSCC_MAC_STATS_32BIT_RX_SIZE65TO127_CNT 0x27
+#define MSCC_MAC_STATS_32BIT_RX_SIZE128TO255_CNT 0x28
+#define MSCC_MAC_STATS_32BIT_RX_SIZE256TO511_CNT 0x29
+#define MSCC_MAC_STATS_32BIT_RX_SIZE512TO1023_CNT 0x2a
+#define MSCC_MAC_STATS_32BIT_RX_SIZE1024TO1518_CNT 0x2b
+#define MSCC_MAC_STATS_32BIT_RX_SIZE1519TOMAX_CNT 0x2c
+#define MSCC_MAC_STATS_32BIT_RX_IPG_SHRINK_CNT 0x2d
+#define MSCC_MAC_STATS_32BIT_TX_PAUSE_CNT 0x2e
+#define MSCC_MAC_STATS_32BIT_TX_UC_CNT 0x2f
+#define MSCC_MAC_STATS_32BIT_TX_MC_CNT 0x30
+#define MSCC_MAC_STATS_32BIT_TX_BC_CNT 0x31
+#define MSCC_MAC_STATS_32BIT_TX_SIZE64_CNT 0x32
+#define MSCC_MAC_STATS_32BIT_TX_SIZE65TO127_CNT 0x33
+#define MSCC_MAC_STATS_32BIT_TX_SIZE128TO255_CNT 0x34
+#define MSCC_MAC_STATS_32BIT_TX_SIZE256TO511_CNT 0x35
+#define MSCC_MAC_STATS_32BIT_TX_SIZE512TO1023_CNT 0x36
+#define MSCC_MAC_STATS_32BIT_TX_SIZE1024TO1518_CNT 0x37
+#define MSCC_MAC_STATS_32BIT_TX_SIZE1519TOMAX_CNT 0x38
+#define MSCC_MAC_STATS_40BIT_RX_BAD_BYTES_CNT 0x39
+#define MSCC_MAC_STATS_40BIT_RX_BAD_BYTES_MSB_CNT 0x3a
+#define MSCC_MAC_STATS_40BIT_RX_OK_BYTES_CNT 0x3b
+#define MSCC_MAC_STATS_40BIT_RX_OK_BYTES_MSB_CNT 0x3c
+#define MSCC_MAC_STATS_40BIT_RX_IN_BYTES_CNT 0x3d
+#define MSCC_MAC_STATS_40BIT_RX_IN_BYTES_MSB_CNT 0x3e
+#define MSCC_MAC_STATS_40BIT_TX_OK_BYTES_CNT 0x3f
+#define MSCC_MAC_STATS_40BIT_TX_OK_BYTES_MSB_CNT 0x40
+#define MSCC_MAC_STATS_40BIT_TX_OUT_BYTES_CNT 0x41
+#define MSCC_MAC_STATS_40BIT_TX_OUT_BYTES_MSB_CNT 0x42
+
+#define MSCC_MAC_CFG_ENA_CFG_RX_CLK_ENA BIT(0)
+#define MSCC_MAC_CFG_ENA_CFG_TX_CLK_ENA BIT(4)
+#define MSCC_MAC_CFG_ENA_CFG_RX_SW_RST BIT(8)
+#define MSCC_MAC_CFG_ENA_CFG_TX_SW_RST BIT(12)
+#define MSCC_MAC_CFG_ENA_CFG_RX_ENA BIT(16)
+#define MSCC_MAC_CFG_ENA_CFG_TX_ENA BIT(20)
+
+#define MSCC_MAC_CFG_MODE_CFG_FORCE_CW_UPDATE_INTERVAL(x) ((x) << 20)
+#define MSCC_MAC_CFG_MODE_CFG_FORCE_CW_UPDATE_INTERVAL_M GENMASK(29, 20)
+#define MSCC_MAC_CFG_MODE_CFG_FORCE_CW_UPDATE BIT(16)
+#define MSCC_MAC_CFG_MODE_CFG_TUNNEL_PAUSE_FRAMES BIT(14)
+#define MSCC_MAC_CFG_MODE_CFG_MAC_PREAMBLE_CFG(x) ((x) << 10)
+#define MSCC_MAC_CFG_MODE_CFG_MAC_PREAMBLE_CFG_M GENMASK(12, 10)
+#define MSCC_MAC_CFG_MODE_CFG_MAC_IPG_CFG BIT(6)
+#define MSCC_MAC_CFG_MODE_CFG_XGMII_GEN_MODE_ENA BIT(4)
+#define MSCC_MAC_CFG_MODE_CFG_HIH_CRC_CHECK BIT(2)
+#define MSCC_MAC_CFG_MODE_CFG_UNDERSIZED_FRAME_DROP_DIS BIT(1)
+#define MSCC_MAC_CFG_MODE_CFG_DISABLE_DIC BIT(0)
+
+#define MSCC_MAC_CFG_MAXLEN_CFG_MAX_LEN_TAG_CHK BIT(16)
+#define MSCC_MAC_CFG_MAXLEN_CFG_MAX_LEN(x) (x)
+#define MSCC_MAC_CFG_MAXLEN_CFG_MAX_LEN_M GENMASK(15, 0)
+
+#define MSCC_MAC_CFG_TAGS_CFG_RSZ 0x4
+#define MSCC_MAC_CFG_TAGS_CFG_TAG_ID(x) ((x) << 16)
+#define MSCC_MAC_CFG_TAGS_CFG_TAG_ID_M GENMASK(31, 16)
+#define MSCC_MAC_CFG_TAGS_CFG_TAG_ENA BIT(4)
+
+#define MSCC_MAC_CFG_ADV_CHK_CFG_EXT_EOP_CHK_ENA BIT(24)
+#define MSCC_MAC_CFG_ADV_CHK_CFG_EXT_SOP_CHK_ENA BIT(20)
+#define MSCC_MAC_CFG_ADV_CHK_CFG_SFD_CHK_ENA BIT(16)
+#define MSCC_MAC_CFG_ADV_CHK_CFG_PRM_SHK_CHK_DIS BIT(12)
+#define MSCC_MAC_CFG_ADV_CHK_CFG_PRM_CHK_ENA BIT(8)
+#define MSCC_MAC_CFG_ADV_CHK_CFG_OOR_ERR_ENA BIT(4)
+#define MSCC_MAC_CFG_ADV_CHK_CFG_INR_ERR_ENA BIT(0)
+
+#define MSCC_MAC_CFG_LFS_CFG_LFS_INH_TX BIT(8)
+#define MSCC_MAC_CFG_LFS_CFG_LFS_DIS_TX BIT(4)
+#define MSCC_MAC_CFG_LFS_CFG_LFS_UNIDIR_ENA BIT(3)
+#define MSCC_MAC_CFG_LFS_CFG_USE_LEADING_EDGE_DETECT BIT(2)
+#define MSCC_MAC_CFG_LFS_CFG_SPURIOUS_Q_DIS BIT(1)
+#define MSCC_MAC_CFG_LFS_CFG_LFS_MODE_ENA BIT(0)
+
+#define MSCC_MAC_CFG_LB_CFG_XGMII_HOST_LB_ENA BIT(4)
+#define MSCC_MAC_CFG_LB_CFG_XGMII_PHY_LB_ENA BIT(0)
+
+#define MSCC_MAC_CFG_PKTINF_CFG_STRIP_FCS_ENA BIT(0)
+#define MSCC_MAC_CFG_PKTINF_CFG_INSERT_FCS_ENA BIT(4)
+#define MSCC_MAC_CFG_PKTINF_CFG_STRIP_PREAMBLE_ENA BIT(8)
+#define MSCC_MAC_CFG_PKTINF_CFG_INSERT_PREAMBLE_ENA BIT(12)
+#define MSCC_MAC_CFG_PKTINF_CFG_LPI_RELAY_ENA BIT(16)
+#define MSCC_MAC_CFG_PKTINF_CFG_LF_RELAY_ENA BIT(20)
+#define MSCC_MAC_CFG_PKTINF_CFG_RF_RELAY_ENA BIT(24)
+#define MSCC_MAC_CFG_PKTINF_CFG_ENABLE_TX_PADDING BIT(25)
+#define MSCC_MAC_CFG_PKTINF_CFG_ENABLE_RX_PADDING BIT(26)
+#define MSCC_MAC_CFG_PKTINF_CFG_ENABLE_4BYTE_PREAMBLE BIT(27)
+#define MSCC_MAC_CFG_PKTINF_CFG_MACSEC_BYPASS_NUM_PTP_STALL_CLKS(x) ((x) << 28)
+#define MSCC_MAC_CFG_PKTINF_CFG_MACSEC_BYPASS_NUM_PTP_STALL_CLKS_M GENMASK(30, 28)
+
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_VALUE(x) ((x) << 16)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_VALUE_M GENMASK(31, 16)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_WAIT_FOR_LPI_LOW BIT(12)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_USE_PAUSE_STALL_ENA BIT(8)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_REPL_MODE BIT(4)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_FRC_FRAME BIT(2)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_MODE(x) (x)
+#define MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_MODE_M GENMASK(1, 0)
+
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_EARLY_PAUSE_DETECT_ENA BIT(16)
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PRE_CRC_MODE BIT(20)
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_TIMER_ENA BIT(12)
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_REACT_ENA BIT(8)
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_FRAME_DROP_ENA BIT(4)
+#define MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_MODE BIT(0)
+
+#define MSCC_MAC_PAUSE_CFG_STATE_PAUSE_STATE BIT(0)
+#define MSCC_MAC_PAUSE_CFG_STATE_MAC_TX_PAUSE_GEN BIT(4)
+
+#define MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL 0x2
+#define MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL_PROTOCOL_MODE(x) (x)
+#define MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL_PROTOCOL_MODE_M GENMASK(2, 0)
+
+#endif /* _MSCC_PHY_LINE_MAC_H_ */
diff --git a/drivers/net/phy/mscc/mscc_macsec.c b/drivers/net/phy/mscc/mscc_macsec.c
new file mode 100644
index 000000000..4f39ba63a
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_macsec.c
@@ -0,0 +1,1011 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Driver for Microsemi VSC85xx PHYs - MACsec support
+ *
+ * Author: Antoine Tenart
+ * License: Dual MIT/GPL
+ * Copyright (c) 2020 Microsemi Corporation
+ */
+
+#include <linux/phy.h>
+#include <dt-bindings/net/mscc-phy-vsc8531.h>
+
+#include <crypto/aes.h>
+
+#include <net/macsec.h>
+
+#include "mscc.h"
+#include "mscc_mac.h"
+#include "mscc_macsec.h"
+#include "mscc_fc_buffer.h"
+
+static u32 vsc8584_macsec_phy_read(struct phy_device *phydev,
+ enum macsec_bank bank, u32 reg)
+{
+ u32 val, val_l = 0, val_h = 0;
+ unsigned long deadline;
+ int rc;
+
+ rc = phy_select_page(phydev, MSCC_PHY_PAGE_MACSEC);
+ if (rc < 0)
+ goto failed;
+
+ __phy_write(phydev, MSCC_EXT_PAGE_MACSEC_20,
+ MSCC_PHY_MACSEC_20_TARGET(bank >> 2));
+
+ if (bank >> 2 == 0x1)
+ /* non-MACsec access */
+ bank &= 0x3;
+ else
+ bank = 0;
+
+ __phy_write(phydev, MSCC_EXT_PAGE_MACSEC_19,
+ MSCC_PHY_MACSEC_19_CMD | MSCC_PHY_MACSEC_19_READ |
+ MSCC_PHY_MACSEC_19_REG_ADDR(reg) |
+ MSCC_PHY_MACSEC_19_TARGET(bank));
+
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ val = __phy_read(phydev, MSCC_EXT_PAGE_MACSEC_19);
+ } while (time_before(jiffies, deadline) && !(val & MSCC_PHY_MACSEC_19_CMD));
+
+ val_l = __phy_read(phydev, MSCC_EXT_PAGE_MACSEC_17);
+ val_h = __phy_read(phydev, MSCC_EXT_PAGE_MACSEC_18);
+
+failed:
+ phy_restore_page(phydev, rc, rc);
+
+ return (val_h << 16) | val_l;
+}
+
+static void vsc8584_macsec_phy_write(struct phy_device *phydev,
+ enum macsec_bank bank, u32 reg, u32 val)
+{
+ unsigned long deadline;
+ int rc;
+
+ rc = phy_select_page(phydev, MSCC_PHY_PAGE_MACSEC);
+ if (rc < 0)
+ goto failed;
+
+ __phy_write(phydev, MSCC_EXT_PAGE_MACSEC_20,
+ MSCC_PHY_MACSEC_20_TARGET(bank >> 2));
+
+ if ((bank >> 2 == 0x1) || (bank >> 2 == 0x3))
+ bank &= 0x3;
+ else
+ /* MACsec access */
+ bank = 0;
+
+ __phy_write(phydev, MSCC_EXT_PAGE_MACSEC_17, (u16)val);
+ __phy_write(phydev, MSCC_EXT_PAGE_MACSEC_18, (u16)(val >> 16));
+
+ __phy_write(phydev, MSCC_EXT_PAGE_MACSEC_19,
+ MSCC_PHY_MACSEC_19_CMD | MSCC_PHY_MACSEC_19_REG_ADDR(reg) |
+ MSCC_PHY_MACSEC_19_TARGET(bank));
+
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ val = __phy_read(phydev, MSCC_EXT_PAGE_MACSEC_19);
+ } while (time_before(jiffies, deadline) && !(val & MSCC_PHY_MACSEC_19_CMD));
+
+failed:
+ phy_restore_page(phydev, rc, rc);
+}
+
+static void vsc8584_macsec_classification(struct phy_device *phydev,
+ enum macsec_bank bank)
+{
+ /* enable VLAN tag parsing */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_CP_TAG,
+ MSCC_MS_SAM_CP_TAG_PARSE_STAG |
+ MSCC_MS_SAM_CP_TAG_PARSE_QTAG |
+ MSCC_MS_SAM_CP_TAG_PARSE_QINQ);
+}
+
+static void vsc8584_macsec_flow_default_action(struct phy_device *phydev,
+ enum macsec_bank bank,
+ bool block)
+{
+ u32 port = (bank == MACSEC_INGR) ?
+ MSCC_MS_PORT_UNCONTROLLED : MSCC_MS_PORT_COMMON;
+ u32 action = MSCC_MS_FLOW_BYPASS;
+
+ if (block)
+ action = MSCC_MS_FLOW_DROP;
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_NM_FLOW_NCP,
+ /* MACsec untagged */
+ MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_DEST_PORT(port) |
+ /* MACsec tagged */
+ MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_DEST_PORT(port) |
+ /* Bad tag */
+ MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_DEST_PORT(port) |
+ /* Kay tag */
+ MSCC_MS_SAM_NM_FLOW_NCP_KAY_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_NCP_KAY_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_NCP_KAY_DEST_PORT(port));
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_NM_FLOW_CP,
+ /* MACsec untagged */
+ MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_CP_UNTAGGED_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_CP_UNTAGGED_DEST_PORT(port) |
+ /* MACsec tagged */
+ MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_CP_TAGGED_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_CP_TAGGED_DEST_PORT(port) |
+ /* Bad tag */
+ MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_CP_BADTAG_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_CP_BADTAG_DEST_PORT(port) |
+ /* Kay tag */
+ MSCC_MS_SAM_NM_FLOW_NCP_KAY_FLOW_TYPE(action) |
+ MSCC_MS_SAM_NM_FLOW_CP_KAY_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_NM_FLOW_CP_KAY_DEST_PORT(port));
+}
+
+static void vsc8584_macsec_integrity_checks(struct phy_device *phydev,
+ enum macsec_bank bank)
+{
+ u32 val;
+
+ if (bank != MACSEC_INGR)
+ return;
+
+ /* Set default rules to pass unmatched frames */
+ val = vsc8584_macsec_phy_read(phydev, bank,
+ MSCC_MS_PARAMS2_IG_CC_CONTROL);
+ val |= MSCC_MS_PARAMS2_IG_CC_CONTROL_NON_MATCH_CTRL_ACT |
+ MSCC_MS_PARAMS2_IG_CC_CONTROL_NON_MATCH_ACT;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_PARAMS2_IG_CC_CONTROL,
+ val);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_PARAMS2_IG_CP_TAG,
+ MSCC_MS_PARAMS2_IG_CP_TAG_PARSE_STAG |
+ MSCC_MS_PARAMS2_IG_CP_TAG_PARSE_QTAG |
+ MSCC_MS_PARAMS2_IG_CP_TAG_PARSE_QINQ);
+}
+
+static void vsc8584_macsec_block_init(struct phy_device *phydev,
+ enum macsec_bank bank)
+{
+ u32 val;
+ int i;
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_ENA_CFG,
+ MSCC_MS_ENA_CFG_SW_RST |
+ MSCC_MS_ENA_CFG_MACSEC_BYPASS_ENA);
+
+ /* Set the MACsec block out of s/w reset and enable clocks */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_ENA_CFG,
+ MSCC_MS_ENA_CFG_CLK_ENA);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_STATUS_CONTEXT_CTRL,
+ bank == MACSEC_INGR ? 0xe5880214 : 0xe5880218);
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_MISC_CONTROL,
+ MSCC_MS_MISC_CONTROL_MC_LATENCY_FIX(bank == MACSEC_INGR ? 57 : 40) |
+ MSCC_MS_MISC_CONTROL_XFORM_REC_SIZE(bank == MACSEC_INGR ? 1 : 2));
+
+ /* Clear the counters */
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MS_COUNT_CONTROL);
+ val |= MSCC_MS_COUNT_CONTROL_AUTO_CNTR_RESET;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_COUNT_CONTROL, val);
+
+ /* Enable octet increment mode */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_PP_CTRL,
+ MSCC_MS_PP_CTRL_MACSEC_OCTET_INCR_MODE);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_BLOCK_CTX_UPDATE, 0x3);
+
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MS_COUNT_CONTROL);
+ val |= MSCC_MS_COUNT_CONTROL_RESET_ALL;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_COUNT_CONTROL, val);
+
+ /* Set the MTU */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_NON_VLAN_MTU_CHECK,
+ MSCC_MS_NON_VLAN_MTU_CHECK_NV_MTU_COMPARE(32761) |
+ MSCC_MS_NON_VLAN_MTU_CHECK_NV_MTU_COMP_DROP);
+
+ for (i = 0; i < 8; i++)
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_VLAN_MTU_CHECK(i),
+ MSCC_MS_VLAN_MTU_CHECK_MTU_COMPARE(32761) |
+ MSCC_MS_VLAN_MTU_CHECK_MTU_COMP_DROP);
+
+ if (bank == MACSEC_EGR) {
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MS_INTR_CTRL_STATUS);
+ val &= ~MSCC_MS_INTR_CTRL_STATUS_INTR_ENABLE_M;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_INTR_CTRL_STATUS, val);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_FC_CFG,
+ MSCC_MS_FC_CFG_FCBUF_ENA |
+ MSCC_MS_FC_CFG_LOW_THRESH(0x1) |
+ MSCC_MS_FC_CFG_HIGH_THRESH(0x4) |
+ MSCC_MS_FC_CFG_LOW_BYTES_VAL(0x4) |
+ MSCC_MS_FC_CFG_HIGH_BYTES_VAL(0x6));
+ }
+
+ vsc8584_macsec_classification(phydev, bank);
+ vsc8584_macsec_flow_default_action(phydev, bank, false);
+ vsc8584_macsec_integrity_checks(phydev, bank);
+
+ /* Enable the MACsec block */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_ENA_CFG,
+ MSCC_MS_ENA_CFG_CLK_ENA |
+ MSCC_MS_ENA_CFG_MACSEC_ENA |
+ MSCC_MS_ENA_CFG_MACSEC_SPEED_MODE(0x5));
+}
+
+static void vsc8584_macsec_mac_init(struct phy_device *phydev,
+ enum macsec_bank bank)
+{
+ u32 val;
+ int i;
+
+ /* Clear host & line stats */
+ for (i = 0; i < 36; i++)
+ vsc8584_macsec_phy_write(phydev, bank, 0x1c + i, 0);
+
+ val = vsc8584_macsec_phy_read(phydev, bank,
+ MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL);
+ val &= ~MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_MODE_M;
+ val |= MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_MODE(2) |
+ MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_PAUSE_VALUE(0xffff);
+ vsc8584_macsec_phy_write(phydev, bank,
+ MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL, val);
+
+ val = vsc8584_macsec_phy_read(phydev, bank,
+ MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_2);
+ val |= 0xffff;
+ vsc8584_macsec_phy_write(phydev, bank,
+ MSCC_MAC_PAUSE_CFG_TX_FRAME_CTRL_2, val);
+
+ val = vsc8584_macsec_phy_read(phydev, bank,
+ MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL);
+ if (bank == HOST_MAC)
+ val |= MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_TIMER_ENA |
+ MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_FRAME_DROP_ENA;
+ else
+ val |= MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_REACT_ENA |
+ MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_FRAME_DROP_ENA |
+ MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_PAUSE_MODE |
+ MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL_EARLY_PAUSE_DETECT_ENA;
+ vsc8584_macsec_phy_write(phydev, bank,
+ MSCC_MAC_PAUSE_CFG_RX_FRAME_CTRL, val);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MAC_CFG_PKTINF_CFG,
+ MSCC_MAC_CFG_PKTINF_CFG_STRIP_FCS_ENA |
+ MSCC_MAC_CFG_PKTINF_CFG_INSERT_FCS_ENA |
+ MSCC_MAC_CFG_PKTINF_CFG_LPI_RELAY_ENA |
+ MSCC_MAC_CFG_PKTINF_CFG_STRIP_PREAMBLE_ENA |
+ MSCC_MAC_CFG_PKTINF_CFG_INSERT_PREAMBLE_ENA |
+ (bank == HOST_MAC ?
+ MSCC_MAC_CFG_PKTINF_CFG_ENABLE_TX_PADDING : 0) |
+ (IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING) ?
+ MSCC_MAC_CFG_PKTINF_CFG_MACSEC_BYPASS_NUM_PTP_STALL_CLKS(0x8) : 0));
+
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MAC_CFG_MODE_CFG);
+ val &= ~MSCC_MAC_CFG_MODE_CFG_DISABLE_DIC;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MAC_CFG_MODE_CFG, val);
+
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MAC_CFG_MAXLEN_CFG);
+ val &= ~MSCC_MAC_CFG_MAXLEN_CFG_MAX_LEN_M;
+ val |= MSCC_MAC_CFG_MAXLEN_CFG_MAX_LEN(10240);
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MAC_CFG_MAXLEN_CFG, val);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MAC_CFG_ADV_CHK_CFG,
+ MSCC_MAC_CFG_ADV_CHK_CFG_SFD_CHK_ENA |
+ MSCC_MAC_CFG_ADV_CHK_CFG_PRM_CHK_ENA |
+ MSCC_MAC_CFG_ADV_CHK_CFG_OOR_ERR_ENA |
+ MSCC_MAC_CFG_ADV_CHK_CFG_INR_ERR_ENA);
+
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MAC_CFG_LFS_CFG);
+ val &= ~MSCC_MAC_CFG_LFS_CFG_LFS_MODE_ENA;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MAC_CFG_LFS_CFG, val);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MAC_CFG_ENA_CFG,
+ MSCC_MAC_CFG_ENA_CFG_RX_CLK_ENA |
+ MSCC_MAC_CFG_ENA_CFG_TX_CLK_ENA |
+ MSCC_MAC_CFG_ENA_CFG_RX_ENA |
+ MSCC_MAC_CFG_ENA_CFG_TX_ENA);
+}
+
+/* Must be called with mdio_lock taken */
+static int __vsc8584_macsec_init(struct phy_device *phydev)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ enum macsec_bank proc_bank;
+ u32 val;
+
+ vsc8584_macsec_block_init(phydev, MACSEC_INGR);
+ vsc8584_macsec_block_init(phydev, MACSEC_EGR);
+ vsc8584_macsec_mac_init(phydev, HOST_MAC);
+ vsc8584_macsec_mac_init(phydev, LINE_MAC);
+
+ vsc8584_macsec_phy_write(phydev, FC_BUFFER,
+ MSCC_FCBUF_FC_READ_THRESH_CFG,
+ MSCC_FCBUF_FC_READ_THRESH_CFG_TX_THRESH(4) |
+ MSCC_FCBUF_FC_READ_THRESH_CFG_RX_THRESH(5));
+
+ val = vsc8584_macsec_phy_read(phydev, FC_BUFFER, MSCC_FCBUF_MODE_CFG);
+ val |= MSCC_FCBUF_MODE_CFG_PAUSE_GEN_ENA |
+ MSCC_FCBUF_MODE_CFG_RX_PPM_RATE_ADAPT_ENA |
+ MSCC_FCBUF_MODE_CFG_TX_PPM_RATE_ADAPT_ENA;
+ vsc8584_macsec_phy_write(phydev, FC_BUFFER, MSCC_FCBUF_MODE_CFG, val);
+
+ vsc8584_macsec_phy_write(phydev, FC_BUFFER, MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG,
+ MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_TX_THRESH(8) |
+ MSCC_FCBUF_PPM_RATE_ADAPT_THRESH_CFG_TX_OFFSET(9));
+
+ val = vsc8584_macsec_phy_read(phydev, FC_BUFFER,
+ MSCC_FCBUF_TX_DATA_QUEUE_CFG);
+ val &= ~(MSCC_FCBUF_TX_DATA_QUEUE_CFG_START_M |
+ MSCC_FCBUF_TX_DATA_QUEUE_CFG_END_M);
+ val |= MSCC_FCBUF_TX_DATA_QUEUE_CFG_START(0) |
+ MSCC_FCBUF_TX_DATA_QUEUE_CFG_END(5119);
+ vsc8584_macsec_phy_write(phydev, FC_BUFFER,
+ MSCC_FCBUF_TX_DATA_QUEUE_CFG, val);
+
+ val = vsc8584_macsec_phy_read(phydev, FC_BUFFER, MSCC_FCBUF_ENA_CFG);
+ val |= MSCC_FCBUF_ENA_CFG_TX_ENA | MSCC_FCBUF_ENA_CFG_RX_ENA;
+ vsc8584_macsec_phy_write(phydev, FC_BUFFER, MSCC_FCBUF_ENA_CFG, val);
+
+ proc_bank = (priv->addr < 2) ? PROC_0 : PROC_2;
+
+ val = vsc8584_macsec_phy_read(phydev, proc_bank,
+ MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL);
+ val &= ~MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL_PROTOCOL_MODE_M;
+ val |= MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL_PROTOCOL_MODE(4);
+ vsc8584_macsec_phy_write(phydev, proc_bank,
+ MSCC_PROC_IP_1588_TOP_CFG_STAT_MODE_CTL, val);
+
+ return 0;
+}
+
+static void vsc8584_macsec_flow(struct phy_device *phydev,
+ struct macsec_flow *flow)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ enum macsec_bank bank = flow->bank;
+ u32 val, match = 0, mask = 0, action = 0, idx = flow->index;
+
+ if (flow->match.tagged)
+ match |= MSCC_MS_SAM_MISC_MATCH_TAGGED;
+ if (flow->match.untagged)
+ match |= MSCC_MS_SAM_MISC_MATCH_UNTAGGED;
+
+ if (bank == MACSEC_INGR && flow->assoc_num >= 0) {
+ match |= MSCC_MS_SAM_MISC_MATCH_AN(flow->assoc_num);
+ mask |= MSCC_MS_SAM_MASK_AN_MASK(0x3);
+ }
+
+ if (bank == MACSEC_INGR && flow->match.sci && flow->rx_sa->sc->sci) {
+ u64 sci = (__force u64)flow->rx_sa->sc->sci;
+
+ match |= MSCC_MS_SAM_MISC_MATCH_TCI(BIT(3));
+ mask |= MSCC_MS_SAM_MASK_TCI_MASK(BIT(3)) |
+ MSCC_MS_SAM_MASK_SCI_MASK;
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_MATCH_SCI_LO(idx),
+ lower_32_bits(sci));
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_MATCH_SCI_HI(idx),
+ upper_32_bits(sci));
+ }
+
+ if (flow->match.etype) {
+ mask |= MSCC_MS_SAM_MASK_MAC_ETYPE_MASK;
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_MAC_SA_MATCH_HI(idx),
+ MSCC_MS_SAM_MAC_SA_MATCH_HI_ETYPE((__force u32)htons(flow->etype)));
+ }
+
+ match |= MSCC_MS_SAM_MISC_MATCH_PRIORITY(flow->priority);
+
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_MISC_MATCH(idx), match);
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_MASK(idx), mask);
+
+ /* Action for matching packets */
+ if (flow->action.drop)
+ action = MSCC_MS_FLOW_DROP;
+ else if (flow->action.bypass || flow->port == MSCC_MS_PORT_UNCONTROLLED)
+ action = MSCC_MS_FLOW_BYPASS;
+ else
+ action = (bank == MACSEC_INGR) ?
+ MSCC_MS_FLOW_INGRESS : MSCC_MS_FLOW_EGRESS;
+
+ val = MSCC_MS_SAM_FLOW_CTRL_FLOW_TYPE(action) |
+ MSCC_MS_SAM_FLOW_CTRL_DROP_ACTION(MSCC_MS_ACTION_DROP) |
+ MSCC_MS_SAM_FLOW_CTRL_DEST_PORT(flow->port);
+
+ if (action == MSCC_MS_FLOW_BYPASS)
+ goto write_ctrl;
+
+ if (bank == MACSEC_INGR) {
+ if (priv->secy->replay_protect)
+ val |= MSCC_MS_SAM_FLOW_CTRL_REPLAY_PROTECT;
+ if (priv->secy->validate_frames == MACSEC_VALIDATE_STRICT)
+ val |= MSCC_MS_SAM_FLOW_CTRL_VALIDATE_FRAMES(MSCC_MS_VALIDATE_STRICT);
+ else if (priv->secy->validate_frames == MACSEC_VALIDATE_CHECK)
+ val |= MSCC_MS_SAM_FLOW_CTRL_VALIDATE_FRAMES(MSCC_MS_VALIDATE_CHECK);
+ } else if (bank == MACSEC_EGR) {
+ if (priv->secy->protect_frames)
+ val |= MSCC_MS_SAM_FLOW_CTRL_PROTECT_FRAME;
+ if (priv->secy->tx_sc.encrypt)
+ val |= MSCC_MS_SAM_FLOW_CTRL_CONF_PROTECT;
+ if (priv->secy->tx_sc.send_sci)
+ val |= MSCC_MS_SAM_FLOW_CTRL_INCLUDE_SCI;
+ }
+
+write_ctrl:
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_FLOW_CTRL(idx), val);
+}
+
+static struct macsec_flow *vsc8584_macsec_find_flow(struct macsec_context *ctx,
+ enum macsec_bank bank)
+{
+ struct vsc8531_private *priv = ctx->phydev->priv;
+ struct macsec_flow *pos, *tmp;
+
+ list_for_each_entry_safe(pos, tmp, &priv->macsec_flows, list)
+ if (pos->assoc_num == ctx->sa.assoc_num && pos->bank == bank)
+ return pos;
+
+ return ERR_PTR(-ENOENT);
+}
+
+static void vsc8584_macsec_flow_enable(struct phy_device *phydev,
+ struct macsec_flow *flow)
+{
+ enum macsec_bank bank = flow->bank;
+ u32 val, idx = flow->index;
+
+ if ((flow->bank == MACSEC_INGR && flow->rx_sa && !flow->rx_sa->active) ||
+ (flow->bank == MACSEC_EGR && flow->tx_sa && !flow->tx_sa->active))
+ return;
+
+ /* Enable */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_ENTRY_SET1, BIT(idx));
+
+ /* Set in-use */
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MS_SAM_FLOW_CTRL(idx));
+ val |= MSCC_MS_SAM_FLOW_CTRL_SA_IN_USE;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_FLOW_CTRL(idx), val);
+}
+
+static void vsc8584_macsec_flow_disable(struct phy_device *phydev,
+ struct macsec_flow *flow)
+{
+ enum macsec_bank bank = flow->bank;
+ u32 val, idx = flow->index;
+
+ /* Disable */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_ENTRY_CLEAR1, BIT(idx));
+
+ /* Clear in-use */
+ val = vsc8584_macsec_phy_read(phydev, bank, MSCC_MS_SAM_FLOW_CTRL(idx));
+ val &= ~MSCC_MS_SAM_FLOW_CTRL_SA_IN_USE;
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_SAM_FLOW_CTRL(idx), val);
+}
+
+static u32 vsc8584_macsec_flow_context_id(struct macsec_flow *flow)
+{
+ if (flow->bank == MACSEC_INGR)
+ return flow->index + MSCC_MS_MAX_FLOWS;
+
+ return flow->index;
+}
+
+/* Derive the AES key to get a key for the hash autentication */
+static int vsc8584_macsec_derive_key(const u8 *key, u16 key_len, u8 hkey[16])
+{
+ const u8 input[AES_BLOCK_SIZE] = {0};
+ struct crypto_aes_ctx ctx;
+ int ret;
+
+ ret = aes_expandkey(&ctx, key, key_len);
+ if (ret)
+ return ret;
+
+ aes_encrypt(&ctx, hkey, input);
+ memzero_explicit(&ctx, sizeof(ctx));
+ return 0;
+}
+
+static int vsc8584_macsec_transformation(struct phy_device *phydev,
+ struct macsec_flow *flow,
+ const u8 *key)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ enum macsec_bank bank = flow->bank;
+ int i, ret, index = flow->index;
+ u32 rec = 0, control = 0;
+ u8 hkey[16];
+ u64 sci;
+
+ ret = vsc8584_macsec_derive_key(key, priv->secy->key_len, hkey);
+ if (ret)
+ return ret;
+
+ switch (priv->secy->key_len) {
+ case 16:
+ control |= CONTROL_CRYPTO_ALG(CTRYPTO_ALG_AES_CTR_128);
+ break;
+ case 32:
+ control |= CONTROL_CRYPTO_ALG(CTRYPTO_ALG_AES_CTR_256);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ control |= (bank == MACSEC_EGR) ?
+ (CONTROL_TYPE_EGRESS | CONTROL_AN(priv->secy->tx_sc.encoding_sa)) :
+ (CONTROL_TYPE_INGRESS | CONTROL_SEQ_MASK);
+
+ control |= CONTROL_UPDATE_SEQ | CONTROL_ENCRYPT_AUTH | CONTROL_KEY_IN_CTX |
+ CONTROL_IV0 | CONTROL_IV1 | CONTROL_IV_IN_SEQ |
+ CONTROL_DIGEST_TYPE(0x2) | CONTROL_SEQ_TYPE(0x1) |
+ CONTROL_AUTH_ALG(AUTH_ALG_AES_GHAS) | CONTROL_CONTEXT_ID;
+
+ /* Set the control word */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_XFORM_REC(index, rec++),
+ control);
+
+ /* Set the context ID. Must be unique. */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_XFORM_REC(index, rec++),
+ vsc8584_macsec_flow_context_id(flow));
+
+ /* Set the encryption/decryption key */
+ for (i = 0; i < priv->secy->key_len / sizeof(u32); i++)
+ vsc8584_macsec_phy_write(phydev, bank,
+ MSCC_MS_XFORM_REC(index, rec++),
+ ((u32 *)key)[i]);
+
+ /* Set the authentication key */
+ for (i = 0; i < 4; i++)
+ vsc8584_macsec_phy_write(phydev, bank,
+ MSCC_MS_XFORM_REC(index, rec++),
+ ((u32 *)hkey)[i]);
+
+ /* Initial sequence number */
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_XFORM_REC(index, rec++),
+ bank == MACSEC_INGR ?
+ flow->rx_sa->next_pn : flow->tx_sa->next_pn);
+
+ if (bank == MACSEC_INGR)
+ /* Set the mask (replay window size) */
+ vsc8584_macsec_phy_write(phydev, bank,
+ MSCC_MS_XFORM_REC(index, rec++),
+ priv->secy->replay_window);
+
+ /* Set the input vectors */
+ sci = (__force u64)(bank == MACSEC_INGR ? flow->rx_sa->sc->sci : priv->secy->sci);
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_XFORM_REC(index, rec++),
+ lower_32_bits(sci));
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_XFORM_REC(index, rec++),
+ upper_32_bits(sci));
+
+ while (rec < 20)
+ vsc8584_macsec_phy_write(phydev, bank, MSCC_MS_XFORM_REC(index, rec++),
+ 0);
+
+ flow->has_transformation = true;
+ return 0;
+}
+
+static struct macsec_flow *vsc8584_macsec_alloc_flow(struct vsc8531_private *priv,
+ enum macsec_bank bank)
+{
+ unsigned long *bitmap = bank == MACSEC_INGR ?
+ &priv->ingr_flows : &priv->egr_flows;
+ struct macsec_flow *flow;
+ int index;
+
+ index = find_first_zero_bit(bitmap, MSCC_MS_MAX_FLOWS);
+
+ if (index == MSCC_MS_MAX_FLOWS)
+ return ERR_PTR(-ENOMEM);
+
+ flow = kzalloc(sizeof(*flow), GFP_KERNEL);
+ if (!flow)
+ return ERR_PTR(-ENOMEM);
+
+ set_bit(index, bitmap);
+ flow->index = index;
+ flow->bank = bank;
+ flow->priority = 8;
+ flow->assoc_num = -1;
+
+ list_add_tail(&flow->list, &priv->macsec_flows);
+ return flow;
+}
+
+static void vsc8584_macsec_free_flow(struct vsc8531_private *priv,
+ struct macsec_flow *flow)
+{
+ unsigned long *bitmap = flow->bank == MACSEC_INGR ?
+ &priv->ingr_flows : &priv->egr_flows;
+
+ list_del(&flow->list);
+ clear_bit(flow->index, bitmap);
+ kfree(flow);
+}
+
+static void vsc8584_macsec_add_flow(struct phy_device *phydev,
+ struct macsec_flow *flow)
+{
+ flow->port = MSCC_MS_PORT_CONTROLLED;
+ vsc8584_macsec_flow(phydev, flow);
+}
+
+static int vsc8584_macsec_default_flows(struct phy_device *phydev)
+{
+ struct macsec_flow *flow;
+
+ /* Add a rule to let the MKA traffic go through, ingress */
+ flow = vsc8584_macsec_alloc_flow(phydev->priv, MACSEC_INGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ flow->priority = 15;
+ flow->port = MSCC_MS_PORT_UNCONTROLLED;
+ flow->match.tagged = 1;
+ flow->match.untagged = 1;
+ flow->match.etype = 1;
+ flow->etype = ETH_P_PAE;
+ flow->action.bypass = 1;
+
+ vsc8584_macsec_flow(phydev, flow);
+ vsc8584_macsec_flow_enable(phydev, flow);
+
+ /* Add a rule to let the MKA traffic go through, egress */
+ flow = vsc8584_macsec_alloc_flow(phydev->priv, MACSEC_EGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ flow->priority = 15;
+ flow->port = MSCC_MS_PORT_COMMON;
+ flow->match.untagged = 1;
+ flow->match.etype = 1;
+ flow->etype = ETH_P_PAE;
+ flow->action.bypass = 1;
+
+ vsc8584_macsec_flow(phydev, flow);
+ vsc8584_macsec_flow_enable(phydev, flow);
+
+ return 0;
+}
+
+static void vsc8584_macsec_del_flow(struct phy_device *phydev,
+ struct macsec_flow *flow)
+{
+ vsc8584_macsec_flow_disable(phydev, flow);
+ vsc8584_macsec_free_flow(phydev->priv, flow);
+}
+
+static int __vsc8584_macsec_add_rxsa(struct macsec_context *ctx,
+ struct macsec_flow *flow, bool update)
+{
+ struct phy_device *phydev = ctx->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+ int ret;
+
+ flow->assoc_num = ctx->sa.assoc_num;
+ flow->rx_sa = ctx->sa.rx_sa;
+
+ /* Always match tagged packets on ingress */
+ flow->match.tagged = 1;
+ flow->match.sci = 1;
+
+ if (priv->secy->validate_frames != MACSEC_VALIDATE_DISABLED)
+ flow->match.untagged = 1;
+
+ vsc8584_macsec_add_flow(phydev, flow);
+
+ if (update)
+ return 0;
+
+ ret = vsc8584_macsec_transformation(phydev, flow, ctx->sa.key);
+ if (ret)
+ vsc8584_macsec_free_flow(phydev->priv, flow);
+
+ return ret;
+}
+
+static int __vsc8584_macsec_add_txsa(struct macsec_context *ctx,
+ struct macsec_flow *flow, bool update)
+{
+ int ret;
+
+ flow->assoc_num = ctx->sa.assoc_num;
+ flow->tx_sa = ctx->sa.tx_sa;
+
+ /* Always match untagged packets on egress */
+ flow->match.untagged = 1;
+
+ vsc8584_macsec_add_flow(ctx->phydev, flow);
+
+ if (update)
+ return 0;
+
+ ret = vsc8584_macsec_transformation(ctx->phydev, flow, ctx->sa.key);
+ if (ret)
+ vsc8584_macsec_free_flow(ctx->phydev->priv, flow);
+
+ return ret;
+}
+
+static int vsc8584_macsec_dev_open(struct macsec_context *ctx)
+{
+ struct vsc8531_private *priv = ctx->phydev->priv;
+ struct macsec_flow *flow, *tmp;
+
+ list_for_each_entry_safe(flow, tmp, &priv->macsec_flows, list)
+ vsc8584_macsec_flow_enable(ctx->phydev, flow);
+
+ return 0;
+}
+
+static int vsc8584_macsec_dev_stop(struct macsec_context *ctx)
+{
+ struct vsc8531_private *priv = ctx->phydev->priv;
+ struct macsec_flow *flow, *tmp;
+
+ list_for_each_entry_safe(flow, tmp, &priv->macsec_flows, list)
+ vsc8584_macsec_flow_disable(ctx->phydev, flow);
+
+ return 0;
+}
+
+static int vsc8584_macsec_add_secy(struct macsec_context *ctx)
+{
+ struct vsc8531_private *priv = ctx->phydev->priv;
+ struct macsec_secy *secy = ctx->secy;
+
+ if (priv->secy)
+ return -EEXIST;
+
+ priv->secy = secy;
+
+ vsc8584_macsec_flow_default_action(ctx->phydev, MACSEC_EGR,
+ secy->validate_frames != MACSEC_VALIDATE_DISABLED);
+ vsc8584_macsec_flow_default_action(ctx->phydev, MACSEC_INGR,
+ secy->validate_frames != MACSEC_VALIDATE_DISABLED);
+
+ return vsc8584_macsec_default_flows(ctx->phydev);
+}
+
+static int vsc8584_macsec_del_secy(struct macsec_context *ctx)
+{
+ struct vsc8531_private *priv = ctx->phydev->priv;
+ struct macsec_flow *flow, *tmp;
+
+ list_for_each_entry_safe(flow, tmp, &priv->macsec_flows, list)
+ vsc8584_macsec_del_flow(ctx->phydev, flow);
+
+ vsc8584_macsec_flow_default_action(ctx->phydev, MACSEC_EGR, false);
+ vsc8584_macsec_flow_default_action(ctx->phydev, MACSEC_INGR, false);
+
+ priv->secy = NULL;
+ return 0;
+}
+
+static int vsc8584_macsec_upd_secy(struct macsec_context *ctx)
+{
+ vsc8584_macsec_del_secy(ctx);
+ return vsc8584_macsec_add_secy(ctx);
+}
+
+static int vsc8584_macsec_add_rxsc(struct macsec_context *ctx)
+{
+ /* Nothing to do */
+ return 0;
+}
+
+static int vsc8584_macsec_upd_rxsc(struct macsec_context *ctx)
+{
+ return -EOPNOTSUPP;
+}
+
+static int vsc8584_macsec_del_rxsc(struct macsec_context *ctx)
+{
+ struct vsc8531_private *priv = ctx->phydev->priv;
+ struct macsec_flow *flow, *tmp;
+
+ list_for_each_entry_safe(flow, tmp, &priv->macsec_flows, list) {
+ if (flow->bank == MACSEC_INGR && flow->rx_sa &&
+ flow->rx_sa->sc->sci == ctx->rx_sc->sci)
+ vsc8584_macsec_del_flow(ctx->phydev, flow);
+ }
+
+ return 0;
+}
+
+static int vsc8584_macsec_add_rxsa(struct macsec_context *ctx)
+{
+ struct phy_device *phydev = ctx->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+ struct macsec_flow *flow;
+ int ret;
+
+ flow = vsc8584_macsec_alloc_flow(priv, MACSEC_INGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ ret = __vsc8584_macsec_add_rxsa(ctx, flow, false);
+ if (ret)
+ return ret;
+
+ vsc8584_macsec_flow_enable(phydev, flow);
+ return 0;
+}
+
+static int vsc8584_macsec_upd_rxsa(struct macsec_context *ctx)
+{
+ struct macsec_flow *flow;
+ int ret;
+
+ if (ctx->sa.update_pn)
+ return -EINVAL;
+
+ flow = vsc8584_macsec_find_flow(ctx, MACSEC_INGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ /* Make sure the flow is disabled before updating it */
+ vsc8584_macsec_flow_disable(ctx->phydev, flow);
+
+ ret = __vsc8584_macsec_add_rxsa(ctx, flow, true);
+ if (ret)
+ return ret;
+
+ vsc8584_macsec_flow_enable(ctx->phydev, flow);
+ return 0;
+}
+
+static int vsc8584_macsec_del_rxsa(struct macsec_context *ctx)
+{
+ struct macsec_flow *flow;
+
+ flow = vsc8584_macsec_find_flow(ctx, MACSEC_INGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ vsc8584_macsec_del_flow(ctx->phydev, flow);
+ return 0;
+}
+
+static int vsc8584_macsec_add_txsa(struct macsec_context *ctx)
+{
+ struct phy_device *phydev = ctx->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+ struct macsec_flow *flow;
+ int ret;
+
+ flow = vsc8584_macsec_alloc_flow(priv, MACSEC_EGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ ret = __vsc8584_macsec_add_txsa(ctx, flow, false);
+ if (ret)
+ return ret;
+
+ vsc8584_macsec_flow_enable(phydev, flow);
+ return 0;
+}
+
+static int vsc8584_macsec_upd_txsa(struct macsec_context *ctx)
+{
+ struct macsec_flow *flow;
+ int ret;
+
+ if (ctx->sa.update_pn)
+ return -EINVAL;
+
+ flow = vsc8584_macsec_find_flow(ctx, MACSEC_EGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ /* Make sure the flow is disabled before updating it */
+ vsc8584_macsec_flow_disable(ctx->phydev, flow);
+
+ ret = __vsc8584_macsec_add_txsa(ctx, flow, true);
+ if (ret)
+ return ret;
+
+ vsc8584_macsec_flow_enable(ctx->phydev, flow);
+ return 0;
+}
+
+static int vsc8584_macsec_del_txsa(struct macsec_context *ctx)
+{
+ struct macsec_flow *flow;
+
+ flow = vsc8584_macsec_find_flow(ctx, MACSEC_EGR);
+ if (IS_ERR(flow))
+ return PTR_ERR(flow);
+
+ vsc8584_macsec_del_flow(ctx->phydev, flow);
+ return 0;
+}
+
+static const struct macsec_ops vsc8584_macsec_ops = {
+ .mdo_dev_open = vsc8584_macsec_dev_open,
+ .mdo_dev_stop = vsc8584_macsec_dev_stop,
+ .mdo_add_secy = vsc8584_macsec_add_secy,
+ .mdo_upd_secy = vsc8584_macsec_upd_secy,
+ .mdo_del_secy = vsc8584_macsec_del_secy,
+ .mdo_add_rxsc = vsc8584_macsec_add_rxsc,
+ .mdo_upd_rxsc = vsc8584_macsec_upd_rxsc,
+ .mdo_del_rxsc = vsc8584_macsec_del_rxsc,
+ .mdo_add_rxsa = vsc8584_macsec_add_rxsa,
+ .mdo_upd_rxsa = vsc8584_macsec_upd_rxsa,
+ .mdo_del_rxsa = vsc8584_macsec_del_rxsa,
+ .mdo_add_txsa = vsc8584_macsec_add_txsa,
+ .mdo_upd_txsa = vsc8584_macsec_upd_txsa,
+ .mdo_del_txsa = vsc8584_macsec_del_txsa,
+};
+
+int vsc8584_macsec_init(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+
+ switch (phydev->phy_id & phydev->drv->phy_id_mask) {
+ case PHY_ID_VSC856X:
+ case PHY_ID_VSC8582:
+ case PHY_ID_VSC8584:
+ INIT_LIST_HEAD(&vsc8531->macsec_flows);
+ vsc8531->secy = NULL;
+
+ phydev->macsec_ops = &vsc8584_macsec_ops;
+
+ return __vsc8584_macsec_init(phydev);
+ }
+
+ return 0;
+}
+
+void vsc8584_handle_macsec_interrupt(struct phy_device *phydev)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ struct macsec_flow *flow, *tmp;
+ u32 cause, rec;
+
+ /* Check MACsec PN rollover */
+ cause = vsc8584_macsec_phy_read(phydev, MACSEC_EGR,
+ MSCC_MS_INTR_CTRL_STATUS);
+ cause &= MSCC_MS_INTR_CTRL_STATUS_INTR_CLR_STATUS_M;
+ if (!(cause & MACSEC_INTR_CTRL_STATUS_ROLLOVER))
+ return;
+
+ rec = 6 + priv->secy->key_len / sizeof(u32);
+ list_for_each_entry_safe(flow, tmp, &priv->macsec_flows, list) {
+ u32 val;
+
+ if (flow->bank != MACSEC_EGR || !flow->has_transformation)
+ continue;
+
+ val = vsc8584_macsec_phy_read(phydev, MACSEC_EGR,
+ MSCC_MS_XFORM_REC(flow->index, rec));
+ if (val == 0xffffffff) {
+ vsc8584_macsec_flow_disable(phydev, flow);
+ macsec_pn_wrapped(priv->secy, flow->tx_sa);
+ return;
+ }
+ }
+}
+
+void vsc8584_config_macsec_intr(struct phy_device *phydev)
+{
+ phy_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_2);
+ phy_write(phydev, MSCC_PHY_EXTENDED_INT, MSCC_PHY_EXTENDED_INT_MS_EGR);
+ phy_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ vsc8584_macsec_phy_write(phydev, MACSEC_EGR, MSCC_MS_AIC_CTRL, 0xf);
+ vsc8584_macsec_phy_write(phydev, MACSEC_EGR, MSCC_MS_INTR_CTRL_STATUS,
+ MSCC_MS_INTR_CTRL_STATUS_INTR_ENABLE(MACSEC_INTR_CTRL_STATUS_ROLLOVER));
+}
diff --git a/drivers/net/phy/mscc/mscc_macsec.h b/drivers/net/phy/mscc/mscc_macsec.h
new file mode 100644
index 000000000..21ce3b892
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_macsec.h
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Copyright (c) 2020 Microsemi Corporation
+ */
+
+#ifndef _MSCC_PHY_MACSEC_H_
+#define _MSCC_PHY_MACSEC_H_
+
+#include <net/macsec.h>
+
+#define MSCC_MS_MAX_FLOWS 16
+
+#define CONTROL_TYPE_EGRESS 0x6
+#define CONTROL_TYPE_INGRESS 0xf
+#define CONTROL_IV0 BIT(5)
+#define CONTROL_IV1 BIT(6)
+#define CONTROL_IV2 BIT(7)
+#define CONTROL_UPDATE_SEQ BIT(13)
+#define CONTROL_IV_IN_SEQ BIT(14)
+#define CONTROL_ENCRYPT_AUTH BIT(15)
+#define CONTROL_KEY_IN_CTX BIT(16)
+#define CONTROL_CRYPTO_ALG(x) ((x) << 17)
+#define CTRYPTO_ALG_AES_CTR_128 0x5
+#define CTRYPTO_ALG_AES_CTR_192 0x6
+#define CTRYPTO_ALG_AES_CTR_256 0x7
+#define CONTROL_DIGEST_TYPE(x) ((x) << 21)
+#define CONTROL_AUTH_ALG(x) ((x) << 23)
+#define AUTH_ALG_AES_GHAS 0x4
+#define CONTROL_AN(x) ((x) << 26)
+#define CONTROL_SEQ_TYPE(x) ((x) << 28)
+#define CONTROL_SEQ_MASK BIT(30)
+#define CONTROL_CONTEXT_ID BIT(31)
+
+enum mscc_macsec_destination_ports {
+ MSCC_MS_PORT_COMMON = 0,
+ MSCC_MS_PORT_RSVD = 1,
+ MSCC_MS_PORT_CONTROLLED = 2,
+ MSCC_MS_PORT_UNCONTROLLED = 3,
+};
+
+enum mscc_macsec_drop_actions {
+ MSCC_MS_ACTION_BYPASS_CRC = 0,
+ MSCC_MS_ACTION_BYPASS_BAD = 1,
+ MSCC_MS_ACTION_DROP = 2,
+ MSCC_MS_ACTION_BYPASS = 3,
+};
+
+enum mscc_macsec_flow_types {
+ MSCC_MS_FLOW_BYPASS = 0,
+ MSCC_MS_FLOW_DROP = 1,
+ MSCC_MS_FLOW_INGRESS = 2,
+ MSCC_MS_FLOW_EGRESS = 3,
+};
+
+enum mscc_macsec_validate_levels {
+ MSCC_MS_VALIDATE_DISABLED = 0,
+ MSCC_MS_VALIDATE_CHECK = 1,
+ MSCC_MS_VALIDATE_STRICT = 2,
+};
+
+enum macsec_bank {
+ FC_BUFFER = 0x04,
+ HOST_MAC = 0x05,
+ LINE_MAC = 0x06,
+ PROC_0 = 0x0e,
+ PROC_2 = 0x0f,
+ MACSEC_INGR = 0x38,
+ MACSEC_EGR = 0x3c,
+};
+
+struct macsec_flow {
+ struct list_head list;
+ enum mscc_macsec_destination_ports port;
+ enum macsec_bank bank;
+ u32 index;
+ int assoc_num;
+ bool has_transformation;
+
+ /* Highest takes precedence [0..15] */
+ u8 priority;
+
+ union {
+ struct macsec_rx_sa *rx_sa;
+ struct macsec_tx_sa *tx_sa;
+ };
+
+ /* Matching */
+ struct {
+ u8 sci:1;
+ u8 tagged:1;
+ u8 untagged:1;
+ u8 etype:1;
+ } match;
+
+ u16 etype;
+
+ /* Action */
+ struct {
+ u8 bypass:1;
+ u8 drop:1;
+ } action;
+};
+
+#define MSCC_EXT_PAGE_MACSEC_17 17
+#define MSCC_EXT_PAGE_MACSEC_18 18
+
+#define MSCC_EXT_PAGE_MACSEC_19 19
+#define MSCC_PHY_MACSEC_19_REG_ADDR(x) (x)
+#define MSCC_PHY_MACSEC_19_TARGET(x) ((x) << 12)
+#define MSCC_PHY_MACSEC_19_READ BIT(14)
+#define MSCC_PHY_MACSEC_19_CMD BIT(15)
+
+#define MSCC_EXT_PAGE_MACSEC_20 20
+#define MSCC_PHY_MACSEC_20_TARGET(x) (x)
+
+#define MSCC_MS_XFORM_REC(x, y) (((x) << 5) + (y))
+#define MSCC_MS_ENA_CFG 0x800
+#define MSCC_MS_FC_CFG 0x804
+#define MSCC_MS_SAM_MAC_SA_MATCH_LO(x) (0x1000 + ((x) << 4))
+#define MSCC_MS_SAM_MAC_SA_MATCH_HI(x) (0x1001 + ((x) << 4))
+#define MSCC_MS_SAM_MISC_MATCH(x) (0x1004 + ((x) << 4))
+#define MSCC_MS_SAM_MATCH_SCI_LO(x) (0x1005 + ((x) << 4))
+#define MSCC_MS_SAM_MATCH_SCI_HI(x) (0x1006 + ((x) << 4))
+#define MSCC_MS_SAM_MASK(x) (0x1007 + ((x) << 4))
+#define MSCC_MS_SAM_ENTRY_SET1 0x1808
+#define MSCC_MS_SAM_ENTRY_CLEAR1 0x180c
+#define MSCC_MS_SAM_FLOW_CTRL(x) (0x1c00 + (x))
+#define MSCC_MS_SAM_CP_TAG 0x1e40
+#define MSCC_MS_SAM_NM_FLOW_NCP 0x1e51
+#define MSCC_MS_SAM_NM_FLOW_CP 0x1e52
+#define MSCC_MS_MISC_CONTROL 0x1e5f
+#define MSCC_MS_COUNT_CONTROL 0x3204
+#define MSCC_MS_PARAMS2_IG_CC_CONTROL 0x3a10
+#define MSCC_MS_PARAMS2_IG_CP_TAG 0x3a14
+#define MSCC_MS_VLAN_MTU_CHECK(x) (0x3c40 + (x))
+#define MSCC_MS_NON_VLAN_MTU_CHECK 0x3c48
+#define MSCC_MS_PP_CTRL 0x3c4b
+#define MSCC_MS_STATUS_CONTEXT_CTRL 0x3d02
+#define MSCC_MS_INTR_CTRL_STATUS 0x3d04
+#define MSCC_MS_BLOCK_CTX_UPDATE 0x3d0c
+#define MSCC_MS_AIC_CTRL 0x3e02
+
+/* MACSEC_ENA_CFG */
+#define MSCC_MS_ENA_CFG_CLK_ENA BIT(0)
+#define MSCC_MS_ENA_CFG_SW_RST BIT(1)
+#define MSCC_MS_ENA_CFG_MACSEC_BYPASS_ENA BIT(8)
+#define MSCC_MS_ENA_CFG_MACSEC_ENA BIT(9)
+#define MSCC_MS_ENA_CFG_MACSEC_SPEED_MODE(x) ((x) << 10)
+#define MSCC_MS_ENA_CFG_MACSEC_SPEED_MODE_M GENMASK(12, 10)
+
+/* MACSEC_FC_CFG */
+#define MSCC_MS_FC_CFG_FCBUF_ENA BIT(0)
+#define MSCC_MS_FC_CFG_USE_PKT_EXPANSION_INDICATION BIT(1)
+#define MSCC_MS_FC_CFG_LOW_THRESH(x) ((x) << 4)
+#define MSCC_MS_FC_CFG_LOW_THRESH_M GENMASK(7, 4)
+#define MSCC_MS_FC_CFG_HIGH_THRESH(x) ((x) << 8)
+#define MSCC_MS_FC_CFG_HIGH_THRESH_M GENMASK(11, 8)
+#define MSCC_MS_FC_CFG_LOW_BYTES_VAL(x) ((x) << 12)
+#define MSCC_MS_FC_CFG_LOW_BYTES_VAL_M GENMASK(14, 12)
+#define MSCC_MS_FC_CFG_HIGH_BYTES_VAL(x) ((x) << 16)
+#define MSCC_MS_FC_CFG_HIGH_BYTES_VAL_M GENMASK(18, 16)
+
+/* MSCC_MS_SAM_MAC_SA_MATCH_HI */
+#define MSCC_MS_SAM_MAC_SA_MATCH_HI_ETYPE(x) ((x) << 16)
+#define MSCC_MS_SAM_MAC_SA_MATCH_HI_ETYPE_M GENMASK(31, 16)
+
+/* MACSEC_SAM_MISC_MATCH */
+#define MSCC_MS_SAM_MISC_MATCH_VLAN_VALID BIT(0)
+#define MSCC_MS_SAM_MISC_MATCH_QINQ_FOUND BIT(1)
+#define MSCC_MS_SAM_MISC_MATCH_STAG_VALID BIT(2)
+#define MSCC_MS_SAM_MISC_MATCH_QTAG_VALID BIT(3)
+#define MSCC_MS_SAM_MISC_MATCH_VLAN_UP(x) ((x) << 4)
+#define MSCC_MS_SAM_MISC_MATCH_VLAN_UP_M GENMASK(6, 4)
+#define MSCC_MS_SAM_MISC_MATCH_CONTROL_PACKET BIT(7)
+#define MSCC_MS_SAM_MISC_MATCH_UNTAGGED BIT(8)
+#define MSCC_MS_SAM_MISC_MATCH_TAGGED BIT(9)
+#define MSCC_MS_SAM_MISC_MATCH_BAD_TAG BIT(10)
+#define MSCC_MS_SAM_MISC_MATCH_KAY_TAG BIT(11)
+#define MSCC_MS_SAM_MISC_MATCH_SOURCE_PORT(x) ((x) << 12)
+#define MSCC_MS_SAM_MISC_MATCH_SOURCE_PORT_M GENMASK(13, 12)
+#define MSCC_MS_SAM_MISC_MATCH_PRIORITY(x) ((x) << 16)
+#define MSCC_MS_SAM_MISC_MATCH_PRIORITY_M GENMASK(19, 16)
+#define MSCC_MS_SAM_MISC_MATCH_AN(x) ((x) << 24)
+#define MSCC_MS_SAM_MISC_MATCH_TCI(x) ((x) << 26)
+
+/* MACSEC_SAM_MASK */
+#define MSCC_MS_SAM_MASK_MAC_SA_MASK(x) (x)
+#define MSCC_MS_SAM_MASK_MAC_SA_MASK_M GENMASK(5, 0)
+#define MSCC_MS_SAM_MASK_MAC_DA_MASK(x) ((x) << 6)
+#define MSCC_MS_SAM_MASK_MAC_DA_MASK_M GENMASK(11, 6)
+#define MSCC_MS_SAM_MASK_MAC_ETYPE_MASK BIT(12)
+#define MSCC_MS_SAM_MASK_VLAN_VLD_MASK BIT(13)
+#define MSCC_MS_SAM_MASK_QINQ_FOUND_MASK BIT(14)
+#define MSCC_MS_SAM_MASK_STAG_VLD_MASK BIT(15)
+#define MSCC_MS_SAM_MASK_QTAG_VLD_MASK BIT(16)
+#define MSCC_MS_SAM_MASK_VLAN_UP_MASK BIT(17)
+#define MSCC_MS_SAM_MASK_VLAN_ID_MASK BIT(18)
+#define MSCC_MS_SAM_MASK_SOURCE_PORT_MASK BIT(19)
+#define MSCC_MS_SAM_MASK_CTL_PACKET_MASK BIT(20)
+#define MSCC_MS_SAM_MASK_VLAN_UP_INNER_MASK BIT(21)
+#define MSCC_MS_SAM_MASK_VLAN_ID_INNER_MASK BIT(22)
+#define MSCC_MS_SAM_MASK_SCI_MASK BIT(23)
+#define MSCC_MS_SAM_MASK_AN_MASK(x) ((x) << 24)
+#define MSCC_MS_SAM_MASK_TCI_MASK(x) ((x) << 26)
+
+/* MACSEC_SAM_FLOW_CTRL_EGR */
+#define MSCC_MS_SAM_FLOW_CTRL_FLOW_TYPE(x) (x)
+#define MSCC_MS_SAM_FLOW_CTRL_FLOW_TYPE_M GENMASK(1, 0)
+#define MSCC_MS_SAM_FLOW_CTRL_DEST_PORT(x) ((x) << 2)
+#define MSCC_MS_SAM_FLOW_CTRL_DEST_PORT_M GENMASK(3, 2)
+#define MSCC_MS_SAM_FLOW_CTRL_RESV_4 BIT(4)
+#define MSCC_MS_SAM_FLOW_CTRL_FLOW_CRYPT_AUTH BIT(5)
+#define MSCC_MS_SAM_FLOW_CTRL_DROP_ACTION(x) ((x) << 6)
+#define MSCC_MS_SAM_FLOW_CTRL_DROP_ACTION_M GENMASK(7, 6)
+#define MSCC_MS_SAM_FLOW_CTRL_RESV_15_TO_8(x) ((x) << 8)
+#define MSCC_MS_SAM_FLOW_CTRL_RESV_15_TO_8_M GENMASK(15, 8)
+#define MSCC_MS_SAM_FLOW_CTRL_PROTECT_FRAME BIT(16)
+#define MSCC_MS_SAM_FLOW_CTRL_REPLAY_PROTECT BIT(16)
+#define MSCC_MS_SAM_FLOW_CTRL_SA_IN_USE BIT(17)
+#define MSCC_MS_SAM_FLOW_CTRL_INCLUDE_SCI BIT(18)
+#define MSCC_MS_SAM_FLOW_CTRL_USE_ES BIT(19)
+#define MSCC_MS_SAM_FLOW_CTRL_USE_SCB BIT(20)
+#define MSCC_MS_SAM_FLOW_CTRL_VALIDATE_FRAMES(x) ((x) << 19)
+#define MSCC_MS_SAM_FLOW_CTRL_TAG_BYPASS_SIZE(x) ((x) << 21)
+#define MSCC_MS_SAM_FLOW_CTRL_TAG_BYPASS_SIZE_M GENMASK(22, 21)
+#define MSCC_MS_SAM_FLOW_CTRL_RESV_23 BIT(23)
+#define MSCC_MS_SAM_FLOW_CTRL_CONFIDENTIALITY_OFFSET(x) ((x) << 24)
+#define MSCC_MS_SAM_FLOW_CTRL_CONFIDENTIALITY_OFFSET_M GENMASK(30, 24)
+#define MSCC_MS_SAM_FLOW_CTRL_CONF_PROTECT BIT(31)
+
+/* MACSEC_SAM_CP_TAG */
+#define MSCC_MS_SAM_CP_TAG_MAP_TBL(x) (x)
+#define MSCC_MS_SAM_CP_TAG_MAP_TBL_M GENMASK(23, 0)
+#define MSCC_MS_SAM_CP_TAG_DEF_UP(x) ((x) << 24)
+#define MSCC_MS_SAM_CP_TAG_DEF_UP_M GENMASK(26, 24)
+#define MSCC_MS_SAM_CP_TAG_STAG_UP_EN BIT(27)
+#define MSCC_MS_SAM_CP_TAG_QTAG_UP_EN BIT(28)
+#define MSCC_MS_SAM_CP_TAG_PARSE_QINQ BIT(29)
+#define MSCC_MS_SAM_CP_TAG_PARSE_STAG BIT(30)
+#define MSCC_MS_SAM_CP_TAG_PARSE_QTAG BIT(31)
+
+/* MACSEC_SAM_NM_FLOW_NCP */
+#define MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_FLOW_TYPE(x) (x)
+#define MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_DEST_PORT(x) ((x) << 2)
+#define MSCC_MS_SAM_NM_FLOW_NCP_UNTAGGED_DROP_ACTION(x) ((x) << 6)
+#define MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_FLOW_TYPE(x) ((x) << 8)
+#define MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_DEST_PORT(x) ((x) << 10)
+#define MSCC_MS_SAM_NM_FLOW_NCP_TAGGED_DROP_ACTION(x) ((x) << 14)
+#define MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_FLOW_TYPE(x) ((x) << 16)
+#define MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_DEST_PORT(x) ((x) << 18)
+#define MSCC_MS_SAM_NM_FLOW_NCP_BADTAG_DROP_ACTION(x) ((x) << 22)
+#define MSCC_MS_SAM_NM_FLOW_NCP_KAY_FLOW_TYPE(x) ((x) << 24)
+#define MSCC_MS_SAM_NM_FLOW_NCP_KAY_DEST_PORT(x) ((x) << 26)
+#define MSCC_MS_SAM_NM_FLOW_NCP_KAY_DROP_ACTION(x) ((x) << 30)
+
+/* MACSEC_SAM_NM_FLOW_CP */
+#define MSCC_MS_SAM_NM_FLOW_CP_UNTAGGED_FLOW_TYPE(x) (x)
+#define MSCC_MS_SAM_NM_FLOW_CP_UNTAGGED_DEST_PORT(x) ((x) << 2)
+#define MSCC_MS_SAM_NM_FLOW_CP_UNTAGGED_DROP_ACTION(x) ((x) << 6)
+#define MSCC_MS_SAM_NM_FLOW_CP_TAGGED_FLOW_TYPE(x) ((x) << 8)
+#define MSCC_MS_SAM_NM_FLOW_CP_TAGGED_DEST_PORT(x) ((x) << 10)
+#define MSCC_MS_SAM_NM_FLOW_CP_TAGGED_DROP_ACTION(x) ((x) << 14)
+#define MSCC_MS_SAM_NM_FLOW_CP_BADTAG_FLOW_TYPE(x) ((x) << 16)
+#define MSCC_MS_SAM_NM_FLOW_CP_BADTAG_DEST_PORT(x) ((x) << 18)
+#define MSCC_MS_SAM_NM_FLOW_CP_BADTAG_DROP_ACTION(x) ((x) << 22)
+#define MSCC_MS_SAM_NM_FLOW_CP_KAY_FLOW_TYPE(x) ((x) << 24)
+#define MSCC_MS_SAM_NM_FLOW_CP_KAY_DEST_PORT(x) ((x) << 26)
+#define MSCC_MS_SAM_NM_FLOW_CP_KAY_DROP_ACTION(x) ((x) << 30)
+
+/* MACSEC_MISC_CONTROL */
+#define MSCC_MS_MISC_CONTROL_MC_LATENCY_FIX(x) (x)
+#define MSCC_MS_MISC_CONTROL_MC_LATENCY_FIX_M GENMASK(5, 0)
+#define MSCC_MS_MISC_CONTROL_STATIC_BYPASS BIT(8)
+#define MSCC_MS_MISC_CONTROL_NM_MACSEC_EN BIT(9)
+#define MSCC_MS_MISC_CONTROL_VALIDATE_FRAMES(x) ((x) << 10)
+#define MSCC_MS_MISC_CONTROL_VALIDATE_FRAMES_M GENMASK(11, 10)
+#define MSCC_MS_MISC_CONTROL_XFORM_REC_SIZE(x) ((x) << 24)
+#define MSCC_MS_MISC_CONTROL_XFORM_REC_SIZE_M GENMASK(25, 24)
+
+/* MACSEC_COUNT_CONTROL */
+#define MSCC_MS_COUNT_CONTROL_RESET_ALL BIT(0)
+#define MSCC_MS_COUNT_CONTROL_DEBUG_ACCESS BIT(1)
+#define MSCC_MS_COUNT_CONTROL_SATURATE_CNTRS BIT(2)
+#define MSCC_MS_COUNT_CONTROL_AUTO_CNTR_RESET BIT(3)
+
+/* MACSEC_PARAMS2_IG_CC_CONTROL */
+#define MSCC_MS_PARAMS2_IG_CC_CONTROL_NON_MATCH_CTRL_ACT BIT(14)
+#define MSCC_MS_PARAMS2_IG_CC_CONTROL_NON_MATCH_ACT BIT(15)
+
+/* MACSEC_PARAMS2_IG_CP_TAG */
+#define MSCC_MS_PARAMS2_IG_CP_TAG_MAP_TBL(x) (x)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_MAP_TBL_M GENMASK(23, 0)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_DEF_UP(x) ((x) << 24)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_DEF_UP_M GENMASK(26, 24)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_STAG_UP_EN BIT(27)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_QTAG_UP_EN BIT(28)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_PARSE_QINQ BIT(29)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_PARSE_STAG BIT(30)
+#define MSCC_MS_PARAMS2_IG_CP_TAG_PARSE_QTAG BIT(31)
+
+/* MACSEC_VLAN_MTU_CHECK */
+#define MSCC_MS_VLAN_MTU_CHECK_MTU_COMPARE(x) (x)
+#define MSCC_MS_VLAN_MTU_CHECK_MTU_COMPARE_M GENMASK(14, 0)
+#define MSCC_MS_VLAN_MTU_CHECK_MTU_COMP_DROP BIT(15)
+
+/* MACSEC_NON_VLAN_MTU_CHECK */
+#define MSCC_MS_NON_VLAN_MTU_CHECK_NV_MTU_COMPARE(x) (x)
+#define MSCC_MS_NON_VLAN_MTU_CHECK_NV_MTU_COMPARE_M GENMASK(14, 0)
+#define MSCC_MS_NON_VLAN_MTU_CHECK_NV_MTU_COMP_DROP BIT(15)
+
+/* MACSEC_PP_CTRL */
+#define MSCC_MS_PP_CTRL_MACSEC_OCTET_INCR_MODE BIT(0)
+
+/* MACSEC_INTR_CTRL_STATUS */
+#define MSCC_MS_INTR_CTRL_STATUS_INTR_CLR_STATUS(x) (x)
+#define MSCC_MS_INTR_CTRL_STATUS_INTR_CLR_STATUS_M GENMASK(15, 0)
+#define MSCC_MS_INTR_CTRL_STATUS_INTR_ENABLE(x) ((x) << 16)
+#define MSCC_MS_INTR_CTRL_STATUS_INTR_ENABLE_M GENMASK(31, 16)
+#define MACSEC_INTR_CTRL_STATUS_ROLLOVER BIT(5)
+
+#endif /* _MSCC_PHY_MACSEC_H_ */
diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c
new file mode 100644
index 000000000..4171f01d3
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_main.c
@@ -0,0 +1,2715 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Author: Nagaraju Lakkaraju
+ * License: Dual MIT/GPL
+ * Copyright (c) 2016 Microsemi Corporation
+ */
+
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#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>
+#include "mscc_serdes.h"
+#include "mscc.h"
+
+static const struct vsc85xx_hw_stat vsc85xx_hw_stats[] = {
+ {
+ .string = "phy_receive_errors",
+ .reg = MSCC_PHY_ERR_RX_CNT,
+ .page = MSCC_PHY_PAGE_STANDARD,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_false_carrier",
+ .reg = MSCC_PHY_ERR_FALSE_CARRIER_CNT,
+ .page = MSCC_PHY_PAGE_STANDARD,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_cu_media_link_disconnect",
+ .reg = MSCC_PHY_ERR_LINK_DISCONNECT_CNT,
+ .page = MSCC_PHY_PAGE_STANDARD,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_cu_media_crc_good_count",
+ .reg = MSCC_PHY_CU_MEDIA_CRC_VALID_CNT,
+ .page = MSCC_PHY_PAGE_EXTENDED,
+ .mask = VALID_CRC_CNT_CRC_MASK,
+ }, {
+ .string = "phy_cu_media_crc_error_count",
+ .reg = MSCC_PHY_EXT_PHY_CNTL_4,
+ .page = MSCC_PHY_PAGE_EXTENDED,
+ .mask = ERR_CNT_MASK,
+ },
+};
+
+static const struct vsc85xx_hw_stat vsc8584_hw_stats[] = {
+ {
+ .string = "phy_receive_errors",
+ .reg = MSCC_PHY_ERR_RX_CNT,
+ .page = MSCC_PHY_PAGE_STANDARD,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_false_carrier",
+ .reg = MSCC_PHY_ERR_FALSE_CARRIER_CNT,
+ .page = MSCC_PHY_PAGE_STANDARD,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_cu_media_link_disconnect",
+ .reg = MSCC_PHY_ERR_LINK_DISCONNECT_CNT,
+ .page = MSCC_PHY_PAGE_STANDARD,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_cu_media_crc_good_count",
+ .reg = MSCC_PHY_CU_MEDIA_CRC_VALID_CNT,
+ .page = MSCC_PHY_PAGE_EXTENDED,
+ .mask = VALID_CRC_CNT_CRC_MASK,
+ }, {
+ .string = "phy_cu_media_crc_error_count",
+ .reg = MSCC_PHY_EXT_PHY_CNTL_4,
+ .page = MSCC_PHY_PAGE_EXTENDED,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_serdes_tx_good_pkt_count",
+ .reg = MSCC_PHY_SERDES_TX_VALID_CNT,
+ .page = MSCC_PHY_PAGE_EXTENDED_3,
+ .mask = VALID_CRC_CNT_CRC_MASK,
+ }, {
+ .string = "phy_serdes_tx_bad_crc_count",
+ .reg = MSCC_PHY_SERDES_TX_CRC_ERR_CNT,
+ .page = MSCC_PHY_PAGE_EXTENDED_3,
+ .mask = ERR_CNT_MASK,
+ }, {
+ .string = "phy_serdes_rx_good_pkt_count",
+ .reg = MSCC_PHY_SERDES_RX_VALID_CNT,
+ .page = MSCC_PHY_PAGE_EXTENDED_3,
+ .mask = VALID_CRC_CNT_CRC_MASK,
+ }, {
+ .string = "phy_serdes_rx_bad_crc_count",
+ .reg = MSCC_PHY_SERDES_RX_CRC_ERR_CNT,
+ .page = MSCC_PHY_PAGE_EXTENDED_3,
+ .mask = ERR_CNT_MASK,
+ },
+};
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+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
+
+static const int vsc85xx_internal_delay[] = {200, 800, 1100, 1700, 2000, 2300,
+ 2600, 3400};
+
+static int vsc85xx_phy_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MSCC_EXT_PAGE_ACCESS);
+}
+
+static int vsc85xx_phy_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MSCC_EXT_PAGE_ACCESS, page);
+}
+
+static int vsc85xx_get_sset_count(struct phy_device *phydev)
+{
+ struct vsc8531_private *priv = phydev->priv;
+
+ if (!priv)
+ return 0;
+
+ return priv->nstats;
+}
+
+static void vsc85xx_get_strings(struct phy_device *phydev, u8 *data)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ int i;
+
+ if (!priv)
+ return;
+
+ for (i = 0; i < priv->nstats; i++)
+ strscpy(data + i * ETH_GSTRING_LEN, priv->hw_stats[i].string,
+ ETH_GSTRING_LEN);
+}
+
+static u64 vsc85xx_get_stat(struct phy_device *phydev, int i)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ int val;
+
+ val = phy_read_paged(phydev, priv->hw_stats[i].page,
+ priv->hw_stats[i].reg);
+ if (val < 0)
+ return U64_MAX;
+
+ val = val & priv->hw_stats[i].mask;
+ priv->stats[i] += val;
+
+ return priv->stats[i];
+}
+
+static void vsc85xx_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ int i;
+
+ if (!priv)
+ return;
+
+ for (i = 0; i < priv->nstats; i++)
+ data[i] = vsc85xx_get_stat(phydev, i);
+}
+
+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);
+ reg_val &= ~LED_MODE_SEL_MASK(led_num);
+ reg_val |= LED_MODE_SEL(led_num, (u16)mode);
+ 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)
+ return rc;
+
+ reg_val = 0;
+
+ 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_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED,
+ MSCC_PHY_EXT_MODE_CNTL, FORCE_MDI_CROSSOVER_MASK,
+ reg_val);
+ if (rc < 0)
+ return rc;
+
+ return genphy_restart_aneg(phydev);
+}
+
+static int vsc85xx_downshift_get(struct phy_device *phydev, u8 *count)
+{
+ int reg_val;
+
+ reg_val = phy_read_paged(phydev, MSCC_PHY_PAGE_EXTENDED,
+ MSCC_PHY_ACTIPHY_CNTL);
+ if (reg_val < 0)
+ return reg_val;
+
+ reg_val &= DOWNSHIFT_CNTL_MASK;
+ if (!(reg_val & DOWNSHIFT_EN))
+ *count = DOWNSHIFT_DEV_DISABLE;
+ else
+ *count = ((reg_val & ~DOWNSHIFT_EN) >> DOWNSHIFT_CNTL_POS) + 2;
+
+ return 0;
+}
+
+static int vsc85xx_downshift_set(struct phy_device *phydev, u8 count)
+{
+ 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);
+ }
+
+ return phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED,
+ MSCC_PHY_ACTIPHY_CNTL, DOWNSHIFT_CNTL_MASK,
+ count);
+}
+
+static int vsc85xx_wol_set(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ const u8 *mac_addr = phydev->attached_dev->dev_addr;
+ int rc;
+ u16 reg_val;
+ u8 i;
+ u16 pwd[3] = {0, 0, 0};
+ struct ethtool_wolinfo *wol_conf = wol;
+
+ rc = phy_select_page(phydev, MSCC_PHY_PAGE_EXTENDED_2);
+ if (rc < 0)
+ return phy_restore_page(phydev, rc, rc);
+
+ 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 = phy_restore_page(phydev, rc, rc > 0 ? 0 : rc);
+ if (rc < 0)
+ return rc;
+
+ 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)
+ return rc;
+ } 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)
+ return rc;
+ }
+ /* Clear WOL iterrupt status */
+ reg_val = phy_read(phydev, MII_VSC85XX_INT_STATUS);
+
+ return 0;
+}
+
+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;
+
+ rc = phy_select_page(phydev, MSCC_PHY_PAGE_EXTENDED_2);
+ if (rc < 0)
+ goto out_restore_page;
+
+ 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;
+ }
+ }
+
+out_restore_page:
+ phy_restore_page(phydev, rc, rc > 0 ? 0 : rc);
+}
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+static int vsc85xx_edge_rate_magic_get(struct phy_device *phydev)
+{
+ u32 vdd, sd;
+ int 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;
+
+ if (of_property_read_u32(of_node, "vsc8531,vddmac", &vdd))
+ vdd = MSCC_VDDMAC_3300;
+
+ if (of_property_read_u32(of_node, "vsc8531,edge-slowdown", &sd))
+ 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,
+ u32 default_mode)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ struct device_node *of_node = dev->of_node;
+ u32 led_mode;
+ int err;
+
+ if (!of_node)
+ return -ENODEV;
+
+ led_mode = default_mode;
+ err = of_property_read_u32(of_node, led, &led_mode);
+ if (!err && !(BIT(led_mode) & priv->supp_led_modes)) {
+ 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_dt_led_modes_get(struct phy_device *phydev,
+ u32 *default_mode)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ char led_dt_prop[28];
+ int i, ret;
+
+ for (i = 0; i < priv->nleds; i++) {
+ ret = sprintf(led_dt_prop, "vsc8531,led-%d-mode", i);
+ if (ret < 0)
+ return ret;
+
+ ret = vsc85xx_dt_led_mode_get(phydev, led_dt_prop,
+ default_mode[i]);
+ if (ret < 0)
+ return ret;
+ priv->leds_mode[i] = ret;
+ }
+
+ return 0;
+}
+
+static int vsc85xx_edge_rate_cntl_set(struct phy_device *phydev, u8 edge_rate)
+{
+ int rc;
+
+ mutex_lock(&phydev->lock);
+ rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED_2,
+ MSCC_PHY_WOL_MAC_CONTROL, EDGE_RATE_CNTL_MASK,
+ edge_rate << EDGE_RATE_CNTL_POS);
+ 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_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ 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)
+ goto out_unlock;
+
+ rc = genphy_soft_reset(phydev);
+
+out_unlock:
+ mutex_unlock(&phydev->lock);
+
+ return rc;
+}
+
+/* Set the RGMII RX and TX clock skews individually, according to the PHY
+ * interface type, to:
+ * * 0.2 ns (their default, and lowest, hardware value) if delays should
+ * not be enabled
+ * * 2.0 ns (which causes the data to be sampled at exactly half way between
+ * clock transitions at 1000 Mbps) if delays should be enabled
+ */
+static int vsc85xx_update_rgmii_cntl(struct phy_device *phydev, u32 rgmii_cntl,
+ u16 rgmii_rx_delay_mask,
+ u16 rgmii_tx_delay_mask)
+{
+ u16 rgmii_rx_delay_pos = ffs(rgmii_rx_delay_mask) - 1;
+ u16 rgmii_tx_delay_pos = ffs(rgmii_tx_delay_mask) - 1;
+ int delay_size = ARRAY_SIZE(vsc85xx_internal_delay);
+ struct device *dev = &phydev->mdio.dev;
+ u16 reg_val = 0;
+ u16 mask = 0;
+ s32 rx_delay;
+ s32 tx_delay;
+ int rc = 0;
+
+ /* For traffic to pass, the VSC8502 family needs the RX_CLK disable bit
+ * to be unset for all PHY modes, so do that as part of the paged
+ * register modification.
+ * For some family members (like VSC8530/31/40/41) this bit is reserved
+ * and read-only, and the RX clock is enabled by default.
+ */
+ if (rgmii_cntl == VSC8502_RGMII_CNTL)
+ mask |= VSC8502_RGMII_RX_CLK_DISABLE;
+
+ if (phy_interface_is_rgmii(phydev))
+ mask |= rgmii_rx_delay_mask | rgmii_tx_delay_mask;
+
+ rx_delay = phy_get_internal_delay(phydev, dev, vsc85xx_internal_delay,
+ delay_size, true);
+ if (rx_delay < 0) {
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+ rx_delay = RGMII_CLK_DELAY_2_0_NS;
+ else
+ rx_delay = RGMII_CLK_DELAY_0_2_NS;
+ }
+
+ tx_delay = phy_get_internal_delay(phydev, dev, vsc85xx_internal_delay,
+ delay_size, false);
+ if (tx_delay < 0) {
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+ tx_delay = RGMII_CLK_DELAY_2_0_NS;
+ else
+ tx_delay = RGMII_CLK_DELAY_0_2_NS;
+ }
+
+ reg_val |= rx_delay << rgmii_rx_delay_pos;
+ reg_val |= tx_delay << rgmii_tx_delay_pos;
+
+ if (mask)
+ rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED_2,
+ rgmii_cntl, mask, reg_val);
+
+ return rc;
+}
+
+static int vsc85xx_default_config(struct phy_device *phydev)
+{
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ return vsc85xx_update_rgmii_cntl(phydev, VSC8502_RGMII_CNTL,
+ VSC8502_RGMII_RX_DELAY_MASK,
+ VSC8502_RGMII_TX_DELAY_MASK);
+}
+
+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;
+ }
+}
+
+/* mdiobus lock should be locked when using this function */
+static void vsc85xx_tr_write(struct phy_device *phydev, u16 addr, u32 val)
+{
+ __phy_write(phydev, MSCC_PHY_TR_MSB, val >> 16);
+ __phy_write(phydev, MSCC_PHY_TR_LSB, val & GENMASK(15, 0));
+ __phy_write(phydev, MSCC_PHY_TR_CNTL, TR_WRITE | TR_ADDR(addr));
+}
+
+static int vsc8531_pre_init_seq_set(struct phy_device *phydev)
+{
+ int rc;
+ static const struct reg_val init_seq[] = {
+ {0x0f90, 0x00688980},
+ {0x0696, 0x00000003},
+ {0x07fa, 0x0050100f},
+ {0x1686, 0x00000004},
+ };
+ unsigned int i;
+ int oldpage;
+
+ rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_STANDARD,
+ MSCC_PHY_EXT_CNTL_STATUS, SMI_BROADCAST_WR_EN,
+ SMI_BROADCAST_WR_EN);
+ if (rc < 0)
+ return rc;
+ rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_TEST,
+ MSCC_PHY_TEST_PAGE_24, 0, 0x0400);
+ if (rc < 0)
+ return rc;
+ rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_TEST,
+ MSCC_PHY_TEST_PAGE_5, 0x0a00, 0x0e00);
+ if (rc < 0)
+ return rc;
+ rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_TEST,
+ MSCC_PHY_TEST_PAGE_8, TR_CLK_DISABLE, TR_CLK_DISABLE);
+ if (rc < 0)
+ return rc;
+
+ mutex_lock(&phydev->lock);
+ oldpage = phy_select_page(phydev, MSCC_PHY_PAGE_TR);
+ if (oldpage < 0)
+ goto out_unlock;
+
+ for (i = 0; i < ARRAY_SIZE(init_seq); i++)
+ vsc85xx_tr_write(phydev, init_seq[i].reg, init_seq[i].val);
+
+out_unlock:
+ oldpage = phy_restore_page(phydev, oldpage, oldpage);
+ mutex_unlock(&phydev->lock);
+
+ return oldpage;
+}
+
+static int vsc85xx_eee_init_seq_set(struct phy_device *phydev)
+{
+ static const struct reg_val init_eee[] = {
+ {0x0f82, 0x0012b00a},
+ {0x1686, 0x00000004},
+ {0x168c, 0x00d2c46f},
+ {0x17a2, 0x00000620},
+ {0x16a0, 0x00eeffdd},
+ {0x16a6, 0x00071448},
+ {0x16a4, 0x0013132f},
+ {0x16a8, 0x00000000},
+ {0x0ffc, 0x00c0a028},
+ {0x0fe8, 0x0091b06c},
+ {0x0fea, 0x00041600},
+ {0x0f80, 0x00000af4},
+ {0x0fec, 0x00901809},
+ {0x0fee, 0x0000a6a1},
+ {0x0ffe, 0x00b01007},
+ {0x16b0, 0x00eeff00},
+ {0x16b2, 0x00007000},
+ {0x16b4, 0x00000814},
+ };
+ unsigned int i;
+ int oldpage;
+
+ mutex_lock(&phydev->lock);
+ oldpage = phy_select_page(phydev, MSCC_PHY_PAGE_TR);
+ if (oldpage < 0)
+ goto out_unlock;
+
+ for (i = 0; i < ARRAY_SIZE(init_eee); i++)
+ vsc85xx_tr_write(phydev, init_eee[i].reg, init_eee[i].val);
+
+out_unlock:
+ oldpage = phy_restore_page(phydev, oldpage, oldpage);
+ mutex_unlock(&phydev->lock);
+
+ return oldpage;
+}
+
+/* phydev->bus->mdio_lock should be locked when using this function */
+int phy_base_write(struct phy_device *phydev, u32 regnum, u16 val)
+{
+ if (unlikely(!mutex_is_locked(&phydev->mdio.bus->mdio_lock))) {
+ dev_err(&phydev->mdio.dev, "MDIO bus lock not held!\n");
+ dump_stack();
+ }
+
+ return __phy_package_write(phydev, regnum, val);
+}
+
+/* phydev->bus->mdio_lock should be locked when using this function */
+int phy_base_read(struct phy_device *phydev, u32 regnum)
+{
+ if (unlikely(!mutex_is_locked(&phydev->mdio.bus->mdio_lock))) {
+ dev_err(&phydev->mdio.dev, "MDIO bus lock not held!\n");
+ dump_stack();
+ }
+
+ return __phy_package_read(phydev, regnum);
+}
+
+u32 vsc85xx_csr_read(struct phy_device *phydev,
+ enum csr_target target, u32 reg)
+{
+ unsigned long deadline;
+ u32 val, val_l, val_h;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_CSR_CNTL);
+
+ /* CSR registers are grouped under different Target IDs.
+ * 6-bit Target_ID is split between MSCC_EXT_PAGE_CSR_CNTL_20 and
+ * MSCC_EXT_PAGE_CSR_CNTL_19 registers.
+ * Target_ID[5:2] maps to bits[3:0] of MSCC_EXT_PAGE_CSR_CNTL_20
+ * and Target_ID[1:0] maps to bits[13:12] of MSCC_EXT_PAGE_CSR_CNTL_19.
+ */
+
+ /* Setup the Target ID */
+ phy_base_write(phydev, MSCC_EXT_PAGE_CSR_CNTL_20,
+ MSCC_PHY_CSR_CNTL_20_TARGET(target >> 2));
+
+ if ((target >> 2 == 0x1) || (target >> 2 == 0x3))
+ /* non-MACsec access */
+ target &= 0x3;
+ else
+ target = 0;
+
+ /* Trigger CSR Action - Read into the CSR's */
+ phy_base_write(phydev, MSCC_EXT_PAGE_CSR_CNTL_19,
+ MSCC_PHY_CSR_CNTL_19_CMD | MSCC_PHY_CSR_CNTL_19_READ |
+ MSCC_PHY_CSR_CNTL_19_REG_ADDR(reg) |
+ MSCC_PHY_CSR_CNTL_19_TARGET(target));
+
+ /* Wait for register access*/
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ usleep_range(500, 1000);
+ val = phy_base_read(phydev, MSCC_EXT_PAGE_CSR_CNTL_19);
+ } while (time_before(jiffies, deadline) &&
+ !(val & MSCC_PHY_CSR_CNTL_19_CMD));
+
+ if (!(val & MSCC_PHY_CSR_CNTL_19_CMD))
+ return 0xffffffff;
+
+ /* Read the Least Significant Word (LSW) (17) */
+ val_l = phy_base_read(phydev, MSCC_EXT_PAGE_CSR_CNTL_17);
+
+ /* Read the Most Significant Word (MSW) (18) */
+ val_h = phy_base_read(phydev, MSCC_EXT_PAGE_CSR_CNTL_18);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_STANDARD);
+
+ return (val_h << 16) | val_l;
+}
+
+int vsc85xx_csr_write(struct phy_device *phydev,
+ enum csr_target target, u32 reg, u32 val)
+{
+ unsigned long deadline;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_CSR_CNTL);
+
+ /* CSR registers are grouped under different Target IDs.
+ * 6-bit Target_ID is split between MSCC_EXT_PAGE_CSR_CNTL_20 and
+ * MSCC_EXT_PAGE_CSR_CNTL_19 registers.
+ * Target_ID[5:2] maps to bits[3:0] of MSCC_EXT_PAGE_CSR_CNTL_20
+ * and Target_ID[1:0] maps to bits[13:12] of MSCC_EXT_PAGE_CSR_CNTL_19.
+ */
+
+ /* Setup the Target ID */
+ phy_base_write(phydev, MSCC_EXT_PAGE_CSR_CNTL_20,
+ MSCC_PHY_CSR_CNTL_20_TARGET(target >> 2));
+
+ /* Write the Least Significant Word (LSW) (17) */
+ phy_base_write(phydev, MSCC_EXT_PAGE_CSR_CNTL_17, (u16)val);
+
+ /* Write the Most Significant Word (MSW) (18) */
+ phy_base_write(phydev, MSCC_EXT_PAGE_CSR_CNTL_18, (u16)(val >> 16));
+
+ if ((target >> 2 == 0x1) || (target >> 2 == 0x3))
+ /* non-MACsec access */
+ target &= 0x3;
+ else
+ target = 0;
+
+ /* Trigger CSR Action - Write into the CSR's */
+ phy_base_write(phydev, MSCC_EXT_PAGE_CSR_CNTL_19,
+ MSCC_PHY_CSR_CNTL_19_CMD |
+ MSCC_PHY_CSR_CNTL_19_REG_ADDR(reg) |
+ MSCC_PHY_CSR_CNTL_19_TARGET(target));
+
+ /* Wait for register access */
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ usleep_range(500, 1000);
+ val = phy_base_read(phydev, MSCC_EXT_PAGE_CSR_CNTL_19);
+ } while (time_before(jiffies, deadline) &&
+ !(val & MSCC_PHY_CSR_CNTL_19_CMD));
+
+ if (!(val & MSCC_PHY_CSR_CNTL_19_CMD))
+ return -ETIMEDOUT;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_STANDARD);
+
+ return 0;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static void vsc8584_csr_write(struct phy_device *phydev, u16 addr, u32 val)
+{
+ phy_base_write(phydev, MSCC_PHY_TR_MSB, val >> 16);
+ phy_base_write(phydev, MSCC_PHY_TR_LSB, val & GENMASK(15, 0));
+ phy_base_write(phydev, MSCC_PHY_TR_CNTL, TR_WRITE | TR_ADDR(addr));
+}
+
+/* bus->mdio_lock should be locked when using this function */
+int vsc8584_cmd(struct phy_device *phydev, u16 val)
+{
+ unsigned long deadline;
+ u16 reg_val;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ phy_base_write(phydev, MSCC_PHY_PROC_CMD, PROC_CMD_NCOMPLETED | val);
+
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ reg_val = phy_base_read(phydev, MSCC_PHY_PROC_CMD);
+ } while (time_before(jiffies, deadline) &&
+ (reg_val & PROC_CMD_NCOMPLETED) &&
+ !(reg_val & PROC_CMD_FAILED));
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ if (reg_val & PROC_CMD_FAILED)
+ return -EIO;
+
+ if (reg_val & PROC_CMD_NCOMPLETED)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static int vsc8584_micro_deassert_reset(struct phy_device *phydev,
+ bool patch_en)
+{
+ u32 enable, release;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ enable = RUN_FROM_INT_ROM | MICRO_CLK_EN | DW8051_CLK_EN;
+ release = MICRO_NSOFT_RESET | RUN_FROM_INT_ROM | DW8051_CLK_EN |
+ MICRO_CLK_EN;
+
+ if (patch_en) {
+ enable |= MICRO_PATCH_EN;
+ release |= MICRO_PATCH_EN;
+
+ /* Clear all patches */
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, READ_RAM);
+ }
+
+ /* Enable 8051 Micro clock; CLEAR/SET patch present; disable PRAM clock
+ * override and addr. auto-incr; operate at 125 MHz
+ */
+ phy_base_write(phydev, MSCC_DW8051_CNTL_STATUS, enable);
+ /* Release 8051 Micro SW reset */
+ phy_base_write(phydev, MSCC_DW8051_CNTL_STATUS, release);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ return 0;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static int vsc8584_micro_assert_reset(struct phy_device *phydev)
+{
+ int ret;
+ u16 reg;
+
+ ret = vsc8584_cmd(phydev, PROC_CMD_NOP);
+ if (ret)
+ return ret;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ reg = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ reg &= ~EN_PATCH_RAM_TRAP_ADDR(4);
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, reg);
+
+ phy_base_write(phydev, MSCC_TRAP_ROM_ADDR(4), 0x005b);
+ phy_base_write(phydev, MSCC_PATCH_RAM_ADDR(4), 0x005b);
+
+ reg = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ reg |= EN_PATCH_RAM_TRAP_ADDR(4);
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, reg);
+
+ phy_base_write(phydev, MSCC_PHY_PROC_CMD, PROC_CMD_NOP);
+
+ reg = phy_base_read(phydev, MSCC_DW8051_CNTL_STATUS);
+ reg &= ~MICRO_NSOFT_RESET;
+ phy_base_write(phydev, MSCC_DW8051_CNTL_STATUS, reg);
+
+ phy_base_write(phydev, MSCC_PHY_PROC_CMD, PROC_CMD_MCB_ACCESS_MAC_CONF |
+ PROC_CMD_SGMII_PORT(0) | PROC_CMD_NO_MAC_CONF |
+ PROC_CMD_READ);
+
+ reg = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ reg &= ~EN_PATCH_RAM_TRAP_ADDR(4);
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ return 0;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static int vsc8584_get_fw_crc(struct phy_device *phydev, u16 start, u16 size,
+ u16 *crc)
+{
+ int ret;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED);
+
+ phy_base_write(phydev, MSCC_PHY_VERIPHY_CNTL_2, start);
+ phy_base_write(phydev, MSCC_PHY_VERIPHY_CNTL_3, size);
+
+ /* Start Micro command */
+ ret = vsc8584_cmd(phydev, PROC_CMD_CRC16);
+ if (ret)
+ goto out;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED);
+
+ *crc = phy_base_read(phydev, MSCC_PHY_VERIPHY_CNTL_2);
+
+out:
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ return ret;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static int vsc8584_patch_fw(struct phy_device *phydev,
+ const struct firmware *fw)
+{
+ int i, ret;
+
+ ret = vsc8584_micro_assert_reset(phydev);
+ if (ret) {
+ dev_err(&phydev->mdio.dev,
+ "%s: failed to assert reset of micro\n", __func__);
+ return ret;
+ }
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ /* Hold 8051 Micro in SW Reset, Enable auto incr address and patch clock
+ * Disable the 8051 Micro clock
+ */
+ phy_base_write(phydev, MSCC_DW8051_CNTL_STATUS, RUN_FROM_INT_ROM |
+ AUTOINC_ADDR | PATCH_RAM_CLK | MICRO_CLK_EN |
+ MICRO_CLK_DIVIDE(2));
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, READ_PRAM | INT_MEM_WRITE_EN |
+ INT_MEM_DATA(2));
+ phy_base_write(phydev, MSCC_INT_MEM_ADDR, 0x0000);
+
+ for (i = 0; i < fw->size; i++)
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, READ_PRAM |
+ INT_MEM_WRITE_EN | fw->data[i]);
+
+ /* Clear internal memory access */
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL, READ_RAM);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ return 0;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static bool vsc8574_is_serdes_init(struct phy_device *phydev)
+{
+ u16 reg;
+ bool ret;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ reg = phy_base_read(phydev, MSCC_TRAP_ROM_ADDR(1));
+ if (reg != 0x3eb7) {
+ ret = false;
+ goto out;
+ }
+
+ reg = phy_base_read(phydev, MSCC_PATCH_RAM_ADDR(1));
+ if (reg != 0x4012) {
+ ret = false;
+ goto out;
+ }
+
+ reg = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ if (reg != EN_PATCH_RAM_TRAP_ADDR(1)) {
+ ret = false;
+ goto out;
+ }
+
+ reg = phy_base_read(phydev, MSCC_DW8051_CNTL_STATUS);
+ if ((MICRO_NSOFT_RESET | RUN_FROM_INT_ROM | DW8051_CLK_EN |
+ MICRO_CLK_EN) != (reg & MSCC_DW8051_VLD_MASK)) {
+ ret = false;
+ goto out;
+ }
+
+ ret = true;
+out:
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ return ret;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static int vsc8574_config_pre_init(struct phy_device *phydev)
+{
+ static const struct reg_val pre_init1[] = {
+ {0x0fae, 0x000401bd},
+ {0x0fac, 0x000f000f},
+ {0x17a0, 0x00a0f147},
+ {0x0fe4, 0x00052f54},
+ {0x1792, 0x0027303d},
+ {0x07fe, 0x00000704},
+ {0x0fe0, 0x00060150},
+ {0x0f82, 0x0012b00a},
+ {0x0f80, 0x00000d74},
+ {0x02e0, 0x00000012},
+ {0x03a2, 0x00050208},
+ {0x03b2, 0x00009186},
+ {0x0fb0, 0x000e3700},
+ {0x1688, 0x00049f81},
+ {0x0fd2, 0x0000ffff},
+ {0x168a, 0x00039fa2},
+ {0x1690, 0x0020640b},
+ {0x0258, 0x00002220},
+ {0x025a, 0x00002a20},
+ {0x025c, 0x00003060},
+ {0x025e, 0x00003fa0},
+ {0x03a6, 0x0000e0f0},
+ {0x0f92, 0x00001489},
+ {0x16a2, 0x00007000},
+ {0x16a6, 0x00071448},
+ {0x16a0, 0x00eeffdd},
+ {0x0fe8, 0x0091b06c},
+ {0x0fea, 0x00041600},
+ {0x16b0, 0x00eeff00},
+ {0x16b2, 0x00007000},
+ {0x16b4, 0x00000814},
+ {0x0f90, 0x00688980},
+ {0x03a4, 0x0000d8f0},
+ {0x0fc0, 0x00000400},
+ {0x07fa, 0x0050100f},
+ {0x0796, 0x00000003},
+ {0x07f8, 0x00c3ff98},
+ {0x0fa4, 0x0018292a},
+ {0x168c, 0x00d2c46f},
+ {0x17a2, 0x00000620},
+ {0x16a4, 0x0013132f},
+ {0x16a8, 0x00000000},
+ {0x0ffc, 0x00c0a028},
+ {0x0fec, 0x00901c09},
+ {0x0fee, 0x0004a6a1},
+ {0x0ffe, 0x00b01807},
+ };
+ static const struct reg_val pre_init2[] = {
+ {0x0486, 0x0008a518},
+ {0x0488, 0x006dc696},
+ {0x048a, 0x00000912},
+ {0x048e, 0x00000db6},
+ {0x049c, 0x00596596},
+ {0x049e, 0x00000514},
+ {0x04a2, 0x00410280},
+ {0x04a4, 0x00000000},
+ {0x04a6, 0x00000000},
+ {0x04a8, 0x00000000},
+ {0x04aa, 0x00000000},
+ {0x04ae, 0x007df7dd},
+ {0x04b0, 0x006d95d4},
+ {0x04b2, 0x00492410},
+ };
+ struct device *dev = &phydev->mdio.dev;
+ const struct firmware *fw;
+ unsigned int i;
+ u16 crc, reg;
+ bool serdes_init;
+ int ret;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ /* all writes below are broadcasted to all PHYs in the same package */
+ reg = phy_base_read(phydev, MSCC_PHY_EXT_CNTL_STATUS);
+ reg |= SMI_BROADCAST_WR_EN;
+ phy_base_write(phydev, MSCC_PHY_EXT_CNTL_STATUS, reg);
+
+ phy_base_write(phydev, MII_VSC85XX_INT_MASK, 0);
+
+ /* The below register writes are tweaking analog and electrical
+ * configuration that were determined through characterization by PHY
+ * engineers. These don't mean anything more than "these are the best
+ * values".
+ */
+ phy_base_write(phydev, MSCC_PHY_EXT_PHY_CNTL_2, 0x0040);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TEST);
+
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_20, 0x4320);
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_24, 0x0c00);
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_9, 0x18ca);
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_5, 0x1b20);
+
+ reg = phy_base_read(phydev, MSCC_PHY_TEST_PAGE_8);
+ reg |= TR_CLK_DISABLE;
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_8, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TR);
+
+ for (i = 0; i < ARRAY_SIZE(pre_init1); i++)
+ vsc8584_csr_write(phydev, pre_init1[i].reg, pre_init1[i].val);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_2);
+
+ phy_base_write(phydev, MSCC_PHY_CU_PMD_TX_CNTL, 0x028e);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TR);
+
+ for (i = 0; i < ARRAY_SIZE(pre_init2); i++)
+ vsc8584_csr_write(phydev, pre_init2[i].reg, pre_init2[i].val);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TEST);
+
+ reg = phy_base_read(phydev, MSCC_PHY_TEST_PAGE_8);
+ reg &= ~TR_CLK_DISABLE;
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_8, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ /* end of write broadcasting */
+ reg = phy_base_read(phydev, MSCC_PHY_EXT_CNTL_STATUS);
+ reg &= ~SMI_BROADCAST_WR_EN;
+ phy_base_write(phydev, MSCC_PHY_EXT_CNTL_STATUS, reg);
+
+ ret = request_firmware(&fw, MSCC_VSC8574_REVB_INT8051_FW, dev);
+ if (ret) {
+ dev_err(dev, "failed to load firmware %s, ret: %d\n",
+ MSCC_VSC8574_REVB_INT8051_FW, ret);
+ return ret;
+ }
+
+ /* Add one byte to size for the one added by the patch_fw function */
+ ret = vsc8584_get_fw_crc(phydev,
+ MSCC_VSC8574_REVB_INT8051_FW_START_ADDR,
+ fw->size + 1, &crc);
+ if (ret)
+ goto out;
+
+ if (crc == MSCC_VSC8574_REVB_INT8051_FW_CRC) {
+ serdes_init = vsc8574_is_serdes_init(phydev);
+
+ if (!serdes_init) {
+ ret = vsc8584_micro_assert_reset(phydev);
+ if (ret) {
+ dev_err(dev,
+ "%s: failed to assert reset of micro\n",
+ __func__);
+ goto out;
+ }
+ }
+ } else {
+ dev_dbg(dev, "FW CRC is not the expected one, patching FW\n");
+
+ serdes_init = false;
+
+ if (vsc8584_patch_fw(phydev, fw))
+ dev_warn(dev,
+ "failed to patch FW, expect non-optimal device\n");
+ }
+
+ if (!serdes_init) {
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ phy_base_write(phydev, MSCC_TRAP_ROM_ADDR(1), 0x3eb7);
+ phy_base_write(phydev, MSCC_PATCH_RAM_ADDR(1), 0x4012);
+ phy_base_write(phydev, MSCC_INT_MEM_CNTL,
+ EN_PATCH_RAM_TRAP_ADDR(1));
+
+ vsc8584_micro_deassert_reset(phydev, false);
+
+ /* Add one byte to size for the one added by the patch_fw
+ * function
+ */
+ ret = vsc8584_get_fw_crc(phydev,
+ MSCC_VSC8574_REVB_INT8051_FW_START_ADDR,
+ fw->size + 1, &crc);
+ if (ret)
+ goto out;
+
+ if (crc != MSCC_VSC8574_REVB_INT8051_FW_CRC)
+ dev_warn(dev,
+ "FW CRC after patching is not the expected one, expect non-optimal device\n");
+ }
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+
+ ret = vsc8584_cmd(phydev, PROC_CMD_1588_DEFAULT_INIT |
+ PROC_CMD_PHY_INIT);
+
+out:
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ release_firmware(fw);
+
+ return ret;
+}
+
+/* Access LCPLL Cfg_2 */
+static void vsc8584_pll5g_cfg2_wr(struct phy_device *phydev,
+ bool disable_fsm)
+{
+ u32 rd_dat;
+
+ rd_dat = vsc85xx_csr_read(phydev, MACRO_CTRL, PHY_S6G_PLL5G_CFG2);
+ rd_dat &= ~BIT(PHY_S6G_CFG2_FSM_DIS);
+ rd_dat |= (disable_fsm << PHY_S6G_CFG2_FSM_DIS);
+ vsc85xx_csr_write(phydev, MACRO_CTRL, PHY_S6G_PLL5G_CFG2, rd_dat);
+}
+
+/* trigger a read to the spcified MCB */
+static int vsc8584_mcb_rd_trig(struct phy_device *phydev,
+ u32 mcb_reg_addr, u8 mcb_slave_num)
+{
+ u32 rd_dat = 0;
+
+ /* read MCB */
+ vsc85xx_csr_write(phydev, MACRO_CTRL, mcb_reg_addr,
+ (0x40000000 | (1L << mcb_slave_num)));
+
+ return read_poll_timeout(vsc85xx_csr_read, rd_dat,
+ !(rd_dat & 0x40000000),
+ 4000, 200000, 0,
+ phydev, MACRO_CTRL, mcb_reg_addr);
+}
+
+/* trigger a write to the spcified MCB */
+static int vsc8584_mcb_wr_trig(struct phy_device *phydev,
+ u32 mcb_reg_addr,
+ u8 mcb_slave_num)
+{
+ u32 rd_dat = 0;
+
+ /* write back MCB */
+ vsc85xx_csr_write(phydev, MACRO_CTRL, mcb_reg_addr,
+ (0x80000000 | (1L << mcb_slave_num)));
+
+ return read_poll_timeout(vsc85xx_csr_read, rd_dat,
+ !(rd_dat & 0x80000000),
+ 4000, 200000, 0,
+ phydev, MACRO_CTRL, mcb_reg_addr);
+}
+
+/* Sequence to Reset LCPLL for the VIPER and ELISE PHY */
+static int vsc8584_pll5g_reset(struct phy_device *phydev)
+{
+ bool dis_fsm;
+ int ret = 0;
+
+ ret = vsc8584_mcb_rd_trig(phydev, 0x11, 0);
+ if (ret < 0)
+ goto done;
+ dis_fsm = 1;
+
+ /* Reset LCPLL */
+ vsc8584_pll5g_cfg2_wr(phydev, dis_fsm);
+
+ /* write back LCPLL MCB */
+ ret = vsc8584_mcb_wr_trig(phydev, 0x11, 0);
+ if (ret < 0)
+ goto done;
+
+ /* 10 mSec sleep while LCPLL is hold in reset */
+ usleep_range(10000, 20000);
+
+ /* read LCPLL MCB into CSRs */
+ ret = vsc8584_mcb_rd_trig(phydev, 0x11, 0);
+ if (ret < 0)
+ goto done;
+ dis_fsm = 0;
+
+ /* Release the Reset of LCPLL */
+ vsc8584_pll5g_cfg2_wr(phydev, dis_fsm);
+
+ /* write back LCPLL MCB */
+ ret = vsc8584_mcb_wr_trig(phydev, 0x11, 0);
+ if (ret < 0)
+ goto done;
+
+ usleep_range(110000, 200000);
+done:
+ return ret;
+}
+
+/* bus->mdio_lock should be locked when using this function */
+static int vsc8584_config_pre_init(struct phy_device *phydev)
+{
+ static const struct reg_val pre_init1[] = {
+ {0x07fa, 0x0050100f},
+ {0x1688, 0x00049f81},
+ {0x0f90, 0x00688980},
+ {0x03a4, 0x0000d8f0},
+ {0x0fc0, 0x00000400},
+ {0x0f82, 0x0012b002},
+ {0x1686, 0x00000004},
+ {0x168c, 0x00d2c46f},
+ {0x17a2, 0x00000620},
+ {0x16a0, 0x00eeffdd},
+ {0x16a6, 0x00071448},
+ {0x16a4, 0x0013132f},
+ {0x16a8, 0x00000000},
+ {0x0ffc, 0x00c0a028},
+ {0x0fe8, 0x0091b06c},
+ {0x0fea, 0x00041600},
+ {0x0f80, 0x00fffaff},
+ {0x0fec, 0x00901809},
+ {0x0ffe, 0x00b01007},
+ {0x16b0, 0x00eeff00},
+ {0x16b2, 0x00007000},
+ {0x16b4, 0x00000814},
+ };
+ static const struct reg_val pre_init2[] = {
+ {0x0486, 0x0008a518},
+ {0x0488, 0x006dc696},
+ {0x048a, 0x00000912},
+ };
+ const struct firmware *fw;
+ struct device *dev = &phydev->mdio.dev;
+ unsigned int i;
+ u16 crc, reg;
+ int ret;
+
+ ret = vsc8584_pll5g_reset(phydev);
+ if (ret < 0) {
+ dev_err(dev, "failed LCPLL reset, ret: %d\n", ret);
+ return ret;
+ }
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ /* all writes below are broadcasted to all PHYs in the same package */
+ reg = phy_base_read(phydev, MSCC_PHY_EXT_CNTL_STATUS);
+ reg |= SMI_BROADCAST_WR_EN;
+ phy_base_write(phydev, MSCC_PHY_EXT_CNTL_STATUS, reg);
+
+ phy_base_write(phydev, MII_VSC85XX_INT_MASK, 0);
+
+ reg = phy_base_read(phydev, MSCC_PHY_BYPASS_CONTROL);
+ reg |= PARALLEL_DET_IGNORE_ADVERTISED;
+ phy_base_write(phydev, MSCC_PHY_BYPASS_CONTROL, reg);
+
+ /* The below register writes are tweaking analog and electrical
+ * configuration that were determined through characterization by PHY
+ * engineers. These don't mean anything more than "these are the best
+ * values".
+ */
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_3);
+
+ phy_base_write(phydev, MSCC_PHY_SERDES_TX_CRC_ERR_CNT, 0x2000);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TEST);
+
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_5, 0x1f20);
+
+ reg = phy_base_read(phydev, MSCC_PHY_TEST_PAGE_8);
+ reg |= TR_CLK_DISABLE;
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_8, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TR);
+
+ phy_base_write(phydev, MSCC_PHY_TR_CNTL, TR_WRITE | TR_ADDR(0x2fa4));
+
+ reg = phy_base_read(phydev, MSCC_PHY_TR_MSB);
+ reg &= ~0x007f;
+ reg |= 0x0019;
+ phy_base_write(phydev, MSCC_PHY_TR_MSB, reg);
+
+ phy_base_write(phydev, MSCC_PHY_TR_CNTL, TR_WRITE | TR_ADDR(0x0fa4));
+
+ for (i = 0; i < ARRAY_SIZE(pre_init1); i++)
+ vsc8584_csr_write(phydev, pre_init1[i].reg, pre_init1[i].val);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_2);
+
+ phy_base_write(phydev, MSCC_PHY_CU_PMD_TX_CNTL, 0x028e);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TR);
+
+ for (i = 0; i < ARRAY_SIZE(pre_init2); i++)
+ vsc8584_csr_write(phydev, pre_init2[i].reg, pre_init2[i].val);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TEST);
+
+ reg = phy_base_read(phydev, MSCC_PHY_TEST_PAGE_8);
+ reg &= ~TR_CLK_DISABLE;
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_8, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ /* end of write broadcasting */
+ reg = phy_base_read(phydev, MSCC_PHY_EXT_CNTL_STATUS);
+ reg &= ~SMI_BROADCAST_WR_EN;
+ phy_base_write(phydev, MSCC_PHY_EXT_CNTL_STATUS, reg);
+
+ ret = request_firmware(&fw, MSCC_VSC8584_REVB_INT8051_FW, dev);
+ if (ret) {
+ dev_err(dev, "failed to load firmware %s, ret: %d\n",
+ MSCC_VSC8584_REVB_INT8051_FW, ret);
+ return ret;
+ }
+
+ /* Add one byte to size for the one added by the patch_fw function */
+ ret = vsc8584_get_fw_crc(phydev,
+ MSCC_VSC8584_REVB_INT8051_FW_START_ADDR,
+ fw->size + 1, &crc);
+ if (ret)
+ goto out;
+
+ if (crc != MSCC_VSC8584_REVB_INT8051_FW_CRC) {
+ dev_dbg(dev, "FW CRC is not the expected one, patching FW\n");
+ if (vsc8584_patch_fw(phydev, fw))
+ dev_warn(dev,
+ "failed to patch FW, expect non-optimal device\n");
+ }
+
+ vsc8584_micro_deassert_reset(phydev, false);
+
+ /* Add one byte to size for the one added by the patch_fw function */
+ ret = vsc8584_get_fw_crc(phydev,
+ MSCC_VSC8584_REVB_INT8051_FW_START_ADDR,
+ fw->size + 1, &crc);
+ if (ret)
+ goto out;
+
+ if (crc != MSCC_VSC8584_REVB_INT8051_FW_CRC)
+ dev_warn(dev,
+ "FW CRC after patching is not the expected one, expect non-optimal device\n");
+
+ ret = vsc8584_micro_assert_reset(phydev);
+ if (ret)
+ goto out;
+
+ /* Write patch vector 0, to skip IB cal polling */
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_GPIO);
+ reg = MSCC_ROM_TRAP_SERDES_6G_CFG; /* ROM address to trap, for patch vector 0 */
+ ret = phy_base_write(phydev, MSCC_TRAP_ROM_ADDR(1), reg);
+ if (ret)
+ goto out;
+
+ reg = MSCC_RAM_TRAP_SERDES_6G_CFG; /* RAM address to jump to, when patch vector 0 enabled */
+ ret = phy_base_write(phydev, MSCC_PATCH_RAM_ADDR(1), reg);
+ if (ret)
+ goto out;
+
+ reg = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ reg |= PATCH_VEC_ZERO_EN; /* bit 8, enable patch vector 0 */
+ ret = phy_base_write(phydev, MSCC_INT_MEM_CNTL, reg);
+ if (ret)
+ goto out;
+
+ vsc8584_micro_deassert_reset(phydev, true);
+
+out:
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ release_firmware(fw);
+
+ return ret;
+}
+
+static void vsc8584_get_base_addr(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ u16 val, addr;
+
+ phy_lock_mdio_bus(phydev);
+ __phy_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED);
+
+ addr = __phy_read(phydev, MSCC_PHY_EXT_PHY_CNTL_4);
+ addr >>= PHY_CNTL_4_ADDR_POS;
+
+ val = __phy_read(phydev, MSCC_PHY_ACTIPHY_CNTL);
+
+ __phy_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+ phy_unlock_mdio_bus(phydev);
+
+ /* In the package, there are two pairs of PHYs (PHY0 + PHY2 and
+ * PHY1 + PHY3). The first PHY of each pair (PHY0 and PHY1) is
+ * the base PHY for timestamping operations.
+ */
+ vsc8531->ts_base_addr = phydev->mdio.addr;
+ vsc8531->ts_base_phy = addr;
+
+ if (val & PHY_ADDR_REVERSED) {
+ vsc8531->base_addr = phydev->mdio.addr + addr;
+ if (addr > 1) {
+ vsc8531->ts_base_addr += 2;
+ vsc8531->ts_base_phy += 2;
+ }
+ } else {
+ vsc8531->base_addr = phydev->mdio.addr - addr;
+ if (addr > 1) {
+ vsc8531->ts_base_addr -= 2;
+ vsc8531->ts_base_phy -= 2;
+ }
+ }
+
+ vsc8531->addr = addr;
+}
+
+static void vsc85xx_coma_mode_release(struct phy_device *phydev)
+{
+ /* The coma mode (pin or reg) provides an optional feature that
+ * may be used to control when the PHYs become active.
+ * Alternatively the COMA_MODE pin may be connected low
+ * so that the PHYs are fully active once out of reset.
+ */
+
+ /* Enable output (mode=0) and write zero to it */
+ vsc85xx_phy_write_page(phydev, MSCC_PHY_PAGE_EXTENDED_GPIO);
+ __phy_modify(phydev, MSCC_PHY_GPIO_CONTROL_2,
+ MSCC_PHY_COMA_MODE | MSCC_PHY_COMA_OUTPUT, 0);
+ vsc85xx_phy_write_page(phydev, MSCC_PHY_PAGE_STANDARD);
+}
+
+static int vsc8584_config_host_serdes(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ int ret;
+ u16 val;
+
+ ret = phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+ if (ret)
+ return ret;
+
+ val = phy_base_read(phydev, MSCC_PHY_MAC_CFG_FASTLINK);
+ val &= ~MAC_CFG_MASK;
+ if (phydev->interface == PHY_INTERFACE_MODE_QSGMII) {
+ val |= MAC_CFG_QSGMII;
+ } else if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ val |= MAC_CFG_SGMII;
+ } else {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = phy_base_write(phydev, MSCC_PHY_MAC_CFG_FASTLINK, val);
+ if (ret)
+ return ret;
+
+ ret = phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_STANDARD);
+ if (ret)
+ return ret;
+
+ val = PROC_CMD_MCB_ACCESS_MAC_CONF | PROC_CMD_RST_CONF_PORT |
+ PROC_CMD_READ_MOD_WRITE_PORT;
+ if (phydev->interface == PHY_INTERFACE_MODE_QSGMII)
+ val |= PROC_CMD_QSGMII_MAC;
+ else
+ val |= PROC_CMD_SGMII_MAC;
+
+ ret = vsc8584_cmd(phydev, val);
+ if (ret)
+ return ret;
+
+ usleep_range(10000, 20000);
+
+ /* Disable SerDes for 100Base-FX */
+ ret = vsc8584_cmd(phydev, PROC_CMD_FIBER_MEDIA_CONF |
+ PROC_CMD_FIBER_PORT(vsc8531->addr) |
+ PROC_CMD_FIBER_DISABLE |
+ PROC_CMD_READ_MOD_WRITE_PORT |
+ PROC_CMD_RST_CONF_PORT | PROC_CMD_FIBER_100BASE_FX);
+ if (ret)
+ return ret;
+
+ /* Disable SerDes for 1000Base-X */
+ ret = vsc8584_cmd(phydev, PROC_CMD_FIBER_MEDIA_CONF |
+ PROC_CMD_FIBER_PORT(vsc8531->addr) |
+ PROC_CMD_FIBER_DISABLE |
+ PROC_CMD_READ_MOD_WRITE_PORT |
+ PROC_CMD_RST_CONF_PORT | PROC_CMD_FIBER_1000BASE_X);
+ if (ret)
+ return ret;
+
+ return vsc85xx_sd6g_config_v2(phydev);
+}
+
+static int vsc8574_config_host_serdes(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ int ret;
+ u16 val;
+
+ ret = phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+ if (ret)
+ return ret;
+
+ val = phy_base_read(phydev, MSCC_PHY_MAC_CFG_FASTLINK);
+ val &= ~MAC_CFG_MASK;
+ if (phydev->interface == PHY_INTERFACE_MODE_QSGMII) {
+ val |= MAC_CFG_QSGMII;
+ } else if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ val |= MAC_CFG_SGMII;
+ } else if (phy_interface_is_rgmii(phydev)) {
+ val |= MAC_CFG_RGMII;
+ } else {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = phy_base_write(phydev, MSCC_PHY_MAC_CFG_FASTLINK, val);
+ if (ret)
+ return ret;
+
+ ret = phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_STANDARD);
+ if (ret)
+ return ret;
+
+ if (!phy_interface_is_rgmii(phydev)) {
+ val = PROC_CMD_MCB_ACCESS_MAC_CONF | PROC_CMD_RST_CONF_PORT |
+ PROC_CMD_READ_MOD_WRITE_PORT;
+ if (phydev->interface == PHY_INTERFACE_MODE_QSGMII)
+ val |= PROC_CMD_QSGMII_MAC;
+ else
+ val |= PROC_CMD_SGMII_MAC;
+
+ ret = vsc8584_cmd(phydev, val);
+ if (ret)
+ return ret;
+
+ usleep_range(10000, 20000);
+ }
+
+ /* Disable SerDes for 100Base-FX */
+ ret = vsc8584_cmd(phydev, PROC_CMD_FIBER_MEDIA_CONF |
+ PROC_CMD_FIBER_PORT(vsc8531->addr) |
+ PROC_CMD_FIBER_DISABLE |
+ PROC_CMD_READ_MOD_WRITE_PORT |
+ PROC_CMD_RST_CONF_PORT | PROC_CMD_FIBER_100BASE_FX);
+ if (ret)
+ return ret;
+
+ /* Disable SerDes for 1000Base-X */
+ return vsc8584_cmd(phydev, PROC_CMD_FIBER_MEDIA_CONF |
+ PROC_CMD_FIBER_PORT(vsc8531->addr) |
+ PROC_CMD_FIBER_DISABLE |
+ PROC_CMD_READ_MOD_WRITE_PORT |
+ PROC_CMD_RST_CONF_PORT | PROC_CMD_FIBER_1000BASE_X);
+}
+
+static int vsc8584_config_init(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ int ret, i;
+ u16 val;
+
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ phy_lock_mdio_bus(phydev);
+
+ /* Some parts of the init sequence are identical for every PHY in the
+ * package. Some parts are modifying the GPIO register bank which is a
+ * set of registers that are affecting all PHYs, a few resetting the
+ * microprocessor common to all PHYs. The CRC check responsible of the
+ * checking the firmware within the 8051 microprocessor can only be
+ * accessed via the PHY whose internal address in the package is 0.
+ * All PHYs' interrupts mask register has to be zeroed before enabling
+ * any PHY's interrupt in this register.
+ * For all these reasons, we need to do the init sequence once and only
+ * once whatever is the first PHY in the package that is initialized and
+ * do the correct init sequence for all PHYs that are package-critical
+ * in this pre-init function.
+ */
+ if (phy_package_init_once(phydev)) {
+ /* The following switch statement assumes that the lowest
+ * nibble of the phy_id_mask is always 0. This works because
+ * the lowest nibble of the PHY_ID's below are also 0.
+ */
+ WARN_ON(phydev->drv->phy_id_mask & 0xf);
+
+ switch (phydev->phy_id & phydev->drv->phy_id_mask) {
+ case PHY_ID_VSC8504:
+ case PHY_ID_VSC8552:
+ case PHY_ID_VSC8572:
+ case PHY_ID_VSC8574:
+ ret = vsc8574_config_pre_init(phydev);
+ if (ret)
+ goto err;
+ ret = vsc8574_config_host_serdes(phydev);
+ if (ret)
+ goto err;
+ break;
+ case PHY_ID_VSC856X:
+ case PHY_ID_VSC8575:
+ case PHY_ID_VSC8582:
+ case PHY_ID_VSC8584:
+ ret = vsc8584_config_pre_init(phydev);
+ if (ret)
+ goto err;
+ ret = vsc8584_config_host_serdes(phydev);
+ if (ret)
+ goto err;
+ vsc85xx_coma_mode_release(phydev);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ goto err;
+ }
+
+ phy_unlock_mdio_bus(phydev);
+
+ ret = vsc8584_macsec_init(phydev);
+ if (ret)
+ return ret;
+
+ ret = vsc8584_ptp_init(phydev);
+ if (ret)
+ return ret;
+
+ val = phy_read(phydev, MSCC_PHY_EXT_PHY_CNTL_1);
+ val &= ~(MEDIA_OP_MODE_MASK | VSC8584_MAC_IF_SELECTION_MASK);
+ val |= (MEDIA_OP_MODE_COPPER << MEDIA_OP_MODE_POS) |
+ (VSC8584_MAC_IF_SELECTION_SGMII << VSC8584_MAC_IF_SELECTION_POS);
+ ret = phy_write(phydev, MSCC_PHY_EXT_PHY_CNTL_1, val);
+ if (ret)
+ return ret;
+
+ ret = vsc85xx_update_rgmii_cntl(phydev, VSC8572_RGMII_CNTL,
+ VSC8572_RGMII_RX_DELAY_MASK,
+ VSC8572_RGMII_TX_DELAY_MASK);
+ if (ret)
+ return ret;
+
+ ret = genphy_soft_reset(phydev);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < vsc8531->nleds; i++) {
+ ret = vsc85xx_led_cntl_set(phydev, i, vsc8531->leds_mode[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+
+err:
+ phy_unlock_mdio_bus(phydev);
+ return ret;
+}
+
+static irqreturn_t vsc8584_handle_interrupt(struct phy_device *phydev)
+{
+ irqreturn_t ret;
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_VSC85XX_INT_STATUS);
+ if (irq_status < 0)
+ return IRQ_NONE;
+
+ /* Timestamping IRQ does not set a bit in the global INT_STATUS, so
+ * irq_status would be 0.
+ */
+ ret = vsc8584_handle_ts_interrupt(phydev);
+ if (!(irq_status & MII_VSC85XX_INT_MASK_MASK))
+ return ret;
+
+ if (irq_status & MII_VSC85XX_INT_MASK_EXT)
+ vsc8584_handle_macsec_interrupt(phydev);
+
+ if (irq_status & MII_VSC85XX_INT_MASK_LINK_CHG)
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int vsc85xx_config_init(struct phy_device *phydev)
+{
+ int rc, i, phy_id;
+ 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;
+
+ phy_id = phydev->drv->phy_id & phydev->drv->phy_id_mask;
+ if (PHY_ID_VSC8531 == phy_id || PHY_ID_VSC8541 == phy_id ||
+ PHY_ID_VSC8530 == phy_id || PHY_ID_VSC8540 == phy_id) {
+ rc = vsc8531_pre_init_seq_set(phydev);
+ if (rc)
+ return rc;
+ }
+
+ rc = vsc85xx_eee_init_seq_set(phydev);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < vsc8531->nleds; i++) {
+ rc = vsc85xx_led_cntl_set(phydev, i, vsc8531->leds_mode[i]);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int __phy_write_mcb_s6g(struct phy_device *phydev, u32 reg, u8 mcb,
+ u32 op)
+{
+ unsigned long deadline;
+ u32 val;
+ int ret;
+
+ ret = vsc85xx_csr_write(phydev, PHY_MCB_TARGET, reg,
+ op | (1 << mcb));
+ if (ret)
+ return -EINVAL;
+
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ usleep_range(500, 1000);
+ val = vsc85xx_csr_read(phydev, PHY_MCB_TARGET, reg);
+
+ if (val == 0xffffffff)
+ return -EIO;
+
+ } while (time_before(jiffies, deadline) && (val & op));
+
+ if (val & op)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+/* Trigger a read to the specified MCB */
+int phy_update_mcb_s6g(struct phy_device *phydev, u32 reg, u8 mcb)
+{
+ return __phy_write_mcb_s6g(phydev, reg, mcb, PHY_MCB_S6G_READ);
+}
+
+/* Trigger a write to the specified MCB */
+int phy_commit_mcb_s6g(struct phy_device *phydev, u32 reg, u8 mcb)
+{
+ return __phy_write_mcb_s6g(phydev, reg, mcb, PHY_MCB_S6G_WRITE);
+}
+
+static int vsc8514_config_host_serdes(struct phy_device *phydev)
+{
+ int ret;
+ u16 val;
+
+ ret = phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+ if (ret)
+ return ret;
+
+ val = phy_base_read(phydev, MSCC_PHY_MAC_CFG_FASTLINK);
+ val &= ~MAC_CFG_MASK;
+ val |= MAC_CFG_QSGMII;
+ ret = phy_base_write(phydev, MSCC_PHY_MAC_CFG_FASTLINK, val);
+ if (ret)
+ return ret;
+
+ ret = phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_STANDARD);
+ if (ret)
+ return ret;
+
+ ret = vsc8584_cmd(phydev, PROC_CMD_NOP);
+ if (ret)
+ return ret;
+
+ ret = vsc8584_cmd(phydev,
+ PROC_CMD_MCB_ACCESS_MAC_CONF |
+ PROC_CMD_RST_CONF_PORT |
+ PROC_CMD_READ_MOD_WRITE_PORT | PROC_CMD_QSGMII_MAC);
+ if (ret) {
+ dev_err(&phydev->mdio.dev, "%s: QSGMII error: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Apply 6G SerDes FOJI Algorithm
+ * Initial condition requirement:
+ * 1. hold 8051 in reset
+ * 2. disable patch vector 0, in order to allow IB cal poll during FoJi
+ * 3. deassert 8051 reset after change patch vector status
+ * 4. proceed with FoJi (vsc85xx_sd6g_config_v2)
+ */
+ vsc8584_micro_assert_reset(phydev);
+ val = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ /* clear bit 8, to disable patch vector 0 */
+ val &= ~PATCH_VEC_ZERO_EN;
+ ret = phy_base_write(phydev, MSCC_INT_MEM_CNTL, val);
+ /* Enable 8051 clock, don't set patch present, disable PRAM clock override */
+ vsc8584_micro_deassert_reset(phydev, false);
+
+ return vsc85xx_sd6g_config_v2(phydev);
+}
+
+static int vsc8514_config_pre_init(struct phy_device *phydev)
+{
+ /* These are the settings to override the silicon default
+ * values to handle hardware performance of PHY. They
+ * are set at Power-On state and remain until PHY Reset.
+ */
+ static const struct reg_val pre_init1[] = {
+ {0x0f90, 0x00688980},
+ {0x0786, 0x00000003},
+ {0x07fa, 0x0050100f},
+ {0x0f82, 0x0012b002},
+ {0x1686, 0x00000004},
+ {0x168c, 0x00d2c46f},
+ {0x17a2, 0x00000620},
+ {0x16a0, 0x00eeffdd},
+ {0x16a6, 0x00071448},
+ {0x16a4, 0x0013132f},
+ {0x16a8, 0x00000000},
+ {0x0ffc, 0x00c0a028},
+ {0x0fe8, 0x0091b06c},
+ {0x0fea, 0x00041600},
+ {0x0f80, 0x00fffaff},
+ {0x0fec, 0x00901809},
+ {0x0ffe, 0x00b01007},
+ {0x16b0, 0x00eeff00},
+ {0x16b2, 0x00007000},
+ {0x16b4, 0x00000814},
+ };
+ struct device *dev = &phydev->mdio.dev;
+ unsigned int i;
+ u16 reg;
+ int ret;
+
+ ret = vsc8584_pll5g_reset(phydev);
+ if (ret < 0) {
+ dev_err(dev, "failed LCPLL reset, ret: %d\n", ret);
+ return ret;
+ }
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ /* all writes below are broadcasted to all PHYs in the same package */
+ reg = phy_base_read(phydev, MSCC_PHY_EXT_CNTL_STATUS);
+ reg |= SMI_BROADCAST_WR_EN;
+ phy_base_write(phydev, MSCC_PHY_EXT_CNTL_STATUS, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TEST);
+
+ reg = phy_base_read(phydev, MSCC_PHY_TEST_PAGE_8);
+ reg |= BIT(15);
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_8, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TR);
+
+ for (i = 0; i < ARRAY_SIZE(pre_init1); i++)
+ vsc8584_csr_write(phydev, pre_init1[i].reg, pre_init1[i].val);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_TEST);
+
+ reg = phy_base_read(phydev, MSCC_PHY_TEST_PAGE_8);
+ reg &= ~BIT(15);
+ phy_base_write(phydev, MSCC_PHY_TEST_PAGE_8, reg);
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ reg = phy_base_read(phydev, MSCC_PHY_EXT_CNTL_STATUS);
+ reg &= ~SMI_BROADCAST_WR_EN;
+ phy_base_write(phydev, MSCC_PHY_EXT_CNTL_STATUS, reg);
+
+ /* Add pre-patching commands to:
+ * 1. enable 8051 clock, operate 8051 clock at 125 MHz
+ * instead of HW default 62.5MHz
+ * 2. write patch vector 0, to skip IB cal polling executed
+ * as part of the 0x80E0 ROM command
+ */
+ vsc8584_micro_deassert_reset(phydev, false);
+
+ vsc8584_micro_assert_reset(phydev);
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_EXTENDED_GPIO);
+ /* ROM address to trap, for patch vector 0 */
+ reg = MSCC_ROM_TRAP_SERDES_6G_CFG;
+ ret = phy_base_write(phydev, MSCC_TRAP_ROM_ADDR(1), reg);
+ if (ret)
+ goto err;
+ /* RAM address to jump to, when patch vector 0 enabled */
+ reg = MSCC_RAM_TRAP_SERDES_6G_CFG;
+ ret = phy_base_write(phydev, MSCC_PATCH_RAM_ADDR(1), reg);
+ if (ret)
+ goto err;
+ reg = phy_base_read(phydev, MSCC_INT_MEM_CNTL);
+ reg |= PATCH_VEC_ZERO_EN; /* bit 8, enable patch vector 0 */
+ ret = phy_base_write(phydev, MSCC_INT_MEM_CNTL, reg);
+ if (ret)
+ goto err;
+
+ /* Enable 8051 clock, don't set patch present
+ * yet, disable PRAM clock override
+ */
+ vsc8584_micro_deassert_reset(phydev, false);
+ return ret;
+ err:
+ /* restore 8051 and bail w error */
+ vsc8584_micro_deassert_reset(phydev, false);
+ return ret;
+}
+
+static int vsc8514_config_init(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ int ret, i;
+
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ phy_lock_mdio_bus(phydev);
+
+ /* Some parts of the init sequence are identical for every PHY in the
+ * package. Some parts are modifying the GPIO register bank which is a
+ * set of registers that are affecting all PHYs, a few resetting the
+ * microprocessor common to all PHYs.
+ * All PHYs' interrupts mask register has to be zeroed before enabling
+ * any PHY's interrupt in this register.
+ * For all these reasons, we need to do the init sequence once and only
+ * once whatever is the first PHY in the package that is initialized and
+ * do the correct init sequence for all PHYs that are package-critical
+ * in this pre-init function.
+ */
+ if (phy_package_init_once(phydev)) {
+ ret = vsc8514_config_pre_init(phydev);
+ if (ret)
+ goto err;
+ ret = vsc8514_config_host_serdes(phydev);
+ if (ret)
+ goto err;
+ vsc85xx_coma_mode_release(phydev);
+ }
+
+ phy_unlock_mdio_bus(phydev);
+
+ ret = phy_modify(phydev, MSCC_PHY_EXT_PHY_CNTL_1, MEDIA_OP_MODE_MASK,
+ MEDIA_OP_MODE_COPPER << MEDIA_OP_MODE_POS);
+
+ if (ret)
+ return ret;
+
+ ret = genphy_soft_reset(phydev);
+
+ if (ret)
+ return ret;
+
+ for (i = 0; i < vsc8531->nleds; i++) {
+ ret = vsc85xx_led_cntl_set(phydev, i, vsc8531->leds_mode[i]);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+
+err:
+ phy_unlock_mdio_bus(phydev);
+ return ret;
+}
+
+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 = vsc85xx_ack_interrupt(phydev);
+ if (rc)
+ return rc;
+
+ vsc8584_config_macsec_intr(phydev);
+ vsc8584_config_ts_intr(phydev);
+
+ 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);
+ if (rc < 0)
+ return rc;
+
+ rc = vsc85xx_ack_interrupt(phydev);
+ }
+
+ return rc;
+}
+
+static irqreturn_t vsc85xx_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_VSC85XX_INT_STATUS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_VSC85XX_INT_MASK_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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 vsc8514_probe(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531;
+ u32 default_mode[4] = {VSC8531_LINK_1000_ACTIVITY,
+ VSC8531_LINK_100_ACTIVITY, VSC8531_LINK_ACTIVITY,
+ VSC8531_DUPLEX_COLLISION};
+
+ vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL);
+ if (!vsc8531)
+ return -ENOMEM;
+
+ phydev->priv = vsc8531;
+
+ vsc8584_get_base_addr(phydev);
+ devm_phy_package_join(&phydev->mdio.dev, phydev,
+ vsc8531->base_addr, 0);
+
+ vsc8531->nleds = 4;
+ vsc8531->supp_led_modes = VSC85XX_SUPP_LED_MODES;
+ vsc8531->hw_stats = vsc85xx_hw_stats;
+ vsc8531->nstats = ARRAY_SIZE(vsc85xx_hw_stats);
+ vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats,
+ sizeof(u64), GFP_KERNEL);
+ if (!vsc8531->stats)
+ return -ENOMEM;
+
+ return vsc85xx_dt_led_modes_get(phydev, default_mode);
+}
+
+static int vsc8574_probe(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531;
+ u32 default_mode[4] = {VSC8531_LINK_1000_ACTIVITY,
+ VSC8531_LINK_100_ACTIVITY, VSC8531_LINK_ACTIVITY,
+ VSC8531_DUPLEX_COLLISION};
+
+ vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL);
+ if (!vsc8531)
+ return -ENOMEM;
+
+ phydev->priv = vsc8531;
+
+ vsc8584_get_base_addr(phydev);
+ devm_phy_package_join(&phydev->mdio.dev, phydev,
+ vsc8531->base_addr, 0);
+
+ vsc8531->nleds = 4;
+ vsc8531->supp_led_modes = VSC8584_SUPP_LED_MODES;
+ vsc8531->hw_stats = vsc8584_hw_stats;
+ vsc8531->nstats = ARRAY_SIZE(vsc8584_hw_stats);
+ vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats,
+ sizeof(u64), GFP_KERNEL);
+ if (!vsc8531->stats)
+ return -ENOMEM;
+
+ return vsc85xx_dt_led_modes_get(phydev, default_mode);
+}
+
+static int vsc8584_probe(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531;
+ u32 default_mode[4] = {VSC8531_LINK_1000_ACTIVITY,
+ VSC8531_LINK_100_ACTIVITY, VSC8531_LINK_ACTIVITY,
+ VSC8531_DUPLEX_COLLISION};
+ int ret;
+
+ if ((phydev->phy_id & MSCC_DEV_REV_MASK) != VSC8584_REVB) {
+ dev_err(&phydev->mdio.dev, "Only VSC8584 revB is supported.\n");
+ return -ENOTSUPP;
+ }
+
+ vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL);
+ if (!vsc8531)
+ return -ENOMEM;
+
+ phydev->priv = vsc8531;
+
+ vsc8584_get_base_addr(phydev);
+ devm_phy_package_join(&phydev->mdio.dev, phydev, vsc8531->base_addr,
+ sizeof(struct vsc85xx_shared_private));
+
+ vsc8531->nleds = 4;
+ vsc8531->supp_led_modes = VSC8584_SUPP_LED_MODES;
+ vsc8531->hw_stats = vsc8584_hw_stats;
+ vsc8531->nstats = ARRAY_SIZE(vsc8584_hw_stats);
+ vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats,
+ sizeof(u64), GFP_KERNEL);
+ if (!vsc8531->stats)
+ return -ENOMEM;
+
+ if (phy_package_probe_once(phydev)) {
+ ret = vsc8584_ptp_probe_once(phydev);
+ if (ret)
+ return ret;
+ }
+
+ ret = vsc8584_ptp_probe(phydev);
+ if (ret)
+ return ret;
+
+ return vsc85xx_dt_led_modes_get(phydev, default_mode);
+}
+
+static int vsc85xx_probe(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531;
+ int rate_magic;
+ u32 default_mode[2] = {VSC8531_LINK_1000_ACTIVITY,
+ VSC8531_LINK_100_ACTIVITY};
+
+ 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;
+ vsc8531->nleds = 2;
+ vsc8531->supp_led_modes = VSC85XX_SUPP_LED_MODES;
+ vsc8531->hw_stats = vsc85xx_hw_stats;
+ vsc8531->nstats = ARRAY_SIZE(vsc85xx_hw_stats);
+ vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats,
+ sizeof(u64), GFP_KERNEL);
+ if (!vsc8531->stats)
+ return -ENOMEM;
+
+ return vsc85xx_dt_led_modes_get(phydev, default_mode);
+}
+
+/* Microsemi VSC85xx PHYs */
+static struct phy_driver vsc85xx_driver[] = {
+{
+ .phy_id = PHY_ID_VSC8501,
+ .name = "Microsemi GE VSC8501 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc85xx_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_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,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8502,
+ .name = "Microsemi GE VSC8502 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc85xx_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_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,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8504,
+ .name = "Microsemi GE VSC8504 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .aneg_done = &genphy_aneg_done,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8574_probe,
+ .set_wol = &vsc85xx_wol_set,
+ .get_wol = &vsc85xx_wol_get,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8514,
+ .name = "Microsemi GE VSC8514 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8514_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8514_probe,
+ .set_wol = &vsc85xx_wol_set,
+ .get_wol = &vsc85xx_wol_get,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8530,
+ .name = "Microsemi FE VSC8530",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc85xx_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_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,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8531,
+ .name = "Microsemi VSC8531",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc85xx_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_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,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8540,
+ .name = "Microsemi FE VSC8540 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc85xx_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_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,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8541,
+ .name = "Microsemi VSC8541 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc85xx_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_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,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8552,
+ .name = "Microsemi GE VSC8552 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8574_probe,
+ .set_wol = &vsc85xx_wol_set,
+ .get_wol = &vsc85xx_wol_get,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC856X,
+ .name = "Microsemi GE VSC856X SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8584_probe,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8572,
+ .name = "Microsemi GE VSC8572 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .aneg_done = &genphy_aneg_done,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = &vsc8584_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8574_probe,
+ .set_wol = &vsc85xx_wol_set,
+ .get_wol = &vsc85xx_wol_get,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8574,
+ .name = "Microsemi GE VSC8574 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .aneg_done = &genphy_aneg_done,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = vsc85xx_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8574_probe,
+ .set_wol = &vsc85xx_wol_set,
+ .get_wol = &vsc85xx_wol_get,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8575,
+ .name = "Microsemi GE VSC8575 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .aneg_done = &genphy_aneg_done,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = &vsc8584_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8584_probe,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8582,
+ .name = "Microsemi GE VSC8582 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .aneg_done = &genphy_aneg_done,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = &vsc8584_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8584_probe,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+},
+{
+ .phy_id = PHY_ID_VSC8584,
+ .name = "Microsemi GE VSC8584 SyncE",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_GBIT_FEATURES */
+ .soft_reset = &genphy_soft_reset,
+ .config_init = &vsc8584_config_init,
+ .config_aneg = &vsc85xx_config_aneg,
+ .aneg_done = &genphy_aneg_done,
+ .read_status = &vsc85xx_read_status,
+ .handle_interrupt = &vsc8584_handle_interrupt,
+ .config_intr = &vsc85xx_config_intr,
+ .suspend = &genphy_suspend,
+ .resume = &genphy_resume,
+ .probe = &vsc8584_probe,
+ .get_tunable = &vsc85xx_get_tunable,
+ .set_tunable = &vsc85xx_set_tunable,
+ .read_page = &vsc85xx_phy_read_page,
+ .write_page = &vsc85xx_phy_write_page,
+ .get_sset_count = &vsc85xx_get_sset_count,
+ .get_strings = &vsc85xx_get_strings,
+ .get_stats = &vsc85xx_get_stats,
+ .link_change_notify = &vsc85xx_link_change_notify,
+}
+
+};
+
+module_phy_driver(vsc85xx_driver);
+
+static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = {
+ { PHY_ID_MATCH_VENDOR(PHY_VENDOR_MSCC) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, vsc85xx_tbl);
+
+MODULE_DESCRIPTION("Microsemi VSC85xx PHY driver");
+MODULE_AUTHOR("Nagaraju Lakkaraju");
+MODULE_LICENSE("Dual MIT/GPL");
+
+MODULE_FIRMWARE(MSCC_VSC8584_REVB_INT8051_FW);
+MODULE_FIRMWARE(MSCC_VSC8574_REVB_INT8051_FW);
diff --git a/drivers/net/phy/mscc/mscc_ptp.c b/drivers/net/phy/mscc/mscc_ptp.c
new file mode 100644
index 000000000..cf728bfd8
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_ptp.c
@@ -0,0 +1,1589 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Driver for Microsemi VSC85xx PHYs - timestamping and PHC support
+ *
+ * Authors: Quentin Schulz & Antoine Tenart
+ * License: Dual MIT/GPL
+ * Copyright (c) 2020 Microsemi Corporation
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/ip.h>
+#include <linux/net_tstamp.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/udp.h>
+#include <asm/unaligned.h>
+
+#include "mscc.h"
+#include "mscc_ptp.h"
+
+/* Two PHYs share the same 1588 processor and it's to be entirely configured
+ * through the base PHY of this processor.
+ */
+/* phydev->bus->mdio_lock should be locked when using this function */
+static int phy_ts_base_write(struct phy_device *phydev, u32 regnum, u16 val)
+{
+ struct vsc8531_private *priv = phydev->priv;
+
+ WARN_ON_ONCE(!mutex_is_locked(&phydev->mdio.bus->mdio_lock));
+ return __mdiobus_write(phydev->mdio.bus, priv->ts_base_addr, regnum,
+ val);
+}
+
+/* phydev->bus->mdio_lock should be locked when using this function */
+static int phy_ts_base_read(struct phy_device *phydev, u32 regnum)
+{
+ struct vsc8531_private *priv = phydev->priv;
+
+ WARN_ON_ONCE(!mutex_is_locked(&phydev->mdio.bus->mdio_lock));
+ return __mdiobus_read(phydev->mdio.bus, priv->ts_base_addr, regnum);
+}
+
+enum ts_blk_hw {
+ INGRESS_ENGINE_0,
+ EGRESS_ENGINE_0,
+ INGRESS_ENGINE_1,
+ EGRESS_ENGINE_1,
+ INGRESS_ENGINE_2,
+ EGRESS_ENGINE_2,
+ PROCESSOR_0,
+ PROCESSOR_1,
+};
+
+enum ts_blk {
+ INGRESS,
+ EGRESS,
+ PROCESSOR,
+};
+
+static u32 vsc85xx_ts_read_csr(struct phy_device *phydev, enum ts_blk blk,
+ u16 addr)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ bool base_port = phydev->mdio.addr == priv->ts_base_addr;
+ u32 val, cnt = 0;
+ enum ts_blk_hw blk_hw;
+
+ switch (blk) {
+ case INGRESS:
+ blk_hw = base_port ? INGRESS_ENGINE_0 : INGRESS_ENGINE_1;
+ break;
+ case EGRESS:
+ blk_hw = base_port ? EGRESS_ENGINE_0 : EGRESS_ENGINE_1;
+ break;
+ case PROCESSOR:
+ default:
+ blk_hw = base_port ? PROCESSOR_0 : PROCESSOR_1;
+ break;
+ }
+
+ phy_lock_mdio_bus(phydev);
+
+ phy_ts_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_1588);
+
+ phy_ts_base_write(phydev, MSCC_PHY_TS_BIU_ADDR_CNTL, BIU_ADDR_EXE |
+ BIU_ADDR_READ | BIU_BLK_ID(blk_hw) |
+ BIU_CSR_ADDR(addr));
+
+ do {
+ val = phy_ts_base_read(phydev, MSCC_PHY_TS_BIU_ADDR_CNTL);
+ } while (!(val & BIU_ADDR_EXE) && cnt++ < BIU_ADDR_CNT_MAX);
+
+ val = phy_ts_base_read(phydev, MSCC_PHY_TS_CSR_DATA_MSB);
+ val <<= 16;
+ val |= phy_ts_base_read(phydev, MSCC_PHY_TS_CSR_DATA_LSB);
+
+ phy_ts_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ phy_unlock_mdio_bus(phydev);
+
+ return val;
+}
+
+static void vsc85xx_ts_write_csr(struct phy_device *phydev, enum ts_blk blk,
+ u16 addr, u32 val)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ bool base_port = phydev->mdio.addr == priv->ts_base_addr;
+ u32 reg, bypass, cnt = 0, lower = val & 0xffff, upper = val >> 16;
+ bool cond = (addr == MSCC_PHY_PTP_LTC_CTRL ||
+ addr == MSCC_PHY_1588_INGR_VSC85XX_INT_MASK ||
+ addr == MSCC_PHY_1588_VSC85XX_INT_MASK ||
+ addr == MSCC_PHY_1588_INGR_VSC85XX_INT_STATUS ||
+ addr == MSCC_PHY_1588_VSC85XX_INT_STATUS) &&
+ blk == PROCESSOR;
+ enum ts_blk_hw blk_hw;
+
+ switch (blk) {
+ case INGRESS:
+ blk_hw = base_port ? INGRESS_ENGINE_0 : INGRESS_ENGINE_1;
+ break;
+ case EGRESS:
+ blk_hw = base_port ? EGRESS_ENGINE_0 : EGRESS_ENGINE_1;
+ break;
+ case PROCESSOR:
+ default:
+ blk_hw = base_port ? PROCESSOR_0 : PROCESSOR_1;
+ break;
+ }
+
+ phy_lock_mdio_bus(phydev);
+
+ bypass = phy_ts_base_read(phydev, MSCC_PHY_BYPASS_CONTROL);
+
+ phy_ts_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_1588);
+
+ if (!cond || upper)
+ phy_ts_base_write(phydev, MSCC_PHY_TS_CSR_DATA_MSB, upper);
+
+ phy_ts_base_write(phydev, MSCC_PHY_TS_CSR_DATA_LSB, lower);
+
+ phy_ts_base_write(phydev, MSCC_PHY_TS_BIU_ADDR_CNTL, BIU_ADDR_EXE |
+ BIU_ADDR_WRITE | BIU_BLK_ID(blk_hw) |
+ BIU_CSR_ADDR(addr));
+
+ do {
+ reg = phy_ts_base_read(phydev, MSCC_PHY_TS_BIU_ADDR_CNTL);
+ } while (!(reg & BIU_ADDR_EXE) && cnt++ < BIU_ADDR_CNT_MAX);
+
+ phy_ts_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ if (cond && upper)
+ phy_ts_base_write(phydev, MSCC_PHY_BYPASS_CONTROL, bypass);
+
+ phy_unlock_mdio_bus(phydev);
+}
+
+/* Pick bytes from PTP header */
+#define PTP_HEADER_TRNSP_MSG 26
+#define PTP_HEADER_DOMAIN_NUM 25
+#define PTP_HEADER_BYTE_8_31(x) (31 - (x))
+#define MAC_ADDRESS_BYTE(x) ((x) + (35 - ETH_ALEN + 1))
+
+static int vsc85xx_ts_fsb_init(struct phy_device *phydev)
+{
+ u8 sig_sel[16] = {};
+ signed char i, pos = 0;
+
+ /* Seq ID is 2B long and starts at 30th byte */
+ for (i = 1; i >= 0; i--)
+ sig_sel[pos++] = PTP_HEADER_BYTE_8_31(30 + i);
+
+ /* DomainNum */
+ sig_sel[pos++] = PTP_HEADER_DOMAIN_NUM;
+
+ /* MsgType */
+ sig_sel[pos++] = PTP_HEADER_TRNSP_MSG;
+
+ /* MAC address is 6B long */
+ for (i = ETH_ALEN - 1; i >= 0; i--)
+ sig_sel[pos++] = MAC_ADDRESS_BYTE(i);
+
+ /* Fill the last bytes of the signature to reach a 16B signature */
+ for (; pos < ARRAY_SIZE(sig_sel); pos++)
+ sig_sel[pos] = PTP_HEADER_TRNSP_MSG;
+
+ for (i = 0; i <= 2; i++) {
+ u32 val = 0;
+
+ for (pos = i * 5 + 4; pos >= i * 5; pos--)
+ val = (val << 6) | sig_sel[pos];
+
+ vsc85xx_ts_write_csr(phydev, EGRESS, MSCC_PHY_ANA_FSB_REG(i),
+ val);
+ }
+
+ vsc85xx_ts_write_csr(phydev, EGRESS, MSCC_PHY_ANA_FSB_REG(3),
+ sig_sel[15]);
+
+ return 0;
+}
+
+static const u32 vsc85xx_egr_latency[] = {
+ /* Copper Egress */
+ 1272, /* 1000Mbps */
+ 12516, /* 100Mbps */
+ 125444, /* 10Mbps */
+ /* Fiber Egress */
+ 1277, /* 1000Mbps */
+ 12537, /* 100Mbps */
+};
+
+static const u32 vsc85xx_egr_latency_macsec[] = {
+ /* Copper Egress ON */
+ 3496, /* 1000Mbps */
+ 34760, /* 100Mbps */
+ 347844, /* 10Mbps */
+ /* Fiber Egress ON */
+ 3502, /* 1000Mbps */
+ 34780, /* 100Mbps */
+};
+
+static const u32 vsc85xx_ingr_latency[] = {
+ /* Copper Ingress */
+ 208, /* 1000Mbps */
+ 304, /* 100Mbps */
+ 2023, /* 10Mbps */
+ /* Fiber Ingress */
+ 98, /* 1000Mbps */
+ 197, /* 100Mbps */
+};
+
+static const u32 vsc85xx_ingr_latency_macsec[] = {
+ /* Copper Ingress */
+ 2408, /* 1000Mbps */
+ 22300, /* 100Mbps */
+ 222009, /* 10Mbps */
+ /* Fiber Ingress */
+ 2299, /* 1000Mbps */
+ 22192, /* 100Mbps */
+};
+
+static void vsc85xx_ts_set_latencies(struct phy_device *phydev)
+{
+ u32 val, ingr_latency, egr_latency;
+ u8 idx;
+
+ /* No need to set latencies of packets if the PHY is not connected */
+ if (!phydev->link)
+ return;
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_STALL_LATENCY,
+ STALL_EGR_LATENCY(phydev->speed));
+
+ switch (phydev->speed) {
+ case SPEED_100:
+ idx = 1;
+ break;
+ case SPEED_1000:
+ idx = 0;
+ break;
+ default:
+ idx = 2;
+ break;
+ }
+
+ ingr_latency = IS_ENABLED(CONFIG_MACSEC) ?
+ vsc85xx_ingr_latency_macsec[idx] : vsc85xx_ingr_latency[idx];
+ egr_latency = IS_ENABLED(CONFIG_MACSEC) ?
+ vsc85xx_egr_latency_macsec[idx] : vsc85xx_egr_latency[idx];
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_LOCAL_LATENCY,
+ PTP_INGR_LOCAL_LATENCY(ingr_latency));
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_TSP_CTRL);
+ val |= PHY_PTP_INGR_TSP_CTRL_LOAD_DELAYS;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_TSP_CTRL,
+ val);
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_LOCAL_LATENCY,
+ PTP_EGR_LOCAL_LATENCY(egr_latency));
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TSP_CTRL);
+ val |= PHY_PTP_EGR_TSP_CTRL_LOAD_DELAYS;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TSP_CTRL, val);
+}
+
+static int vsc85xx_ts_disable_flows(struct phy_device *phydev, enum ts_blk blk)
+{
+ u8 i;
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_NXT_COMP, 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_UDP_CHKSUM,
+ IP1_NXT_PROT_UDP_CHKSUM_WIDTH(2));
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP2_NXT_PROT_NXT_COMP, 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP2_NXT_PROT_UDP_CHKSUM,
+ IP2_NXT_PROT_UDP_CHKSUM_WIDTH(2));
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_PHY_ANA_MPLS_COMP_NXT_COMP, 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_PHY_ANA_ETH1_NTX_PROT, 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_PHY_ANA_ETH2_NTX_PROT, 0);
+
+ for (i = 0; i < COMP_MAX_FLOWS; i++) {
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_ENA(i),
+ IP1_FLOW_VALID_CH0 | IP1_FLOW_VALID_CH1);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP2_FLOW_ENA(i),
+ IP2_FLOW_VALID_CH0 | IP2_FLOW_VALID_CH1);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_ENA(i),
+ ETH1_FLOW_VALID_CH0 | ETH1_FLOW_VALID_CH1);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH2_FLOW_ENA(i),
+ ETH2_FLOW_VALID_CH0 | ETH2_FLOW_VALID_CH1);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_MPLS_FLOW_CTRL(i),
+ MPLS_FLOW_VALID_CH0 | MPLS_FLOW_VALID_CH1);
+
+ if (i >= PTP_COMP_MAX_FLOWS)
+ continue;
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_PTP_FLOW_ENA(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_DOMAIN_RANGE(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_MASK_UPPER(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_MASK_LOWER(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_MATCH_UPPER(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_MATCH_LOWER(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_PTP_ACTION(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_PTP_ACTION2(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_PTP_0_FIELD(i), 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_OAM_PTP_FLOW_ENA(i),
+ 0);
+ }
+
+ return 0;
+}
+
+static int vsc85xx_ts_eth_cmp1_sig(struct phy_device *phydev)
+{
+ u32 val;
+
+ val = vsc85xx_ts_read_csr(phydev, EGRESS, MSCC_PHY_ANA_ETH1_NTX_PROT);
+ val &= ~ANA_ETH1_NTX_PROT_SIG_OFF_MASK;
+ val |= ANA_ETH1_NTX_PROT_SIG_OFF(0);
+ vsc85xx_ts_write_csr(phydev, EGRESS, MSCC_PHY_ANA_ETH1_NTX_PROT, val);
+
+ val = vsc85xx_ts_read_csr(phydev, EGRESS, MSCC_PHY_ANA_FSB_CFG);
+ val &= ~ANA_FSB_ADDR_FROM_BLOCK_SEL_MASK;
+ val |= ANA_FSB_ADDR_FROM_ETH1;
+ vsc85xx_ts_write_csr(phydev, EGRESS, MSCC_PHY_ANA_FSB_CFG, val);
+
+ return 0;
+}
+
+static struct vsc85xx_ptphdr *get_ptp_header_l4(struct sk_buff *skb,
+ struct iphdr *iphdr,
+ struct udphdr *udphdr)
+{
+ if (iphdr->version != 4 || iphdr->protocol != IPPROTO_UDP)
+ return NULL;
+
+ return (struct vsc85xx_ptphdr *)(((unsigned char *)udphdr) + UDP_HLEN);
+}
+
+static struct vsc85xx_ptphdr *get_ptp_header_tx(struct sk_buff *skb)
+{
+ struct ethhdr *ethhdr = eth_hdr(skb);
+ struct udphdr *udphdr;
+ struct iphdr *iphdr;
+
+ if (ethhdr->h_proto == htons(ETH_P_1588))
+ return (struct vsc85xx_ptphdr *)(((unsigned char *)ethhdr) +
+ skb_mac_header_len(skb));
+
+ if (ethhdr->h_proto != htons(ETH_P_IP))
+ return NULL;
+
+ iphdr = ip_hdr(skb);
+ udphdr = udp_hdr(skb);
+
+ return get_ptp_header_l4(skb, iphdr, udphdr);
+}
+
+static struct vsc85xx_ptphdr *get_ptp_header_rx(struct sk_buff *skb,
+ enum hwtstamp_rx_filters rx_filter)
+{
+ struct udphdr *udphdr;
+ struct iphdr *iphdr;
+
+ if (rx_filter == HWTSTAMP_FILTER_PTP_V2_L2_EVENT)
+ return (struct vsc85xx_ptphdr *)skb->data;
+
+ iphdr = (struct iphdr *)skb->data;
+ udphdr = (struct udphdr *)(skb->data + iphdr->ihl * 4);
+
+ return get_ptp_header_l4(skb, iphdr, udphdr);
+}
+
+static int get_sig(struct sk_buff *skb, u8 *sig)
+{
+ struct vsc85xx_ptphdr *ptphdr = get_ptp_header_tx(skb);
+ struct ethhdr *ethhdr = eth_hdr(skb);
+ unsigned int i;
+
+ if (!ptphdr)
+ return -EOPNOTSUPP;
+
+ sig[0] = (__force u16)ptphdr->seq_id >> 8;
+ sig[1] = (__force u16)ptphdr->seq_id & GENMASK(7, 0);
+ sig[2] = ptphdr->domain;
+ sig[3] = ptphdr->tsmt & GENMASK(3, 0);
+
+ memcpy(&sig[4], ethhdr->h_dest, ETH_ALEN);
+
+ /* Fill the last bytes of the signature to reach a 16B signature */
+ for (i = 10; i < 16; i++)
+ sig[i] = ptphdr->tsmt & GENMASK(3, 0);
+
+ return 0;
+}
+
+static void vsc85xx_dequeue_skb(struct vsc85xx_ptp *ptp)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct vsc85xx_ts_fifo fifo;
+ struct sk_buff *skb;
+ u8 skb_sig[16], *p;
+ int i, len;
+ u32 reg;
+
+ memset(&fifo, 0, sizeof(fifo));
+ p = (u8 *)&fifo;
+
+ reg = vsc85xx_ts_read_csr(ptp->phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_TS_FIFO(0));
+ if (reg & PTP_EGR_TS_FIFO_EMPTY)
+ return;
+
+ *p++ = reg & 0xff;
+ *p++ = (reg >> 8) & 0xff;
+
+ /* Read the current FIFO item. Reading FIFO6 pops the next one. */
+ for (i = 1; i < 7; i++) {
+ reg = vsc85xx_ts_read_csr(ptp->phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_TS_FIFO(i));
+ *p++ = reg & 0xff;
+ *p++ = (reg >> 8) & 0xff;
+ *p++ = (reg >> 16) & 0xff;
+ *p++ = (reg >> 24) & 0xff;
+ }
+
+ len = skb_queue_len(&ptp->tx_queue);
+ if (len < 1)
+ return;
+
+ while (len--) {
+ skb = __skb_dequeue(&ptp->tx_queue);
+ if (!skb)
+ return;
+
+ /* Can't get the signature of the packet, won't ever
+ * be able to have one so let's dequeue the packet.
+ */
+ if (get_sig(skb, skb_sig) < 0) {
+ kfree_skb(skb);
+ continue;
+ }
+
+ /* Check if we found the signature we were looking for. */
+ if (!memcmp(skb_sig, fifo.sig, sizeof(fifo.sig))) {
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ktime_set(fifo.secs, fifo.ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+
+ return;
+ }
+
+ /* Valid signature but does not match the one of the
+ * packet in the FIFO right now, reschedule it for later
+ * packets.
+ */
+ __skb_queue_tail(&ptp->tx_queue, skb);
+ }
+}
+
+static void vsc85xx_get_tx_ts(struct vsc85xx_ptp *ptp)
+{
+ u32 reg;
+
+ do {
+ vsc85xx_dequeue_skb(ptp);
+
+ /* If other timestamps are available in the FIFO, process them. */
+ reg = vsc85xx_ts_read_csr(ptp->phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_TS_FIFO_CTRL);
+ } while (PTP_EGR_FIFO_LEVEL_LAST_READ(reg) > 1);
+}
+
+static int vsc85xx_ptp_cmp_init(struct phy_device *phydev, enum ts_blk blk)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ bool base = phydev->mdio.addr == vsc8531->ts_base_addr;
+ static const u8 msgs[] = {
+ PTP_MSGTYPE_SYNC,
+ PTP_MSGTYPE_DELAY_REQ
+ };
+ u32 val;
+ u8 i;
+
+ for (i = 0; i < ARRAY_SIZE(msgs); i++) {
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_PTP_FLOW_ENA(i),
+ base ? PTP_FLOW_VALID_CH0 :
+ PTP_FLOW_VALID_CH1);
+
+ val = vsc85xx_ts_read_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_DOMAIN_RANGE(i));
+ val &= ~PTP_FLOW_DOMAIN_RANGE_ENA;
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_DOMAIN_RANGE(i), val);
+
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_MATCH_UPPER(i),
+ msgs[i] << 24);
+
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_MASK_UPPER(i),
+ PTP_FLOW_MSG_TYPE_MASK);
+ }
+
+ return 0;
+}
+
+static int vsc85xx_eth_cmp1_init(struct phy_device *phydev, enum ts_blk blk)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ bool base = phydev->mdio.addr == vsc8531->ts_base_addr;
+ u32 val;
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_PHY_ANA_ETH1_NXT_PROT_TAG, 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_PHY_ANA_ETH1_NTX_PROT_VLAN_TPID,
+ ANA_ETH1_NTX_PROT_VLAN_TPID(ETH_P_8021AD));
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_ENA(0),
+ base ? ETH1_FLOW_VALID_CH0 : ETH1_FLOW_VALID_CH1);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_MATCH_MODE(0),
+ ANA_ETH1_FLOW_MATCH_VLAN_TAG2);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_ADDR_MATCH1(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_ADDR_MATCH2(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_VLAN_RANGE_I_TAG(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_VLAN_TAG1(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_VLAN_TAG2_I_TAG(0), 0);
+
+ val = vsc85xx_ts_read_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_MATCH_MODE(0));
+ val &= ~ANA_ETH1_FLOW_MATCH_VLAN_TAG_MASK;
+ val |= ANA_ETH1_FLOW_MATCH_VLAN_VERIFY;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_MATCH_MODE(0),
+ val);
+
+ return 0;
+}
+
+static int vsc85xx_ip_cmp1_init(struct phy_device *phydev, enum ts_blk blk)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ bool base = phydev->mdio.addr == vsc8531->ts_base_addr;
+ u32 val;
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_MATCH2_UPPER,
+ PTP_EV_PORT);
+ /* Match on dest port only, ignore src */
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_MASK2_UPPER,
+ 0xffff);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_MATCH2_LOWER,
+ 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_MASK2_LOWER, 0);
+
+ val = vsc85xx_ts_read_csr(phydev, blk, MSCC_ANA_IP1_FLOW_ENA(0));
+ val &= ~IP1_FLOW_ENA_CHANNEL_MASK_MASK;
+ val |= base ? IP1_FLOW_VALID_CH0 : IP1_FLOW_VALID_CH1;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_ENA(0), val);
+
+ /* Match all IPs */
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MATCH_UPPER(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MASK_UPPER(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MATCH_UPPER_MID(0),
+ 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MASK_UPPER_MID(0),
+ 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MATCH_LOWER_MID(0),
+ 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MASK_LOWER_MID(0),
+ 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MATCH_LOWER(0), 0);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_MASK_LOWER(0), 0);
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_PTP_IP_CHKSUM_SEL, 0);
+
+ return 0;
+}
+
+static int vsc85xx_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+ struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps);
+ struct phy_device *phydev = ptp->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+ u64 adj = 0;
+ u32 val;
+
+ if (abs(scaled_ppm) < 66 || abs(scaled_ppm) > 65536UL * 1000000UL)
+ return 0;
+
+ adj = div64_u64(1000000ULL * 65536ULL, abs(scaled_ppm));
+ if (adj > 1000000000L)
+ adj = 1000000000L;
+
+ val = PTP_AUTO_ADJ_NS_ROLLOVER(adj);
+ val |= scaled_ppm > 0 ? PTP_AUTO_ADJ_ADD_1NS : PTP_AUTO_ADJ_SUB_1NS;
+
+ mutex_lock(&priv->phc_lock);
+
+ /* Update the ppb val in nano seconds to the auto adjust reg. */
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_AUTO_ADJ,
+ val);
+
+ /* The auto adjust update val is set to 0 after write operation. */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL);
+ val |= PTP_LTC_CTRL_AUTO_ADJ_UPDATE;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL, val);
+
+ mutex_unlock(&priv->phc_lock);
+
+ return 0;
+}
+
+static int __vsc85xx_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
+{
+ struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps);
+ struct phy_device *phydev = ptp->phydev;
+ struct vsc85xx_shared_private *shared =
+ (struct vsc85xx_shared_private *)phydev->shared->priv;
+ struct vsc8531_private *priv = phydev->priv;
+ u32 val;
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL);
+ val |= PTP_LTC_CTRL_SAVE_ENA;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL, val);
+
+ /* Local Time Counter (LTC) is put in SAVE* regs on rising edge of
+ * LOAD_SAVE pin.
+ */
+ mutex_lock(&shared->gpio_lock);
+ gpiod_set_value(priv->load_save, 1);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_LTC_SAVED_SEC_MSB);
+
+ ts->tv_sec = ((time64_t)val) << 32;
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_LTC_SAVED_SEC_LSB);
+ ts->tv_sec += val;
+
+ ts->tv_nsec = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_LTC_SAVED_NS);
+
+ gpiod_set_value(priv->load_save, 0);
+ mutex_unlock(&shared->gpio_lock);
+
+ return 0;
+}
+
+static int vsc85xx_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
+{
+ struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps);
+ struct phy_device *phydev = ptp->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+
+ mutex_lock(&priv->phc_lock);
+ __vsc85xx_gettime(info, ts);
+ mutex_unlock(&priv->phc_lock);
+
+ return 0;
+}
+
+static int __vsc85xx_settime(struct ptp_clock_info *info,
+ const struct timespec64 *ts)
+{
+ struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps);
+ struct phy_device *phydev = ptp->phydev;
+ struct vsc85xx_shared_private *shared =
+ (struct vsc85xx_shared_private *)phydev->shared->priv;
+ struct vsc8531_private *priv = phydev->priv;
+ u32 val;
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_LOAD_SEC_MSB,
+ PTP_LTC_LOAD_SEC_MSB(ts->tv_sec));
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_LOAD_SEC_LSB,
+ PTP_LTC_LOAD_SEC_LSB(ts->tv_sec));
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_LOAD_NS,
+ PTP_LTC_LOAD_NS(ts->tv_nsec));
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL);
+ val |= PTP_LTC_CTRL_LOAD_ENA;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL, val);
+
+ /* Local Time Counter (LTC) is set from LOAD* regs on rising edge of
+ * LOAD_SAVE pin.
+ */
+ mutex_lock(&shared->gpio_lock);
+ gpiod_set_value(priv->load_save, 1);
+
+ val &= ~PTP_LTC_CTRL_LOAD_ENA;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL, val);
+
+ gpiod_set_value(priv->load_save, 0);
+ mutex_unlock(&shared->gpio_lock);
+
+ return 0;
+}
+
+static int vsc85xx_settime(struct ptp_clock_info *info,
+ const struct timespec64 *ts)
+{
+ struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps);
+ struct phy_device *phydev = ptp->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+
+ mutex_lock(&priv->phc_lock);
+ __vsc85xx_settime(info, ts);
+ mutex_unlock(&priv->phc_lock);
+
+ return 0;
+}
+
+static int vsc85xx_adjtime(struct ptp_clock_info *info, s64 delta)
+{
+ struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps);
+ struct phy_device *phydev = ptp->phydev;
+ struct vsc8531_private *priv = phydev->priv;
+ u32 val;
+
+ /* Can't recover that big of an offset. Let's set the time directly. */
+ if (abs(delta) >= NSEC_PER_SEC) {
+ struct timespec64 ts;
+ u64 now;
+
+ mutex_lock(&priv->phc_lock);
+
+ __vsc85xx_gettime(info, &ts);
+ now = ktime_to_ns(timespec64_to_ktime(ts));
+ ts = ns_to_timespec64(now + delta);
+ __vsc85xx_settime(info, &ts);
+
+ mutex_unlock(&priv->phc_lock);
+
+ return 0;
+ }
+
+ mutex_lock(&priv->phc_lock);
+
+ val = PTP_LTC_OFFSET_VAL(abs(delta)) | PTP_LTC_OFFSET_ADJ;
+ if (delta > 0)
+ val |= PTP_LTC_OFFSET_ADD;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_OFFSET, val);
+
+ mutex_unlock(&priv->phc_lock);
+
+ return 0;
+}
+
+static int vsc85xx_eth1_next_comp(struct phy_device *phydev, enum ts_blk blk,
+ u32 next_comp, u32 etype)
+{
+ u32 val;
+
+ val = vsc85xx_ts_read_csr(phydev, blk, MSCC_PHY_ANA_ETH1_NTX_PROT);
+ val &= ~ANA_ETH1_NTX_PROT_COMPARATOR_MASK;
+ val |= next_comp;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_PHY_ANA_ETH1_NTX_PROT, val);
+
+ val = ANA_ETH1_NXT_PROT_ETYPE_MATCH(etype) |
+ ANA_ETH1_NXT_PROT_ETYPE_MATCH_ENA;
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_PHY_ANA_ETH1_NXT_PROT_ETYPE_MATCH, val);
+
+ return 0;
+}
+
+static int vsc85xx_ip1_next_comp(struct phy_device *phydev, enum ts_blk blk,
+ u32 next_comp, u32 header)
+{
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_NXT_COMP,
+ ANA_IP1_NXT_PROT_NXT_COMP_BYTES_HDR(header) |
+ next_comp);
+
+ return 0;
+}
+
+static int vsc85xx_ts_ptp_action_flow(struct phy_device *phydev, enum ts_blk blk, u8 flow, enum ptp_cmd cmd)
+{
+ u32 val;
+
+ /* Check non-zero reserved field */
+ val = PTP_FLOW_PTP_0_FIELD_PTP_FRAME | PTP_FLOW_PTP_0_FIELD_RSVRD_CHECK;
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_PTP_0_FIELD(flow), val);
+
+ val = PTP_FLOW_PTP_ACTION_CORR_OFFSET(8) |
+ PTP_FLOW_PTP_ACTION_TIME_OFFSET(8) |
+ PTP_FLOW_PTP_ACTION_PTP_CMD(cmd == PTP_SAVE_IN_TS_FIFO ?
+ PTP_NOP : cmd);
+ if (cmd == PTP_SAVE_IN_TS_FIFO)
+ val |= PTP_FLOW_PTP_ACTION_SAVE_LOCAL_TIME;
+ else if (cmd == PTP_WRITE_NS)
+ val |= PTP_FLOW_PTP_ACTION_MOD_FRAME_STATUS_UPDATE |
+ PTP_FLOW_PTP_ACTION_MOD_FRAME_STATUS_BYTE_OFFSET(6);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_PTP_FLOW_PTP_ACTION(flow),
+ val);
+
+ if (cmd == PTP_WRITE_1588)
+ /* Rewrite timestamp directly in frame */
+ val = PTP_FLOW_PTP_ACTION2_REWRITE_OFFSET(34) |
+ PTP_FLOW_PTP_ACTION2_REWRITE_BYTES(10);
+ else if (cmd == PTP_SAVE_IN_TS_FIFO)
+ /* no rewrite */
+ val = PTP_FLOW_PTP_ACTION2_REWRITE_OFFSET(0) |
+ PTP_FLOW_PTP_ACTION2_REWRITE_BYTES(0);
+ else
+ /* Write in reserved field */
+ val = PTP_FLOW_PTP_ACTION2_REWRITE_OFFSET(16) |
+ PTP_FLOW_PTP_ACTION2_REWRITE_BYTES(4);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_PTP_ACTION2(flow), val);
+
+ return 0;
+}
+
+static int vsc85xx_ptp_conf(struct phy_device *phydev, enum ts_blk blk,
+ bool one_step, bool enable)
+{
+ static const u8 msgs[] = {
+ PTP_MSGTYPE_SYNC,
+ PTP_MSGTYPE_DELAY_REQ
+ };
+ u32 val;
+ u8 i;
+
+ for (i = 0; i < ARRAY_SIZE(msgs); i++) {
+ if (blk == INGRESS)
+ vsc85xx_ts_ptp_action_flow(phydev, blk, msgs[i],
+ PTP_WRITE_NS);
+ else if (msgs[i] == PTP_MSGTYPE_SYNC && one_step)
+ /* no need to know Sync t when sending in one_step */
+ vsc85xx_ts_ptp_action_flow(phydev, blk, msgs[i],
+ PTP_WRITE_1588);
+ else
+ vsc85xx_ts_ptp_action_flow(phydev, blk, msgs[i],
+ PTP_SAVE_IN_TS_FIFO);
+
+ val = vsc85xx_ts_read_csr(phydev, blk,
+ MSCC_ANA_PTP_FLOW_ENA(i));
+ val &= ~PTP_FLOW_ENA;
+ if (enable)
+ val |= PTP_FLOW_ENA;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_PTP_FLOW_ENA(i),
+ val);
+ }
+
+ return 0;
+}
+
+static int vsc85xx_eth1_conf(struct phy_device *phydev, enum ts_blk blk,
+ bool enable)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ u32 val = ANA_ETH1_FLOW_ADDR_MATCH2_DEST;
+
+ if (vsc8531->ptp->rx_filter == HWTSTAMP_FILTER_PTP_V2_L2_EVENT) {
+ /* PTP over Ethernet multicast address for SYNC and DELAY msg */
+ u8 ptp_multicast[6] = {0x01, 0x1b, 0x19, 0x00, 0x00, 0x00};
+
+ val |= ANA_ETH1_FLOW_ADDR_MATCH2_FULL_ADDR |
+ get_unaligned_be16(&ptp_multicast[4]);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_ADDR_MATCH2(0), val);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_ADDR_MATCH1(0),
+ get_unaligned_be32(ptp_multicast));
+ } else {
+ val |= ANA_ETH1_FLOW_ADDR_MATCH2_ANY_MULTICAST;
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_ADDR_MATCH2(0), val);
+ vsc85xx_ts_write_csr(phydev, blk,
+ MSCC_ANA_ETH1_FLOW_ADDR_MATCH1(0), 0);
+ }
+
+ val = vsc85xx_ts_read_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_ENA(0));
+ val &= ~ETH1_FLOW_ENA;
+ if (enable)
+ val |= ETH1_FLOW_ENA;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_ETH1_FLOW_ENA(0), val);
+
+ return 0;
+}
+
+static int vsc85xx_ip1_conf(struct phy_device *phydev, enum ts_blk blk,
+ bool enable)
+{
+ u32 val;
+
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_IP1_MODE,
+ ANA_IP1_NXT_PROT_IPV4 |
+ ANA_IP1_NXT_PROT_FLOW_OFFSET_IPV4);
+
+ /* Matching UDP protocol number */
+ val = ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MASK(0xff) |
+ ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MATCH(IPPROTO_UDP) |
+ ANA_IP1_NXT_PROT_IP_MATCH1_PROT_OFF(9);
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_IP_MATCH1,
+ val);
+
+ /* End of IP protocol, start of next protocol (UDP) */
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_OFFSET2,
+ ANA_IP1_NXT_PROT_OFFSET2(20));
+
+ val = vsc85xx_ts_read_csr(phydev, blk,
+ MSCC_ANA_IP1_NXT_PROT_UDP_CHKSUM);
+ val &= ~(IP1_NXT_PROT_UDP_CHKSUM_OFF_MASK |
+ IP1_NXT_PROT_UDP_CHKSUM_WIDTH_MASK);
+ val |= IP1_NXT_PROT_UDP_CHKSUM_WIDTH(2);
+
+ val &= ~(IP1_NXT_PROT_UDP_CHKSUM_UPDATE |
+ IP1_NXT_PROT_UDP_CHKSUM_CLEAR);
+ /* UDP checksum offset in IPv4 packet
+ * according to: https://tools.ietf.org/html/rfc768
+ */
+ val |= IP1_NXT_PROT_UDP_CHKSUM_OFF(26) | IP1_NXT_PROT_UDP_CHKSUM_CLEAR;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_NXT_PROT_UDP_CHKSUM,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, blk, MSCC_ANA_IP1_FLOW_ENA(0));
+ val &= ~(IP1_FLOW_MATCH_ADDR_MASK | IP1_FLOW_ENA);
+ val |= IP1_FLOW_MATCH_DEST_SRC_ADDR;
+ if (enable)
+ val |= IP1_FLOW_ENA;
+ vsc85xx_ts_write_csr(phydev, blk, MSCC_ANA_IP1_FLOW_ENA(0), val);
+
+ return 0;
+}
+
+static int vsc85xx_ts_engine_init(struct phy_device *phydev, bool one_step)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ bool ptp_l4, base = phydev->mdio.addr == vsc8531->ts_base_addr;
+ u8 eng_id = base ? 0 : 1;
+ u32 val;
+
+ ptp_l4 = vsc8531->ptp->rx_filter == HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ANALYZER_MODE);
+ /* Disable INGRESS and EGRESS so engine eng_id can be reconfigured */
+ val &= ~(PTP_ANALYZER_MODE_EGR_ENA(BIT(eng_id)) |
+ PTP_ANALYZER_MODE_INGR_ENA(BIT(eng_id)));
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ANALYZER_MODE,
+ val);
+
+ if (vsc8531->ptp->rx_filter == HWTSTAMP_FILTER_PTP_V2_L2_EVENT) {
+ vsc85xx_eth1_next_comp(phydev, INGRESS,
+ ANA_ETH1_NTX_PROT_PTP_OAM, ETH_P_1588);
+ vsc85xx_eth1_next_comp(phydev, EGRESS,
+ ANA_ETH1_NTX_PROT_PTP_OAM, ETH_P_1588);
+ } else {
+ vsc85xx_eth1_next_comp(phydev, INGRESS,
+ ANA_ETH1_NTX_PROT_IP_UDP_ACH_1,
+ ETH_P_IP);
+ vsc85xx_eth1_next_comp(phydev, EGRESS,
+ ANA_ETH1_NTX_PROT_IP_UDP_ACH_1,
+ ETH_P_IP);
+ /* Header length of IPv[4/6] + UDP */
+ vsc85xx_ip1_next_comp(phydev, INGRESS,
+ ANA_ETH1_NTX_PROT_PTP_OAM, 28);
+ vsc85xx_ip1_next_comp(phydev, EGRESS,
+ ANA_ETH1_NTX_PROT_PTP_OAM, 28);
+ }
+
+ vsc85xx_eth1_conf(phydev, INGRESS,
+ vsc8531->ptp->rx_filter != HWTSTAMP_FILTER_NONE);
+ vsc85xx_ip1_conf(phydev, INGRESS,
+ ptp_l4 && vsc8531->ptp->rx_filter != HWTSTAMP_FILTER_NONE);
+ vsc85xx_ptp_conf(phydev, INGRESS, one_step,
+ vsc8531->ptp->rx_filter != HWTSTAMP_FILTER_NONE);
+
+ vsc85xx_eth1_conf(phydev, EGRESS,
+ vsc8531->ptp->tx_type != HWTSTAMP_TX_OFF);
+ vsc85xx_ip1_conf(phydev, EGRESS,
+ ptp_l4 && vsc8531->ptp->tx_type != HWTSTAMP_TX_OFF);
+ vsc85xx_ptp_conf(phydev, EGRESS, one_step,
+ vsc8531->ptp->tx_type != HWTSTAMP_TX_OFF);
+
+ val &= ~PTP_ANALYZER_MODE_EGR_ENA(BIT(eng_id));
+ if (vsc8531->ptp->tx_type != HWTSTAMP_TX_OFF)
+ val |= PTP_ANALYZER_MODE_EGR_ENA(BIT(eng_id));
+
+ val &= ~PTP_ANALYZER_MODE_INGR_ENA(BIT(eng_id));
+ if (vsc8531->ptp->rx_filter != HWTSTAMP_FILTER_NONE)
+ val |= PTP_ANALYZER_MODE_INGR_ENA(BIT(eng_id));
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ANALYZER_MODE,
+ val);
+
+ return 0;
+}
+
+void vsc85xx_link_change_notify(struct phy_device *phydev)
+{
+ struct vsc8531_private *priv = phydev->priv;
+
+ mutex_lock(&priv->ts_lock);
+ vsc85xx_ts_set_latencies(phydev);
+ mutex_unlock(&priv->ts_lock);
+}
+
+static void vsc85xx_ts_reset_fifo(struct phy_device *phydev)
+{
+ u32 val;
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_TS_FIFO_CTRL);
+ val |= PTP_EGR_TS_FIFO_RESET;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TS_FIFO_CTRL,
+ val);
+
+ val &= ~PTP_EGR_TS_FIFO_RESET;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TS_FIFO_CTRL,
+ val);
+}
+
+static int vsc85xx_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
+{
+ struct vsc8531_private *vsc8531 =
+ container_of(mii_ts, struct vsc8531_private, mii_ts);
+ struct phy_device *phydev = vsc8531->ptp->phydev;
+ struct hwtstamp_config cfg;
+ bool one_step = false;
+ u32 val;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_ONESTEP_SYNC:
+ one_step = true;
+ break;
+ case HWTSTAMP_TX_ON:
+ break;
+ case HWTSTAMP_TX_OFF:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ vsc8531->ptp->tx_type = cfg.tx_type;
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ /* ETH->IP->UDP->PTP */
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ /* ETH->PTP */
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ vsc8531->ptp->rx_filter = cfg.rx_filter;
+
+ mutex_lock(&vsc8531->ts_lock);
+
+ __skb_queue_purge(&vsc8531->ptp->tx_queue);
+ __skb_queue_head_init(&vsc8531->ptp->tx_queue);
+
+ /* Disable predictor while configuring the 1588 block */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_PREDICTOR);
+ val &= ~PTP_INGR_PREDICTOR_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_PREDICTOR,
+ val);
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_PREDICTOR);
+ val &= ~PTP_EGR_PREDICTOR_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_PREDICTOR,
+ val);
+
+ /* Bypass egress or ingress blocks if timestamping isn't used */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_IFACE_CTRL);
+ val &= ~(PTP_IFACE_CTRL_EGR_BYPASS | PTP_IFACE_CTRL_INGR_BYPASS);
+ if (vsc8531->ptp->tx_type == HWTSTAMP_TX_OFF)
+ val |= PTP_IFACE_CTRL_EGR_BYPASS;
+ if (vsc8531->ptp->rx_filter == HWTSTAMP_FILTER_NONE)
+ val |= PTP_IFACE_CTRL_INGR_BYPASS;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_IFACE_CTRL, val);
+
+ /* Resetting FIFO so that it's empty after reconfiguration */
+ vsc85xx_ts_reset_fifo(phydev);
+
+ vsc85xx_ts_engine_init(phydev, one_step);
+
+ /* Re-enable predictors now */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_PREDICTOR);
+ val |= PTP_INGR_PREDICTOR_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_PREDICTOR,
+ val);
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_PREDICTOR);
+ val |= PTP_EGR_PREDICTOR_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_PREDICTOR,
+ val);
+
+ vsc8531->ptp->configured = 1;
+ mutex_unlock(&vsc8531->ts_lock);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static int vsc85xx_ts_info(struct mii_timestamper *mii_ts,
+ struct ethtool_ts_info *info)
+{
+ struct vsc8531_private *vsc8531 =
+ container_of(mii_ts, struct vsc8531_private, mii_ts);
+
+ info->phc_index = ptp_clock_index(vsc8531->ptp->ptp_clock);
+ info->so_timestamping =
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+ 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_V2_L2_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT);
+
+ return 0;
+}
+
+static void vsc85xx_txtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct vsc8531_private *vsc8531 =
+ container_of(mii_ts, struct vsc8531_private, mii_ts);
+
+ if (!vsc8531->ptp->configured)
+ return;
+
+ if (vsc8531->ptp->tx_type == HWTSTAMP_TX_OFF) {
+ kfree_skb(skb);
+ return;
+ }
+
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+
+ mutex_lock(&vsc8531->ts_lock);
+ __skb_queue_tail(&vsc8531->ptp->tx_queue, skb);
+ mutex_unlock(&vsc8531->ts_lock);
+}
+
+static bool vsc85xx_rxtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct vsc8531_private *vsc8531 =
+ container_of(mii_ts, struct vsc8531_private, mii_ts);
+ struct skb_shared_hwtstamps *shhwtstamps = NULL;
+ struct vsc85xx_ptphdr *ptphdr;
+ struct timespec64 ts;
+ unsigned long ns;
+
+ if (!vsc8531->ptp->configured)
+ return false;
+
+ if (vsc8531->ptp->rx_filter == HWTSTAMP_FILTER_NONE ||
+ type == PTP_CLASS_NONE)
+ return false;
+
+ vsc85xx_gettime(&vsc8531->ptp->caps, &ts);
+
+ ptphdr = get_ptp_header_rx(skb, vsc8531->ptp->rx_filter);
+ if (!ptphdr)
+ return false;
+
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
+
+ ns = ntohl(ptphdr->rsrvd2);
+
+ /* nsec is in reserved field */
+ if (ts.tv_nsec < ns)
+ ts.tv_sec--;
+
+ shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, ns);
+ netif_rx(skb);
+
+ return true;
+}
+
+static const struct ptp_clock_info vsc85xx_clk_caps = {
+ .owner = THIS_MODULE,
+ .name = "VSC85xx timer",
+ .max_adj = S32_MAX,
+ .n_alarm = 0,
+ .n_pins = 0,
+ .n_ext_ts = 0,
+ .n_per_out = 0,
+ .pps = 0,
+ .adjtime = &vsc85xx_adjtime,
+ .adjfine = &vsc85xx_adjfine,
+ .gettime64 = &vsc85xx_gettime,
+ .settime64 = &vsc85xx_settime,
+};
+
+static struct vsc8531_private *vsc8584_base_priv(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+
+ if (vsc8531->ts_base_addr != phydev->mdio.addr) {
+ struct mdio_device *dev;
+
+ dev = phydev->mdio.bus->mdio_map[vsc8531->ts_base_addr];
+ phydev = container_of(dev, struct phy_device, mdio);
+
+ return phydev->priv;
+ }
+
+ return vsc8531;
+}
+
+static bool vsc8584_is_1588_input_clk_configured(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = vsc8584_base_priv(phydev);
+
+ return vsc8531->input_clk_init;
+}
+
+static void vsc8584_set_input_clk_configured(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = vsc8584_base_priv(phydev);
+
+ vsc8531->input_clk_init = true;
+}
+
+static int __vsc8584_init_ptp(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ static const u32 ltc_seq_e[] = { 0, 400000, 0, 0, 0 };
+ static const u8 ltc_seq_a[] = { 8, 6, 5, 4, 2 };
+ u32 val;
+
+ if (!vsc8584_is_1588_input_clk_configured(phydev)) {
+ phy_lock_mdio_bus(phydev);
+
+ /* 1588_DIFF_INPUT_CLK configuration: Use an external clock for
+ * the LTC, as per 3.13.29 in the VSC8584 datasheet.
+ */
+ phy_ts_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_1588);
+ phy_ts_base_write(phydev, 29, 0x7ae0);
+ phy_ts_base_write(phydev, 30, 0xb71c);
+ phy_ts_base_write(phydev, MSCC_EXT_PAGE_ACCESS,
+ MSCC_PHY_PAGE_STANDARD);
+
+ phy_unlock_mdio_bus(phydev);
+
+ vsc8584_set_input_clk_configured(phydev);
+ }
+
+ /* Disable predictor before configuring the 1588 block */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_PREDICTOR);
+ val &= ~PTP_INGR_PREDICTOR_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_PREDICTOR,
+ val);
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_PREDICTOR);
+ val &= ~PTP_EGR_PREDICTOR_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_PREDICTOR,
+ val);
+
+ /* By default, the internal clock of fixed rate 250MHz is used */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL);
+ val &= ~PTP_LTC_CTRL_CLK_SEL_MASK;
+ val |= PTP_LTC_CTRL_CLK_SEL_INTERNAL_250;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_CTRL, val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_SEQUENCE);
+ val &= ~PTP_LTC_SEQUENCE_A_MASK;
+ val |= PTP_LTC_SEQUENCE_A(ltc_seq_a[PHC_CLK_250MHZ]);
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_SEQUENCE, val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_SEQ);
+ val &= ~(PTP_LTC_SEQ_ERR_MASK | PTP_LTC_SEQ_ADD_SUB);
+ if (ltc_seq_e[PHC_CLK_250MHZ])
+ val |= PTP_LTC_SEQ_ADD_SUB;
+ val |= PTP_LTC_SEQ_ERR(ltc_seq_e[PHC_CLK_250MHZ]);
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_SEQ, val);
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_LTC_1PPS_WIDTH_ADJ,
+ PPS_WIDTH_ADJ);
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_DELAY_FIFO,
+ IS_ENABLED(CONFIG_MACSEC) ?
+ PTP_INGR_DELAY_FIFO_DEPTH_MACSEC :
+ PTP_INGR_DELAY_FIFO_DEPTH_DEFAULT);
+
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_DELAY_FIFO,
+ IS_ENABLED(CONFIG_MACSEC) ?
+ PTP_EGR_DELAY_FIFO_DEPTH_MACSEC :
+ PTP_EGR_DELAY_FIFO_DEPTH_DEFAULT);
+
+ /* Enable n-phase sampler for Viper Rev-B */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ACCUR_CFG_STATUS);
+ val &= ~(PTP_ACCUR_PPS_OUT_BYPASS | PTP_ACCUR_PPS_IN_BYPASS |
+ PTP_ACCUR_EGR_SOF_BYPASS | PTP_ACCUR_INGR_SOF_BYPASS |
+ PTP_ACCUR_LOAD_SAVE_BYPASS);
+ val |= PTP_ACCUR_PPS_OUT_CALIB_ERR | PTP_ACCUR_PPS_OUT_CALIB_DONE |
+ PTP_ACCUR_PPS_IN_CALIB_ERR | PTP_ACCUR_PPS_IN_CALIB_DONE |
+ PTP_ACCUR_EGR_SOF_CALIB_ERR | PTP_ACCUR_EGR_SOF_CALIB_DONE |
+ PTP_ACCUR_INGR_SOF_CALIB_ERR | PTP_ACCUR_INGR_SOF_CALIB_DONE |
+ PTP_ACCUR_LOAD_SAVE_CALIB_ERR | PTP_ACCUR_LOAD_SAVE_CALIB_DONE;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ACCUR_CFG_STATUS,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ACCUR_CFG_STATUS);
+ val |= PTP_ACCUR_CALIB_TRIGG;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ACCUR_CFG_STATUS,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ACCUR_CFG_STATUS);
+ val &= ~PTP_ACCUR_CALIB_TRIGG;
+ val |= PTP_ACCUR_PPS_OUT_CALIB_ERR | PTP_ACCUR_PPS_OUT_CALIB_DONE |
+ PTP_ACCUR_PPS_IN_CALIB_ERR | PTP_ACCUR_PPS_IN_CALIB_DONE |
+ PTP_ACCUR_EGR_SOF_CALIB_ERR | PTP_ACCUR_EGR_SOF_CALIB_DONE |
+ PTP_ACCUR_INGR_SOF_CALIB_ERR | PTP_ACCUR_INGR_SOF_CALIB_DONE |
+ PTP_ACCUR_LOAD_SAVE_CALIB_ERR | PTP_ACCUR_LOAD_SAVE_CALIB_DONE;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ACCUR_CFG_STATUS,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ACCUR_CFG_STATUS);
+ val |= PTP_ACCUR_CALIB_TRIGG;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ACCUR_CFG_STATUS,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ACCUR_CFG_STATUS);
+ val &= ~PTP_ACCUR_CALIB_TRIGG;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ACCUR_CFG_STATUS,
+ val);
+
+ /* Do not access FIFO via SI */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_TSTAMP_FIFO_SI);
+ val &= ~PTP_TSTAMP_FIFO_SI_EN;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_TSTAMP_FIFO_SI,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_REWRITER_CTRL);
+ val &= ~PTP_INGR_REWRITER_REDUCE_PREAMBLE;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_REWRITER_CTRL,
+ val);
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_REWRITER_CTRL);
+ val &= ~PTP_EGR_REWRITER_REDUCE_PREAMBLE;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_REWRITER_CTRL,
+ val);
+
+ /* Put the flag that indicates the frame has been modified to bit 7 */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_REWRITER_CTRL);
+ val |= PTP_INGR_REWRITER_FLAG_BIT_OFF(7) | PTP_INGR_REWRITER_FLAG_VAL;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_REWRITER_CTRL,
+ val);
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_REWRITER_CTRL);
+ val |= PTP_EGR_REWRITER_FLAG_BIT_OFF(7);
+ val &= ~PTP_EGR_REWRITER_FLAG_VAL;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_REWRITER_CTRL,
+ val);
+
+ /* 30bit mode for RX timestamp, only the nanoseconds are kept in
+ * reserved field.
+ */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_INGR_TSP_CTRL);
+ val |= PHY_PTP_INGR_TSP_CTRL_FRACT_NS;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_INGR_TSP_CTRL,
+ val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TSP_CTRL);
+ val |= PHY_PTP_EGR_TSP_CTRL_FRACT_NS;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TSP_CTRL, val);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_SERIAL_TOD_IFACE);
+ val |= PTP_SERIAL_TOD_IFACE_LS_AUTO_CLR;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_SERIAL_TOD_IFACE,
+ val);
+
+ vsc85xx_ts_fsb_init(phydev);
+
+ /* Set the Egress timestamp FIFO configuration and status register */
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_EGR_TS_FIFO_CTRL);
+ val &= ~(PTP_EGR_TS_FIFO_SIG_BYTES_MASK | PTP_EGR_TS_FIFO_THRESH_MASK);
+ /* 16 bytes for the signature, 10 for the timestamp in the TS FIFO */
+ val |= PTP_EGR_TS_FIFO_SIG_BYTES(16) | PTP_EGR_TS_FIFO_THRESH(7);
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_EGR_TS_FIFO_CTRL,
+ val);
+
+ vsc85xx_ts_reset_fifo(phydev);
+
+ val = PTP_IFACE_CTRL_CLK_ENA;
+ if (!IS_ENABLED(CONFIG_MACSEC))
+ val |= PTP_IFACE_CTRL_GMII_PROT;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_IFACE_CTRL, val);
+
+ vsc85xx_ts_set_latencies(phydev);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_VERSION_CODE);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR, MSCC_PHY_PTP_IFACE_CTRL);
+ val |= PTP_IFACE_CTRL_EGR_BYPASS;
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_IFACE_CTRL, val);
+
+ vsc85xx_ts_disable_flows(phydev, EGRESS);
+ vsc85xx_ts_disable_flows(phydev, INGRESS);
+
+ val = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_PTP_ANALYZER_MODE);
+ /* Disable INGRESS and EGRESS so engine eng_id can be reconfigured */
+ val &= ~(PTP_ANALYZER_MODE_EGR_ENA_MASK |
+ PTP_ANALYZER_MODE_INGR_ENA_MASK |
+ PTP_ANA_INGR_ENCAP_FLOW_MODE_MASK |
+ PTP_ANA_EGR_ENCAP_FLOW_MODE_MASK);
+ /* Strict matching in flow (packets should match flows from the same
+ * index in all enabled comparators (except PTP)).
+ */
+ val |= PTP_ANA_SPLIT_ENCAP_FLOW | PTP_ANA_INGR_ENCAP_FLOW_MODE(0x7) |
+ PTP_ANA_EGR_ENCAP_FLOW_MODE(0x7);
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_PTP_ANALYZER_MODE,
+ val);
+
+ /* Initialized for ingress and egress flows:
+ * - The Ethernet comparator.
+ * - The IP comparator.
+ * - The PTP comparator.
+ */
+ vsc85xx_eth_cmp1_init(phydev, INGRESS);
+ vsc85xx_ip_cmp1_init(phydev, INGRESS);
+ vsc85xx_ptp_cmp_init(phydev, INGRESS);
+ vsc85xx_eth_cmp1_init(phydev, EGRESS);
+ vsc85xx_ip_cmp1_init(phydev, EGRESS);
+ vsc85xx_ptp_cmp_init(phydev, EGRESS);
+
+ vsc85xx_ts_eth_cmp1_sig(phydev);
+
+ vsc8531->mii_ts.rxtstamp = vsc85xx_rxtstamp;
+ vsc8531->mii_ts.txtstamp = vsc85xx_txtstamp;
+ vsc8531->mii_ts.hwtstamp = vsc85xx_hwtstamp;
+ vsc8531->mii_ts.ts_info = vsc85xx_ts_info;
+ phydev->mii_ts = &vsc8531->mii_ts;
+
+ memcpy(&vsc8531->ptp->caps, &vsc85xx_clk_caps, sizeof(vsc85xx_clk_caps));
+
+ vsc8531->ptp->ptp_clock = ptp_clock_register(&vsc8531->ptp->caps,
+ &phydev->mdio.dev);
+ return PTR_ERR_OR_ZERO(vsc8531->ptp->ptp_clock);
+}
+
+void vsc8584_config_ts_intr(struct phy_device *phydev)
+{
+ struct vsc8531_private *priv = phydev->priv;
+
+ mutex_lock(&priv->ts_lock);
+ vsc85xx_ts_write_csr(phydev, PROCESSOR, MSCC_PHY_1588_VSC85XX_INT_MASK,
+ VSC85XX_1588_INT_MASK_MASK);
+ mutex_unlock(&priv->ts_lock);
+}
+
+int vsc8584_ptp_init(struct phy_device *phydev)
+{
+ switch (phydev->phy_id & phydev->drv->phy_id_mask) {
+ case PHY_ID_VSC8572:
+ case PHY_ID_VSC8574:
+ case PHY_ID_VSC8575:
+ case PHY_ID_VSC8582:
+ case PHY_ID_VSC8584:
+ return __vsc8584_init_ptp(phydev);
+ }
+
+ return 0;
+}
+
+irqreturn_t vsc8584_handle_ts_interrupt(struct phy_device *phydev)
+{
+ struct vsc8531_private *priv = phydev->priv;
+ int rc;
+
+ mutex_lock(&priv->ts_lock);
+ rc = vsc85xx_ts_read_csr(phydev, PROCESSOR,
+ MSCC_PHY_1588_VSC85XX_INT_STATUS);
+ /* Ack the PTP interrupt */
+ vsc85xx_ts_write_csr(phydev, PROCESSOR,
+ MSCC_PHY_1588_VSC85XX_INT_STATUS, rc);
+
+ if (!(rc & VSC85XX_1588_INT_MASK_MASK)) {
+ mutex_unlock(&priv->ts_lock);
+ return IRQ_NONE;
+ }
+
+ if (rc & VSC85XX_1588_INT_FIFO_ADD) {
+ vsc85xx_get_tx_ts(priv->ptp);
+ } else if (rc & VSC85XX_1588_INT_FIFO_OVERFLOW) {
+ __skb_queue_purge(&priv->ptp->tx_queue);
+ vsc85xx_ts_reset_fifo(phydev);
+ }
+
+ mutex_unlock(&priv->ts_lock);
+ return IRQ_HANDLED;
+}
+
+int vsc8584_ptp_probe(struct phy_device *phydev)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+
+ vsc8531->ptp = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531->ptp),
+ GFP_KERNEL);
+ if (!vsc8531->ptp)
+ return -ENOMEM;
+
+ mutex_init(&vsc8531->phc_lock);
+ mutex_init(&vsc8531->ts_lock);
+
+ /* Retrieve the shared load/save GPIO. Request it as non exclusive as
+ * the same GPIO can be requested by all the PHYs of the same package.
+ * This GPIO must be used with the gpio_lock taken (the lock is shared
+ * between all PHYs).
+ */
+ vsc8531->load_save = devm_gpiod_get_optional(&phydev->mdio.dev, "load-save",
+ GPIOD_FLAGS_BIT_NONEXCLUSIVE |
+ GPIOD_OUT_LOW);
+ if (IS_ERR(vsc8531->load_save)) {
+ phydev_err(phydev, "Can't get load-save GPIO (%ld)\n",
+ PTR_ERR(vsc8531->load_save));
+ return PTR_ERR(vsc8531->load_save);
+ }
+
+ vsc8531->ptp->phydev = phydev;
+
+ return 0;
+}
+
+int vsc8584_ptp_probe_once(struct phy_device *phydev)
+{
+ struct vsc85xx_shared_private *shared =
+ (struct vsc85xx_shared_private *)phydev->shared->priv;
+
+ /* Initialize shared GPIO lock */
+ mutex_init(&shared->gpio_lock);
+
+ return 0;
+}
diff --git a/drivers/net/phy/mscc/mscc_ptp.h b/drivers/net/phy/mscc/mscc_ptp.h
new file mode 100644
index 000000000..da3465360
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_ptp.h
@@ -0,0 +1,472 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Copyright (c) 2020 Microsemi Corporation
+ */
+
+#ifndef _MSCC_PHY_PTP_H_
+#define _MSCC_PHY_PTP_H_
+
+/* 1588 page Registers */
+#define MSCC_PHY_TS_BIU_ADDR_CNTL 16
+#define BIU_ADDR_EXE 0x8000
+#define BIU_ADDR_READ 0x4000
+#define BIU_ADDR_WRITE 0x0000
+#define BIU_BLK_ID(x) ((x) << 11)
+#define BIU_CSR_ADDR(x) (x)
+#define BIU_ADDR_CNT_MAX 8
+
+#define MSCC_PHY_TS_CSR_DATA_LSB 17
+#define MSCC_PHY_TS_CSR_DATA_MSB 18
+
+#define MSCC_PHY_1588_INGR_VSC85XX_INT_STATUS 0x002d
+#define MSCC_PHY_1588_VSC85XX_INT_STATUS 0x004d
+#define VSC85XX_1588_INT_FIFO_ADD 0x0004
+#define VSC85XX_1588_INT_FIFO_OVERFLOW 0x0001
+
+#define MSCC_PHY_1588_INGR_VSC85XX_INT_MASK 0x002e
+#define MSCC_PHY_1588_VSC85XX_INT_MASK 0x004e
+#define VSC85XX_1588_INT_MASK_MASK (VSC85XX_1588_INT_FIFO_ADD | \
+ VSC85XX_1588_INT_FIFO_OVERFLOW)
+
+/* TS CSR addresses */
+#define MSCC_PHY_ANA_ETH1_NTX_PROT 0x0000
+#define ANA_ETH1_NTX_PROT_SIG_OFF_MASK GENMASK(20, 16)
+#define ANA_ETH1_NTX_PROT_SIG_OFF(x) (((x) << 16) & ANA_ETH1_NTX_PROT_SIG_OFF_MASK)
+#define ANA_ETH1_NTX_PROT_COMPARATOR_MASK GENMASK(2, 0)
+#define ANA_ETH1_NTX_PROT_PTP_OAM 0x0005
+#define ANA_ETH1_NTX_PROT_MPLS 0x0004
+#define ANA_ETH1_NTX_PROT_IP_UDP_ACH_2 0x0003
+#define ANA_ETH1_NTX_PROT_IP_UDP_ACH_1 0x0002
+#define ANA_ETH1_NTX_PROT_ETH2 0x0001
+
+#define MSCC_PHY_PTP_IFACE_CTRL 0x0000
+#define PTP_IFACE_CTRL_CLK_ENA 0x0040
+#define PTP_IFACE_CTRL_INGR_BYPASS 0x0008
+#define PTP_IFACE_CTRL_EGR_BYPASS 0x0004
+#define PTP_IFACE_CTRL_MII_PROT 0x0003
+#define PTP_IFACE_CTRL_GMII_PROT 0x0002
+#define PTP_IFACE_CTRL_XGMII_64_PROT 0x0000
+
+#define MSCC_PHY_ANA_ETH1_NTX_PROT_VLAN_TPID 0x0001
+#define ANA_ETH1_NTX_PROT_VLAN_TPID_MASK GENMASK(31, 16)
+#define ANA_ETH1_NTX_PROT_VLAN_TPID(x) (((x) << 16) & ANA_ETH1_NTX_PROT_VLAN_TPID_MASK)
+
+#define MSCC_PHY_PTP_ANALYZER_MODE 0x0001
+#define PTP_ANA_SPLIT_ENCAP_FLOW 0x1000000
+#define PTP_ANA_EGR_ENCAP_FLOW_MODE_MASK GENMASK(22, 20)
+#define PTP_ANA_EGR_ENCAP_FLOW_MODE(x) (((x) << 20) & PTP_ANA_EGR_ENCAP_FLOW_MODE_MASK)
+#define PTP_ANA_INGR_ENCAP_FLOW_MODE_MASK GENMASK(18, 16)
+#define PTP_ANA_INGR_ENCAP_FLOW_MODE(x) (((x) << 16) & PTP_ANA_INGR_ENCAP_FLOW_MODE_MASK)
+#define PTP_ANALYZER_MODE_EGR_ENA_MASK GENMASK(6, 4)
+#define PTP_ANALYZER_MODE_EGR_ENA(x) (((x) << 4) & PTP_ANALYZER_MODE_EGR_ENA_MASK)
+#define PTP_ANALYZER_MODE_INGR_ENA_MASK GENMASK(2, 0)
+#define PTP_ANALYZER_MODE_INGR_ENA(x) ((x) & PTP_ANALYZER_MODE_INGR_ENA_MASK)
+
+#define MSCC_PHY_ANA_ETH1_NXT_PROT_TAG 0x0002
+#define ANA_ETH1_NXT_PROT_TAG_ENA 0x0001
+
+#define MSCC_PHY_PTP_MODE_CTRL 0x0002
+#define PTP_MODE_CTRL_MODE_MASK GENMASK(2, 0)
+#define PTP_MODE_CTRL_PKT_MODE 0x0004
+
+#define MSCC_PHY_ANA_ETH1_NXT_PROT_ETYPE_MATCH 0x0003
+#define ANA_ETH1_NXT_PROT_ETYPE_MATCH_ENA 0x10000
+#define ANA_ETH1_NXT_PROT_ETYPE_MATCH_MASK GENMASK(15, 0)
+#define ANA_ETH1_NXT_PROT_ETYPE_MATCH(x) ((x) & ANA_ETH1_NXT_PROT_ETYPE_MATCH_MASK)
+
+#define MSCC_PHY_PTP_VERSION_CODE 0x0003
+#define PTP_IP_VERSION_MASK GENMASK(7, 0)
+#define PTP_IP_VERSION_2_1 0x0021
+
+#define MSCC_ANA_ETH1_FLOW_ENA(x) (0x0010 + ((x) << 4))
+#define ETH1_FLOW_ENA_CHANNEL_MASK_MASK GENMASK(9, 8)
+#define ETH1_FLOW_ENA_CHANNEL_MASK(x) (((x) << 8) & ETH1_FLOW_ENA_CHANNEL_MASK_MASK)
+#define ETH1_FLOW_VALID_CH1 ETH1_FLOW_ENA_CHANNEL_MASK(2)
+#define ETH1_FLOW_VALID_CH0 ETH1_FLOW_ENA_CHANNEL_MASK(1)
+#define ETH1_FLOW_ENA 0x0001
+
+#define MSCC_ANA_ETH1_FLOW_MATCH_MODE(x) (MSCC_ANA_ETH1_FLOW_ENA(x) + 1)
+#define ANA_ETH1_FLOW_MATCH_VLAN_TAG_MASK GENMASK(7, 6)
+#define ANA_ETH1_FLOW_MATCH_VLAN_TAG(x) (((x) << 6) & ANA_ETH1_FLOW_MATCH_VLAN_TAG_MASK)
+#define ANA_ETH1_FLOW_MATCH_VLAN_TAG2 0x0200
+#define ANA_ETH1_FLOW_MATCH_VLAN_VERIFY 0x0010
+
+#define MSCC_ANA_ETH1_FLOW_ADDR_MATCH1(x) (MSCC_ANA_ETH1_FLOW_ENA(x) + 2)
+
+#define MSCC_ANA_ETH1_FLOW_ADDR_MATCH2(x) (MSCC_ANA_ETH1_FLOW_ENA(x) + 3)
+#define ANA_ETH1_FLOW_ADDR_MATCH2_MASK_MASK GENMASK(22, 20)
+#define ANA_ETH1_FLOW_ADDR_MATCH2_ANY_MULTICAST 0x400000
+#define ANA_ETH1_FLOW_ADDR_MATCH2_FULL_ADDR 0x100000
+#define ANA_ETH1_FLOW_ADDR_MATCH2_SRC_DEST_MASK GENMASK(17, 16)
+#define ANA_ETH1_FLOW_ADDR_MATCH2_SRC_DEST 0x020000
+#define ANA_ETH1_FLOW_ADDR_MATCH2_SRC 0x010000
+#define ANA_ETH1_FLOW_ADDR_MATCH2_DEST 0x000000
+
+#define MSCC_ANA_ETH1_FLOW_VLAN_RANGE_I_TAG(x) (MSCC_ANA_ETH1_FLOW_ENA(x) + 4)
+#define MSCC_ANA_ETH1_FLOW_VLAN_TAG1(x) (MSCC_ANA_ETH1_FLOW_ENA(x) + 5)
+#define MSCC_ANA_ETH1_FLOW_VLAN_TAG2_I_TAG(x) (MSCC_ANA_ETH1_FLOW_ENA(x) + 6)
+
+#define MSCC_PHY_PTP_LTC_CTRL 0x0010
+#define PTP_LTC_CTRL_CLK_SEL_MASK GENMASK(14, 12)
+#define PTP_LTC_CTRL_CLK_SEL(x) (((x) << 12) & PTP_LTC_CTRL_CLK_SEL_MASK)
+#define PTP_LTC_CTRL_CLK_SEL_INTERNAL_250 PTP_LTC_CTRL_CLK_SEL(5)
+#define PTP_LTC_CTRL_AUTO_ADJ_UPDATE 0x0010
+#define PTP_LTC_CTRL_ADD_SUB_1NS_REQ 0x0008
+#define PTP_LTC_CTRL_ADD_1NS 0x0004
+#define PTP_LTC_CTRL_SAVE_ENA 0x0002
+#define PTP_LTC_CTRL_LOAD_ENA 0x0001
+
+#define MSCC_PHY_PTP_LTC_LOAD_SEC_MSB 0x0011
+#define PTP_LTC_LOAD_SEC_MSB(x) (((x) & GENMASK_ULL(47, 32)) >> 32)
+
+#define MSCC_PHY_PTP_LTC_LOAD_SEC_LSB 0x0012
+#define PTP_LTC_LOAD_SEC_LSB(x) ((x) & GENMASK(31, 0))
+
+#define MSCC_PHY_PTP_LTC_LOAD_NS 0x0013
+#define PTP_LTC_LOAD_NS(x) ((x) & GENMASK(31, 0))
+
+#define MSCC_PHY_PTP_LTC_SAVED_SEC_MSB 0x0014
+#define MSCC_PHY_PTP_LTC_SAVED_SEC_LSB 0x0015
+#define MSCC_PHY_PTP_LTC_SAVED_NS 0x0016
+
+#define MSCC_PHY_PTP_LTC_SEQUENCE 0x0017
+#define PTP_LTC_SEQUENCE_A_MASK GENMASK(3, 0)
+#define PTP_LTC_SEQUENCE_A(x) ((x) & PTP_LTC_SEQUENCE_A_MASK)
+
+#define MSCC_PHY_PTP_LTC_SEQ 0x0018
+#define PTP_LTC_SEQ_ADD_SUB 0x80000
+#define PTP_LTC_SEQ_ERR_MASK GENMASK(18, 0)
+#define PTP_LTC_SEQ_ERR(x) ((x) & PTP_LTC_SEQ_ERR_MASK)
+
+#define MSCC_PHY_PTP_LTC_AUTO_ADJ 0x001a
+#define PTP_AUTO_ADJ_NS_ROLLOVER(x) ((x) & GENMASK(29, 0))
+#define PTP_AUTO_ADJ_ADD_SUB_1NS_MASK GENMASK(31, 30)
+#define PTP_AUTO_ADJ_SUB_1NS 0x80000000
+#define PTP_AUTO_ADJ_ADD_1NS 0x40000000
+
+#define MSCC_PHY_PTP_LTC_1PPS_WIDTH_ADJ 0x001b
+#define PTP_LTC_1PPS_WIDTH_ADJ_MASK GENMASK(29, 0)
+
+#define MSCC_PHY_PTP_TSTAMP_FIFO_SI 0x0020
+#define PTP_TSTAMP_FIFO_SI_EN 0x0001
+
+#define MSCC_PHY_PTP_INGR_PREDICTOR 0x0022
+#define PTP_INGR_PREDICTOR_EN 0x0001
+
+#define MSCC_PHY_PTP_EGR_PREDICTOR 0x0026
+#define PTP_EGR_PREDICTOR_EN 0x0001
+
+#define MSCC_PHY_PTP_INGR_TSP_CTRL 0x0035
+#define PHY_PTP_INGR_TSP_CTRL_FRACT_NS 0x0004
+#define PHY_PTP_INGR_TSP_CTRL_LOAD_DELAYS 0x0001
+
+#define MSCC_PHY_PTP_INGR_LOCAL_LATENCY 0x0037
+#define PTP_INGR_LOCAL_LATENCY_MASK GENMASK(22, 0)
+#define PTP_INGR_LOCAL_LATENCY(x) ((x) & PTP_INGR_LOCAL_LATENCY_MASK)
+
+#define MSCC_PHY_PTP_INGR_DELAY_FIFO 0x003a
+#define PTP_INGR_DELAY_FIFO_DEPTH_MACSEC 0x0013
+#define PTP_INGR_DELAY_FIFO_DEPTH_DEFAULT 0x000f
+
+#define MSCC_PHY_PTP_INGR_TS_FIFO(x) (0x005c + (x))
+#define PTP_INGR_TS_FIFO_EMPTY 0x80000000
+
+#define MSCC_PHY_PTP_INGR_REWRITER_CTRL 0x0044
+#define PTP_INGR_REWRITER_REDUCE_PREAMBLE 0x0010
+#define PTP_INGR_REWRITER_FLAG_VAL 0x0008
+#define PTP_INGR_REWRITER_FLAG_BIT_OFF_M GENMASK(2, 0)
+#define PTP_INGR_REWRITER_FLAG_BIT_OFF(x) ((x) & PTP_INGR_REWRITER_FLAG_BIT_OFF_M)
+
+#define MSCC_PHY_PTP_EGR_STALL_LATENCY 0x004f
+
+#define MSCC_PHY_PTP_EGR_TSP_CTRL 0x0055
+#define PHY_PTP_EGR_TSP_CTRL_FRACT_NS 0x0004
+#define PHY_PTP_EGR_TSP_CTRL_LOAD_DELAYS 0x0001
+
+#define MSCC_PHY_PTP_EGR_LOCAL_LATENCY 0x0057
+#define PTP_EGR_LOCAL_LATENCY_MASK GENMASK(22, 0)
+#define PTP_EGR_LOCAL_LATENCY(x) ((x) & PTP_EGR_LOCAL_LATENCY_MASK)
+
+#define MSCC_PHY_PTP_EGR_DELAY_FIFO 0x005a
+#define PTP_EGR_DELAY_FIFO_DEPTH_MACSEC 0x0013
+#define PTP_EGR_DELAY_FIFO_DEPTH_DEFAULT 0x000f
+
+#define MSCC_PHY_PTP_EGR_TS_FIFO_CTRL 0x005b
+#define PTP_EGR_TS_FIFO_RESET 0x10000
+#define PTP_EGR_FIFO_LEVEL_LAST_READ_MASK GENMASK(15, 12)
+#define PTP_EGR_FIFO_LEVEL_LAST_READ(x) (((x) & PTP_EGR_FIFO_LEVEL_LAST_READ_MASK) >> 12)
+#define PTP_EGR_TS_FIFO_THRESH_MASK GENMASK(11, 8)
+#define PTP_EGR_TS_FIFO_THRESH(x) (((x) << 8) & PTP_EGR_TS_FIFO_THRESH_MASK)
+#define PTP_EGR_TS_FIFO_SIG_BYTES_MASK GENMASK(4, 0)
+#define PTP_EGR_TS_FIFO_SIG_BYTES(x) ((x) & PTP_EGR_TS_FIFO_SIG_BYTES_MASK)
+
+#define MSCC_PHY_PTP_EGR_TS_FIFO(x) (0x005c + (x))
+#define PTP_EGR_TS_FIFO_EMPTY 0x80000000
+#define PTP_EGR_TS_FIFO_0_MASK GENMASK(15, 0)
+
+#define MSCC_PHY_PTP_EGR_REWRITER_CTRL 0x0064
+#define PTP_EGR_REWRITER_REDUCE_PREAMBLE 0x0010
+#define PTP_EGR_REWRITER_FLAG_VAL 0x0008
+#define PTP_EGR_REWRITER_FLAG_BIT_OFF_M GENMASK(2, 0)
+#define PTP_EGR_REWRITER_FLAG_BIT_OFF(x) ((x) & PTP_EGR_REWRITER_FLAG_BIT_OFF_M)
+
+#define MSCC_PHY_PTP_SERIAL_TOD_IFACE 0x006e
+#define PTP_SERIAL_TOD_IFACE_LS_AUTO_CLR 0x0004
+
+#define MSCC_PHY_PTP_LTC_OFFSET 0x0070
+#define PTP_LTC_OFFSET_ADJ BIT(31)
+#define PTP_LTC_OFFSET_ADD BIT(30)
+#define PTP_LTC_OFFSET_VAL(x) (x)
+
+#define MSCC_PHY_PTP_ACCUR_CFG_STATUS 0x0074
+#define PTP_ACCUR_PPS_OUT_CALIB_ERR 0x20000
+#define PTP_ACCUR_PPS_OUT_CALIB_DONE 0x10000
+#define PTP_ACCUR_PPS_IN_CALIB_ERR 0x4000
+#define PTP_ACCUR_PPS_IN_CALIB_DONE 0x2000
+#define PTP_ACCUR_EGR_SOF_CALIB_ERR 0x1000
+#define PTP_ACCUR_EGR_SOF_CALIB_DONE 0x0800
+#define PTP_ACCUR_INGR_SOF_CALIB_ERR 0x0400
+#define PTP_ACCUR_INGR_SOF_CALIB_DONE 0x0200
+#define PTP_ACCUR_LOAD_SAVE_CALIB_ERR 0x0100
+#define PTP_ACCUR_LOAD_SAVE_CALIB_DONE 0x0080
+#define PTP_ACCUR_CALIB_TRIGG 0x0040
+#define PTP_ACCUR_PPS_OUT_BYPASS 0x0010
+#define PTP_ACCUR_PPS_IN_BYPASS 0x0008
+#define PTP_ACCUR_EGR_SOF_BYPASS 0x0004
+#define PTP_ACCUR_INGR_SOF_BYPASS 0x0002
+#define PTP_ACCUR_LOAD_SAVE_BYPASS 0x0001
+
+#define MSCC_PHY_ANA_ETH2_NTX_PROT 0x0090
+#define ANA_ETH2_NTX_PROT_COMPARATOR_MASK GENMASK(2, 0)
+#define ANA_ETH2_NTX_PROT_PTP_OAM 0x0005
+#define ANA_ETH2_NTX_PROT_MPLS 0x0004
+#define ANA_ETH2_NTX_PROT_IP_UDP_ACH_2 0x0003
+#define ANA_ETH2_NTX_PROT_IP_UDP_ACH_1 0x0002
+#define ANA_ETH2_NTX_PROT_ETH2 0x0001
+
+#define MSCC_PHY_ANA_ETH2_NXT_PROT_ETYPE_MATCH 0x0003
+#define ANA_ETH2_NXT_PROT_ETYPE_MATCH_ENA 0x10000
+#define ANA_ETH2_NXT_PROT_ETYPE_MATCH_MASK GENMASK(15, 0)
+#define ANA_ETH2_NXT_PROT_ETYPE_MATCH(x) ((x) & ANA_ETH2_NXT_PROT_ETYPE_MATCH_MASK)
+
+#define MSCC_ANA_ETH2_FLOW_ENA(x) (0x00a0 + ((x) << 4))
+#define ETH2_FLOW_ENA_CHANNEL_MASK_MASK GENMASK(9, 8)
+#define ETH2_FLOW_ENA_CHANNEL_MASK(x) (((x) << 8) & ETH2_FLOW_ENA_CHANNEL_MASK_MASK)
+#define ETH2_FLOW_VALID_CH1 ETH2_FLOW_ENA_CHANNEL_MASK(2)
+#define ETH2_FLOW_VALID_CH0 ETH2_FLOW_ENA_CHANNEL_MASK(1)
+
+#define MSCC_PHY_ANA_MPLS_COMP_NXT_COMP 0x0120
+#define ANA_MPLS_NTX_PROT_COMPARATOR_MASK GENMASK(2, 0)
+#define ANA_MPLS_NTX_PROT_PTP_OAM 0x0005
+#define ANA_MPLS_NTX_PROT_MPLS 0x0004
+#define ANA_MPLS_NTX_PROT_IP_UDP_ACH_2 0x0003
+#define ANA_MPLS_NTX_PROT_IP_UDP_ACH_1 0x0002
+#define ANA_MPLS_NTX_PROT_ETH2 0x0001
+
+#define MSCC_ANA_MPLS_FLOW_CTRL(x) (0x0130 + ((x) << 4))
+#define MPLS_FLOW_CTRL_CHANNEL_MASK_MASK GENMASK(25, 24)
+#define MPLS_FLOW_CTRL_CHANNEL_MASK(x) (((x) << 24) & MPLS_FLOW_CTRL_CHANNEL_MASK_MASK)
+#define MPLS_FLOW_VALID_CH1 MPLS_FLOW_CTRL_CHANNEL_MASK(2)
+#define MPLS_FLOW_VALID_CH0 MPLS_FLOW_CTRL_CHANNEL_MASK(1)
+
+#define MSCC_ANA_IP1_NXT_PROT_NXT_COMP 0x01b0
+#define ANA_IP1_NXT_PROT_NXT_COMP_BYTES_HDR_MASK GENMASK(15, 8)
+#define ANA_IP1_NXT_PROT_NXT_COMP_BYTES_HDR(x) (((x) << 8) & ANA_IP1_NXT_PROT_NXT_COMP_BYTES_HDR_MASK)
+#define ANA_IP1_NXT_PROT_NXT_COMP_PTP_OAM 0x0005
+#define ANA_IP1_NXT_PROT_NXT_COMP_IP_UDP_ACH2 0x0003
+
+#define MSCC_ANA_IP1_NXT_PROT_IP1_MODE 0x01b1
+#define ANA_IP1_NXT_PROT_FLOW_OFFSET_IPV4 0x0c00
+#define ANA_IP1_NXT_PROT_FLOW_OFFSET_IPV6 0x0800
+#define ANA_IP1_NXT_PROT_IPV6 0x0001
+#define ANA_IP1_NXT_PROT_IPV4 0x0000
+
+#define MSCC_ANA_IP1_NXT_PROT_IP_MATCH1 0x01b2
+#define ANA_IP1_NXT_PROT_IP_MATCH1_PROT_OFF_MASK GENMASK(20, 16)
+#define ANA_IP1_NXT_PROT_IP_MATCH1_PROT_OFF(x) (((x) << 16) & ANA_IP1_NXT_PROT_IP_MATCH1_PROT_OFF_MASK)
+#define ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MASK_MASK GENMASK(15, 8)
+#define ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MASK(x) (((x) << 15) & ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MASK_MASK)
+#define ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MATCH_MASK GENMASK(7, 0)
+#define ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MATCH(x) ((x) & ANA_IP1_NXT_PROT_IP_MATCH1_PROT_MATCH_MASK)
+
+#define MSCC_ANA_IP1_NXT_PROT_MATCH2_UPPER 0x01b3
+#define MSCC_ANA_IP1_NXT_PROT_MATCH2_LOWER 0x01b4
+#define MSCC_ANA_IP1_NXT_PROT_MASK2_UPPER 0x01b5
+#define MSCC_ANA_IP1_NXT_PROT_MASK2_LOWER 0x01b6
+
+#define MSCC_ANA_IP1_NXT_PROT_OFFSET2 0x01b7
+#define ANA_IP1_NXT_PROT_OFFSET2_MASK GENMASK(6, 0)
+#define ANA_IP1_NXT_PROT_OFFSET2(x) ((x) & ANA_IP1_NXT_PROT_OFFSET2_MASK)
+
+#define MSCC_ANA_IP1_NXT_PROT_UDP_CHKSUM 0x01b8
+#define IP1_NXT_PROT_UDP_CHKSUM_OFF_MASK GENMASK(15, 8)
+#define IP1_NXT_PROT_UDP_CHKSUM_OFF(x) (((x) << 8) & IP1_NXT_PROT_UDP_CHKSUM_OFF_MASK)
+#define IP1_NXT_PROT_UDP_CHKSUM_WIDTH_MASK GENMASK(5, 4)
+#define IP1_NXT_PROT_UDP_CHKSUM_WIDTH(x) (((x) << 4) & IP1_NXT_PROT_UDP_CHKSUM_WIDTH_MASK)
+#define IP1_NXT_PROT_UDP_CHKSUM_UPDATE 0x0002
+#define IP1_NXT_PROT_UDP_CHKSUM_CLEAR 0x0001
+
+#define MSCC_ANA_IP1_FLOW_ENA(x) (0x01c0 + ((x) << 4))
+#define IP1_FLOW_MATCH_ADDR_MASK GENMASK(9, 8)
+#define IP1_FLOW_MATCH_DEST_SRC_ADDR 0x0200
+#define IP1_FLOW_MATCH_DEST_ADDR 0x0100
+#define IP1_FLOW_MATCH_SRC_ADDR 0x0000
+#define IP1_FLOW_ENA_CHANNEL_MASK_MASK GENMASK(5, 4)
+#define IP1_FLOW_ENA_CHANNEL_MASK(x) (((x) << 4) & IP1_FLOW_ENA_CHANNEL_MASK_MASK)
+#define IP1_FLOW_VALID_CH1 IP1_FLOW_ENA_CHANNEL_MASK(2)
+#define IP1_FLOW_VALID_CH0 IP1_FLOW_ENA_CHANNEL_MASK(1)
+#define IP1_FLOW_ENA 0x0001
+
+#define MSCC_ANA_OAM_PTP_FLOW_ENA(x) (0x1e0 + ((x) << 4))
+#define MSCC_ANA_OAM_PTP_FLOW_MATCH_LOWER(x) (MSCC_ANA_OAM_PTP_FLOW_ENA(x) + 2)
+#define MSCC_ANA_OAM_PTP_FLOW_MASK_LOWER(x) (MSCC_ANA_OAM_PTP_FLOW_ENA(x) + 4)
+
+#define MSCC_ANA_OAM_PTP_FLOW_PTP_0_FIELD(x) (MSCC_ANA_OAM_PTP_FLOW_ENA(x) + 8)
+
+#define MSCC_ANA_IP1_FLOW_MATCH_UPPER(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 1)
+#define MSCC_ANA_IP1_FLOW_MATCH_UPPER_MID(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 2)
+#define MSCC_ANA_IP1_FLOW_MATCH_LOWER_MID(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 3)
+#define MSCC_ANA_IP1_FLOW_MATCH_LOWER(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 4)
+#define MSCC_ANA_IP1_FLOW_MASK_UPPER(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 5)
+#define MSCC_ANA_IP1_FLOW_MASK_UPPER_MID(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 6)
+#define MSCC_ANA_IP1_FLOW_MASK_LOWER_MID(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 7)
+#define MSCC_ANA_IP1_FLOW_MASK_LOWER(x) (MSCC_ANA_IP1_FLOW_ENA(x) + 8)
+
+#define MSCC_ANA_IP2_NXT_PROT_NXT_COMP 0x0240
+#define ANA_IP2_NXT_PROT_NXT_COMP_BYTES_HDR_MASK GENMASK(15, 8)
+#define ANA_IP2_NXT_PROT_NXT_COMP_BYTES_HDR(x) (((x) << 8) & ANA_IP2_NXT_PROT_NXT_COMP_BYTES_HDR_MASK)
+#define ANA_IP2_NXT_PROT_NXT_COMP_PTP_OAM 0x0005
+#define ANA_IP2_NXT_PROT_NXT_COMP_IP_UDP_ACH2 0x0003
+
+#define MSCC_ANA_IP2_NXT_PROT_UDP_CHKSUM 0x0248
+#define IP2_NXT_PROT_UDP_CHKSUM_OFF_MASK GENMASK(15, 8)
+#define IP2_NXT_PROT_UDP_CHKSUM_OFF(x) (((x) << 8) & IP2_NXT_PROT_UDP_CHKSUM_OFF_MASK)
+#define IP2_NXT_PROT_UDP_CHKSUM_WIDTH_MASK GENMASK(5, 4)
+#define IP2_NXT_PROT_UDP_CHKSUM_WIDTH(x) (((x) << 4) & IP2_NXT_PROT_UDP_CHKSUM_WIDTH_MASK)
+
+#define MSCC_ANA_IP2_FLOW_ENA(x) (0x0250 + ((x) << 4))
+#define IP2_FLOW_ENA_CHANNEL_MASK_MASK GENMASK(5, 4)
+#define IP2_FLOW_ENA_CHANNEL_MASK(x) (((x) << 4) & IP2_FLOW_ENA_CHANNEL_MASK_MASK)
+#define IP2_FLOW_VALID_CH1 IP2_FLOW_ENA_CHANNEL_MASK(2)
+#define IP2_FLOW_VALID_CH0 IP2_FLOW_ENA_CHANNEL_MASK(1)
+
+#define MSCC_ANA_PTP_FLOW_ENA(x) (0x02d0 + ((x) << 4))
+#define PTP_FLOW_ENA_CHANNEL_MASK_MASK GENMASK(5, 4)
+#define PTP_FLOW_ENA_CHANNEL_MASK(x) (((x) << 4) & PTP_FLOW_ENA_CHANNEL_MASK_MASK)
+#define PTP_FLOW_VALID_CH1 PTP_FLOW_ENA_CHANNEL_MASK(2)
+#define PTP_FLOW_VALID_CH0 PTP_FLOW_ENA_CHANNEL_MASK(1)
+#define PTP_FLOW_ENA 0x0001
+
+#define MSCC_ANA_PTP_FLOW_MATCH_UPPER(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 1)
+#define PTP_FLOW_MSG_TYPE_MASK 0x0F000000
+#define PTP_FLOW_MSG_PDELAY_RESP 0x04000000
+#define PTP_FLOW_MSG_PDELAY_REQ 0x02000000
+#define PTP_FLOW_MSG_DELAY_REQ 0x01000000
+#define PTP_FLOW_MSG_SYNC 0x00000000
+
+#define MSCC_ANA_PTP_FLOW_MATCH_LOWER(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 2)
+#define MSCC_ANA_PTP_FLOW_MASK_UPPER(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 3)
+#define MSCC_ANA_PTP_FLOW_MASK_LOWER(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 4)
+
+#define MSCC_ANA_PTP_FLOW_DOMAIN_RANGE(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 5)
+#define PTP_FLOW_DOMAIN_RANGE_ENA 0x0001
+
+#define MSCC_ANA_PTP_FLOW_PTP_ACTION(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 6)
+#define PTP_FLOW_PTP_ACTION_MOD_FRAME_STATUS_UPDATE 0x10000000
+#define PTP_FLOW_PTP_ACTION_MOD_FRAME_STATUS_BYTE_OFFSET_MASK GENMASK(26, 24)
+#define PTP_FLOW_PTP_ACTION_MOD_FRAME_STATUS_BYTE_OFFSET(x) (((x) << 24) & PTP_FLOW_PTP_ACTION_MOD_FRAME_STATUS_BYTE_OFFSET_MASK)
+#define PTP_FLOW_PTP_ACTION_PTP_CMD_MASK GENMASK(3, 0)
+#define PTP_FLOW_PTP_ACTION_PTP_CMD(x) ((x) & PTP_FLOW_PTP_ACTION_PTP_CMD_MASK)
+#define PTP_FLOW_PTP_ACTION_SUB_DELAY_ASYM 0x00200000
+#define PTP_FLOW_PTP_ACTION_ADD_DELAY_ASYM 0x00100000
+#define PTP_FLOW_PTP_ACTION_TIME_OFFSET_MASK GENMASK(15, 10)
+#define PTP_FLOW_PTP_ACTION_TIME_OFFSET(x) (((x) << 10) & PTP_FLOW_PTP_ACTION_TIME_OFFSET_MASK)
+#define PTP_FLOW_PTP_ACTION_CORR_OFFSET_MASK GENMASK(9, 5)
+#define PTP_FLOW_PTP_ACTION_CORR_OFFSET(x) (((x) << 5) & PTP_FLOW_PTP_ACTION_CORR_OFFSET_MASK)
+#define PTP_FLOW_PTP_ACTION_SAVE_LOCAL_TIME 0x00000010
+
+#define MSCC_ANA_PTP_FLOW_PTP_ACTION2(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 7)
+#define PTP_FLOW_PTP_ACTION2_REWRITE_OFFSET_MASK GENMASK(15, 8)
+#define PTP_FLOW_PTP_ACTION2_REWRITE_OFFSET(x) (((x) << 8) & PTP_FLOW_PTP_ACTION2_REWRITE_OFFSET_MASK)
+#define PTP_FLOW_PTP_ACTION2_REWRITE_BYTES_MASK GENMASK(3, 0)
+#define PTP_FLOW_PTP_ACTION2_REWRITE_BYTES(x) ((x) & PTP_FLOW_PTP_ACTION2_REWRITE_BYTES_MASK)
+
+#define MSCC_ANA_PTP_FLOW_PTP_0_FIELD(x) (MSCC_ANA_PTP_FLOW_ENA(x) + 8)
+#define PTP_FLOW_PTP_0_FIELD_PTP_FRAME 0x8000
+#define PTP_FLOW_PTP_0_FIELD_RSVRD_CHECK 0x4000
+#define PTP_FLOW_PTP_0_FIELD_OFFSET_MASK GENMASK(13, 8)
+#define PTP_FLOW_PTP_0_FIELD_OFFSET(x) (((x) << 8) & PTP_FLOW_PTP_0_FIELD_OFFSET_MASK)
+#define PTP_FLOW_PTP_0_FIELD_BYTES_MASK GENMASK(3, 0)
+#define PTP_FLOW_PTP_0_FIELD_BYTES(x) ((x) & PTP_FLOW_PTP_0_FIELD_BYTES_MASK)
+
+#define MSCC_ANA_PTP_IP_CHKSUM_SEL 0x0330
+#define ANA_PTP_IP_CHKSUM_SEL_IP_COMP_2 0x0001
+#define ANA_PTP_IP_CHKSUM_SEL_IP_COMP_1 0x0000
+
+#define MSCC_PHY_ANA_FSB_CFG 0x331
+#define ANA_FSB_ADDR_FROM_BLOCK_SEL_MASK GENMASK(1, 0)
+#define ANA_FSB_ADDR_FROM_IP2 0x0003
+#define ANA_FSB_ADDR_FROM_IP1 0x0002
+#define ANA_FSB_ADDR_FROM_ETH2 0x0001
+#define ANA_FSB_ADDR_FROM_ETH1 0x0000
+
+#define MSCC_PHY_ANA_FSB_REG(x) (0x332 + (x))
+
+#define COMP_MAX_FLOWS 8
+#define PTP_COMP_MAX_FLOWS 6
+
+#define PPS_WIDTH_ADJ 0x1dcd6500
+#define STALL_EGR_LATENCY(x) (1536000 / (x))
+
+/* PHC clock available frequencies. */
+enum {
+ PHC_CLK_125MHZ,
+ PHC_CLK_156_25MHZ,
+ PHC_CLK_200MHZ,
+ PHC_CLK_250MHZ,
+ PHC_CLK_500MHZ,
+};
+
+enum ptp_cmd {
+ PTP_NOP = 0,
+ PTP_WRITE_1588 = 5,
+ PTP_WRITE_NS = 7,
+ PTP_SAVE_IN_TS_FIFO = 11, /* invalid when writing in reg */
+};
+
+struct vsc85xx_ptphdr {
+ u8 tsmt; /* transportSpecific | messageType */
+ u8 ver; /* reserved0 | versionPTP */
+ __be16 msglen;
+ u8 domain;
+ u8 rsrvd1;
+ __be16 flags;
+ __be64 correction;
+ __be32 rsrvd2;
+ __be64 clk_identity;
+ __be16 src_port_id;
+ __be16 seq_id;
+ u8 ctrl;
+ u8 log_interval;
+} __attribute__((__packed__));
+
+/* Represents an entry in the timestamping FIFO */
+struct vsc85xx_ts_fifo {
+ u32 ns;
+ u64 secs:48;
+ u8 sig[16];
+} __attribute__((__packed__));
+
+struct vsc85xx_ptp {
+ struct phy_device *phydev;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info caps;
+ struct sk_buff_head tx_queue;
+ enum hwtstamp_tx_types tx_type;
+ enum hwtstamp_rx_filters rx_filter;
+ u8 configured:1;
+};
+
+#endif /* _MSCC_PHY_PTP_H_ */
diff --git a/drivers/net/phy/mscc/mscc_serdes.c b/drivers/net/phy/mscc/mscc_serdes.c
new file mode 100644
index 000000000..b3e854f53
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_serdes.c
@@ -0,0 +1,650 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Author: Bjarni Jonasson <bjarni.jonassoni@microchip.com>
+ * License: Dual MIT/GPL
+ * Copyright (c) 2021 Microsemi Corporation
+ */
+
+#include <linux/phy.h>
+#include "mscc_serdes.h"
+#include "mscc.h"
+
+static int pll5g_detune(struct phy_device *phydev)
+{
+ u32 rd_dat;
+ int ret;
+
+ rd_dat = vsc85xx_csr_read(phydev, MACRO_CTRL, PHY_S6G_PLL5G_CFG2);
+ rd_dat &= ~PHY_S6G_PLL5G_CFG2_GAIN_MASK;
+ rd_dat |= PHY_S6G_PLL5G_CFG2_ENA_GAIN;
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_PLL5G_CFG2, rd_dat);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int pll5g_tune(struct phy_device *phydev)
+{
+ u32 rd_dat;
+ int ret;
+
+ rd_dat = vsc85xx_csr_read(phydev, MACRO_CTRL, PHY_S6G_PLL5G_CFG2);
+ rd_dat &= ~PHY_S6G_PLL5G_CFG2_ENA_GAIN;
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_PLL5G_CFG2, rd_dat);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_pll_cfg_wr(struct phy_device *phydev,
+ const u32 pll_ena_offs,
+ const u32 pll_fsm_ctrl_data,
+ const u32 pll_fsm_ena)
+{
+ int ret;
+
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_PLL_CFG,
+ (pll_fsm_ena << PHY_S6G_PLL_ENA_OFFS_POS) |
+ (pll_fsm_ctrl_data << PHY_S6G_PLL_FSM_CTRL_DATA_POS) |
+ (pll_ena_offs << PHY_S6G_PLL_FSM_ENA_POS));
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_common_cfg_wr(struct phy_device *phydev,
+ const u32 sys_rst,
+ const u32 ena_lane,
+ const u32 ena_loop,
+ const u32 qrate,
+ const u32 if_mode,
+ const u32 pwd_tx)
+{
+ /* ena_loop = 8 for eloop */
+ /* = 4 for floop */
+ /* = 2 for iloop */
+ /* = 1 for ploop */
+ /* qrate = 1 for SGMII, 0 for QSGMII */
+ /* if_mode = 1 for SGMII, 3 for QSGMII */
+
+ int ret;
+
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_COMMON_CFG,
+ (sys_rst << PHY_S6G_SYS_RST_POS) |
+ (ena_lane << PHY_S6G_ENA_LANE_POS) |
+ (ena_loop << PHY_S6G_ENA_LOOP_POS) |
+ (qrate << PHY_S6G_QRATE_POS) |
+ (if_mode << PHY_S6G_IF_MODE_POS));
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_des_cfg_wr(struct phy_device *phydev,
+ const u32 des_phy_ctrl,
+ const u32 des_mbtr_ctrl,
+ const u32 des_bw_hyst,
+ const u32 des_bw_ana,
+ const u32 des_cpmd_sel)
+{
+ u32 reg_val;
+ int ret;
+
+ /* configurable terms */
+ reg_val = (des_phy_ctrl << PHY_S6G_DES_PHY_CTRL_POS) |
+ (des_mbtr_ctrl << PHY_S6G_DES_MBTR_CTRL_POS) |
+ (des_cpmd_sel << PHY_S6G_DES_CPMD_SEL_POS) |
+ (des_bw_hyst << PHY_S6G_DES_BW_HYST_POS) |
+ (des_bw_ana << PHY_S6G_DES_BW_ANA_POS);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_DES_CFG,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_ib_cfg0_wr(struct phy_device *phydev,
+ const u32 ib_rtrm_adj,
+ const u32 ib_sig_det_clk_sel,
+ const u32 ib_reg_pat_sel_offset,
+ const u32 ib_cal_ena)
+{
+ u32 base_val;
+ u32 reg_val;
+ int ret;
+
+ /* constant terms */
+ base_val = 0x60a85837;
+ /* configurable terms */
+ reg_val = base_val | (ib_rtrm_adj << 25) |
+ (ib_sig_det_clk_sel << 16) |
+ (ib_reg_pat_sel_offset << 8) |
+ (ib_cal_ena << 3);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_IB_CFG0,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_ib_cfg1_wr(struct phy_device *phydev,
+ const u32 ib_tjtag,
+ const u32 ib_tsdet,
+ const u32 ib_scaly,
+ const u32 ib_frc_offset,
+ const u32 ib_filt_offset)
+{
+ u32 ib_filt_val;
+ u32 reg_val = 0;
+ int ret;
+
+ /* constant terms */
+ ib_filt_val = 0xe0;
+ /* configurable terms */
+ reg_val = (ib_tjtag << 17) + (ib_tsdet << 12) + (ib_scaly << 8) +
+ ib_filt_val + (ib_filt_offset << 4) + (ib_frc_offset << 0);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_IB_CFG1,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_ib_cfg2_wr(struct phy_device *phydev,
+ const u32 ib_tinfv,
+ const u32 ib_tcalv,
+ const u32 ib_ureg)
+{
+ u32 ib_cfg2_val;
+ u32 base_val;
+ int ret;
+
+ /* constant terms */
+ base_val = 0x0f878010;
+ /* configurable terms */
+ ib_cfg2_val = base_val | ((ib_tinfv) << 28) | ((ib_tcalv) << 5) |
+ (ib_ureg << 0);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_IB_CFG2,
+ ib_cfg2_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_ib_cfg3_wr(struct phy_device *phydev,
+ const u32 ib_ini_hp,
+ const u32 ib_ini_mid,
+ const u32 ib_ini_lp,
+ const u32 ib_ini_offset)
+{
+ u32 reg_val;
+ int ret;
+
+ reg_val = (ib_ini_hp << 24) + (ib_ini_mid << 16) +
+ (ib_ini_lp << 8) + (ib_ini_offset << 0);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_IB_CFG3,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_ib_cfg4_wr(struct phy_device *phydev,
+ const u32 ib_max_hp,
+ const u32 ib_max_mid,
+ const u32 ib_max_lp,
+ const u32 ib_max_offset)
+{
+ u32 reg_val;
+ int ret;
+
+ reg_val = (ib_max_hp << 24) + (ib_max_mid << 16) +
+ (ib_max_lp << 8) + (ib_max_offset << 0);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_IB_CFG4,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_misc_cfg_wr(struct phy_device *phydev,
+ const u32 lane_rst)
+{
+ int ret;
+
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_MISC_CFG,
+ lane_rst);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_gp_cfg_wr(struct phy_device *phydev, const u32 gp_cfg_val)
+{
+ int ret;
+
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_GP_CFG,
+ gp_cfg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_dft_cfg2_wr(struct phy_device *phydev,
+ const u32 rx_ji_ampl,
+ const u32 rx_step_freq,
+ const u32 rx_ji_ena,
+ const u32 rx_waveform_sel,
+ const u32 rx_freqoff_dir,
+ const u32 rx_freqoff_ena)
+{
+ u32 reg_val;
+ int ret;
+
+ /* configurable terms */
+ reg_val = (rx_ji_ampl << 8) | (rx_step_freq << 4) |
+ (rx_ji_ena << 3) | (rx_waveform_sel << 2) |
+ (rx_freqoff_dir << 1) | rx_freqoff_ena;
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_IB_DFT_CFG2,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+static int vsc85xx_sd6g_dft_cfg0_wr(struct phy_device *phydev,
+ const u32 prbs_sel,
+ const u32 test_mode,
+ const u32 rx_dft_ena)
+{
+ u32 reg_val;
+ int ret;
+
+ /* configurable terms */
+ reg_val = (prbs_sel << 20) | (test_mode << 16) | (rx_dft_ena << 2);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_DFT_CFG0,
+ reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+/* Access LCPLL Cfg_0 */
+static int vsc85xx_pll5g_cfg0_wr(struct phy_device *phydev,
+ const u32 selbgv820)
+{
+ u32 base_val;
+ u32 reg_val;
+ int ret;
+
+ /* constant terms */
+ base_val = 0x7036f145;
+ /* configurable terms */
+ reg_val = base_val | (selbgv820 << 23);
+ ret = vsc85xx_csr_write(phydev, MACRO_CTRL,
+ PHY_S6G_PLL5G_CFG0, reg_val);
+ if (ret)
+ dev_err(&phydev->mdio.dev, "%s: write error\n", __func__);
+ return ret;
+}
+
+int vsc85xx_sd6g_config_v2(struct phy_device *phydev)
+{
+ u32 ib_sig_det_clk_sel_cal = 0;
+ u32 ib_sig_det_clk_sel_mm = 7;
+ u32 pll_fsm_ctrl_data = 60;
+ unsigned long deadline;
+ u32 des_bw_ana_val = 3;
+ u32 ib_tsdet_cal = 16;
+ u32 ib_tsdet_mm = 5;
+ u32 ib_rtrm_adj;
+ u32 if_mode = 1;
+ u32 gp_iter = 5;
+ u32 val32 = 0;
+ u32 qrate = 1;
+ u32 iter;
+ int val = 0;
+ int ret;
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+
+ /* Detune/Unlock LCPLL */
+ ret = pll5g_detune(phydev);
+ if (ret)
+ return ret;
+
+ /* 0. Reset RCPLL */
+ ret = vsc85xx_sd6g_pll_cfg_wr(phydev, 3, pll_fsm_ctrl_data, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_common_cfg_wr(phydev, 0, 0, 0, qrate, if_mode, 0);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_des_cfg_wr(phydev, 6, 2, 5, des_bw_ana_val, 0);
+ if (ret)
+ return ret;
+
+ /* 1. Configure sd6g for SGMII prior to sd6g_IB_CAL */
+ ib_rtrm_adj = 13;
+ ret = vsc85xx_sd6g_ib_cfg0_wr(phydev, ib_rtrm_adj, ib_sig_det_clk_sel_mm, 0, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg1_wr(phydev, 8, ib_tsdet_mm, 15, 0, 1);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg2_wr(phydev, 3, 13, 5);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg3_wr(phydev, 0, 31, 1, 31);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg4_wr(phydev, 63, 63, 2, 63);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_common_cfg_wr(phydev, 1, 1, 0, qrate, if_mode, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_misc_cfg_wr(phydev, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 2. Start rcpll_fsm */
+ ret = vsc85xx_sd6g_pll_cfg_wr(phydev, 3, pll_fsm_ctrl_data, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ usleep_range(500, 1000);
+ ret = phy_update_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ val32 = vsc85xx_csr_read(phydev, MACRO_CTRL,
+ PHY_S6G_PLL_STATUS);
+ /* wait for bit 12 to clear */
+ } while (time_before(jiffies, deadline) && (val32 & BIT(12)));
+
+ if (val32 & BIT(12))
+ return -ETIMEDOUT;
+
+ /* 4. Release digital reset and disable transmitter */
+ ret = vsc85xx_sd6g_misc_cfg_wr(phydev, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_common_cfg_wr(phydev, 1, 1, 0, qrate, if_mode, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 5. Apply a frequency offset on RX-side (using internal FoJi logic) */
+ ret = vsc85xx_sd6g_gp_cfg_wr(phydev, 768);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_dft_cfg2_wr(phydev, 0, 2, 0, 0, 0, 1);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_dft_cfg0_wr(phydev, 0, 0, 1);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_des_cfg_wr(phydev, 6, 2, 5, des_bw_ana_val, 2);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 6. Prepare required settings for IBCAL */
+ ret = vsc85xx_sd6g_ib_cfg1_wr(phydev, 8, ib_tsdet_cal, 15, 1, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg0_wr(phydev, ib_rtrm_adj, ib_sig_det_clk_sel_cal, 0, 0);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 7. Start IB_CAL */
+ ret = vsc85xx_sd6g_ib_cfg0_wr(phydev, ib_rtrm_adj,
+ ib_sig_det_clk_sel_cal, 0, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ /* 11 cycles (for ViperA) or 5 cycles (for ViperB & Elise) w/ SW clock */
+ for (iter = 0; iter < gp_iter; iter++) {
+ /* set gp(0) */
+ ret = vsc85xx_sd6g_gp_cfg_wr(phydev, 769);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ /* clear gp(0) */
+ ret = vsc85xx_sd6g_gp_cfg_wr(phydev, 768);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ }
+
+ ret = vsc85xx_sd6g_ib_cfg1_wr(phydev, 8, ib_tsdet_cal, 15, 1, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg1_wr(phydev, 8, ib_tsdet_cal, 15, 0, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 8. Wait for IB cal to complete */
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ usleep_range(500, 1000);
+ ret = phy_update_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ val32 = vsc85xx_csr_read(phydev, MACRO_CTRL,
+ PHY_S6G_IB_STATUS0);
+ /* wait for bit 8 to set */
+ } while (time_before(jiffies, deadline) && (~val32 & BIT(8)));
+
+ if (~val32 & BIT(8))
+ return -ETIMEDOUT;
+
+ /* 9. Restore cfg values for mission mode */
+ ret = vsc85xx_sd6g_ib_cfg0_wr(phydev, ib_rtrm_adj, ib_sig_det_clk_sel_mm, 0, 1);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg1_wr(phydev, 8, ib_tsdet_mm, 15, 0, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 10. Re-enable transmitter */
+ ret = vsc85xx_sd6g_common_cfg_wr(phydev, 1, 1, 0, qrate, if_mode, 0);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 11. Disable frequency offset generation (using internal FoJi logic) */
+ ret = vsc85xx_sd6g_dft_cfg2_wr(phydev, 0, 0, 0, 0, 0, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_dft_cfg0_wr(phydev, 0, 0, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_des_cfg_wr(phydev, 6, 2, 5, des_bw_ana_val, 0);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* Tune/Re-lock LCPLL */
+ ret = pll5g_tune(phydev);
+ if (ret)
+ return ret;
+
+ /* 12. Configure for Final Configuration and Settings */
+ /* a. Reset RCPLL */
+ ret = vsc85xx_sd6g_pll_cfg_wr(phydev, 3, pll_fsm_ctrl_data, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_common_cfg_wr(phydev, 0, 1, 0, qrate, if_mode, 0);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* b. Configure sd6g for desired operating mode */
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_EXTENDED_GPIO);
+ ret = phy_base_read(phydev, MSCC_PHY_MAC_CFG_FASTLINK);
+ if ((ret & MAC_CFG_MASK) == MAC_CFG_QSGMII) {
+ /* QSGMII */
+ pll_fsm_ctrl_data = 120;
+ qrate = 0;
+ if_mode = 3;
+ des_bw_ana_val = 5;
+ val = PROC_CMD_MCB_ACCESS_MAC_CONF | PROC_CMD_RST_CONF_PORT |
+ PROC_CMD_READ_MOD_WRITE_PORT | PROC_CMD_QSGMII_MAC;
+
+ ret = vsc8584_cmd(phydev, val);
+ if (ret) {
+ dev_err(&phydev->mdio.dev, "%s: QSGMII error: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+ } else if ((ret & MAC_CFG_MASK) == MAC_CFG_SGMII) {
+ /* SGMII */
+ pll_fsm_ctrl_data = 60;
+ qrate = 1;
+ if_mode = 1;
+ des_bw_ana_val = 3;
+
+ val = PROC_CMD_MCB_ACCESS_MAC_CONF | PROC_CMD_RST_CONF_PORT |
+ PROC_CMD_READ_MOD_WRITE_PORT | PROC_CMD_SGMII_MAC;
+
+ ret = vsc8584_cmd(phydev, val);
+ if (ret) {
+ dev_err(&phydev->mdio.dev, "%s: SGMII error: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ phy_base_write(phydev, MSCC_EXT_PAGE_ACCESS, MSCC_PHY_PAGE_STANDARD);
+ } else {
+ dev_err(&phydev->mdio.dev, "%s: invalid mac_if: %x\n",
+ __func__, ret);
+ }
+
+ ret = phy_update_mcb_s6g(phydev, PHY_S6G_LCPLL_CFG, 0);
+ if (ret)
+ return ret;
+ ret = phy_update_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_pll5g_cfg0_wr(phydev, 4);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_S6G_LCPLL_CFG, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_des_cfg_wr(phydev, 6, 2, 5, des_bw_ana_val, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg0_wr(phydev, ib_rtrm_adj, ib_sig_det_clk_sel_mm, 0, 1);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg1_wr(phydev, 8, ib_tsdet_mm, 15, 0, 1);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_common_cfg_wr(phydev, 1, 1, 0, qrate, if_mode, 0);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg2_wr(phydev, 3, 13, 5);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg3_wr(phydev, 0, 31, 1, 31);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_ib_cfg4_wr(phydev, 63, 63, 2, 63);
+ if (ret)
+ return ret;
+ ret = vsc85xx_sd6g_misc_cfg_wr(phydev, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 13. Start rcpll_fsm */
+ ret = vsc85xx_sd6g_pll_cfg_wr(phydev, 3, pll_fsm_ctrl_data, 1);
+ if (ret)
+ return ret;
+ ret = phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+
+ /* 14. Wait for PLL cal to complete */
+ deadline = jiffies + msecs_to_jiffies(PROC_CMD_NCOMPLETED_TIMEOUT_MS);
+ do {
+ usleep_range(500, 1000);
+ ret = phy_update_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+ if (ret)
+ return ret;
+ val32 = vsc85xx_csr_read(phydev, MACRO_CTRL,
+ PHY_S6G_PLL_STATUS);
+ /* wait for bit 12 to clear */
+ } while (time_before(jiffies, deadline) && (val32 & BIT(12)));
+
+ if (val32 & BIT(12))
+ return -ETIMEDOUT;
+
+ /* release lane reset */
+ ret = vsc85xx_sd6g_misc_cfg_wr(phydev, 0);
+ if (ret)
+ return ret;
+
+ return phy_commit_mcb_s6g(phydev, PHY_MCB_S6G_CFG, 0);
+}
diff --git a/drivers/net/phy/mscc/mscc_serdes.h b/drivers/net/phy/mscc/mscc_serdes.h
new file mode 100644
index 000000000..2a6371322
--- /dev/null
+++ b/drivers/net/phy/mscc/mscc_serdes.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Driver for Microsemi VSC85xx PHYs
+ *
+ * Copyright (c) 2021 Microsemi Corporation
+ */
+
+#ifndef _MSCC_SERDES_PHY_H_
+#define _MSCC_SERDES_PHY_H_
+
+#define PHY_S6G_PLL5G_CFG2_GAIN_MASK GENMASK(9, 5)
+#define PHY_S6G_PLL5G_CFG2_ENA_GAIN 1
+
+#define PHY_S6G_DES_PHY_CTRL_POS 13
+#define PHY_S6G_DES_MBTR_CTRL_POS 10
+#define PHY_S6G_DES_CPMD_SEL_POS 8
+#define PHY_S6G_DES_BW_HYST_POS 5
+#define PHY_S6G_DES_BW_ANA_POS 1
+#define PHY_S6G_DES_CFG 0x21
+#define PHY_S6G_IB_CFG0 0x22
+#define PHY_S6G_IB_CFG1 0x23
+#define PHY_S6G_IB_CFG2 0x24
+#define PHY_S6G_IB_CFG3 0x25
+#define PHY_S6G_IB_CFG4 0x26
+#define PHY_S6G_GP_CFG 0x2E
+#define PHY_S6G_DFT_CFG0 0x35
+#define PHY_S6G_IB_DFT_CFG2 0x37
+
+int vsc85xx_sd6g_config_v2(struct phy_device *phydev);
+
+#endif /* _MSCC_PHY_SERDES_H_ */
diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c
new file mode 100644
index 000000000..ea1073adc
--- /dev/null
+++ b/drivers/net/phy/mxl-gpy.c
@@ -0,0 +1,1043 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2021 Maxlinear Corporation
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * Drivers for Maxlinear Ethernet GPY
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/mutex.h>
+#include <linux/phy.h>
+#include <linux/polynomial.h>
+#include <linux/property.h>
+#include <linux/netdevice.h>
+
+/* PHY ID */
+#define PHY_ID_GPYx15B_MASK 0xFFFFFFFC
+#define PHY_ID_GPY21xB_MASK 0xFFFFFFF9
+#define PHY_ID_GPY2xx 0x67C9DC00
+#define PHY_ID_GPY115B 0x67C9DF00
+#define PHY_ID_GPY115C 0x67C9DF10
+#define PHY_ID_GPY211B 0x67C9DE08
+#define PHY_ID_GPY211C 0x67C9DE10
+#define PHY_ID_GPY212B 0x67C9DE09
+#define PHY_ID_GPY212C 0x67C9DE20
+#define PHY_ID_GPY215B 0x67C9DF04
+#define PHY_ID_GPY215C 0x67C9DF20
+#define PHY_ID_GPY241B 0x67C9DE40
+#define PHY_ID_GPY241BM 0x67C9DE80
+#define PHY_ID_GPY245B 0x67C9DEC0
+
+#define PHY_CTL1 0x13
+#define PHY_CTL1_MDICD BIT(3)
+#define PHY_CTL1_MDIAB BIT(2)
+#define PHY_CTL1_AMDIX BIT(0)
+#define PHY_MIISTAT 0x18 /* MII state */
+#define PHY_IMASK 0x19 /* interrupt mask */
+#define PHY_ISTAT 0x1A /* interrupt status */
+#define PHY_FWV 0x1E /* firmware version */
+
+#define PHY_MIISTAT_SPD_MASK GENMASK(2, 0)
+#define PHY_MIISTAT_DPX BIT(3)
+#define PHY_MIISTAT_LS BIT(10)
+
+#define PHY_MIISTAT_SPD_10 0
+#define PHY_MIISTAT_SPD_100 1
+#define PHY_MIISTAT_SPD_1000 2
+#define PHY_MIISTAT_SPD_2500 4
+
+#define PHY_IMASK_WOL BIT(15) /* Wake-on-LAN */
+#define PHY_IMASK_ANC BIT(10) /* Auto-Neg complete */
+#define PHY_IMASK_ADSC BIT(5) /* Link auto-downspeed detect */
+#define PHY_IMASK_DXMC BIT(2) /* Duplex mode change */
+#define PHY_IMASK_LSPC BIT(1) /* Link speed change */
+#define PHY_IMASK_LSTC BIT(0) /* Link state change */
+#define PHY_IMASK_MASK (PHY_IMASK_LSTC | \
+ PHY_IMASK_LSPC | \
+ PHY_IMASK_DXMC | \
+ PHY_IMASK_ADSC | \
+ PHY_IMASK_ANC)
+
+#define PHY_FWV_REL_MASK BIT(15)
+#define PHY_FWV_MAJOR_MASK GENMASK(11, 8)
+#define PHY_FWV_MINOR_MASK GENMASK(7, 0)
+
+#define PHY_PMA_MGBT_POLARITY 0x82
+#define PHY_MDI_MDI_X_MASK GENMASK(1, 0)
+#define PHY_MDI_MDI_X_NORMAL 0x3
+#define PHY_MDI_MDI_X_AB 0x2
+#define PHY_MDI_MDI_X_CD 0x1
+#define PHY_MDI_MDI_X_CROSS 0x0
+
+/* SGMII */
+#define VSPEC1_SGMII_CTRL 0x08
+#define VSPEC1_SGMII_CTRL_ANEN BIT(12) /* Aneg enable */
+#define VSPEC1_SGMII_CTRL_ANRS BIT(9) /* Restart Aneg */
+#define VSPEC1_SGMII_ANEN_ANRS (VSPEC1_SGMII_CTRL_ANEN | \
+ VSPEC1_SGMII_CTRL_ANRS)
+
+/* Temperature sensor */
+#define VSPEC1_TEMP_STA 0x0E
+#define VSPEC1_TEMP_STA_DATA GENMASK(9, 0)
+
+/* Mailbox */
+#define VSPEC1_MBOX_DATA 0x5
+#define VSPEC1_MBOX_ADDRLO 0x6
+#define VSPEC1_MBOX_CMD 0x7
+#define VSPEC1_MBOX_CMD_ADDRHI GENMASK(7, 0)
+#define VSPEC1_MBOX_CMD_RD (0 << 8)
+#define VSPEC1_MBOX_CMD_READY BIT(15)
+
+/* WoL */
+#define VPSPEC2_WOL_CTL 0x0E06
+#define VPSPEC2_WOL_AD01 0x0E08
+#define VPSPEC2_WOL_AD23 0x0E09
+#define VPSPEC2_WOL_AD45 0x0E0A
+#define WOL_EN BIT(0)
+
+/* Internal registers, access via mbox */
+#define REG_GPIO0_OUT 0xd3ce00
+
+struct gpy_priv {
+ /* serialize mailbox acesses */
+ struct mutex mbox_lock;
+
+ u8 fw_major;
+ u8 fw_minor;
+
+ /* It takes 3 seconds to fully switch out of loopback mode before
+ * it can safely re-enter loopback mode. Record the time when
+ * loopback is disabled. Check and wait if necessary before loopback
+ * is enabled.
+ */
+ u64 lb_dis_to;
+};
+
+static const struct {
+ int major;
+ int minor;
+} ver_need_sgmii_reaneg[] = {
+ {7, 0x6D},
+ {8, 0x6D},
+ {9, 0x73},
+};
+
+#if IS_ENABLED(CONFIG_HWMON)
+/* The original translation formulae of the temperature (in degrees of Celsius)
+ * are as follows:
+ *
+ * T = -2.5761e-11*(N^4) + 9.7332e-8*(N^3) + -1.9165e-4*(N^2) +
+ * 3.0762e-1*(N^1) + -5.2156e1
+ *
+ * where [-52.156, 137.961]C and N = [0, 1023].
+ *
+ * They must be accordingly altered to be suitable for the integer arithmetics.
+ * The technique is called 'factor redistribution', which just makes sure the
+ * multiplications and divisions are made so to have a result of the operations
+ * within the integer numbers limit. In addition we need to translate the
+ * formulae to accept millidegrees of Celsius. Here what it looks like after
+ * the alterations:
+ *
+ * T = -25761e-12*(N^4) + 97332e-9*(N^3) + -191650e-6*(N^2) +
+ * 307620e-3*(N^1) + -52156
+ *
+ * where T = [-52156, 137961]mC and N = [0, 1023].
+ */
+static const struct polynomial poly_N_to_temp = {
+ .terms = {
+ {4, -25761, 1000, 1},
+ {3, 97332, 1000, 1},
+ {2, -191650, 1000, 1},
+ {1, 307620, 1000, 1},
+ {0, -52156, 1, 1}
+ }
+};
+
+static int gpy_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 ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_TEMP_STA);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -ENODATA;
+
+ *value = polynomial_calc(&poly_N_to_temp,
+ FIELD_GET(VSPEC1_TEMP_STA_DATA, ret));
+
+ return 0;
+}
+
+static umode_t gpy_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ return 0444;
+}
+
+static const struct hwmon_channel_info * const gpy_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops gpy_hwmon_hwmon_ops = {
+ .is_visible = gpy_hwmon_is_visible,
+ .read = gpy_hwmon_read,
+};
+
+static const struct hwmon_chip_info gpy_hwmon_chip_info = {
+ .ops = &gpy_hwmon_hwmon_ops,
+ .info = gpy_hwmon_info,
+};
+
+static int gpy_hwmon_register(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct device *hwmon_dev;
+ char *hwmon_name;
+
+ hwmon_name = devm_hwmon_sanitize_name(dev, dev_name(dev));
+ if (IS_ERR(hwmon_name))
+ return PTR_ERR(hwmon_name);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
+ phydev,
+ &gpy_hwmon_chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+#else
+static int gpy_hwmon_register(struct phy_device *phydev)
+{
+ return 0;
+}
+#endif
+
+static int gpy_mbox_read(struct phy_device *phydev, u32 addr)
+{
+ struct gpy_priv *priv = phydev->priv;
+ int val, ret;
+ u16 cmd;
+
+ mutex_lock(&priv->mbox_lock);
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_ADDRLO,
+ addr);
+ if (ret)
+ goto out;
+
+ cmd = VSPEC1_MBOX_CMD_RD;
+ cmd |= FIELD_PREP(VSPEC1_MBOX_CMD_ADDRHI, addr >> 16);
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_CMD, cmd);
+ if (ret)
+ goto out;
+
+ /* The mbox read is used in the interrupt workaround. It was observed
+ * that a read might take up to 2.5ms. This is also the time for which
+ * the interrupt line is stuck low. To be on the safe side, poll the
+ * ready bit for 10ms.
+ */
+ ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ VSPEC1_MBOX_CMD, val,
+ (val & VSPEC1_MBOX_CMD_READY),
+ 500, 10000, false);
+ if (ret)
+ goto out;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_DATA);
+
+out:
+ mutex_unlock(&priv->mbox_lock);
+ return ret;
+}
+
+static int gpy_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Mask all interrupts */
+ ret = phy_write(phydev, PHY_IMASK, 0);
+ if (ret)
+ return ret;
+
+ /* Clear all pending interrupts */
+ ret = phy_read(phydev, PHY_ISTAT);
+ return ret < 0 ? ret : 0;
+}
+
+static int gpy_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct gpy_priv *priv;
+ int fw_version;
+ int ret;
+
+ if (!phydev->is_c45) {
+ ret = phy_get_c45_ids(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ phydev->priv = priv;
+ mutex_init(&priv->mbox_lock);
+
+ if (!device_property_present(dev, "maxlinear,use-broken-interrupts"))
+ phydev->dev_flags |= PHY_F_NO_IRQ;
+
+ fw_version = phy_read(phydev, PHY_FWV);
+ if (fw_version < 0)
+ return fw_version;
+ priv->fw_major = FIELD_GET(PHY_FWV_MAJOR_MASK, fw_version);
+ priv->fw_minor = FIELD_GET(PHY_FWV_MINOR_MASK, fw_version);
+
+ ret = gpy_hwmon_register(phydev);
+ if (ret)
+ return ret;
+
+ /* Show GPY PHY FW version in dmesg */
+ phydev_info(phydev, "Firmware Version: %d.%d (0x%04X%s)\n",
+ priv->fw_major, priv->fw_minor, fw_version,
+ fw_version & PHY_FWV_REL_MASK ? "" : " test version");
+
+ return 0;
+}
+
+static bool gpy_sgmii_need_reaneg(struct phy_device *phydev)
+{
+ struct gpy_priv *priv = phydev->priv;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(ver_need_sgmii_reaneg); i++) {
+ if (priv->fw_major != ver_need_sgmii_reaneg[i].major)
+ continue;
+ if (priv->fw_minor < ver_need_sgmii_reaneg[i].minor)
+ return true;
+ break;
+ }
+
+ return false;
+}
+
+static bool gpy_2500basex_chk(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, PHY_MIISTAT);
+ if (ret < 0) {
+ phydev_err(phydev, "Error: MDIO register access failed: %d\n",
+ ret);
+ return false;
+ }
+
+ if (!(ret & PHY_MIISTAT_LS) ||
+ FIELD_GET(PHY_MIISTAT_SPD_MASK, ret) != PHY_MIISTAT_SPD_2500)
+ return false;
+
+ phydev->speed = SPEED_2500;
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
+ VSPEC1_SGMII_CTRL_ANEN, 0);
+ return true;
+}
+
+static bool gpy_sgmii_aneg_en(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL);
+ if (ret < 0) {
+ phydev_err(phydev, "Error: MMD register access failed: %d\n",
+ ret);
+ return true;
+ }
+
+ return (ret & VSPEC1_SGMII_CTRL_ANEN) ? true : false;
+}
+
+static int gpy_config_mdix(struct phy_device *phydev, u8 ctrl)
+{
+ int ret;
+ u16 val;
+
+ switch (ctrl) {
+ case ETH_TP_MDI_AUTO:
+ val = PHY_CTL1_AMDIX;
+ break;
+ case ETH_TP_MDI_X:
+ val = (PHY_CTL1_MDIAB | PHY_CTL1_MDICD);
+ break;
+ case ETH_TP_MDI:
+ val = 0;
+ break;
+ default:
+ return 0;
+ }
+
+ ret = phy_modify(phydev, PHY_CTL1, PHY_CTL1_AMDIX | PHY_CTL1_MDIAB |
+ PHY_CTL1_MDICD, val);
+ if (ret < 0)
+ return ret;
+
+ return genphy_c45_restart_aneg(phydev);
+}
+
+static int gpy_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ u32 adv;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE) {
+ /* Configure half duplex with genphy_setup_forced,
+ * because genphy_c45_pma_setup_forced does not support.
+ */
+ return phydev->duplex != DUPLEX_FULL
+ ? genphy_setup_forced(phydev)
+ : genphy_c45_pma_setup_forced(phydev);
+ }
+
+ ret = gpy_config_mdix(phydev, phydev->mdix_ctrl);
+ if (ret < 0)
+ return ret;
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+ ret = phy_modify_changed(phydev, MII_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ adv);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ ret = genphy_c45_check_and_restart_aneg(phydev, changed);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
+ phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
+ return 0;
+
+ /* No need to trigger re-ANEG if link speed is 2.5G or SGMII ANEG is
+ * disabled.
+ */
+ if (!gpy_sgmii_need_reaneg(phydev) || gpy_2500basex_chk(phydev) ||
+ !gpy_sgmii_aneg_en(phydev))
+ return 0;
+
+ /* There is a design constraint in GPY2xx device where SGMII AN is
+ * only triggered when there is change of speed. If, PHY link
+ * partner`s speed is still same even after PHY TPI is down and up
+ * again, SGMII AN is not triggered and hence no new in-band message
+ * from GPY to MAC side SGMII.
+ * This could cause an issue during power up, when PHY is up prior to
+ * MAC. At this condition, once MAC side SGMII is up, MAC side SGMII
+ * wouldn`t receive new in-band message from GPY with correct link
+ * status, speed and duplex info.
+ *
+ * 1) If PHY is already up and TPI link status is still down (such as
+ * hard reboot), TPI link status is polled for 4 seconds before
+ * retriggerring SGMII AN.
+ * 2) If PHY is already up and TPI link status is also up (such as soft
+ * reboot), polling of TPI link status is not needed and SGMII AN is
+ * immediately retriggered.
+ * 3) Other conditions such as PHY is down, speed change etc, skip
+ * retriggering SGMII AN. Note: in case of speed change, GPY FW will
+ * initiate SGMII AN.
+ */
+
+ if (phydev->state != PHY_UP)
+ return 0;
+
+ ret = phy_read_poll_timeout(phydev, MII_BMSR, ret, ret & BMSR_LSTATUS,
+ 20000, 4000000, false);
+ if (ret == -ETIMEDOUT)
+ return 0;
+ else if (ret < 0)
+ return ret;
+
+ /* Trigger SGMII AN. */
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
+ VSPEC1_SGMII_CTRL_ANRS, VSPEC1_SGMII_CTRL_ANRS);
+}
+
+static int gpy_update_mdix(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, PHY_CTL1);
+ if (ret < 0)
+ return ret;
+
+ if (ret & PHY_CTL1_AMDIX)
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ else
+ if (ret & PHY_CTL1_MDICD || ret & PHY_CTL1_MDIAB)
+ phydev->mdix_ctrl = ETH_TP_MDI_X;
+ else
+ phydev->mdix_ctrl = ETH_TP_MDI;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PHY_PMA_MGBT_POLARITY);
+ if (ret < 0)
+ return ret;
+
+ if ((ret & PHY_MDI_MDI_X_MASK) < PHY_MDI_MDI_X_NORMAL)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+
+ return 0;
+}
+
+static int gpy_update_interface(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Interface mode is fixed for USXGMII and integrated PHY */
+ if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
+ phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
+ return -EINVAL;
+
+ /* Automatically switch SERDES interface between SGMII and 2500-BaseX
+ * according to speed. Disable ANEG in 2500-BaseX mode.
+ */
+ switch (phydev->speed) {
+ case SPEED_2500:
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
+ VSPEC1_SGMII_CTRL_ANEN, 0);
+ if (ret < 0) {
+ phydev_err(phydev,
+ "Error: Disable of SGMII ANEG failed: %d\n",
+ ret);
+ return ret;
+ }
+ break;
+ case SPEED_1000:
+ case SPEED_100:
+ case SPEED_10:
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ if (gpy_sgmii_aneg_en(phydev))
+ break;
+ /* Enable and restart SGMII ANEG for 10/100/1000Mbps link speed
+ * if ANEG is disabled (in 2500-BaseX mode).
+ */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
+ VSPEC1_SGMII_ANEN_ANRS,
+ VSPEC1_SGMII_ANEN_ANRS);
+ if (ret < 0) {
+ phydev_err(phydev,
+ "Error: Enable of SGMII ANEG failed: %d\n",
+ ret);
+ return ret;
+ }
+ break;
+ }
+
+ if (phydev->speed == SPEED_2500 || phydev->speed == SPEED_1000) {
+ ret = genphy_read_master_slave(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return gpy_update_mdix(phydev);
+}
+
+static int gpy_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_update_link(phydev);
+ if (ret)
+ return ret;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
+ ret = genphy_c45_read_lpa(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* Read the link partner's 1G advertisement */
+ ret = phy_read(phydev, MII_STAT1000);
+ if (ret < 0)
+ return ret;
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, ret);
+ } else if (phydev->autoneg == AUTONEG_DISABLE) {
+ linkmode_zero(phydev->lp_advertising);
+ }
+
+ ret = phy_read(phydev, PHY_MIISTAT);
+ if (ret < 0)
+ return ret;
+
+ phydev->link = (ret & PHY_MIISTAT_LS) ? 1 : 0;
+ phydev->duplex = (ret & PHY_MIISTAT_DPX) ? DUPLEX_FULL : DUPLEX_HALF;
+ switch (FIELD_GET(PHY_MIISTAT_SPD_MASK, ret)) {
+ case PHY_MIISTAT_SPD_10:
+ phydev->speed = SPEED_10;
+ break;
+ case PHY_MIISTAT_SPD_100:
+ phydev->speed = SPEED_100;
+ break;
+ case PHY_MIISTAT_SPD_1000:
+ phydev->speed = SPEED_1000;
+ break;
+ case PHY_MIISTAT_SPD_2500:
+ phydev->speed = SPEED_2500;
+ break;
+ }
+
+ if (phydev->link) {
+ ret = gpy_update_interface(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gpy_config_intr(struct phy_device *phydev)
+{
+ u16 mask = 0;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ mask = PHY_IMASK_MASK;
+
+ return phy_write(phydev, PHY_IMASK, mask);
+}
+
+static irqreturn_t gpy_handle_interrupt(struct phy_device *phydev)
+{
+ int reg;
+
+ reg = phy_read(phydev, PHY_ISTAT);
+ if (reg < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(reg & PHY_IMASK_MASK))
+ return IRQ_NONE;
+
+ /* The PHY might leave the interrupt line asserted even after PHY_ISTAT
+ * is read. To avoid interrupt storms, delay the interrupt handling as
+ * long as the PHY drives the interrupt line. An internal bus read will
+ * stall as long as the interrupt line is asserted, thus just read a
+ * random register here.
+ * Because we cannot access the internal bus at all while the interrupt
+ * is driven by the PHY, there is no way to make the interrupt line
+ * unstuck (e.g. by changing the pinmux to GPIO input) during that time
+ * frame. Therefore, polling is the best we can do and won't do any more
+ * harm.
+ * It was observed that this bug happens on link state and link speed
+ * changes independent of the firmware version.
+ */
+ if (reg & (PHY_IMASK_LSTC | PHY_IMASK_LSPC)) {
+ reg = gpy_mbox_read(phydev, REG_GPIO0_OUT);
+ if (reg < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+ }
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int gpy_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *attach_dev = phydev->attached_dev;
+ int ret;
+
+ if (wol->wolopts & WAKE_MAGIC) {
+ /* MAC address - Byte0:Byte1:Byte2:Byte3:Byte4:Byte5
+ * VPSPEC2_WOL_AD45 = Byte0:Byte1
+ * VPSPEC2_WOL_AD23 = Byte2:Byte3
+ * VPSPEC2_WOL_AD01 = Byte4:Byte5
+ */
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ VPSPEC2_WOL_AD45,
+ ((attach_dev->dev_addr[0] << 8) |
+ attach_dev->dev_addr[1]));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ VPSPEC2_WOL_AD23,
+ ((attach_dev->dev_addr[2] << 8) |
+ attach_dev->dev_addr[3]));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ VPSPEC2_WOL_AD01,
+ ((attach_dev->dev_addr[4] << 8) |
+ attach_dev->dev_addr[5]));
+ if (ret < 0)
+ return ret;
+
+ /* Enable the WOL interrupt */
+ ret = phy_write(phydev, PHY_IMASK, PHY_IMASK_WOL);
+ if (ret < 0)
+ return ret;
+
+ /* Enable magic packet matching */
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ VPSPEC2_WOL_CTL,
+ WOL_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Clear the interrupt status register.
+ * Only WoL is enabled so clear all.
+ */
+ ret = phy_read(phydev, PHY_ISTAT);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* Disable magic packet matching */
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+ VPSPEC2_WOL_CTL,
+ WOL_EN);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (wol->wolopts & WAKE_PHY) {
+ /* Enable the link state change interrupt */
+ ret = phy_set_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
+ if (ret < 0)
+ return ret;
+
+ /* Clear the interrupt status register */
+ ret = phy_read(phydev, PHY_ISTAT);
+ if (ret < 0)
+ return ret;
+
+ if (ret & (PHY_IMASK_MASK & ~PHY_IMASK_LSTC))
+ phy_trigger_machine(phydev);
+
+ return 0;
+ }
+
+ /* Disable the link state change interrupt */
+ return phy_clear_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
+}
+
+static void gpy_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ int ret;
+
+ wol->supported = WAKE_MAGIC | WAKE_PHY;
+ wol->wolopts = 0;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, VPSPEC2_WOL_CTL);
+ if (ret & WOL_EN)
+ wol->wolopts |= WAKE_MAGIC;
+
+ ret = phy_read(phydev, PHY_IMASK);
+ if (ret & PHY_IMASK_LSTC)
+ wol->wolopts |= WAKE_PHY;
+}
+
+static int gpy_loopback(struct phy_device *phydev, bool enable)
+{
+ struct gpy_priv *priv = phydev->priv;
+ u16 set = 0;
+ int ret;
+
+ if (enable) {
+ u64 now = get_jiffies_64();
+
+ /* wait until 3 seconds from last disable */
+ if (time_before64(now, priv->lb_dis_to))
+ msleep(jiffies64_to_msecs(priv->lb_dis_to - now));
+
+ set = BMCR_LOOPBACK;
+ }
+
+ ret = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, set);
+ if (ret <= 0)
+ return ret;
+
+ if (enable) {
+ /* It takes some time for PHY device to switch into
+ * loopback mode.
+ */
+ msleep(100);
+ } else {
+ priv->lb_dis_to = get_jiffies_64() + HZ * 3;
+ }
+
+ return 0;
+}
+
+static int gpy115_loopback(struct phy_device *phydev, bool enable)
+{
+ struct gpy_priv *priv = phydev->priv;
+
+ if (enable)
+ return gpy_loopback(phydev, enable);
+
+ if (priv->fw_minor > 0x76)
+ return gpy_loopback(phydev, 0);
+
+ return genphy_soft_reset(phydev);
+}
+
+static struct phy_driver gpy_drivers[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
+ .name = "Maxlinear Ethernet GPY2xx",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ .phy_id = PHY_ID_GPY115B,
+ .phy_id_mask = PHY_ID_GPYx15B_MASK,
+ .name = "Maxlinear Ethernet GPY115B",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy115_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
+ .name = "Maxlinear Ethernet GPY115C",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy115_loopback,
+ },
+ {
+ .phy_id = PHY_ID_GPY211B,
+ .phy_id_mask = PHY_ID_GPY21xB_MASK,
+ .name = "Maxlinear Ethernet GPY211B",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
+ .name = "Maxlinear Ethernet GPY211C",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ .phy_id = PHY_ID_GPY212B,
+ .phy_id_mask = PHY_ID_GPY21xB_MASK,
+ .name = "Maxlinear Ethernet GPY212B",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
+ .name = "Maxlinear Ethernet GPY212C",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ .phy_id = PHY_ID_GPY215B,
+ .phy_id_mask = PHY_ID_GPYx15B_MASK,
+ .name = "Maxlinear Ethernet GPY215B",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
+ .name = "Maxlinear Ethernet GPY215C",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
+ .name = "Maxlinear Ethernet GPY241B",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM),
+ .name = "Maxlinear Ethernet GPY241BM",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY245B),
+ .name = "Maxlinear Ethernet GPY245B",
+ .get_features = genphy_c45_pma_read_abilities,
+ .config_init = gpy_config_init,
+ .probe = gpy_probe,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_aneg = gpy_config_aneg,
+ .aneg_done = genphy_c45_aneg_done,
+ .read_status = gpy_read_status,
+ .config_intr = gpy_config_intr,
+ .handle_interrupt = gpy_handle_interrupt,
+ .set_wol = gpy_set_wol,
+ .get_wol = gpy_get_wol,
+ .set_loopback = gpy_loopback,
+ },
+};
+module_phy_driver(gpy_drivers);
+
+static struct mdio_device_id __maybe_unused gpy_tbl[] = {
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx)},
+ {PHY_ID_GPY115B, PHY_ID_GPYx15B_MASK},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY115C)},
+ {PHY_ID_GPY211B, PHY_ID_GPY21xB_MASK},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY211C)},
+ {PHY_ID_GPY212B, PHY_ID_GPY21xB_MASK},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY212C)},
+ {PHY_ID_GPY215B, PHY_ID_GPYx15B_MASK},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY215C)},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY241B)},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)},
+ {PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)},
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, gpy_tbl);
+
+MODULE_DESCRIPTION("Maxlinear Ethernet GPY Driver");
+MODULE_AUTHOR("Xu Liang");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/national.c b/drivers/net/phy/national.c
new file mode 100644
index 000000000..9ae9cc6b2
--- /dev/null
+++ b/drivers/net/phy/national.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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
+ */
+
+#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>
+
+/* 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_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 irqreturn_t ns_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, DP83865_INT_STATUS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & DP83865_INT_MASK_DEFAULT))
+ return IRQ_NONE;
+
+ /* clear the interrupt */
+ phy_write(phydev, DP83865_INT_CLEAR, irq_status & ~0x7);
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int ns_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = ns_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, DP83865_INT_MASK,
+ DP83865_INT_MASK_DEFAULT);
+ } else {
+ err = phy_write(phydev, DP83865_INT_MASK, 0);
+ if (err)
+ return err;
+
+ err = ns_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+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",
+ /* PHY_GBIT_FEATURES */
+ .config_init = ns_config_init,
+ .config_intr = ns_config_intr,
+ .handle_interrupt = ns_handle_interrupt,
+} };
+
+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/ncn26000.c b/drivers/net/phy/ncn26000.c
new file mode 100644
index 000000000..5680584f6
--- /dev/null
+++ b/drivers/net/phy/ncn26000.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * Driver for the onsemi 10BASE-T1S NCN26000 PHYs family.
+ *
+ * Copyright 2022 onsemi
+ */
+#include <linux/kernel.h>
+#include <linux/bitfield.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+#include "mdio-open-alliance.h"
+
+#define PHY_ID_NCN26000 0x180FF5A1
+
+#define NCN26000_REG_IRQ_CTL 16
+#define NCN26000_REG_IRQ_STATUS 17
+
+// the NCN26000 maps link_ctrl to BMCR_ANENABLE
+#define NCN26000_BCMR_LINK_CTRL_BIT BMCR_ANENABLE
+
+// the NCN26000 maps link_status to BMSR_ANEGCOMPLETE
+#define NCN26000_BMSR_LINK_STATUS_BIT BMSR_ANEGCOMPLETE
+
+#define NCN26000_IRQ_LINKST_BIT BIT(0)
+#define NCN26000_IRQ_PLCAST_BIT BIT(1)
+#define NCN26000_IRQ_LJABBER_BIT BIT(2)
+#define NCN26000_IRQ_RJABBER_BIT BIT(3)
+#define NCN26000_IRQ_PLCAREC_BIT BIT(4)
+#define NCN26000_IRQ_PHYSCOL_BIT BIT(5)
+#define NCN26000_IRQ_RESET_BIT BIT(15)
+
+#define TO_TMR_DEFAULT 32
+
+static int ncn26000_config_init(struct phy_device *phydev)
+{
+ /* HW bug workaround: the default value of the PLCA TO_TIMER should be
+ * 32, where the current version of NCN26000 reports 24. This will be
+ * fixed in future PHY versions. For the time being, we force the
+ * correct default here.
+ */
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
+ TO_TMR_DEFAULT);
+}
+
+static int ncn26000_config_aneg(struct phy_device *phydev)
+{
+ /* Note: the NCN26000 supports only P2MP link mode. Therefore, AN is not
+ * supported. However, this function is invoked by phylib to enable the
+ * PHY, regardless of the AN support.
+ */
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ phydev->mdix = ETH_TP_MDI;
+
+ // bring up the link
+ return phy_write(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT);
+}
+
+static int ncn26000_read_status(struct phy_device *phydev)
+{
+ /* The NCN26000 reports NCN26000_LINK_STATUS_BIT if the link status of
+ * the PHY is up. It further reports the logical AND of the link status
+ * and the PLCA status in the BMSR_LSTATUS bit.
+ */
+ int ret;
+
+ /* 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 except
+ * the link was already down.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ ret = phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ return ret;
+ else if (ret & NCN26000_BMSR_LINK_STATUS_BIT)
+ goto upd_link;
+ }
+
+ ret = phy_read(phydev, MII_BMSR);
+ if (ret < 0)
+ return ret;
+
+upd_link:
+ // update link status
+ if (ret & NCN26000_BMSR_LINK_STATUS_BIT) {
+ phydev->link = 1;
+ phydev->pause = 0;
+ phydev->duplex = DUPLEX_HALF;
+ phydev->speed = SPEED_10;
+ } else {
+ phydev->link = 0;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->speed = SPEED_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
+{
+ int ret;
+
+ // read and aknowledge the IRQ status register
+ ret = phy_read(phydev, NCN26000_REG_IRQ_STATUS);
+
+ // check only link status changes
+ if (ret < 0 || (ret & NCN26000_REG_IRQ_STATUS) == 0)
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+ return IRQ_HANDLED;
+}
+
+static int ncn26000_config_intr(struct phy_device *phydev)
+{
+ int ret;
+ u16 irqe;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ // acknowledge IRQs
+ ret = phy_read(phydev, NCN26000_REG_IRQ_STATUS);
+ if (ret < 0)
+ return ret;
+
+ // get link status notifications
+ irqe = NCN26000_IRQ_LINKST_BIT;
+ } else {
+ // disable all IRQs
+ irqe = 0;
+ }
+
+ ret = phy_write(phydev, NCN26000_REG_IRQ_CTL, irqe);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static struct phy_driver ncn26000_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_NCN26000),
+ .name = "NCN26000",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .config_init = ncn26000_config_init,
+ .config_intr = ncn26000_config_intr,
+ .config_aneg = ncn26000_config_aneg,
+ .read_status = ncn26000_read_status,
+ .handle_interrupt = ncn26000_handle_interrupt,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ .soft_reset = genphy_soft_reset,
+ },
+};
+
+module_phy_driver(ncn26000_driver);
+
+static struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, ncn26000_tbl);
+
+MODULE_AUTHOR("Piergiorgio Beruto");
+MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/nxp-c45-tja11xx.c b/drivers/net/phy/nxp-c45-tja11xx.c
new file mode 100644
index 000000000..7ab080ff0
--- /dev/null
+++ b/drivers/net/phy/nxp-c45-tja11xx.c
@@ -0,0 +1,1978 @@
+// SPDX-License-Identifier: GPL-2.0
+/* NXP C45 PHY driver
+ * Copyright (C) 2021 NXP
+ * Author: Radu Pirea <radu-nicolae.pirea@oss.nxp.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/processor.h>
+#include <linux/property.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+
+#define PHY_ID_TJA_1103 0x001BB010
+#define PHY_ID_TJA_1120 0x001BB031
+
+#define VEND1_DEVICE_CONTROL 0x0040
+#define DEVICE_CONTROL_RESET BIT(15)
+#define DEVICE_CONTROL_CONFIG_GLOBAL_EN BIT(14)
+#define DEVICE_CONTROL_CONFIG_ALL_EN BIT(13)
+
+#define VEND1_DEVICE_CONFIG 0x0048
+
+#define TJA1120_VEND1_EXT_TS_MODE 0x1012
+
+#define TJA1120_GLOBAL_INFRA_IRQ_ACK 0x2C08
+#define TJA1120_GLOBAL_INFRA_IRQ_EN 0x2C0A
+#define TJA1120_GLOBAL_INFRA_IRQ_STATUS 0x2C0C
+#define TJA1120_DEV_BOOT_DONE BIT(1)
+
+#define TJA1120_VEND1_PTP_TRIG_DATA_S 0x1070
+
+#define TJA1120_EGRESS_TS_DATA_S 0x9060
+#define TJA1120_EGRESS_TS_END 0x9067
+#define TJA1120_TS_VALID BIT(0)
+#define TJA1120_MORE_TS BIT(15)
+
+#define VEND1_PHY_IRQ_ACK 0x80A0
+#define VEND1_PHY_IRQ_EN 0x80A1
+#define VEND1_PHY_IRQ_STATUS 0x80A2
+#define PHY_IRQ_LINK_EVENT BIT(1)
+
+#define VEND1_ALWAYS_ACCESSIBLE 0x801F
+#define FUSA_PASS BIT(4)
+
+#define VEND1_PHY_CONTROL 0x8100
+#define PHY_CONFIG_EN BIT(14)
+#define PHY_START_OP BIT(0)
+
+#define VEND1_PHY_CONFIG 0x8108
+#define PHY_CONFIG_AUTO BIT(0)
+
+#define TJA1120_EPHY_RESETS 0x810A
+#define EPHY_PCS_RESET BIT(3)
+
+#define VEND1_SIGNAL_QUALITY 0x8320
+#define SQI_VALID BIT(14)
+#define SQI_MASK GENMASK(2, 0)
+#define MAX_SQI SQI_MASK
+
+#define CABLE_TEST_ENABLE BIT(15)
+#define CABLE_TEST_START BIT(14)
+#define CABLE_TEST_OK 0x00
+#define CABLE_TEST_SHORTED 0x01
+#define CABLE_TEST_OPEN 0x02
+#define CABLE_TEST_UNKNOWN 0x07
+
+#define VEND1_PORT_CONTROL 0x8040
+#define PORT_CONTROL_EN BIT(14)
+
+#define VEND1_PORT_ABILITIES 0x8046
+#define PTP_ABILITY BIT(3)
+
+#define VEND1_PORT_FUNC_IRQ_EN 0x807A
+#define PTP_IRQS BIT(3)
+
+#define VEND1_PTP_IRQ_ACK 0x9008
+#define EGR_TS_IRQ BIT(1)
+
+#define VEND1_PORT_INFRA_CONTROL 0xAC00
+#define PORT_INFRA_CONTROL_EN BIT(14)
+
+#define VEND1_RXID 0xAFCC
+#define VEND1_TXID 0xAFCD
+#define ID_ENABLE BIT(15)
+
+#define VEND1_ABILITIES 0xAFC4
+#define RGMII_ID_ABILITY BIT(15)
+#define RGMII_ABILITY BIT(14)
+#define RMII_ABILITY BIT(10)
+#define REVMII_ABILITY BIT(9)
+#define MII_ABILITY BIT(8)
+#define SGMII_ABILITY BIT(0)
+
+#define VEND1_MII_BASIC_CONFIG 0xAFC6
+#define MII_BASIC_CONFIG_REV BIT(4)
+#define MII_BASIC_CONFIG_SGMII 0x9
+#define MII_BASIC_CONFIG_RGMII 0x7
+#define MII_BASIC_CONFIG_RMII 0x5
+#define MII_BASIC_CONFIG_MII 0x4
+
+#define VEND1_SYMBOL_ERROR_CNT_XTD 0x8351
+#define EXTENDED_CNT_EN BIT(15)
+#define VEND1_MONITOR_STATUS 0xAC80
+#define MONITOR_RESET BIT(15)
+#define VEND1_MONITOR_CONFIG 0xAC86
+#define LOST_FRAMES_CNT_EN BIT(9)
+#define ALL_FRAMES_CNT_EN BIT(8)
+
+#define VEND1_SYMBOL_ERROR_COUNTER 0x8350
+#define VEND1_LINK_DROP_COUNTER 0x8352
+#define VEND1_LINK_LOSSES_AND_FAILURES 0x8353
+#define VEND1_RX_PREAMBLE_COUNT 0xAFCE
+#define VEND1_TX_PREAMBLE_COUNT 0xAFCF
+#define VEND1_RX_IPG_LENGTH 0xAFD0
+#define VEND1_TX_IPG_LENGTH 0xAFD1
+#define COUNTER_EN BIT(15)
+
+#define VEND1_PTP_CONFIG 0x1102
+#define EXT_TRG_EDGE BIT(1)
+
+#define TJA1120_SYNC_TRIG_FILTER 0x1010
+#define PTP_TRIG_RISE_TS BIT(3)
+#define PTP_TRIG_FALLING_TS BIT(2)
+
+#define CLK_RATE_ADJ_LD BIT(15)
+#define CLK_RATE_ADJ_DIR BIT(14)
+
+#define VEND1_RX_TS_INSRT_CTRL 0x114D
+#define TJA1103_RX_TS_INSRT_MODE2 0x02
+
+#define TJA1120_RX_TS_INSRT_CTRL 0x9012
+#define TJA1120_RX_TS_INSRT_EN BIT(15)
+#define TJA1120_TS_INSRT_MODE BIT(4)
+
+#define VEND1_EGR_RING_DATA_0 0x114E
+#define VEND1_EGR_RING_CTRL 0x1154
+
+#define RING_DATA_0_TS_VALID BIT(15)
+
+#define RING_DONE BIT(0)
+
+#define TS_SEC_MASK GENMASK(1, 0)
+
+#define VEND1_PORT_FUNC_ENABLES 0x8048
+#define PTP_ENABLE BIT(3)
+#define PHY_TEST_ENABLE BIT(0)
+
+#define VEND1_PORT_PTP_CONTROL 0x9000
+#define PORT_PTP_CONTROL_BYPASS BIT(11)
+
+#define PTP_CLK_PERIOD_100BT1 15ULL
+#define PTP_CLK_PERIOD_1000BT1 8ULL
+
+#define EVENT_MSG_FILT_ALL 0x0F
+#define EVENT_MSG_FILT_NONE 0x00
+
+#define VEND1_GPIO_FUNC_CONFIG_BASE 0x2C40
+#define GPIO_FUNC_EN BIT(15)
+#define GPIO_FUNC_PTP BIT(6)
+#define GPIO_SIGNAL_PTP_TRIGGER 0x01
+#define GPIO_SIGNAL_PPS_OUT 0x12
+#define GPIO_DISABLE 0
+#define GPIO_PPS_OUT_CFG (GPIO_FUNC_EN | GPIO_FUNC_PTP | \
+ GPIO_SIGNAL_PPS_OUT)
+#define GPIO_EXTTS_OUT_CFG (GPIO_FUNC_EN | GPIO_FUNC_PTP | \
+ GPIO_SIGNAL_PTP_TRIGGER)
+
+#define RGMII_PERIOD_PS 8000U
+#define PS_PER_DEGREE div_u64(RGMII_PERIOD_PS, 360)
+#define MIN_ID_PS 1644U
+#define MAX_ID_PS 2260U
+#define DEFAULT_ID_PS 2000U
+
+#define PPM_TO_SUBNS_INC(ppb, ptp_clk_period) div_u64(GENMASK_ULL(31, 0) * \
+ (ppb) * (ptp_clk_period), NSEC_PER_SEC)
+
+#define NXP_C45_SKB_CB(skb) ((struct nxp_c45_skb_cb *)(skb)->cb)
+
+struct nxp_c45_phy;
+
+struct nxp_c45_skb_cb {
+ struct ptp_header *header;
+ unsigned int type;
+};
+
+#define NXP_C45_REG_FIELD(_reg, _devad, _offset, _size) \
+ ((struct nxp_c45_reg_field) { \
+ .reg = _reg, \
+ .devad = _devad, \
+ .offset = _offset, \
+ .size = _size, \
+ })
+
+struct nxp_c45_reg_field {
+ u16 reg;
+ u8 devad;
+ u8 offset;
+ u8 size;
+};
+
+struct nxp_c45_hwts {
+ u32 nsec;
+ u32 sec;
+ u8 domain_number;
+ u16 sequence_id;
+ u8 msg_type;
+};
+
+struct nxp_c45_regmap {
+ /* PTP config regs. */
+ u16 vend1_ptp_clk_period;
+ u16 vend1_event_msg_filt;
+
+ /* LTC bits and regs. */
+ struct nxp_c45_reg_field ltc_read;
+ struct nxp_c45_reg_field ltc_write;
+ struct nxp_c45_reg_field ltc_lock_ctrl;
+ u16 vend1_ltc_wr_nsec_0;
+ u16 vend1_ltc_wr_nsec_1;
+ u16 vend1_ltc_wr_sec_0;
+ u16 vend1_ltc_wr_sec_1;
+ u16 vend1_ltc_rd_nsec_0;
+ u16 vend1_ltc_rd_nsec_1;
+ u16 vend1_ltc_rd_sec_0;
+ u16 vend1_ltc_rd_sec_1;
+ u16 vend1_rate_adj_subns_0;
+ u16 vend1_rate_adj_subns_1;
+
+ /* External trigger reg fields. */
+ struct nxp_c45_reg_field irq_egr_ts_en;
+ struct nxp_c45_reg_field irq_egr_ts_status;
+ struct nxp_c45_reg_field domain_number;
+ struct nxp_c45_reg_field msg_type;
+ struct nxp_c45_reg_field sequence_id;
+ struct nxp_c45_reg_field sec_1_0;
+ struct nxp_c45_reg_field sec_4_2;
+ struct nxp_c45_reg_field nsec_15_0;
+ struct nxp_c45_reg_field nsec_29_16;
+
+ /* PPS and EXT Trigger bits and regs. */
+ struct nxp_c45_reg_field pps_enable;
+ struct nxp_c45_reg_field pps_polarity;
+ u16 vend1_ext_trg_data_0;
+ u16 vend1_ext_trg_data_1;
+ u16 vend1_ext_trg_data_2;
+ u16 vend1_ext_trg_data_3;
+ u16 vend1_ext_trg_ctrl;
+
+ /* Cable test reg fields. */
+ u16 cable_test;
+ struct nxp_c45_reg_field cable_test_valid;
+ struct nxp_c45_reg_field cable_test_result;
+};
+
+struct nxp_c45_phy_stats {
+ const char *name;
+ const struct nxp_c45_reg_field counter;
+};
+
+struct nxp_c45_phy_data {
+ const struct nxp_c45_regmap *regmap;
+ const struct nxp_c45_phy_stats *stats;
+ int n_stats;
+ u8 ptp_clk_period;
+ bool ext_ts_both_edges;
+ bool ack_ptp_irq;
+ void (*counters_enable)(struct phy_device *phydev);
+ bool (*get_egressts)(struct nxp_c45_phy *priv,
+ struct nxp_c45_hwts *hwts);
+ bool (*get_extts)(struct nxp_c45_phy *priv, struct timespec64 *extts);
+ void (*ptp_init)(struct phy_device *phydev);
+ void (*ptp_enable)(struct phy_device *phydev, bool enable);
+ void (*nmi_handler)(struct phy_device *phydev,
+ irqreturn_t *irq_status);
+};
+
+struct nxp_c45_phy {
+ const struct nxp_c45_phy_data *phy_data;
+ struct phy_device *phydev;
+ struct mii_timestamper mii_ts;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info caps;
+ struct sk_buff_head tx_queue;
+ struct sk_buff_head rx_queue;
+ /* used to access the PTP registers atomic */
+ struct mutex ptp_lock;
+ int hwts_tx;
+ int hwts_rx;
+ u32 tx_delay;
+ u32 rx_delay;
+ struct timespec64 extts_ts;
+ int extts_index;
+ bool extts;
+};
+
+static const
+struct nxp_c45_phy_data *nxp_c45_get_data(struct phy_device *phydev)
+{
+ return phydev->drv->driver_data;
+}
+
+static const
+struct nxp_c45_regmap *nxp_c45_get_regmap(struct phy_device *phydev)
+{
+ const struct nxp_c45_phy_data *phy_data = nxp_c45_get_data(phydev);
+
+ return phy_data->regmap;
+}
+
+static int nxp_c45_read_reg_field(struct phy_device *phydev,
+ const struct nxp_c45_reg_field *reg_field)
+{
+ u16 mask;
+ int ret;
+
+ if (reg_field->size == 0) {
+ phydev_err(phydev, "Trying to read a reg field of size 0.\n");
+ return -EINVAL;
+ }
+
+ ret = phy_read_mmd(phydev, reg_field->devad, reg_field->reg);
+ if (ret < 0)
+ return ret;
+
+ mask = reg_field->size == 1 ? BIT(reg_field->offset) :
+ GENMASK(reg_field->offset + reg_field->size - 1,
+ reg_field->offset);
+ ret &= mask;
+ ret >>= reg_field->offset;
+
+ return ret;
+}
+
+static int nxp_c45_write_reg_field(struct phy_device *phydev,
+ const struct nxp_c45_reg_field *reg_field,
+ u16 val)
+{
+ u16 mask;
+ u16 set;
+
+ if (reg_field->size == 0) {
+ phydev_err(phydev, "Trying to write a reg field of size 0.\n");
+ return -EINVAL;
+ }
+
+ mask = reg_field->size == 1 ? BIT(reg_field->offset) :
+ GENMASK(reg_field->offset + reg_field->size - 1,
+ reg_field->offset);
+ set = val << reg_field->offset;
+
+ return phy_modify_mmd_changed(phydev, reg_field->devad,
+ reg_field->reg, mask, set);
+}
+
+static int nxp_c45_set_reg_field(struct phy_device *phydev,
+ const struct nxp_c45_reg_field *reg_field)
+{
+ if (reg_field->size != 1) {
+ phydev_err(phydev, "Trying to set a reg field of size different than 1.\n");
+ return -EINVAL;
+ }
+
+ return nxp_c45_write_reg_field(phydev, reg_field, 1);
+}
+
+static int nxp_c45_clear_reg_field(struct phy_device *phydev,
+ const struct nxp_c45_reg_field *reg_field)
+{
+ if (reg_field->size != 1) {
+ phydev_err(phydev, "Trying to set a reg field of size different than 1.\n");
+ return -EINVAL;
+ }
+
+ return nxp_c45_write_reg_field(phydev, reg_field, 0);
+}
+
+static bool nxp_c45_poll_txts(struct phy_device *phydev)
+{
+ return phydev->irq <= 0;
+}
+
+static int _nxp_c45_ptp_gettimex64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(priv->phydev);
+
+ nxp_c45_set_reg_field(priv->phydev, &regmap->ltc_read);
+ ts->tv_nsec = phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ltc_rd_nsec_0);
+ ts->tv_nsec |= phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ltc_rd_nsec_1) << 16;
+ ts->tv_sec = phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ltc_rd_sec_0);
+ ts->tv_sec |= phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ltc_rd_sec_1) << 16;
+
+ return 0;
+}
+
+static int nxp_c45_ptp_gettimex64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+
+ mutex_lock(&priv->ptp_lock);
+ _nxp_c45_ptp_gettimex64(ptp, ts, sts);
+ mutex_unlock(&priv->ptp_lock);
+
+ return 0;
+}
+
+static int _nxp_c45_ptp_settime64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(priv->phydev);
+
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1, regmap->vend1_ltc_wr_nsec_0,
+ ts->tv_nsec);
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1, regmap->vend1_ltc_wr_nsec_1,
+ ts->tv_nsec >> 16);
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1, regmap->vend1_ltc_wr_sec_0,
+ ts->tv_sec);
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1, regmap->vend1_ltc_wr_sec_1,
+ ts->tv_sec >> 16);
+ nxp_c45_set_reg_field(priv->phydev, &regmap->ltc_write);
+
+ return 0;
+}
+
+static int nxp_c45_ptp_settime64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+
+ mutex_lock(&priv->ptp_lock);
+ _nxp_c45_ptp_settime64(ptp, ts);
+ mutex_unlock(&priv->ptp_lock);
+
+ return 0;
+}
+
+static int nxp_c45_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+ const struct nxp_c45_phy_data *data = nxp_c45_get_data(priv->phydev);
+ const struct nxp_c45_regmap *regmap = data->regmap;
+ s32 ppb = scaled_ppm_to_ppb(scaled_ppm);
+ u64 subns_inc_val;
+ bool inc;
+
+ mutex_lock(&priv->ptp_lock);
+ inc = ppb >= 0;
+ ppb = abs(ppb);
+
+ subns_inc_val = PPM_TO_SUBNS_INC(ppb, data->ptp_clk_period);
+
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_rate_adj_subns_0,
+ subns_inc_val);
+ subns_inc_val >>= 16;
+ subns_inc_val |= CLK_RATE_ADJ_LD;
+ if (inc)
+ subns_inc_val |= CLK_RATE_ADJ_DIR;
+
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_rate_adj_subns_1,
+ subns_inc_val);
+ mutex_unlock(&priv->ptp_lock);
+
+ return 0;
+}
+
+static int nxp_c45_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+ struct timespec64 now, then;
+
+ mutex_lock(&priv->ptp_lock);
+ then = ns_to_timespec64(delta);
+ _nxp_c45_ptp_gettimex64(ptp, &now, NULL);
+ now = timespec64_add(now, then);
+ _nxp_c45_ptp_settime64(ptp, &now);
+ mutex_unlock(&priv->ptp_lock);
+
+ return 0;
+}
+
+static void nxp_c45_reconstruct_ts(struct timespec64 *ts,
+ struct nxp_c45_hwts *hwts)
+{
+ ts->tv_nsec = hwts->nsec;
+ if ((ts->tv_sec & TS_SEC_MASK) < (hwts->sec & TS_SEC_MASK))
+ ts->tv_sec -= TS_SEC_MASK + 1;
+ ts->tv_sec &= ~TS_SEC_MASK;
+ ts->tv_sec |= hwts->sec & TS_SEC_MASK;
+}
+
+static bool nxp_c45_match_ts(struct ptp_header *header,
+ struct nxp_c45_hwts *hwts,
+ unsigned int type)
+{
+ return ntohs(header->sequence_id) == hwts->sequence_id &&
+ ptp_get_msgtype(header, type) == hwts->msg_type &&
+ header->domain_number == hwts->domain_number;
+}
+
+static bool nxp_c45_get_extts(struct nxp_c45_phy *priv,
+ struct timespec64 *extts)
+{
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(priv->phydev);
+
+ extts->tv_nsec = phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_data_0);
+ extts->tv_nsec |= phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_data_1) << 16;
+ extts->tv_sec = phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_data_2);
+ extts->tv_sec |= phy_read_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_data_3) << 16;
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_ctrl, RING_DONE);
+
+ return true;
+}
+
+static bool tja1120_extts_is_valid(struct phy_device *phydev)
+{
+ bool valid;
+ int reg;
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_VEND1_PTP_TRIG_DATA_S);
+ valid = !!(reg & TJA1120_TS_VALID);
+
+ return valid;
+}
+
+static bool tja1120_get_extts(struct nxp_c45_phy *priv,
+ struct timespec64 *extts)
+{
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(priv->phydev);
+ struct phy_device *phydev = priv->phydev;
+ bool more_ts;
+ bool valid;
+ u16 reg;
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_ctrl);
+ more_ts = !!(reg & TJA1120_MORE_TS);
+
+ valid = tja1120_extts_is_valid(phydev);
+ if (!valid) {
+ if (!more_ts)
+ goto tja1120_get_extts_out;
+
+ /* Bug workaround for TJA1120 engineering samples: move the new
+ * timestamp from the FIFO to the buffer.
+ */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ regmap->vend1_ext_trg_ctrl, RING_DONE);
+ valid = tja1120_extts_is_valid(phydev);
+ if (!valid)
+ goto tja1120_get_extts_out;
+ }
+
+ nxp_c45_get_extts(priv, extts);
+tja1120_get_extts_out:
+ return valid;
+}
+
+static void nxp_c45_read_egress_ts(struct nxp_c45_phy *priv,
+ struct nxp_c45_hwts *hwts)
+{
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(priv->phydev);
+ struct phy_device *phydev = priv->phydev;
+
+ hwts->domain_number =
+ nxp_c45_read_reg_field(phydev, &regmap->domain_number);
+ hwts->msg_type =
+ nxp_c45_read_reg_field(phydev, &regmap->msg_type);
+ hwts->sequence_id =
+ nxp_c45_read_reg_field(phydev, &regmap->sequence_id);
+ hwts->nsec =
+ nxp_c45_read_reg_field(phydev, &regmap->nsec_15_0);
+ hwts->nsec |=
+ nxp_c45_read_reg_field(phydev, &regmap->nsec_29_16) << 16;
+ hwts->sec = nxp_c45_read_reg_field(phydev, &regmap->sec_1_0);
+ hwts->sec |= nxp_c45_read_reg_field(phydev, &regmap->sec_4_2) << 2;
+}
+
+static bool nxp_c45_get_hwtxts(struct nxp_c45_phy *priv,
+ struct nxp_c45_hwts *hwts)
+{
+ bool valid;
+ u16 reg;
+
+ mutex_lock(&priv->ptp_lock);
+ phy_write_mmd(priv->phydev, MDIO_MMD_VEND1, VEND1_EGR_RING_CTRL,
+ RING_DONE);
+ reg = phy_read_mmd(priv->phydev, MDIO_MMD_VEND1, VEND1_EGR_RING_DATA_0);
+ valid = !!(reg & RING_DATA_0_TS_VALID);
+ if (!valid)
+ goto nxp_c45_get_hwtxts_out;
+
+ nxp_c45_read_egress_ts(priv, hwts);
+nxp_c45_get_hwtxts_out:
+ mutex_unlock(&priv->ptp_lock);
+ return valid;
+}
+
+static bool tja1120_egress_ts_is_valid(struct phy_device *phydev)
+{
+ bool valid;
+ u16 reg;
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, TJA1120_EGRESS_TS_DATA_S);
+ valid = !!(reg & TJA1120_TS_VALID);
+
+ return valid;
+}
+
+static bool tja1120_get_hwtxts(struct nxp_c45_phy *priv,
+ struct nxp_c45_hwts *hwts)
+{
+ struct phy_device *phydev = priv->phydev;
+ bool more_ts;
+ bool valid;
+ u16 reg;
+
+ mutex_lock(&priv->ptp_lock);
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, TJA1120_EGRESS_TS_END);
+ more_ts = !!(reg & TJA1120_MORE_TS);
+ valid = tja1120_egress_ts_is_valid(phydev);
+ if (!valid) {
+ if (!more_ts)
+ goto tja1120_get_hwtxts_out;
+
+ /* Bug workaround for TJA1120 engineering samples: move the
+ * new timestamp from the FIFO to the buffer.
+ */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_EGRESS_TS_END, TJA1120_TS_VALID);
+ valid = tja1120_egress_ts_is_valid(phydev);
+ if (!valid)
+ goto tja1120_get_hwtxts_out;
+ }
+ nxp_c45_read_egress_ts(priv, hwts);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, TJA1120_EGRESS_TS_DATA_S,
+ TJA1120_TS_VALID);
+tja1120_get_hwtxts_out:
+ mutex_unlock(&priv->ptp_lock);
+ return valid;
+}
+
+static void nxp_c45_process_txts(struct nxp_c45_phy *priv,
+ struct nxp_c45_hwts *txts)
+{
+ struct sk_buff *skb, *tmp, *skb_match = NULL;
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct timespec64 ts;
+ unsigned long flags;
+ bool ts_match;
+ s64 ts_ns;
+
+ spin_lock_irqsave(&priv->tx_queue.lock, flags);
+ skb_queue_walk_safe(&priv->tx_queue, skb, tmp) {
+ ts_match = nxp_c45_match_ts(NXP_C45_SKB_CB(skb)->header, txts,
+ NXP_C45_SKB_CB(skb)->type);
+ if (!ts_match)
+ continue;
+ skb_match = skb;
+ __skb_unlink(skb, &priv->tx_queue);
+ break;
+ }
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+
+ if (skb_match) {
+ nxp_c45_ptp_gettimex64(&priv->caps, &ts, NULL);
+ nxp_c45_reconstruct_ts(&ts, txts);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ ts_ns = timespec64_to_ns(&ts);
+ shhwtstamps.hwtstamp = ns_to_ktime(ts_ns);
+ skb_complete_tx_timestamp(skb_match, &shhwtstamps);
+ } else {
+ phydev_warn(priv->phydev,
+ "the tx timestamp doesn't match with any skb\n");
+ }
+}
+
+static long nxp_c45_do_aux_work(struct ptp_clock_info *ptp)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+ const struct nxp_c45_phy_data *data = nxp_c45_get_data(priv->phydev);
+ bool poll_txts = nxp_c45_poll_txts(priv->phydev);
+ struct skb_shared_hwtstamps *shhwtstamps_rx;
+ struct ptp_clock_event event;
+ struct nxp_c45_hwts hwts;
+ bool reschedule = false;
+ struct timespec64 ts;
+ struct sk_buff *skb;
+ bool ts_valid;
+ u32 ts_raw;
+
+ while (!skb_queue_empty_lockless(&priv->tx_queue) && poll_txts) {
+ ts_valid = data->get_egressts(priv, &hwts);
+ if (unlikely(!ts_valid)) {
+ /* Still more skbs in the queue */
+ reschedule = true;
+ break;
+ }
+
+ nxp_c45_process_txts(priv, &hwts);
+ }
+
+ while ((skb = skb_dequeue(&priv->rx_queue)) != NULL) {
+ nxp_c45_ptp_gettimex64(&priv->caps, &ts, NULL);
+ ts_raw = __be32_to_cpu(NXP_C45_SKB_CB(skb)->header->reserved2);
+ hwts.sec = ts_raw >> 30;
+ hwts.nsec = ts_raw & GENMASK(29, 0);
+ nxp_c45_reconstruct_ts(&ts, &hwts);
+ shhwtstamps_rx = skb_hwtstamps(skb);
+ shhwtstamps_rx->hwtstamp = ns_to_ktime(timespec64_to_ns(&ts));
+ NXP_C45_SKB_CB(skb)->header->reserved2 = 0;
+ netif_rx(skb);
+ }
+
+ if (priv->extts) {
+ ts_valid = data->get_extts(priv, &ts);
+ if (ts_valid && timespec64_compare(&ts, &priv->extts_ts) != 0) {
+ priv->extts_ts = ts;
+ event.index = priv->extts_index;
+ event.type = PTP_CLOCK_EXTTS;
+ event.timestamp = ns_to_ktime(timespec64_to_ns(&ts));
+ ptp_clock_event(priv->ptp_clock, &event);
+ }
+ reschedule = true;
+ }
+
+ return reschedule ? 1 : -1;
+}
+
+static void nxp_c45_gpio_config(struct nxp_c45_phy *priv,
+ int pin, u16 pin_cfg)
+{
+ struct phy_device *phydev = priv->phydev;
+
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_GPIO_FUNC_CONFIG_BASE + pin, pin_cfg);
+}
+
+static int nxp_c45_perout_enable(struct nxp_c45_phy *priv,
+ struct ptp_perout_request *perout, int on)
+{
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(priv->phydev);
+ struct phy_device *phydev = priv->phydev;
+ int pin;
+
+ if (perout->flags & ~PTP_PEROUT_PHASE)
+ return -EOPNOTSUPP;
+
+ pin = ptp_find_pin(priv->ptp_clock, PTP_PF_PEROUT, perout->index);
+ if (pin < 0)
+ return pin;
+
+ if (!on) {
+ nxp_c45_clear_reg_field(priv->phydev,
+ &regmap->pps_enable);
+ nxp_c45_clear_reg_field(priv->phydev,
+ &regmap->pps_polarity);
+
+ nxp_c45_gpio_config(priv, pin, GPIO_DISABLE);
+
+ return 0;
+ }
+
+ /* The PPS signal is fixed to 1 second and is always generated when the
+ * seconds counter is incremented. The start time is not configurable.
+ * If the clock is adjusted, the PPS signal is automatically readjusted.
+ */
+ if (perout->period.sec != 1 || perout->period.nsec != 0) {
+ phydev_warn(phydev, "The period can be set only to 1 second.");
+ return -EINVAL;
+ }
+
+ if (!(perout->flags & PTP_PEROUT_PHASE)) {
+ if (perout->start.sec != 0 || perout->start.nsec != 0) {
+ phydev_warn(phydev, "The start time is not configurable. Should be set to 0 seconds and 0 nanoseconds.");
+ return -EINVAL;
+ }
+ } else {
+ if (perout->phase.nsec != 0 &&
+ perout->phase.nsec != (NSEC_PER_SEC >> 1)) {
+ phydev_warn(phydev, "The phase can be set only to 0 or 500000000 nanoseconds.");
+ return -EINVAL;
+ }
+
+ if (perout->phase.nsec == 0)
+ nxp_c45_clear_reg_field(priv->phydev,
+ &regmap->pps_polarity);
+ else
+ nxp_c45_set_reg_field(priv->phydev,
+ &regmap->pps_polarity);
+ }
+
+ nxp_c45_gpio_config(priv, pin, GPIO_PPS_OUT_CFG);
+
+ nxp_c45_set_reg_field(priv->phydev, &regmap->pps_enable);
+
+ return 0;
+}
+
+static void nxp_c45_set_rising_or_falling(struct phy_device *phydev,
+ struct ptp_extts_request *extts)
+{
+ if (extts->flags & PTP_RISING_EDGE)
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PTP_CONFIG, EXT_TRG_EDGE);
+
+ if (extts->flags & PTP_FALLING_EDGE)
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PTP_CONFIG, EXT_TRG_EDGE);
+}
+
+static void nxp_c45_set_rising_and_falling(struct phy_device *phydev,
+ struct ptp_extts_request *extts)
+{
+ /* PTP_EXTTS_REQUEST may have only the PTP_ENABLE_FEATURE flag set. In
+ * this case external ts will be enabled on rising edge.
+ */
+ if (extts->flags & PTP_RISING_EDGE ||
+ extts->flags == PTP_ENABLE_FEATURE)
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_SYNC_TRIG_FILTER,
+ PTP_TRIG_RISE_TS);
+ else
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_SYNC_TRIG_FILTER,
+ PTP_TRIG_RISE_TS);
+
+ if (extts->flags & PTP_FALLING_EDGE)
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_SYNC_TRIG_FILTER,
+ PTP_TRIG_FALLING_TS);
+ else
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_SYNC_TRIG_FILTER,
+ PTP_TRIG_FALLING_TS);
+}
+
+static int nxp_c45_extts_enable(struct nxp_c45_phy *priv,
+ struct ptp_extts_request *extts, int on)
+{
+ const struct nxp_c45_phy_data *data = nxp_c45_get_data(priv->phydev);
+ int pin;
+
+ if (extts->flags & ~(PTP_ENABLE_FEATURE |
+ PTP_RISING_EDGE |
+ PTP_FALLING_EDGE |
+ PTP_STRICT_FLAGS))
+ return -EOPNOTSUPP;
+
+ /* Sampling on both edges is not supported */
+ if ((extts->flags & PTP_RISING_EDGE) &&
+ (extts->flags & PTP_FALLING_EDGE) &&
+ !data->ext_ts_both_edges)
+ return -EOPNOTSUPP;
+
+ pin = ptp_find_pin(priv->ptp_clock, PTP_PF_EXTTS, extts->index);
+ if (pin < 0)
+ return pin;
+
+ if (!on) {
+ nxp_c45_gpio_config(priv, pin, GPIO_DISABLE);
+ priv->extts = false;
+
+ return 0;
+ }
+
+ if (data->ext_ts_both_edges)
+ nxp_c45_set_rising_and_falling(priv->phydev, extts);
+ else
+ nxp_c45_set_rising_or_falling(priv->phydev, extts);
+
+ nxp_c45_gpio_config(priv, pin, GPIO_EXTTS_OUT_CFG);
+ priv->extts = true;
+ priv->extts_index = extts->index;
+ ptp_schedule_worker(priv->ptp_clock, 0);
+
+ return 0;
+}
+
+static int nxp_c45_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *req, int on)
+{
+ struct nxp_c45_phy *priv = container_of(ptp, struct nxp_c45_phy, caps);
+
+ switch (req->type) {
+ case PTP_CLK_REQ_EXTTS:
+ return nxp_c45_extts_enable(priv, &req->extts, on);
+ case PTP_CLK_REQ_PEROUT:
+ return nxp_c45_perout_enable(priv, &req->perout, on);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static struct ptp_pin_desc nxp_c45_ptp_pins[] = {
+ { "nxp_c45_gpio0", 0, PTP_PF_NONE},
+ { "nxp_c45_gpio1", 1, PTP_PF_NONE},
+ { "nxp_c45_gpio2", 2, PTP_PF_NONE},
+ { "nxp_c45_gpio3", 3, PTP_PF_NONE},
+ { "nxp_c45_gpio4", 4, PTP_PF_NONE},
+ { "nxp_c45_gpio5", 5, PTP_PF_NONE},
+ { "nxp_c45_gpio6", 6, PTP_PF_NONE},
+ { "nxp_c45_gpio7", 7, PTP_PF_NONE},
+ { "nxp_c45_gpio8", 8, PTP_PF_NONE},
+ { "nxp_c45_gpio9", 9, PTP_PF_NONE},
+ { "nxp_c45_gpio10", 10, PTP_PF_NONE},
+ { "nxp_c45_gpio11", 11, PTP_PF_NONE},
+};
+
+static int nxp_c45_ptp_verify_pin(struct ptp_clock_info *ptp, unsigned int pin,
+ enum ptp_pin_function func, unsigned int chan)
+{
+ if (pin >= ARRAY_SIZE(nxp_c45_ptp_pins))
+ return -EINVAL;
+
+ switch (func) {
+ case PTP_PF_NONE:
+ case PTP_PF_PEROUT:
+ case PTP_PF_EXTTS:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int nxp_c45_init_ptp_clock(struct nxp_c45_phy *priv)
+{
+ priv->caps = (struct ptp_clock_info) {
+ .owner = THIS_MODULE,
+ .name = "NXP C45 PHC",
+ .max_adj = 16666666,
+ .adjfine = nxp_c45_ptp_adjfine,
+ .adjtime = nxp_c45_ptp_adjtime,
+ .gettimex64 = nxp_c45_ptp_gettimex64,
+ .settime64 = nxp_c45_ptp_settime64,
+ .enable = nxp_c45_ptp_enable,
+ .verify = nxp_c45_ptp_verify_pin,
+ .do_aux_work = nxp_c45_do_aux_work,
+ .pin_config = nxp_c45_ptp_pins,
+ .n_pins = ARRAY_SIZE(nxp_c45_ptp_pins),
+ .n_ext_ts = 1,
+ .n_per_out = 1,
+ };
+
+ priv->ptp_clock = ptp_clock_register(&priv->caps,
+ &priv->phydev->mdio.dev);
+
+ if (IS_ERR(priv->ptp_clock))
+ return PTR_ERR(priv->ptp_clock);
+
+ if (!priv->ptp_clock)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void nxp_c45_txtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy,
+ mii_ts);
+
+ switch (priv->hwts_tx) {
+ case HWTSTAMP_TX_ON:
+ NXP_C45_SKB_CB(skb)->type = type;
+ NXP_C45_SKB_CB(skb)->header = ptp_parse_header(skb, type);
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ skb_queue_tail(&priv->tx_queue, skb);
+ if (nxp_c45_poll_txts(priv->phydev))
+ ptp_schedule_worker(priv->ptp_clock, 0);
+ break;
+ case HWTSTAMP_TX_OFF:
+ default:
+ kfree_skb(skb);
+ break;
+ }
+}
+
+static bool nxp_c45_rxtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy,
+ mii_ts);
+ struct ptp_header *header = ptp_parse_header(skb, type);
+
+ if (!header)
+ return false;
+
+ if (!priv->hwts_rx)
+ return false;
+
+ NXP_C45_SKB_CB(skb)->header = header;
+ skb_queue_tail(&priv->rx_queue, skb);
+ ptp_schedule_worker(priv->ptp_clock, 0);
+
+ return true;
+}
+
+static int nxp_c45_hwtstamp(struct mii_timestamper *mii_ts,
+ struct ifreq *ifreq)
+{
+ struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy,
+ mii_ts);
+ struct phy_device *phydev = priv->phydev;
+ const struct nxp_c45_phy_data *data;
+ struct hwtstamp_config cfg;
+
+ if (copy_from_user(&cfg, ifreq->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.tx_type < 0 || cfg.tx_type > HWTSTAMP_TX_ON)
+ return -ERANGE;
+
+ data = nxp_c45_get_data(phydev);
+ priv->hwts_tx = cfg.tx_type;
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ priv->hwts_rx = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ priv->hwts_rx = 1;
+ cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ if (priv->hwts_rx || priv->hwts_tx) {
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ data->regmap->vend1_event_msg_filt,
+ EVENT_MSG_FILT_ALL);
+ data->ptp_enable(phydev, true);
+ } else {
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ data->regmap->vend1_event_msg_filt,
+ EVENT_MSG_FILT_NONE);
+ data->ptp_enable(phydev, false);
+ }
+
+ if (nxp_c45_poll_txts(priv->phydev))
+ goto nxp_c45_no_ptp_irq;
+
+ if (priv->hwts_tx)
+ nxp_c45_set_reg_field(phydev, &data->regmap->irq_egr_ts_en);
+ else
+ nxp_c45_clear_reg_field(phydev, &data->regmap->irq_egr_ts_en);
+
+nxp_c45_no_ptp_irq:
+ return copy_to_user(ifreq->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static int nxp_c45_ts_info(struct mii_timestamper *mii_ts,
+ struct ethtool_ts_info *ts_info)
+{
+ struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy,
+ mii_ts);
+
+ ts_info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+ ts_info->phc_index = ptp_clock_index(priv->ptp_clock);
+ ts_info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON);
+ ts_info->rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT);
+
+ return 0;
+}
+
+static const struct nxp_c45_phy_stats common_hw_stats[] = {
+ { "phy_link_status_drop_cnt",
+ NXP_C45_REG_FIELD(0x8352, MDIO_MMD_VEND1, 8, 6), },
+ { "phy_link_availability_drop_cnt",
+ NXP_C45_REG_FIELD(0x8352, MDIO_MMD_VEND1, 0, 6), },
+ { "phy_link_loss_cnt",
+ NXP_C45_REG_FIELD(0x8353, MDIO_MMD_VEND1, 10, 6), },
+ { "phy_link_failure_cnt",
+ NXP_C45_REG_FIELD(0x8353, MDIO_MMD_VEND1, 0, 10), },
+ { "phy_symbol_error_cnt",
+ NXP_C45_REG_FIELD(0x8350, MDIO_MMD_VEND1, 0, 16) },
+};
+
+static const struct nxp_c45_phy_stats tja1103_hw_stats[] = {
+ { "rx_preamble_count",
+ NXP_C45_REG_FIELD(0xAFCE, MDIO_MMD_VEND1, 0, 6), },
+ { "tx_preamble_count",
+ NXP_C45_REG_FIELD(0xAFCF, MDIO_MMD_VEND1, 0, 6), },
+ { "rx_ipg_length",
+ NXP_C45_REG_FIELD(0xAFD0, MDIO_MMD_VEND1, 0, 9), },
+ { "tx_ipg_length",
+ NXP_C45_REG_FIELD(0xAFD1, MDIO_MMD_VEND1, 0, 9), },
+};
+
+static const struct nxp_c45_phy_stats tja1120_hw_stats[] = {
+ { "phy_symbol_error_cnt_ext",
+ NXP_C45_REG_FIELD(0x8351, MDIO_MMD_VEND1, 0, 14) },
+ { "tx_frames_xtd",
+ NXP_C45_REG_FIELD(0xACA1, MDIO_MMD_VEND1, 0, 8), },
+ { "tx_frames",
+ NXP_C45_REG_FIELD(0xACA0, MDIO_MMD_VEND1, 0, 16), },
+ { "rx_frames_xtd",
+ NXP_C45_REG_FIELD(0xACA3, MDIO_MMD_VEND1, 0, 8), },
+ { "rx_frames",
+ NXP_C45_REG_FIELD(0xACA2, MDIO_MMD_VEND1, 0, 16), },
+ { "tx_lost_frames_xtd",
+ NXP_C45_REG_FIELD(0xACA5, MDIO_MMD_VEND1, 0, 8), },
+ { "tx_lost_frames",
+ NXP_C45_REG_FIELD(0xACA4, MDIO_MMD_VEND1, 0, 16), },
+ { "rx_lost_frames_xtd",
+ NXP_C45_REG_FIELD(0xACA7, MDIO_MMD_VEND1, 0, 8), },
+ { "rx_lost_frames",
+ NXP_C45_REG_FIELD(0xACA6, MDIO_MMD_VEND1, 0, 16), },
+};
+
+static int nxp_c45_get_sset_count(struct phy_device *phydev)
+{
+ const struct nxp_c45_phy_data *phy_data = nxp_c45_get_data(phydev);
+
+ return ARRAY_SIZE(common_hw_stats) + (phy_data ? phy_data->n_stats : 0);
+}
+
+static void nxp_c45_get_strings(struct phy_device *phydev, u8 *data)
+{
+ const struct nxp_c45_phy_data *phy_data = nxp_c45_get_data(phydev);
+ size_t count = nxp_c45_get_sset_count(phydev);
+ size_t idx;
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ if (i < ARRAY_SIZE(common_hw_stats)) {
+ strscpy(data + i * ETH_GSTRING_LEN,
+ common_hw_stats[i].name, ETH_GSTRING_LEN);
+ continue;
+ }
+ idx = i - ARRAY_SIZE(common_hw_stats);
+ strscpy(data + i * ETH_GSTRING_LEN,
+ phy_data->stats[idx].name, ETH_GSTRING_LEN);
+ }
+}
+
+static void nxp_c45_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ const struct nxp_c45_phy_data *phy_data = nxp_c45_get_data(phydev);
+ size_t count = nxp_c45_get_sset_count(phydev);
+ const struct nxp_c45_reg_field *reg_field;
+ size_t idx;
+ size_t i;
+ int ret;
+
+ for (i = 0; i < count; i++) {
+ if (i < ARRAY_SIZE(common_hw_stats)) {
+ reg_field = &common_hw_stats[i].counter;
+ } else {
+ idx = i - ARRAY_SIZE(common_hw_stats);
+ reg_field = &phy_data->stats[idx].counter;
+ }
+
+ ret = nxp_c45_read_reg_field(phydev, reg_field);
+ if (ret < 0)
+ data[i] = U64_MAX;
+ else
+ data[i] = ret;
+ }
+}
+
+static int nxp_c45_config_enable(struct phy_device *phydev)
+{
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_DEVICE_CONTROL,
+ DEVICE_CONTROL_CONFIG_GLOBAL_EN |
+ DEVICE_CONTROL_CONFIG_ALL_EN);
+ usleep_range(400, 450);
+
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PORT_CONTROL,
+ PORT_CONTROL_EN);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_CONTROL,
+ PHY_CONFIG_EN);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PORT_INFRA_CONTROL,
+ PORT_INFRA_CONTROL_EN);
+
+ return 0;
+}
+
+static int nxp_c45_start_op(struct phy_device *phydev)
+{
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_CONTROL,
+ PHY_START_OP);
+}
+
+static int nxp_c45_config_intr(struct phy_device *phydev)
+{
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PHY_IRQ_EN, PHY_IRQ_LINK_EVENT);
+ else
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PHY_IRQ_EN, PHY_IRQ_LINK_EVENT);
+}
+
+static int tja1103_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ /* We can't disable the FUSA IRQ for TJA1103, but we can clean it up. */
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_ALWAYS_ACCESSIBLE,
+ FUSA_PASS);
+ if (ret)
+ return ret;
+
+ return nxp_c45_config_intr(phydev);
+}
+
+static int tja1120_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_GLOBAL_INFRA_IRQ_EN,
+ TJA1120_DEV_BOOT_DONE);
+ else
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_GLOBAL_INFRA_IRQ_EN,
+ TJA1120_DEV_BOOT_DONE);
+ if (ret)
+ return ret;
+
+ return nxp_c45_config_intr(phydev);
+}
+
+static irqreturn_t nxp_c45_handle_interrupt(struct phy_device *phydev)
+{
+ const struct nxp_c45_phy_data *data = nxp_c45_get_data(phydev);
+ struct nxp_c45_phy *priv = phydev->priv;
+ irqreturn_t ret = IRQ_NONE;
+ struct nxp_c45_hwts hwts;
+ int irq;
+
+ irq = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_IRQ_STATUS);
+ if (irq & PHY_IRQ_LINK_EVENT) {
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_IRQ_ACK,
+ PHY_IRQ_LINK_EVENT);
+ phy_trigger_machine(phydev);
+ ret = IRQ_HANDLED;
+ }
+
+ irq = nxp_c45_read_reg_field(phydev, &data->regmap->irq_egr_ts_status);
+ if (irq) {
+ /* If ack_ptp_irq is false, the IRQ bit is self-clear and will
+ * be cleared when the EGR TS FIFO is empty. Otherwise, the
+ * IRQ bit should be cleared before reading the timestamp,
+ */
+ if (data->ack_ptp_irq)
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PTP_IRQ_ACK, EGR_TS_IRQ);
+ while (data->get_egressts(priv, &hwts))
+ nxp_c45_process_txts(priv, &hwts);
+
+ ret = IRQ_HANDLED;
+ }
+
+ data->nmi_handler(phydev, &ret);
+
+ return ret;
+}
+
+static int nxp_c45_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_DEVICE_CONTROL,
+ DEVICE_CONTROL_RESET);
+ if (ret)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ VEND1_DEVICE_CONTROL, ret,
+ !(ret & DEVICE_CONTROL_RESET), 20000,
+ 240000, false);
+}
+
+static int nxp_c45_cable_test_start(struct phy_device *phydev)
+{
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(phydev);
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_FUNC_ENABLES, PHY_TEST_ENABLE);
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, regmap->cable_test,
+ CABLE_TEST_ENABLE | CABLE_TEST_START);
+}
+
+static int nxp_c45_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ const struct nxp_c45_regmap *regmap = nxp_c45_get_regmap(phydev);
+ int ret;
+ u8 cable_test_result;
+
+ ret = nxp_c45_read_reg_field(phydev, &regmap->cable_test_valid);
+ if (!ret) {
+ *finished = false;
+ return 0;
+ }
+
+ *finished = true;
+ cable_test_result = nxp_c45_read_reg_field(phydev,
+ &regmap->cable_test_result);
+
+ switch (cable_test_result) {
+ case CABLE_TEST_OK:
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_RESULT_CODE_OK);
+ break;
+ case CABLE_TEST_SHORTED:
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT);
+ break;
+ case CABLE_TEST_OPEN:
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_RESULT_CODE_OPEN);
+ break;
+ default:
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC);
+ }
+
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, regmap->cable_test,
+ CABLE_TEST_ENABLE);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_FUNC_ENABLES, PHY_TEST_ENABLE);
+
+ return nxp_c45_start_op(phydev);
+}
+
+static int nxp_c45_get_sqi(struct phy_device *phydev)
+{
+ int reg;
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_SIGNAL_QUALITY);
+ if (!(reg & SQI_VALID))
+ return -EINVAL;
+
+ reg &= SQI_MASK;
+
+ return reg;
+}
+
+static void tja1120_link_change_notify(struct phy_device *phydev)
+{
+ /* Bug workaround for TJA1120 enegineering samples: fix egress
+ * timestamps lost after link recovery.
+ */
+ if (phydev->state == PHY_NOLINK) {
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_EPHY_RESETS, EPHY_PCS_RESET);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_EPHY_RESETS, EPHY_PCS_RESET);
+ }
+}
+
+static int nxp_c45_get_sqi_max(struct phy_device *phydev)
+{
+ return MAX_SQI;
+}
+
+static int nxp_c45_check_delay(struct phy_device *phydev, u32 delay)
+{
+ if (delay < MIN_ID_PS) {
+ phydev_err(phydev, "delay value smaller than %u\n", MIN_ID_PS);
+ return -EINVAL;
+ }
+
+ if (delay > MAX_ID_PS) {
+ phydev_err(phydev, "delay value higher than %u\n", MAX_ID_PS);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void nxp_c45_counters_enable(struct phy_device *phydev)
+{
+ const struct nxp_c45_phy_data *data = nxp_c45_get_data(phydev);
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_LINK_DROP_COUNTER,
+ COUNTER_EN);
+
+ data->counters_enable(phydev);
+}
+
+static void nxp_c45_ptp_init(struct phy_device *phydev)
+{
+ const struct nxp_c45_phy_data *data = nxp_c45_get_data(phydev);
+
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ data->regmap->vend1_ptp_clk_period,
+ data->ptp_clk_period);
+ nxp_c45_clear_reg_field(phydev, &data->regmap->ltc_lock_ctrl);
+
+ data->ptp_init(phydev);
+}
+
+static u64 nxp_c45_get_phase_shift(u64 phase_offset_raw)
+{
+ /* The delay in degree phase is 73.8 + phase_offset_raw * 0.9.
+ * To avoid floating point operations we'll multiply by 10
+ * and get 1 decimal point precision.
+ */
+ phase_offset_raw *= 10;
+ phase_offset_raw -= 738;
+ return div_u64(phase_offset_raw, 9);
+}
+
+static void nxp_c45_disable_delays(struct phy_device *phydev)
+{
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_TXID, ID_ENABLE);
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_RXID, ID_ENABLE);
+}
+
+static void nxp_c45_set_delays(struct phy_device *phydev)
+{
+ struct nxp_c45_phy *priv = phydev->priv;
+ u64 tx_delay = priv->tx_delay;
+ u64 rx_delay = priv->rx_delay;
+ u64 degree;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+ degree = div_u64(tx_delay, PS_PER_DEGREE);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_TXID,
+ ID_ENABLE | nxp_c45_get_phase_shift(degree));
+ } else {
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_TXID,
+ ID_ENABLE);
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+ degree = div_u64(rx_delay, PS_PER_DEGREE);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_RXID,
+ ID_ENABLE | nxp_c45_get_phase_shift(degree));
+ } else {
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_RXID,
+ ID_ENABLE);
+ }
+}
+
+static int nxp_c45_get_delays(struct phy_device *phydev)
+{
+ struct nxp_c45_phy *priv = phydev->priv;
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
+ ret = device_property_read_u32(&phydev->mdio.dev,
+ "tx-internal-delay-ps",
+ &priv->tx_delay);
+ if (ret)
+ priv->tx_delay = DEFAULT_ID_PS;
+
+ ret = nxp_c45_check_delay(phydev, priv->tx_delay);
+ if (ret) {
+ phydev_err(phydev,
+ "tx-internal-delay-ps invalid value\n");
+ return ret;
+ }
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+ ret = device_property_read_u32(&phydev->mdio.dev,
+ "rx-internal-delay-ps",
+ &priv->rx_delay);
+ if (ret)
+ priv->rx_delay = DEFAULT_ID_PS;
+
+ ret = nxp_c45_check_delay(phydev, priv->rx_delay);
+ if (ret) {
+ phydev_err(phydev,
+ "rx-internal-delay-ps invalid value\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int nxp_c45_set_phy_mode(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_ABILITIES);
+ phydev_dbg(phydev, "Clause 45 managed PHY abilities 0x%x\n", ret);
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ if (!(ret & RGMII_ABILITY)) {
+ phydev_err(phydev, "rgmii mode not supported\n");
+ return -EINVAL;
+ }
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
+ MII_BASIC_CONFIG_RGMII);
+ nxp_c45_disable_delays(phydev);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ if (!(ret & RGMII_ID_ABILITY)) {
+ phydev_err(phydev, "rgmii-id, rgmii-txid, rgmii-rxid modes are not supported\n");
+ return -EINVAL;
+ }
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
+ MII_BASIC_CONFIG_RGMII);
+ ret = nxp_c45_get_delays(phydev);
+ if (ret)
+ return ret;
+
+ nxp_c45_set_delays(phydev);
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ if (!(ret & MII_ABILITY)) {
+ phydev_err(phydev, "mii mode not supported\n");
+ return -EINVAL;
+ }
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
+ MII_BASIC_CONFIG_MII);
+ break;
+ case PHY_INTERFACE_MODE_REVMII:
+ if (!(ret & REVMII_ABILITY)) {
+ phydev_err(phydev, "rev-mii mode not supported\n");
+ return -EINVAL;
+ }
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
+ MII_BASIC_CONFIG_MII | MII_BASIC_CONFIG_REV);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (!(ret & RMII_ABILITY)) {
+ phydev_err(phydev, "rmii mode not supported\n");
+ return -EINVAL;
+ }
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
+ MII_BASIC_CONFIG_RMII);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ if (!(ret & SGMII_ABILITY)) {
+ phydev_err(phydev, "sgmii mode not supported\n");
+ return -EINVAL;
+ }
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
+ MII_BASIC_CONFIG_SGMII);
+ break;
+ case PHY_INTERFACE_MODE_INTERNAL:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nxp_c45_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = nxp_c45_config_enable(phydev);
+ if (ret) {
+ phydev_err(phydev, "Failed to enable config\n");
+ return ret;
+ }
+
+ /* Bug workaround for SJA1110 rev B: enable write access
+ * to MDIO_MMD_PMAPMD
+ */
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F8, 1);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F9, 2);
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_CONFIG,
+ PHY_CONFIG_AUTO);
+
+ ret = nxp_c45_set_phy_mode(phydev);
+ if (ret)
+ return ret;
+
+ phydev->autoneg = AUTONEG_DISABLE;
+
+ nxp_c45_counters_enable(phydev);
+ nxp_c45_ptp_init(phydev);
+
+ return nxp_c45_start_op(phydev);
+}
+
+static int nxp_c45_get_features(struct phy_device *phydev)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_MII_BIT, phydev->supported);
+
+ return genphy_c45_pma_read_abilities(phydev);
+}
+
+static int nxp_c45_probe(struct phy_device *phydev)
+{
+ struct nxp_c45_phy *priv;
+ int ptp_ability;
+ int ret = 0;
+
+ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ skb_queue_head_init(&priv->tx_queue);
+ skb_queue_head_init(&priv->rx_queue);
+
+ priv->phydev = phydev;
+
+ phydev->priv = priv;
+
+ mutex_init(&priv->ptp_lock);
+
+ ptp_ability = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_ABILITIES);
+ ptp_ability = !!(ptp_ability & PTP_ABILITY);
+ if (!ptp_ability) {
+ phydev_dbg(phydev, "the phy does not support PTP");
+ goto no_ptp_support;
+ }
+
+ if (IS_ENABLED(CONFIG_PTP_1588_CLOCK) &&
+ IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING)) {
+ priv->mii_ts.rxtstamp = nxp_c45_rxtstamp;
+ priv->mii_ts.txtstamp = nxp_c45_txtstamp;
+ priv->mii_ts.hwtstamp = nxp_c45_hwtstamp;
+ priv->mii_ts.ts_info = nxp_c45_ts_info;
+ phydev->mii_ts = &priv->mii_ts;
+ ret = nxp_c45_init_ptp_clock(priv);
+ } else {
+ phydev_dbg(phydev, "PTP support not enabled even if the phy supports it");
+ }
+
+no_ptp_support:
+
+ return ret;
+}
+
+static void nxp_c45_remove(struct phy_device *phydev)
+{
+ struct nxp_c45_phy *priv = phydev->priv;
+
+ if (priv->ptp_clock)
+ ptp_clock_unregister(priv->ptp_clock);
+
+ skb_queue_purge(&priv->tx_queue);
+ skb_queue_purge(&priv->rx_queue);
+}
+
+static void tja1103_counters_enable(struct phy_device *phydev)
+{
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_RX_PREAMBLE_COUNT,
+ COUNTER_EN);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_TX_PREAMBLE_COUNT,
+ COUNTER_EN);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_RX_IPG_LENGTH,
+ COUNTER_EN);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_TX_IPG_LENGTH,
+ COUNTER_EN);
+}
+
+static void tja1103_ptp_init(struct phy_device *phydev)
+{
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_RX_TS_INSRT_CTRL,
+ TJA1103_RX_TS_INSRT_MODE2);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PORT_FUNC_ENABLES,
+ PTP_ENABLE);
+}
+
+static void tja1103_ptp_enable(struct phy_device *phydev, bool enable)
+{
+ if (enable)
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_PTP_CONTROL,
+ PORT_PTP_CONTROL_BYPASS);
+ else
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_PTP_CONTROL,
+ PORT_PTP_CONTROL_BYPASS);
+}
+
+static void tja1103_nmi_handler(struct phy_device *phydev,
+ irqreturn_t *irq_status)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_ALWAYS_ACCESSIBLE);
+ if (ret & FUSA_PASS) {
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_ALWAYS_ACCESSIBLE,
+ FUSA_PASS);
+ *irq_status = IRQ_HANDLED;
+ }
+}
+
+static const struct nxp_c45_regmap tja1103_regmap = {
+ .vend1_ptp_clk_period = 0x1104,
+ .vend1_event_msg_filt = 0x1148,
+ .pps_enable =
+ NXP_C45_REG_FIELD(0x1102, MDIO_MMD_VEND1, 3, 1),
+ .pps_polarity =
+ NXP_C45_REG_FIELD(0x1102, MDIO_MMD_VEND1, 2, 1),
+ .ltc_lock_ctrl =
+ NXP_C45_REG_FIELD(0x1115, MDIO_MMD_VEND1, 0, 1),
+ .ltc_read =
+ NXP_C45_REG_FIELD(0x1105, MDIO_MMD_VEND1, 2, 1),
+ .ltc_write =
+ NXP_C45_REG_FIELD(0x1105, MDIO_MMD_VEND1, 0, 1),
+ .vend1_ltc_wr_nsec_0 = 0x1106,
+ .vend1_ltc_wr_nsec_1 = 0x1107,
+ .vend1_ltc_wr_sec_0 = 0x1108,
+ .vend1_ltc_wr_sec_1 = 0x1109,
+ .vend1_ltc_rd_nsec_0 = 0x110A,
+ .vend1_ltc_rd_nsec_1 = 0x110B,
+ .vend1_ltc_rd_sec_0 = 0x110C,
+ .vend1_ltc_rd_sec_1 = 0x110D,
+ .vend1_rate_adj_subns_0 = 0x110F,
+ .vend1_rate_adj_subns_1 = 0x1110,
+ .irq_egr_ts_en =
+ NXP_C45_REG_FIELD(0x1131, MDIO_MMD_VEND1, 0, 1),
+ .irq_egr_ts_status =
+ NXP_C45_REG_FIELD(0x1132, MDIO_MMD_VEND1, 0, 1),
+ .domain_number =
+ NXP_C45_REG_FIELD(0x114E, MDIO_MMD_VEND1, 0, 8),
+ .msg_type =
+ NXP_C45_REG_FIELD(0x114E, MDIO_MMD_VEND1, 8, 4),
+ .sequence_id =
+ NXP_C45_REG_FIELD(0x114F, MDIO_MMD_VEND1, 0, 16),
+ .sec_1_0 =
+ NXP_C45_REG_FIELD(0x1151, MDIO_MMD_VEND1, 14, 2),
+ .sec_4_2 =
+ NXP_C45_REG_FIELD(0x114E, MDIO_MMD_VEND1, 12, 3),
+ .nsec_15_0 =
+ NXP_C45_REG_FIELD(0x1150, MDIO_MMD_VEND1, 0, 16),
+ .nsec_29_16 =
+ NXP_C45_REG_FIELD(0x1151, MDIO_MMD_VEND1, 0, 14),
+ .vend1_ext_trg_data_0 = 0x1121,
+ .vend1_ext_trg_data_1 = 0x1122,
+ .vend1_ext_trg_data_2 = 0x1123,
+ .vend1_ext_trg_data_3 = 0x1124,
+ .vend1_ext_trg_ctrl = 0x1126,
+ .cable_test = 0x8330,
+ .cable_test_valid =
+ NXP_C45_REG_FIELD(0x8330, MDIO_MMD_VEND1, 13, 1),
+ .cable_test_result =
+ NXP_C45_REG_FIELD(0x8330, MDIO_MMD_VEND1, 0, 3),
+};
+
+static const struct nxp_c45_phy_data tja1103_phy_data = {
+ .regmap = &tja1103_regmap,
+ .stats = tja1103_hw_stats,
+ .n_stats = ARRAY_SIZE(tja1103_hw_stats),
+ .ptp_clk_period = PTP_CLK_PERIOD_100BT1,
+ .ext_ts_both_edges = false,
+ .ack_ptp_irq = false,
+ .counters_enable = tja1103_counters_enable,
+ .get_egressts = nxp_c45_get_hwtxts,
+ .get_extts = nxp_c45_get_extts,
+ .ptp_init = tja1103_ptp_init,
+ .ptp_enable = tja1103_ptp_enable,
+ .nmi_handler = tja1103_nmi_handler,
+};
+
+static void tja1120_counters_enable(struct phy_device *phydev)
+{
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_SYMBOL_ERROR_CNT_XTD,
+ EXTENDED_CNT_EN);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_MONITOR_STATUS,
+ MONITOR_RESET);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_MONITOR_CONFIG,
+ ALL_FRAMES_CNT_EN | LOST_FRAMES_CNT_EN);
+}
+
+static void tja1120_ptp_init(struct phy_device *phydev)
+{
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, TJA1120_RX_TS_INSRT_CTRL,
+ TJA1120_RX_TS_INSRT_EN | TJA1120_TS_INSRT_MODE);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, TJA1120_VEND1_EXT_TS_MODE,
+ TJA1120_TS_INSRT_MODE);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_DEVICE_CONFIG,
+ PTP_ENABLE);
+}
+
+static void tja1120_ptp_enable(struct phy_device *phydev, bool enable)
+{
+ if (enable)
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_FUNC_ENABLES,
+ PTP_ENABLE);
+ else
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ VEND1_PORT_FUNC_ENABLES,
+ PTP_ENABLE);
+}
+
+static void tja1120_nmi_handler(struct phy_device *phydev,
+ irqreturn_t *irq_status)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_GLOBAL_INFRA_IRQ_STATUS);
+ if (ret & TJA1120_DEV_BOOT_DONE) {
+ phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ TJA1120_GLOBAL_INFRA_IRQ_ACK,
+ TJA1120_DEV_BOOT_DONE);
+ *irq_status = IRQ_HANDLED;
+ }
+}
+
+static const struct nxp_c45_regmap tja1120_regmap = {
+ .vend1_ptp_clk_period = 0x1020,
+ .vend1_event_msg_filt = 0x9010,
+ .pps_enable =
+ NXP_C45_REG_FIELD(0x1006, MDIO_MMD_VEND1, 4, 1),
+ .pps_polarity =
+ NXP_C45_REG_FIELD(0x1006, MDIO_MMD_VEND1, 5, 1),
+ .ltc_lock_ctrl =
+ NXP_C45_REG_FIELD(0x1006, MDIO_MMD_VEND1, 2, 1),
+ .ltc_read =
+ NXP_C45_REG_FIELD(0x1000, MDIO_MMD_VEND1, 1, 1),
+ .ltc_write =
+ NXP_C45_REG_FIELD(0x1000, MDIO_MMD_VEND1, 2, 1),
+ .vend1_ltc_wr_nsec_0 = 0x1040,
+ .vend1_ltc_wr_nsec_1 = 0x1041,
+ .vend1_ltc_wr_sec_0 = 0x1042,
+ .vend1_ltc_wr_sec_1 = 0x1043,
+ .vend1_ltc_rd_nsec_0 = 0x1048,
+ .vend1_ltc_rd_nsec_1 = 0x1049,
+ .vend1_ltc_rd_sec_0 = 0x104A,
+ .vend1_ltc_rd_sec_1 = 0x104B,
+ .vend1_rate_adj_subns_0 = 0x1030,
+ .vend1_rate_adj_subns_1 = 0x1031,
+ .irq_egr_ts_en =
+ NXP_C45_REG_FIELD(0x900A, MDIO_MMD_VEND1, 1, 1),
+ .irq_egr_ts_status =
+ NXP_C45_REG_FIELD(0x900C, MDIO_MMD_VEND1, 1, 1),
+ .domain_number =
+ NXP_C45_REG_FIELD(0x9061, MDIO_MMD_VEND1, 8, 8),
+ .msg_type =
+ NXP_C45_REG_FIELD(0x9061, MDIO_MMD_VEND1, 4, 4),
+ .sequence_id =
+ NXP_C45_REG_FIELD(0x9062, MDIO_MMD_VEND1, 0, 16),
+ .sec_1_0 =
+ NXP_C45_REG_FIELD(0x9065, MDIO_MMD_VEND1, 0, 2),
+ .sec_4_2 =
+ NXP_C45_REG_FIELD(0x9065, MDIO_MMD_VEND1, 2, 3),
+ .nsec_15_0 =
+ NXP_C45_REG_FIELD(0x9063, MDIO_MMD_VEND1, 0, 16),
+ .nsec_29_16 =
+ NXP_C45_REG_FIELD(0x9064, MDIO_MMD_VEND1, 0, 14),
+ .vend1_ext_trg_data_0 = 0x1071,
+ .vend1_ext_trg_data_1 = 0x1072,
+ .vend1_ext_trg_data_2 = 0x1073,
+ .vend1_ext_trg_data_3 = 0x1074,
+ .vend1_ext_trg_ctrl = 0x1075,
+ .cable_test = 0x8360,
+ .cable_test_valid =
+ NXP_C45_REG_FIELD(0x8361, MDIO_MMD_VEND1, 15, 1),
+ .cable_test_result =
+ NXP_C45_REG_FIELD(0x8361, MDIO_MMD_VEND1, 0, 3),
+};
+
+static const struct nxp_c45_phy_data tja1120_phy_data = {
+ .regmap = &tja1120_regmap,
+ .stats = tja1120_hw_stats,
+ .n_stats = ARRAY_SIZE(tja1120_hw_stats),
+ .ptp_clk_period = PTP_CLK_PERIOD_1000BT1,
+ .ext_ts_both_edges = true,
+ .ack_ptp_irq = true,
+ .counters_enable = tja1120_counters_enable,
+ .get_egressts = tja1120_get_hwtxts,
+ .get_extts = tja1120_get_extts,
+ .ptp_init = tja1120_ptp_init,
+ .ptp_enable = tja1120_ptp_enable,
+ .nmi_handler = tja1120_nmi_handler,
+};
+
+static struct phy_driver nxp_c45_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_TJA_1103),
+ .name = "NXP C45 TJA1103",
+ .get_features = nxp_c45_get_features,
+ .driver_data = &tja1103_phy_data,
+ .probe = nxp_c45_probe,
+ .soft_reset = nxp_c45_soft_reset,
+ .config_aneg = genphy_c45_config_aneg,
+ .config_init = nxp_c45_config_init,
+ .config_intr = tja1103_config_intr,
+ .handle_interrupt = nxp_c45_handle_interrupt,
+ .read_status = genphy_c45_read_status,
+ .suspend = genphy_c45_pma_suspend,
+ .resume = genphy_c45_pma_resume,
+ .get_sset_count = nxp_c45_get_sset_count,
+ .get_strings = nxp_c45_get_strings,
+ .get_stats = nxp_c45_get_stats,
+ .cable_test_start = nxp_c45_cable_test_start,
+ .cable_test_get_status = nxp_c45_cable_test_get_status,
+ .set_loopback = genphy_c45_loopback,
+ .get_sqi = nxp_c45_get_sqi,
+ .get_sqi_max = nxp_c45_get_sqi_max,
+ .remove = nxp_c45_remove,
+ },
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_TJA_1120),
+ .name = "NXP C45 TJA1120",
+ .get_features = nxp_c45_get_features,
+ .driver_data = &tja1120_phy_data,
+ .probe = nxp_c45_probe,
+ .soft_reset = nxp_c45_soft_reset,
+ .config_aneg = genphy_c45_config_aneg,
+ .config_init = nxp_c45_config_init,
+ .config_intr = tja1120_config_intr,
+ .handle_interrupt = nxp_c45_handle_interrupt,
+ .read_status = genphy_c45_read_status,
+ .link_change_notify = tja1120_link_change_notify,
+ .suspend = genphy_c45_pma_suspend,
+ .resume = genphy_c45_pma_resume,
+ .get_sset_count = nxp_c45_get_sset_count,
+ .get_strings = nxp_c45_get_strings,
+ .get_stats = nxp_c45_get_stats,
+ .cable_test_start = nxp_c45_cable_test_start,
+ .cable_test_get_status = nxp_c45_cable_test_get_status,
+ .set_loopback = genphy_c45_loopback,
+ .get_sqi = nxp_c45_get_sqi,
+ .get_sqi_max = nxp_c45_get_sqi_max,
+ .remove = nxp_c45_remove,
+ },
+};
+
+module_phy_driver(nxp_c45_driver);
+
+static struct mdio_device_id __maybe_unused nxp_c45_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_TJA_1103) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_TJA_1120) },
+ { /*sentinel*/ },
+};
+
+MODULE_DEVICE_TABLE(mdio, nxp_c45_tbl);
+
+MODULE_AUTHOR("Radu Pirea <radu-nicolae.pirea@oss.nxp.com>");
+MODULE_DESCRIPTION("NXP C45 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/nxp-cbtx.c b/drivers/net/phy/nxp-cbtx.c
new file mode 100644
index 000000000..145703f0a
--- /dev/null
+++ b/drivers/net/phy/nxp-cbtx.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for 100BASE-TX PHY embedded into NXP SJA1110 switch
+ *
+ * Copyright 2022-2023 NXP
+ */
+
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define PHY_ID_CBTX_SJA1110 0x001bb020
+
+/* Registers */
+#define CBTX_MODE_CTRL_STAT 0x11
+#define CBTX_PDOWN_CTRL 0x18
+#define CBTX_RX_ERR_COUNTER 0x1a
+#define CBTX_IRQ_STAT 0x1d
+#define CBTX_IRQ_ENABLE 0x1e
+
+/* Fields */
+#define CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN BIT(7)
+#define CBTX_MODE_CTRL_STAT_MDIX_MODE BIT(6)
+
+#define CBTX_PDOWN_CTL_TRUE_PDOWN BIT(0)
+
+#define CBTX_IRQ_ENERGYON BIT(7)
+#define CBTX_IRQ_AN_COMPLETE BIT(6)
+#define CBTX_IRQ_REM_FAULT BIT(5)
+#define CBTX_IRQ_LINK_DOWN BIT(4)
+#define CBTX_IRQ_AN_LP_ACK BIT(3)
+#define CBTX_IRQ_PARALLEL_DETECT_FAULT BIT(2)
+#define CBTX_IRQ_AN_PAGE_RECV BIT(1)
+
+static int cbtx_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Can't soft reset unless we remove PHY from true power down mode */
+ ret = phy_clear_bits(phydev, CBTX_PDOWN_CTRL,
+ CBTX_PDOWN_CTL_TRUE_PDOWN);
+ if (ret)
+ return ret;
+
+ return genphy_soft_reset(phydev);
+}
+
+static int cbtx_config_init(struct phy_device *phydev)
+{
+ /* Wait for cbtx_config_aneg() to kick in and apply this */
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ return 0;
+}
+
+static int cbtx_mdix_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, CBTX_MODE_CTRL_STAT);
+ if (ret < 0)
+ return ret;
+
+ if (ret & CBTX_MODE_CTRL_STAT_MDIX_MODE)
+ phydev->mdix = ETH_TP_MDI_X;
+ else
+ phydev->mdix = ETH_TP_MDI;
+
+ return 0;
+}
+
+static int cbtx_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = cbtx_mdix_status(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_read_status(phydev);
+}
+
+static int cbtx_mdix_config(struct phy_device *phydev)
+{
+ int ret;
+
+ switch (phydev->mdix_ctrl) {
+ case ETH_TP_MDI_AUTO:
+ return phy_set_bits(phydev, CBTX_MODE_CTRL_STAT,
+ CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN);
+ case ETH_TP_MDI:
+ ret = phy_clear_bits(phydev, CBTX_MODE_CTRL_STAT,
+ CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN);
+ if (ret)
+ return ret;
+
+ return phy_clear_bits(phydev, CBTX_MODE_CTRL_STAT,
+ CBTX_MODE_CTRL_STAT_MDIX_MODE);
+ case ETH_TP_MDI_X:
+ ret = phy_clear_bits(phydev, CBTX_MODE_CTRL_STAT,
+ CBTX_MODE_CTRL_STAT_AUTO_MDIX_EN);
+ if (ret)
+ return ret;
+
+ return phy_set_bits(phydev, CBTX_MODE_CTRL_STAT,
+ CBTX_MODE_CTRL_STAT_MDIX_MODE);
+ }
+
+ return 0;
+}
+
+static int cbtx_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = cbtx_mdix_config(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_config_aneg(phydev);
+}
+
+static int cbtx_ack_interrupts(struct phy_device *phydev)
+{
+ return phy_read(phydev, CBTX_IRQ_STAT);
+}
+
+static int cbtx_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = cbtx_ack_interrupts(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, CBTX_IRQ_ENABLE, CBTX_IRQ_LINK_DOWN |
+ CBTX_IRQ_AN_COMPLETE | CBTX_IRQ_ENERGYON);
+ if (ret)
+ return ret;
+ } else {
+ ret = phy_write(phydev, CBTX_IRQ_ENABLE, 0);
+ if (ret)
+ return ret;
+
+ ret = cbtx_ack_interrupts(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static irqreturn_t cbtx_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_stat, irq_enabled;
+
+ irq_stat = cbtx_ack_interrupts(phydev);
+ if (irq_stat < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_enabled = phy_read(phydev, CBTX_IRQ_ENABLE);
+ if (irq_enabled < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_enabled & irq_stat))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int cbtx_get_sset_count(struct phy_device *phydev)
+{
+ return 1;
+}
+
+static void cbtx_get_strings(struct phy_device *phydev, u8 *data)
+{
+ strncpy(data, "100btx_rx_err", ETH_GSTRING_LEN);
+}
+
+static void cbtx_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ int ret;
+
+ ret = phy_read(phydev, CBTX_RX_ERR_COUNTER);
+ data[0] = (ret < 0) ? U64_MAX : ret;
+}
+
+static struct phy_driver cbtx_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_CBTX_SJA1110),
+ .name = "NXP CBTX (SJA1110)",
+ /* PHY_BASIC_FEATURES */
+ .soft_reset = cbtx_soft_reset,
+ .config_init = cbtx_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .config_intr = cbtx_config_intr,
+ .handle_interrupt = cbtx_handle_interrupt,
+ .read_status = cbtx_read_status,
+ .config_aneg = cbtx_config_aneg,
+ .get_sset_count = cbtx_get_sset_count,
+ .get_strings = cbtx_get_strings,
+ .get_stats = cbtx_get_stats,
+ },
+};
+
+module_phy_driver(cbtx_driver);
+
+static struct mdio_device_id __maybe_unused cbtx_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_CBTX_SJA1110) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(mdio, cbtx_tbl);
+
+MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>");
+MODULE_DESCRIPTION("NXP CBTX PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/nxp-tja11xx.c b/drivers/net/phy/nxp-tja11xx.c
new file mode 100644
index 000000000..b13e15310
--- /dev/null
+++ b/drivers/net/phy/nxp-tja11xx.c
@@ -0,0 +1,904 @@
+// SPDX-License-Identifier: GPL-2.0
+/* NXP TJA1100 BroadRReach PHY driver
+ *
+ * Copyright (C) 2018 Marek Vasut <marex@denx.de>
+ */
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
+#include <linux/kernel.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/hwmon.h>
+#include <linux/bitfield.h>
+#include <linux/of_mdio.h>
+#include <linux/of_irq.h>
+
+#define PHY_ID_MASK 0xfffffff0
+#define PHY_ID_TJA1100 0x0180dc40
+#define PHY_ID_TJA1101 0x0180dd00
+#define PHY_ID_TJA1102 0x0180dc80
+
+#define MII_ECTRL 17
+#define MII_ECTRL_LINK_CONTROL BIT(15)
+#define MII_ECTRL_POWER_MODE_MASK GENMASK(14, 11)
+#define MII_ECTRL_POWER_MODE_NO_CHANGE (0x0 << 11)
+#define MII_ECTRL_POWER_MODE_NORMAL (0x3 << 11)
+#define MII_ECTRL_POWER_MODE_STANDBY (0xc << 11)
+#define MII_ECTRL_CABLE_TEST BIT(5)
+#define MII_ECTRL_CONFIG_EN BIT(2)
+#define MII_ECTRL_WAKE_REQUEST BIT(0)
+
+#define MII_CFG1 18
+#define MII_CFG1_MASTER_SLAVE BIT(15)
+#define MII_CFG1_AUTO_OP BIT(14)
+#define MII_CFG1_INTERFACE_MODE_MASK GENMASK(9, 8)
+#define MII_CFG1_MII_MODE (0x0 << 8)
+#define MII_CFG1_RMII_MODE_REFCLK_IN BIT(8)
+#define MII_CFG1_RMII_MODE_REFCLK_OUT BIT(9)
+#define MII_CFG1_REVMII_MODE GENMASK(9, 8)
+#define MII_CFG1_SLEEP_CONFIRM BIT(6)
+#define MII_CFG1_LED_MODE_MASK GENMASK(5, 4)
+#define MII_CFG1_LED_MODE_LINKUP 0
+#define MII_CFG1_LED_ENABLE BIT(3)
+
+#define MII_CFG2 19
+#define MII_CFG2_SLEEP_REQUEST_TO GENMASK(1, 0)
+#define MII_CFG2_SLEEP_REQUEST_TO_16MS 0x3
+
+#define MII_INTSRC 21
+#define MII_INTSRC_LINK_FAIL BIT(10)
+#define MII_INTSRC_LINK_UP BIT(9)
+#define MII_INTSRC_MASK (MII_INTSRC_LINK_FAIL | MII_INTSRC_LINK_UP)
+#define MII_INTSRC_UV_ERR BIT(3)
+#define MII_INTSRC_TEMP_ERR BIT(1)
+
+#define MII_INTEN 22
+#define MII_INTEN_LINK_FAIL BIT(10)
+#define MII_INTEN_LINK_UP BIT(9)
+#define MII_INTEN_UV_ERR BIT(3)
+#define MII_INTEN_TEMP_ERR BIT(1)
+
+#define MII_COMMSTAT 23
+#define MII_COMMSTAT_LINK_UP BIT(15)
+#define MII_COMMSTAT_SQI_STATE GENMASK(7, 5)
+#define MII_COMMSTAT_SQI_MAX 7
+
+#define MII_GENSTAT 24
+#define MII_GENSTAT_PLL_LOCKED BIT(14)
+
+#define MII_EXTSTAT 25
+#define MII_EXTSTAT_SHORT_DETECT BIT(8)
+#define MII_EXTSTAT_OPEN_DETECT BIT(7)
+#define MII_EXTSTAT_POLARITY_DETECT BIT(6)
+
+#define MII_COMMCFG 27
+#define MII_COMMCFG_AUTO_OP BIT(15)
+
+/* Configure REF_CLK as input in RMII mode */
+#define TJA110X_RMII_MODE_REFCLK_IN BIT(0)
+
+struct tja11xx_priv {
+ char *hwmon_name;
+ struct device *hwmon_dev;
+ struct phy_device *phydev;
+ struct work_struct phy_register_work;
+ u32 flags;
+};
+
+struct tja11xx_phy_stats {
+ const char *string;
+ u8 reg;
+ u8 off;
+ u16 mask;
+};
+
+static struct tja11xx_phy_stats tja11xx_hw_stats[] = {
+ { "phy_symbol_error_count", 20, 0, GENMASK(15, 0) },
+ { "phy_polarity_detect", 25, 6, BIT(6) },
+ { "phy_open_detect", 25, 7, BIT(7) },
+ { "phy_short_detect", 25, 8, BIT(8) },
+ { "phy_rem_rcvr_count", 26, 0, GENMASK(7, 0) },
+ { "phy_loc_rcvr_count", 26, 8, GENMASK(15, 8) },
+};
+
+static int tja11xx_check(struct phy_device *phydev, u8 reg, u16 mask, u16 set)
+{
+ int val;
+
+ return phy_read_poll_timeout(phydev, reg, val, (val & mask) == set,
+ 150, 30000, false);
+}
+
+static int phy_modify_check(struct phy_device *phydev, u8 reg,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ ret = phy_modify(phydev, reg, mask, set);
+ if (ret)
+ return ret;
+
+ return tja11xx_check(phydev, reg, mask, set);
+}
+
+static int tja11xx_enable_reg_write(struct phy_device *phydev)
+{
+ return phy_set_bits(phydev, MII_ECTRL, MII_ECTRL_CONFIG_EN);
+}
+
+static int tja11xx_enable_link_control(struct phy_device *phydev)
+{
+ return phy_set_bits(phydev, MII_ECTRL, MII_ECTRL_LINK_CONTROL);
+}
+
+static int tja11xx_disable_link_control(struct phy_device *phydev)
+{
+ return phy_clear_bits(phydev, MII_ECTRL, MII_ECTRL_LINK_CONTROL);
+}
+
+static int tja11xx_wakeup(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_ECTRL);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & MII_ECTRL_POWER_MODE_MASK) {
+ case MII_ECTRL_POWER_MODE_NO_CHANGE:
+ break;
+ case MII_ECTRL_POWER_MODE_NORMAL:
+ ret = phy_set_bits(phydev, MII_ECTRL, MII_ECTRL_WAKE_REQUEST);
+ if (ret)
+ return ret;
+
+ ret = phy_clear_bits(phydev, MII_ECTRL, MII_ECTRL_WAKE_REQUEST);
+ if (ret)
+ return ret;
+ break;
+ case MII_ECTRL_POWER_MODE_STANDBY:
+ ret = phy_modify_check(phydev, MII_ECTRL,
+ MII_ECTRL_POWER_MODE_MASK,
+ MII_ECTRL_POWER_MODE_STANDBY);
+ if (ret)
+ return ret;
+
+ ret = phy_modify(phydev, MII_ECTRL, MII_ECTRL_POWER_MODE_MASK,
+ MII_ECTRL_POWER_MODE_NORMAL);
+ if (ret)
+ return ret;
+
+ ret = phy_modify_check(phydev, MII_GENSTAT,
+ MII_GENSTAT_PLL_LOCKED,
+ MII_GENSTAT_PLL_LOCKED);
+ if (ret)
+ return ret;
+
+ return tja11xx_enable_link_control(phydev);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int tja11xx_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = tja11xx_enable_reg_write(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_soft_reset(phydev);
+}
+
+static int tja11xx_config_aneg_cable_test(struct phy_device *phydev)
+{
+ bool finished = false;
+ int ret;
+
+ if (phydev->link)
+ return 0;
+
+ if (!phydev->drv->cable_test_start ||
+ !phydev->drv->cable_test_get_status)
+ return 0;
+
+ ret = ethnl_cable_test_alloc(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);
+ if (ret)
+ return ret;
+
+ ret = phydev->drv->cable_test_start(phydev);
+ if (ret)
+ return ret;
+
+ /* According to the documentation this test takes 100 usec */
+ usleep_range(100, 200);
+
+ ret = phydev->drv->cable_test_get_status(phydev, &finished);
+ if (ret)
+ return ret;
+
+ if (finished)
+ ethnl_cable_test_finished(phydev);
+
+ return 0;
+}
+
+static int tja11xx_config_aneg(struct phy_device *phydev)
+{
+ int ret, changed = 0;
+ u16 ctl = 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl |= MII_CFG1_MASTER_SLAVE;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ goto do_test;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -ENOTSUPP;
+ }
+
+ changed = phy_modify_changed(phydev, MII_CFG1, MII_CFG1_MASTER_SLAVE, ctl);
+ if (changed < 0)
+ return changed;
+
+do_test:
+ ret = tja11xx_config_aneg_cable_test(phydev);
+ if (ret)
+ return ret;
+
+ return __genphy_config_aneg(phydev, changed);
+}
+
+static int tja11xx_get_interface_mode(struct phy_device *phydev)
+{
+ struct tja11xx_priv *priv = phydev->priv;
+ int mii_mode;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_MII:
+ mii_mode = MII_CFG1_MII_MODE;
+ break;
+ case PHY_INTERFACE_MODE_REVMII:
+ mii_mode = MII_CFG1_REVMII_MODE;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (priv->flags & TJA110X_RMII_MODE_REFCLK_IN)
+ mii_mode = MII_CFG1_RMII_MODE_REFCLK_IN;
+ else
+ mii_mode = MII_CFG1_RMII_MODE_REFCLK_OUT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return mii_mode;
+}
+
+static int tja11xx_config_init(struct phy_device *phydev)
+{
+ u16 reg_mask, reg_val;
+ int ret;
+
+ ret = tja11xx_enable_reg_write(phydev);
+ if (ret)
+ return ret;
+
+ phydev->autoneg = AUTONEG_DISABLE;
+ phydev->speed = SPEED_100;
+ phydev->duplex = DUPLEX_FULL;
+
+ switch (phydev->phy_id & PHY_ID_MASK) {
+ case PHY_ID_TJA1100:
+ reg_mask = MII_CFG1_AUTO_OP | MII_CFG1_LED_MODE_MASK |
+ MII_CFG1_LED_ENABLE;
+ reg_val = MII_CFG1_AUTO_OP | MII_CFG1_LED_MODE_LINKUP |
+ MII_CFG1_LED_ENABLE;
+
+ reg_mask |= MII_CFG1_INTERFACE_MODE_MASK;
+ ret = tja11xx_get_interface_mode(phydev);
+ if (ret < 0)
+ return ret;
+
+ reg_val |= (ret & 0xffff);
+ ret = phy_modify(phydev, MII_CFG1, reg_mask, reg_val);
+ if (ret)
+ return ret;
+ break;
+ case PHY_ID_TJA1101:
+ reg_mask = MII_CFG1_INTERFACE_MODE_MASK;
+ ret = tja11xx_get_interface_mode(phydev);
+ if (ret < 0)
+ return ret;
+
+ reg_val = ret & 0xffff;
+ ret = phy_modify(phydev, MII_CFG1, reg_mask, reg_val);
+ if (ret)
+ return ret;
+ fallthrough;
+ case PHY_ID_TJA1102:
+ ret = phy_set_bits(phydev, MII_COMMCFG, MII_COMMCFG_AUTO_OP);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = phy_clear_bits(phydev, MII_CFG1, MII_CFG1_SLEEP_CONFIRM);
+ if (ret)
+ return ret;
+
+ ret = phy_modify(phydev, MII_CFG2, MII_CFG2_SLEEP_REQUEST_TO,
+ MII_CFG2_SLEEP_REQUEST_TO_16MS);
+ if (ret)
+ return ret;
+
+ ret = tja11xx_wakeup(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* ACK interrupts by reading the status register */
+ ret = phy_read(phydev, MII_INTSRC);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int tja11xx_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
+
+ ret = genphy_update_link(phydev);
+ if (ret)
+ return ret;
+
+ ret = phy_read(phydev, MII_CFG1);
+ if (ret < 0)
+ return ret;
+
+ if (ret & MII_CFG1_MASTER_SLAVE)
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+ else
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+
+ if (phydev->link) {
+ ret = phy_read(phydev, MII_COMMSTAT);
+ if (ret < 0)
+ return ret;
+
+ if (!(ret & MII_COMMSTAT_LINK_UP))
+ phydev->link = 0;
+ }
+
+ return 0;
+}
+
+static int tja11xx_get_sqi(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_COMMSTAT);
+ if (ret < 0)
+ return ret;
+
+ return FIELD_GET(MII_COMMSTAT_SQI_STATE, ret);
+}
+
+static int tja11xx_get_sqi_max(struct phy_device *phydev)
+{
+ return MII_COMMSTAT_SQI_MAX;
+}
+
+static int tja11xx_get_sset_count(struct phy_device *phydev)
+{
+ return ARRAY_SIZE(tja11xx_hw_stats);
+}
+
+static void tja11xx_get_strings(struct phy_device *phydev, u8 *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tja11xx_hw_stats); i++) {
+ strncpy(data + i * ETH_GSTRING_LEN,
+ tja11xx_hw_stats[i].string, ETH_GSTRING_LEN);
+ }
+}
+
+static void tja11xx_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(tja11xx_hw_stats); i++) {
+ ret = phy_read(phydev, tja11xx_hw_stats[i].reg);
+ if (ret < 0)
+ data[i] = U64_MAX;
+ else {
+ data[i] = ret & tja11xx_hw_stats[i].mask;
+ data[i] >>= tja11xx_hw_stats[i].off;
+ }
+ }
+}
+
+static int tja11xx_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 ret;
+
+ if (type == hwmon_in && attr == hwmon_in_lcrit_alarm) {
+ ret = phy_read(phydev, MII_INTSRC);
+ if (ret < 0)
+ return ret;
+
+ *value = !!(ret & MII_INTSRC_TEMP_ERR);
+ return 0;
+ }
+
+ if (type == hwmon_temp && attr == hwmon_temp_crit_alarm) {
+ ret = phy_read(phydev, MII_INTSRC);
+ if (ret < 0)
+ return ret;
+
+ *value = !!(ret & MII_INTSRC_UV_ERR);
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t tja11xx_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (type == hwmon_in && attr == hwmon_in_lcrit_alarm)
+ return 0444;
+
+ if (type == hwmon_temp && attr == hwmon_temp_crit_alarm)
+ return 0444;
+
+ return 0;
+}
+
+static const struct hwmon_channel_info * const tja11xx_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(in, HWMON_I_LCRIT_ALARM),
+ HWMON_CHANNEL_INFO(temp, HWMON_T_CRIT_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops tja11xx_hwmon_hwmon_ops = {
+ .is_visible = tja11xx_hwmon_is_visible,
+ .read = tja11xx_hwmon_read,
+};
+
+static const struct hwmon_chip_info tja11xx_hwmon_chip_info = {
+ .ops = &tja11xx_hwmon_hwmon_ops,
+ .info = tja11xx_hwmon_info,
+};
+
+static int tja11xx_hwmon_register(struct phy_device *phydev,
+ struct tja11xx_priv *priv)
+{
+ struct device *dev = &phydev->mdio.dev;
+
+ priv->hwmon_name = devm_hwmon_sanitize_name(dev, dev_name(dev));
+ if (IS_ERR(priv->hwmon_name))
+ return PTR_ERR(priv->hwmon_name);
+
+ priv->hwmon_dev =
+ devm_hwmon_device_register_with_info(dev, priv->hwmon_name,
+ phydev,
+ &tja11xx_hwmon_chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(priv->hwmon_dev);
+}
+
+static int tja11xx_parse_dt(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ struct tja11xx_priv *priv = phydev->priv;
+
+ if (!IS_ENABLED(CONFIG_OF_MDIO))
+ return 0;
+
+ if (of_property_read_bool(node, "nxp,rmii-refclk-in"))
+ priv->flags |= TJA110X_RMII_MODE_REFCLK_IN;
+
+ return 0;
+}
+
+static int tja11xx_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct tja11xx_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->phydev = phydev;
+ phydev->priv = priv;
+
+ ret = tja11xx_parse_dt(phydev);
+ if (ret)
+ return ret;
+
+ return tja11xx_hwmon_register(phydev, priv);
+}
+
+static void tja1102_p1_register(struct work_struct *work)
+{
+ struct tja11xx_priv *priv = container_of(work, struct tja11xx_priv,
+ phy_register_work);
+ struct phy_device *phydev_phy0 = priv->phydev;
+ struct mii_bus *bus = phydev_phy0->mdio.bus;
+ struct device *dev = &phydev_phy0->mdio.dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ int ret;
+
+ for_each_available_child_of_node(np, child) {
+ struct phy_device *phy;
+ int addr;
+
+ addr = of_mdio_parse_addr(dev, child);
+ if (addr < 0) {
+ dev_err(dev, "Can't parse addr\n");
+ continue;
+ } else if (addr != phydev_phy0->mdio.addr + 1) {
+ /* Currently we care only about double PHY chip TJA1102.
+ * If some day NXP will decide to bring chips with more
+ * PHYs, this logic should be reworked.
+ */
+ dev_err(dev, "Unexpected address. Should be: %i\n",
+ phydev_phy0->mdio.addr + 1);
+ continue;
+ }
+
+ if (mdiobus_is_registered_device(bus, addr)) {
+ dev_err(dev, "device is already registered\n");
+ continue;
+ }
+
+ /* Real PHY ID of Port 1 is 0 */
+ phy = phy_device_create(bus, addr, PHY_ID_TJA1102, false, NULL);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "Can't create PHY device for Port 1: %i\n",
+ addr);
+ continue;
+ }
+
+ /* Overwrite parent device. phy_device_create() set parent to
+ * the mii_bus->dev, which is not correct in case.
+ */
+ phy->mdio.dev.parent = dev;
+
+ ret = of_mdiobus_phy_device_register(bus, phy, child, addr);
+ if (ret) {
+ /* All resources needed for Port 1 should be already
+ * available for Port 0. Both ports use the same
+ * interrupt line, so -EPROBE_DEFER would make no sense
+ * here.
+ */
+ dev_err(dev, "Can't register Port 1. Unexpected error: %i\n",
+ ret);
+ phy_device_free(phy);
+ }
+ }
+}
+
+static int tja1102_p0_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct tja11xx_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->phydev = phydev;
+ INIT_WORK(&priv->phy_register_work, tja1102_p1_register);
+
+ ret = tja11xx_hwmon_register(phydev, priv);
+ if (ret)
+ return ret;
+
+ schedule_work(&priv->phy_register_work);
+
+ return 0;
+}
+
+static int tja1102_match_phy_device(struct phy_device *phydev, bool port0)
+{
+ int ret;
+
+ if ((phydev->phy_id & PHY_ID_MASK) != PHY_ID_TJA1102)
+ return 0;
+
+ ret = phy_read(phydev, MII_PHYSID2);
+ if (ret < 0)
+ return ret;
+
+ /* TJA1102 Port 1 has phyid 0 and doesn't support temperature
+ * and undervoltage alarms.
+ */
+ if (port0)
+ return ret ? 1 : 0;
+
+ return !ret;
+}
+
+static int tja1102_p0_match_phy_device(struct phy_device *phydev)
+{
+ return tja1102_match_phy_device(phydev, true);
+}
+
+static int tja1102_p1_match_phy_device(struct phy_device *phydev)
+{
+ return tja1102_match_phy_device(phydev, false);
+}
+
+static int tja11xx_ack_interrupt(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_INTSRC);
+
+ return (ret < 0) ? ret : 0;
+}
+
+static int tja11xx_config_intr(struct phy_device *phydev)
+{
+ int value = 0;
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = tja11xx_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ value = MII_INTEN_LINK_FAIL | MII_INTEN_LINK_UP |
+ MII_INTEN_UV_ERR | MII_INTEN_TEMP_ERR;
+ err = phy_write(phydev, MII_INTEN, value);
+ } else {
+ err = phy_write(phydev, MII_INTEN, value);
+ if (err)
+ return err;
+
+ err = tja11xx_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t tja11xx_handle_interrupt(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_INTSRC);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (irq_status & MII_INTSRC_TEMP_ERR)
+ dev_warn(dev, "Overtemperature error detected (temp > 155C°).\n");
+ if (irq_status & MII_INTSRC_UV_ERR)
+ dev_warn(dev, "Undervoltage error detected.\n");
+
+ if (!(irq_status & MII_INTSRC_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int tja11xx_cable_test_start(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_clear_bits(phydev, MII_COMMCFG, MII_COMMCFG_AUTO_OP);
+ if (ret)
+ return ret;
+
+ ret = tja11xx_wakeup(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = tja11xx_disable_link_control(phydev);
+ if (ret < 0)
+ return ret;
+
+ return phy_set_bits(phydev, MII_ECTRL, MII_ECTRL_CABLE_TEST);
+}
+
+/*
+ * | BI_DA+ | BI_DA- | Result
+ * | open | open | open
+ * | + short to - | - short to + | short
+ * | short to Vdd | open | open
+ * | open | shot to Vdd | open
+ * | short to Vdd | short to Vdd | short
+ * | shot to GND | open | open
+ * | open | shot to GND | open
+ * | short to GND | shot to GND | short
+ * | connected to active link partner (master) | shot and open
+ */
+static int tja11xx_cable_test_report_trans(u32 result)
+{
+ u32 mask = MII_EXTSTAT_SHORT_DETECT | MII_EXTSTAT_OPEN_DETECT;
+
+ if ((result & mask) == mask) {
+ /* connected to active link partner (master) */
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ } else if ((result & mask) == 0) {
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ } else if (result & MII_EXTSTAT_SHORT_DETECT) {
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+ } else if (result & MII_EXTSTAT_OPEN_DETECT) {
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+ } else {
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+ }
+}
+
+static int tja11xx_cable_test_report(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, MII_EXTSTAT);
+ if (ret < 0)
+ return ret;
+
+ ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+ tja11xx_cable_test_report_trans(ret));
+
+ return 0;
+}
+
+static int tja11xx_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ int ret;
+
+ *finished = false;
+
+ ret = phy_read(phydev, MII_ECTRL);
+ if (ret < 0)
+ return ret;
+
+ if (!(ret & MII_ECTRL_CABLE_TEST)) {
+ *finished = true;
+
+ ret = phy_set_bits(phydev, MII_COMMCFG, MII_COMMCFG_AUTO_OP);
+ if (ret)
+ return ret;
+
+ return tja11xx_cable_test_report(phydev);
+ }
+
+ return 0;
+}
+
+static struct phy_driver tja11xx_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_TJA1100),
+ .name = "NXP TJA1100",
+ .features = PHY_BASIC_T1_FEATURES,
+ .probe = tja11xx_probe,
+ .soft_reset = tja11xx_soft_reset,
+ .config_aneg = tja11xx_config_aneg,
+ .config_init = tja11xx_config_init,
+ .read_status = tja11xx_read_status,
+ .get_sqi = tja11xx_get_sqi,
+ .get_sqi_max = tja11xx_get_sqi_max,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .set_loopback = genphy_loopback,
+ /* Statistics */
+ .get_sset_count = tja11xx_get_sset_count,
+ .get_strings = tja11xx_get_strings,
+ .get_stats = tja11xx_get_stats,
+ }, {
+ PHY_ID_MATCH_MODEL(PHY_ID_TJA1101),
+ .name = "NXP TJA1101",
+ .features = PHY_BASIC_T1_FEATURES,
+ .probe = tja11xx_probe,
+ .soft_reset = tja11xx_soft_reset,
+ .config_aneg = tja11xx_config_aneg,
+ .config_init = tja11xx_config_init,
+ .read_status = tja11xx_read_status,
+ .get_sqi = tja11xx_get_sqi,
+ .get_sqi_max = tja11xx_get_sqi_max,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .set_loopback = genphy_loopback,
+ /* Statistics */
+ .get_sset_count = tja11xx_get_sset_count,
+ .get_strings = tja11xx_get_strings,
+ .get_stats = tja11xx_get_stats,
+ }, {
+ .name = "NXP TJA1102 Port 0",
+ .features = PHY_BASIC_T1_FEATURES,
+ .flags = PHY_POLL_CABLE_TEST,
+ .probe = tja1102_p0_probe,
+ .soft_reset = tja11xx_soft_reset,
+ .config_aneg = tja11xx_config_aneg,
+ .config_init = tja11xx_config_init,
+ .read_status = tja11xx_read_status,
+ .get_sqi = tja11xx_get_sqi,
+ .get_sqi_max = tja11xx_get_sqi_max,
+ .match_phy_device = tja1102_p0_match_phy_device,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .set_loopback = genphy_loopback,
+ /* Statistics */
+ .get_sset_count = tja11xx_get_sset_count,
+ .get_strings = tja11xx_get_strings,
+ .get_stats = tja11xx_get_stats,
+ .config_intr = tja11xx_config_intr,
+ .handle_interrupt = tja11xx_handle_interrupt,
+ .cable_test_start = tja11xx_cable_test_start,
+ .cable_test_get_status = tja11xx_cable_test_get_status,
+ }, {
+ .name = "NXP TJA1102 Port 1",
+ .features = PHY_BASIC_T1_FEATURES,
+ .flags = PHY_POLL_CABLE_TEST,
+ /* currently no probe for Port 1 is need */
+ .soft_reset = tja11xx_soft_reset,
+ .config_aneg = tja11xx_config_aneg,
+ .config_init = tja11xx_config_init,
+ .read_status = tja11xx_read_status,
+ .get_sqi = tja11xx_get_sqi,
+ .get_sqi_max = tja11xx_get_sqi_max,
+ .match_phy_device = tja1102_p1_match_phy_device,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .set_loopback = genphy_loopback,
+ /* Statistics */
+ .get_sset_count = tja11xx_get_sset_count,
+ .get_strings = tja11xx_get_strings,
+ .get_stats = tja11xx_get_stats,
+ .config_intr = tja11xx_config_intr,
+ .handle_interrupt = tja11xx_handle_interrupt,
+ .cable_test_start = tja11xx_cable_test_start,
+ .cable_test_get_status = tja11xx_cable_test_get_status,
+ }
+};
+
+module_phy_driver(tja11xx_driver);
+
+static struct mdio_device_id __maybe_unused tja11xx_tbl[] = {
+ { PHY_ID_MATCH_MODEL(PHY_ID_TJA1100) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_TJA1101) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_TJA1102) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, tja11xx_tbl);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("NXP TJA11xx BoardR-Reach PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
new file mode 100644
index 000000000..8e6fd4962
--- /dev/null
+++ b/drivers/net/phy/phy-c45.c
@@ -0,0 +1,1517 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Clause 45 PHY support
+ */
+#include <linux/ethtool.h>
+#include <linux/export.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+#include "mdio-open-alliance.h"
+
+/**
+ * genphy_c45_baset1_able - checks if the PMA has BASE-T1 extended abilities
+ * @phydev: target phy_device struct
+ */
+static bool genphy_c45_baset1_able(struct phy_device *phydev)
+{
+ int val;
+
+ if (phydev->pma_extable == -ENODATA) {
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_EXTABLE);
+ if (val < 0)
+ return false;
+
+ phydev->pma_extable = val;
+ }
+
+ return !!(phydev->pma_extable & MDIO_PMA_EXTABLE_BT1);
+}
+
+/**
+ * genphy_c45_pma_can_sleep - checks if the PMA have sleep support
+ * @phydev: target phy_device struct
+ */
+static bool genphy_c45_pma_can_sleep(struct phy_device *phydev)
+{
+ int stat1;
+
+ stat1 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT1);
+ if (stat1 < 0)
+ return false;
+
+ return !!(stat1 & MDIO_STAT1_LPOWERABLE);
+}
+
+/**
+ * genphy_c45_pma_resume - wakes up the PMA module
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_resume(struct phy_device *phydev)
+{
+ if (!genphy_c45_pma_can_sleep(phydev))
+ return -EOPNOTSUPP;
+
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+ MDIO_CTRL1_LPOWER);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_resume);
+
+/**
+ * genphy_c45_pma_suspend - suspends the PMA module
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_suspend(struct phy_device *phydev)
+{
+ if (!genphy_c45_pma_can_sleep(phydev))
+ return -EOPNOTSUPP;
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+ MDIO_CTRL1_LPOWER);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_suspend);
+
+/**
+ * genphy_c45_pma_baset1_setup_master_slave - configures forced master/slave
+ * role of BaseT1 devices.
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_baset1_setup_master_slave(struct phy_device *phydev)
+{
+ int ctl = 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl = MDIO_PMA_PMD_BT1_CTRL_CFG_MST;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL,
+ MDIO_PMA_PMD_BT1_CTRL_CFG_MST, ctl);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_baset1_setup_master_slave);
+
+/**
+ * genphy_c45_pma_setup_forced - configures a forced speed
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_setup_forced(struct phy_device *phydev)
+{
+ int bt1_ctrl, 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:
+ if (genphy_c45_baset1_able(phydev))
+ ctrl2 |= MDIO_PMA_CTRL2_BASET1;
+ else
+ 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_2500:
+ ctrl1 |= MDIO_CTRL1_SPEED2_5G;
+ /* Assume 2.5Gbase-T */
+ ctrl2 |= MDIO_PMA_CTRL2_2_5GBT;
+ break;
+ case SPEED_5000:
+ ctrl1 |= MDIO_CTRL1_SPEED5G;
+ /* Assume 5Gbase-T */
+ ctrl2 |= MDIO_PMA_CTRL2_5GBT;
+ 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;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2, ctrl2);
+ if (ret < 0)
+ return ret;
+
+ if (genphy_c45_baset1_able(phydev)) {
+ ret = genphy_c45_pma_baset1_setup_master_slave(phydev);
+ if (ret < 0)
+ return ret;
+
+ bt1_ctrl = 0;
+ if (phydev->speed == SPEED_1000)
+ bt1_ctrl = MDIO_PMA_PMD_BT1_CTRL_STRAP_B1000;
+
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL,
+ MDIO_PMA_PMD_BT1_CTRL_STRAP, bt1_ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ return genphy_c45_an_disable_aneg(phydev);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_setup_forced);
+
+/* Sets master/slave preference and supported technologies.
+ * The preference is set in the BIT(4) of BASE-T1 AN
+ * advertisement register 7.515 and whether the status
+ * is forced or not, it is set in the BIT(12) of BASE-T1
+ * AN advertisement register 7.514.
+ * Sets 10BASE-T1L Ability BIT(14) in BASE-T1 autonegotiation
+ * advertisement register [31:16] if supported.
+ */
+static int genphy_c45_baset1_an_config_aneg(struct phy_device *phydev)
+{
+ u16 adv_l_mask, adv_l = 0;
+ u16 adv_m_mask, adv_m = 0;
+ int changed = 0;
+ int ret;
+
+ adv_l_mask = MDIO_AN_T1_ADV_L_FORCE_MS | MDIO_AN_T1_ADV_L_PAUSE_CAP |
+ MDIO_AN_T1_ADV_L_PAUSE_ASYM;
+ adv_m_mask = MDIO_AN_T1_ADV_M_MST | MDIO_AN_T1_ADV_M_B10L;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ adv_m |= MDIO_AN_T1_ADV_M_MST;
+ fallthrough;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ adv_l |= MDIO_AN_T1_ADV_L_FORCE_MS;
+ break;
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ adv_m |= MDIO_AN_T1_ADV_M_MST;
+ fallthrough;
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ /* if master/slave role is not specified, do not overwrite it */
+ adv_l_mask &= ~MDIO_AN_T1_ADV_L_FORCE_MS;
+ adv_m_mask &= ~MDIO_AN_T1_ADV_M_MST;
+ break;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ adv_l |= linkmode_adv_to_mii_t1_adv_l_t(phydev->advertising);
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_L,
+ adv_l_mask, adv_l);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = 1;
+
+ adv_m |= linkmode_adv_to_mii_t1_adv_m_t(phydev->advertising);
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_M,
+ adv_m_mask, adv_m);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = 1;
+
+ return changed;
+}
+
+/**
+ * genphy_c45_an_config_aneg - configure advertisement registers
+ * @phydev: target phy_device struct
+ *
+ * Configure advertisement registers based on modes set in phydev->advertising
+ *
+ * Returns negative errno code on failure, 0 if advertisement didn't change,
+ * or 1 if advertised modes changed.
+ */
+int genphy_c45_an_config_aneg(struct phy_device *phydev)
+{
+ int changed = 0, ret;
+ u32 adv;
+
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ ret = genphy_c45_an_config_eee_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ changed = true;
+
+ if (genphy_c45_baset1_able(phydev))
+ return genphy_c45_baset1_an_config_aneg(phydev);
+
+ adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_100BASE4 |
+ ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+ adv);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = 1;
+
+ adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising);
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+ MDIO_AN_10GBT_CTRL_ADV10G |
+ MDIO_AN_10GBT_CTRL_ADV5G |
+ MDIO_AN_10GBT_CTRL_ADV2_5G, adv);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = 1;
+
+ return changed;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_an_config_aneg);
+
+/**
+ * genphy_c45_an_disable_aneg - disable auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * Disable auto-negotiation in the Clause 45 PHY. The link 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)
+{
+ u16 reg = MDIO_CTRL1;
+
+ if (genphy_c45_baset1_able(phydev))
+ reg = MDIO_AN_T1_CTRL;
+
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
+ MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
+}
+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)
+{
+ u16 reg = MDIO_CTRL1;
+
+ if (genphy_c45_baset1_able(phydev))
+ reg = MDIO_AN_T1_CTRL;
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_AN, reg,
+ MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_restart_aneg);
+
+/**
+ * genphy_c45_check_and_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: target phy_device struct
+ * @restart: whether aneg restart is requested
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Check, and restart auto-negotiation if needed.
+ */
+int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart)
+{
+ u16 reg = MDIO_CTRL1;
+ int ret;
+
+ if (genphy_c45_baset1_able(phydev))
+ reg = MDIO_AN_T1_CTRL;
+
+ if (!restart) {
+ /* Configure and restart aneg if it wasn't set before */
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+ if (ret < 0)
+ return ret;
+
+ if (!(ret & MDIO_AN_CTRL1_ENABLE))
+ restart = true;
+ }
+
+ if (restart)
+ return genphy_c45_restart_aneg(phydev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg);
+
+/**
+ * genphy_c45_aneg_done - return auto-negotiation complete status
+ * @phydev: target phy_device struct
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Reads the status register from the auto-negotiation MMD, returning:
+ * - positive if auto-negotiation is complete
+ * - negative errno code on error
+ * - zero otherwise
+ */
+int genphy_c45_aneg_done(struct phy_device *phydev)
+{
+ int reg = MDIO_STAT1;
+ int val;
+
+ if (genphy_c45_baset1_able(phydev))
+ reg = MDIO_AN_T1_STAT;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+
+ 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
+ *
+ * Read the link status from the specified MMDs, and if they all indicate
+ * that the link is up, set phydev->link to 1. 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 = MDIO_DEVS_PMAPMD;
+ int val, devad;
+ bool link = true;
+
+ if (phydev->c45_ids.mmds_present & MDIO_DEVS_AN) {
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+ if (val < 0)
+ return val;
+
+ /* Autoneg is being started, therefore disregard current
+ * link status and report link as down.
+ */
+ if (val & MDIO_AN_CTRL1_RESTART) {
+ phydev->link = 0;
+ return 0;
+ }
+ }
+
+ while (mmd_mask && link) {
+ 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 except
+ * the link was already down.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ val = phy_read_mmd(phydev, devad, MDIO_STAT1);
+ if (val < 0)
+ return val;
+ else if (val & MDIO_STAT1_LSTATUS)
+ continue;
+ }
+
+ val = phy_read_mmd(phydev, devad, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (!(val & MDIO_STAT1_LSTATUS))
+ link = false;
+ }
+
+ phydev->link = link;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_link);
+
+/* Read the Clause 45 defined BASE-T1 AN (7.513) status register to check
+ * if autoneg is complete. If so read the BASE-T1 Autonegotiation
+ * Advertisement registers filling in the link partner advertisement,
+ * pause and asym_pause members in phydev.
+ */
+static int genphy_c45_baset1_read_lpa(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_STAT);
+ if (val < 0)
+ return val;
+
+ if (!(val & MDIO_AN_STAT1_COMPLETE)) {
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->lp_advertising);
+ mii_t1_adv_l_mod_linkmode_t(phydev->lp_advertising, 0);
+ mii_t1_adv_m_mod_linkmode_t(phydev->lp_advertising, 0);
+
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ return 0;
+ }
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->lp_advertising, 1);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_LP_L);
+ if (val < 0)
+ return val;
+
+ mii_t1_adv_l_mod_linkmode_t(phydev->lp_advertising, val);
+ phydev->pause = val & MDIO_AN_T1_ADV_L_PAUSE_CAP ? 1 : 0;
+ phydev->asym_pause = val & MDIO_AN_T1_ADV_L_PAUSE_ASYM ? 1 : 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_LP_M);
+ if (val < 0)
+ return val;
+
+ mii_t1_adv_m_mod_linkmode_t(phydev->lp_advertising, val);
+
+ return 0;
+}
+
+/**
+ * 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;
+
+ if (genphy_c45_baset1_able(phydev))
+ return genphy_c45_baset1_read_lpa(phydev);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (!(val & MDIO_AN_STAT1_COMPLETE)) {
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->lp_advertising);
+ mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+ mii_adv_mod_linkmode_adv_t(phydev->lp_advertising, 0);
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ return 0;
+ }
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->lp_advertising,
+ val & MDIO_AN_STAT1_LPABLE);
+
+ /* Read the link partner's base page advertisement */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA);
+ if (val < 0)
+ return val;
+
+ mii_adv_mod_linkmode_adv_t(phydev->lp_advertising, 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;
+
+ mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_lpa);
+
+/**
+ * genphy_c45_pma_baset1_read_master_slave - read forced master/slave
+ * configuration
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_baset1_read_master_slave(struct phy_device *phydev)
+{
+ int val;
+
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_PMA_PMD_BT1_CTRL_CFG_MST) {
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+ } else {
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_baset1_read_master_slave);
+
+/**
+ * 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;
+
+ linkmode_zero(phydev->lp_advertising);
+
+ 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_SPEED2_5G:
+ phydev->speed = SPEED_2500;
+ break;
+ case MDIO_CTRL1_SPEED5G:
+ phydev->speed = SPEED_5000;
+ break;
+ case MDIO_CTRL1_SPEED10G:
+ phydev->speed = SPEED_10000;
+ break;
+ default:
+ phydev->speed = SPEED_UNKNOWN;
+ break;
+ }
+
+ phydev->duplex = DUPLEX_FULL;
+
+ if (genphy_c45_baset1_able(phydev)) {
+ val = genphy_c45_pma_baset1_read_master_slave(phydev);
+ if (val < 0)
+ return val;
+ }
+
+ 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);
+
+/**
+ * genphy_c45_write_eee_adv - write advertised EEE link modes
+ * @phydev: target phy_device struct
+ * @adv: the linkmode advertisement settings
+ */
+int genphy_c45_write_eee_adv(struct phy_device *phydev, unsigned long *adv)
+{
+ int val, changed = 0;
+
+ if (linkmode_intersects(phydev->supported_eee, PHY_EEE_CAP1_FEATURES)) {
+ val = linkmode_to_mii_eee_cap1_t(adv);
+
+ /* In eee_broken_modes are stored MDIO_AN_EEE_ADV specific raw
+ * register values.
+ */
+ val &= ~phydev->eee_broken_modes;
+
+ /* IEEE 802.3-2018 45.2.7.13 EEE advertisement 1
+ * (Register 7.60)
+ */
+ val = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+ MDIO_AN_EEE_ADV,
+ MDIO_EEE_100TX | MDIO_EEE_1000T |
+ MDIO_EEE_10GT | MDIO_EEE_1000KX |
+ MDIO_EEE_10GKX4 | MDIO_EEE_10GKR,
+ val);
+ if (val < 0)
+ return val;
+ if (val > 0)
+ changed = 1;
+ }
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported_eee)) {
+ val = linkmode_adv_to_mii_10base_t1_t(adv);
+ /* IEEE 802.3cg-2019 45.2.7.25 10BASE-T1 AN control register
+ * (Register 7.526)
+ */
+ val = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+ MDIO_AN_10BT1_AN_CTRL,
+ MDIO_AN_10BT1_AN_CTRL_ADV_EEE_T1L,
+ val);
+ if (val < 0)
+ return val;
+ if (val > 0)
+ changed = 1;
+ }
+
+ return changed;
+}
+
+/**
+ * genphy_c45_read_eee_adv - read advertised EEE link modes
+ * @phydev: target phy_device struct
+ * @adv: the linkmode advertisement status
+ */
+int genphy_c45_read_eee_adv(struct phy_device *phydev, unsigned long *adv)
+{
+ int val;
+
+ if (linkmode_intersects(phydev->supported_eee, PHY_EEE_CAP1_FEATURES)) {
+ /* IEEE 802.3-2018 45.2.7.13 EEE advertisement 1
+ * (Register 7.60)
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
+ if (val < 0)
+ return val;
+
+ mii_eee_cap1_mod_linkmode_t(adv, val);
+ }
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported_eee)) {
+ /* IEEE 802.3cg-2019 45.2.7.25 10BASE-T1 AN control register
+ * (Register 7.526)
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10BT1_AN_CTRL);
+ if (val < 0)
+ return val;
+
+ mii_10base_t1_adv_mod_linkmode_t(adv, val);
+ }
+
+ return 0;
+}
+
+/**
+ * genphy_c45_read_eee_lpa - read advertised LP EEE link modes
+ * @phydev: target phy_device struct
+ * @lpa: the linkmode LP advertisement status
+ */
+static int genphy_c45_read_eee_lpa(struct phy_device *phydev,
+ unsigned long *lpa)
+{
+ int val;
+
+ if (linkmode_intersects(phydev->supported_eee, PHY_EEE_CAP1_FEATURES)) {
+ /* IEEE 802.3-2018 45.2.7.14 EEE link partner ability 1
+ * (Register 7.61)
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
+ if (val < 0)
+ return val;
+
+ mii_eee_cap1_mod_linkmode_t(lpa, val);
+ }
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported_eee)) {
+ /* IEEE 802.3cg-2019 45.2.7.26 10BASE-T1 AN status register
+ * (Register 7.527)
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10BT1_AN_STAT);
+ if (val < 0)
+ return val;
+
+ mii_10base_t1_adv_mod_linkmode_t(lpa, val);
+ }
+
+ return 0;
+}
+
+/**
+ * genphy_c45_read_eee_cap1 - read supported EEE link modes from register 3.20
+ * @phydev: target phy_device struct
+ */
+static int genphy_c45_read_eee_cap1(struct phy_device *phydev)
+{
+ int val;
+
+ /* IEEE 802.3-2018 45.2.3.10 EEE control and capability 1
+ * (Register 3.20)
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
+ if (val < 0)
+ return val;
+
+ /* The 802.3 2018 standard says the top 2 bits are reserved and should
+ * read as 0. Also, it seems unlikely anybody will build a PHY which
+ * supports 100GBASE-R deep sleep all the way down to 100BASE-TX EEE.
+ * If MDIO_PCS_EEE_ABLE is 0xffff assume EEE is not supported.
+ */
+ if (val == 0xffff)
+ return 0;
+
+ mii_eee_cap1_mod_linkmode_t(phydev->supported_eee, val);
+
+ /* Some buggy devices indicate EEE link modes in MDIO_PCS_EEE_ABLE
+ * which they don't support as indicated by BMSR, ESTATUS etc.
+ */
+ linkmode_and(phydev->supported_eee, phydev->supported_eee,
+ phydev->supported);
+
+ return 0;
+}
+
+/**
+ * genphy_c45_read_eee_abilities - read supported EEE link modes
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_read_eee_abilities(struct phy_device *phydev)
+{
+ int val;
+
+ /* There is not indicator whether optional register
+ * "EEE control and capability 1" (3.20) is supported. Read it only
+ * on devices with appropriate linkmodes.
+ */
+ if (linkmode_intersects(phydev->supported, PHY_EEE_CAP1_FEATURES)) {
+ val = genphy_c45_read_eee_cap1(phydev);
+ if (val)
+ return val;
+ }
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported)) {
+ /* IEEE 802.3cg-2019 45.2.1.186b 10BASE-T1L PMA status register
+ * (Register 1.2295)
+ */
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10T1L_STAT);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported_eee,
+ val & MDIO_PMA_10T1L_STAT_EEE);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_eee_abilities);
+
+/**
+ * genphy_c45_an_config_eee_aneg - configure EEE advertisement
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_an_config_eee_aneg(struct phy_device *phydev)
+{
+ if (!phydev->eee_enabled) {
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(adv) = {};
+
+ return genphy_c45_write_eee_adv(phydev, adv);
+ }
+
+ return genphy_c45_write_eee_adv(phydev, phydev->advertising_eee);
+}
+
+/**
+ * genphy_c45_pma_baset1_read_abilities - read supported baset1 link modes from PMA
+ * @phydev: target phy_device struct
+ *
+ * Read the supported link modes from the extended BASE-T1 ability register
+ */
+int genphy_c45_pma_baset1_read_abilities(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_PMD_BT1_B10L_ABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_PMD_BT1_B100_ABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT1_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_PMD_BT1_B1000_ABLE);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_STAT);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->supported,
+ val & MDIO_AN_STAT1_ABLE);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_baset1_read_abilities);
+
+/**
+ * genphy_c45_pma_read_abilities - read supported link modes from PMA
+ * @phydev: target phy_device struct
+ *
+ * Read the supported link modes from the PMA Status 2 (1.8) register. If bit
+ * 1.8.9 is set, the list of supported modes is build using the values in the
+ * PMA Extended Abilities (1.11) register, indicating 1000BASET an 10G related
+ * modes. If bit 1.11.14 is set, then the list is also extended with the modes
+ * in the 2.5G/5G PMA Extended register (1.21), indicating if 2.5GBASET and
+ * 5GBASET are supported.
+ */
+int genphy_c45_pma_read_abilities(struct phy_device *phydev)
+{
+ int val;
+
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+ if (phydev->c45_ids.mmds_present & MDIO_DEVS_AN) {
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_STAT1_ABLE)
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->supported);
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT2);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_STAT2_10GBSR);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_STAT2_10GBLR);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_STAT2_10GBER);
+
+ if (val & MDIO_PMA_STAT2_EXTABLE) {
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_EXTABLE);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_10GBLRM);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_10GBT);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_10GBKX4);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_10GBKR);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_1000BT);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_1000BKX);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_100BTX);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_100BTX);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_10BT);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ phydev->supported,
+ val & MDIO_PMA_EXTABLE_10BT);
+
+ if (val & MDIO_PMA_EXTABLE_NBT) {
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
+ MDIO_PMA_NG_EXTABLE);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_NG_EXTABLE_2_5GBT);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_NG_EXTABLE_5GBT);
+ }
+
+ if (val & MDIO_PMA_EXTABLE_BT1) {
+ val = genphy_c45_pma_baset1_read_abilities(phydev);
+ if (val < 0)
+ return val;
+ }
+ }
+
+ /* This is optional functionality. If not supported, we may get an error
+ * which should be ignored.
+ */
+ genphy_c45_read_eee_abilities(phydev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_read_abilities);
+
+/* Read master/slave preference from registers.
+ * The preference is read from the BIT(4) of BASE-T1 AN
+ * advertisement register 7.515 and whether the preference
+ * is forced or not, it is read from BASE-T1 AN advertisement
+ * register 7.514.
+ */
+int genphy_c45_baset1_read_status(struct phy_device *phydev)
+{
+ int ret;
+ int cfg;
+
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_L);
+ if (ret < 0)
+ return ret;
+
+ cfg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_M);
+ if (cfg < 0)
+ return cfg;
+
+ if (ret & MDIO_AN_T1_ADV_L_FORCE_MS) {
+ if (cfg & MDIO_AN_T1_ADV_M_MST)
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+ else
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+ } else {
+ if (cfg & MDIO_AN_T1_ADV_M_MST)
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+ else
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_baset1_read_status);
+
+/**
+ * genphy_c45_read_status - read PHY status
+ * @phydev: target phy_device struct
+ *
+ * Reads status from PHY and sets phy_device members accordingly.
+ */
+int genphy_c45_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_read_link(phydev);
+ if (ret)
+ return ret;
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ ret = genphy_c45_read_lpa(phydev);
+ if (ret)
+ return ret;
+
+ if (genphy_c45_baset1_able(phydev)) {
+ ret = genphy_c45_baset1_read_status(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ phy_resolve_aneg_linkmode(phydev);
+ } else {
+ ret = genphy_c45_read_pma(phydev);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_status);
+
+/**
+ * genphy_c45_config_aneg - restart auto-negotiation or forced setup
+ * @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 force a configuration.
+ */
+int genphy_c45_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return genphy_c45_pma_setup_forced(phydev);
+
+ ret = genphy_c45_an_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_config_aneg);
+
+/* 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 genphy_c45_loopback(struct phy_device *phydev, bool enable)
+{
+ return phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1,
+ MDIO_PCS_CTRL1_LOOPBACK,
+ enable ? MDIO_PCS_CTRL1_LOOPBACK : 0);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_loopback);
+
+/**
+ * genphy_c45_fast_retrain - configure fast retrain registers
+ * @phydev: target phy_device struct
+ * @enable: enable fast retrain or not
+ *
+ * Description: If fast-retrain is enabled, we configure PHY as
+ * advertising fast retrain capable and THP Bypass Request, then
+ * enable fast retrain. If it is not enabled, we configure fast
+ * retrain disabled.
+ */
+int genphy_c45_fast_retrain(struct phy_device *phydev, bool enable)
+{
+ int ret;
+
+ if (!enable)
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_FSRT_CSR,
+ MDIO_PMA_10GBR_FSRT_ENABLE);
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported)) {
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+ MDIO_AN_10GBT_CTRL_ADVFSRT2_5G);
+ if (ret)
+ return ret;
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_CTRL2,
+ MDIO_AN_THP_BP2_5GT);
+ if (ret)
+ return ret;
+ }
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_FSRT_CSR,
+ MDIO_PMA_10GBR_FSRT_ENABLE);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_fast_retrain);
+
+/**
+ * genphy_c45_plca_get_cfg - get PLCA configuration from standard registers
+ * @phydev: target phy_device struct
+ * @plca_cfg: output structure to store the PLCA configuration
+ *
+ * Description: if the PHY complies to the Open Alliance TC14 10BASE-T1S PLCA
+ * Management Registers specifications, this function can be used to retrieve
+ * the current PLCA configuration from the standard registers in MMD 31.
+ */
+int genphy_c45_plca_get_cfg(struct phy_device *phydev,
+ struct phy_plca_cfg *plca_cfg)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_IDVER);
+ if (ret < 0)
+ return ret;
+
+ if ((ret & MDIO_OATC14_PLCA_IDM) != OATC14_IDM)
+ return -ENODEV;
+
+ plca_cfg->version = ret & ~MDIO_OATC14_PLCA_IDM;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_CTRL0);
+ if (ret < 0)
+ return ret;
+
+ plca_cfg->enabled = !!(ret & MDIO_OATC14_PLCA_EN);
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_CTRL1);
+ if (ret < 0)
+ return ret;
+
+ plca_cfg->node_cnt = (ret & MDIO_OATC14_PLCA_NCNT) >> 8;
+ plca_cfg->node_id = (ret & MDIO_OATC14_PLCA_ID);
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR);
+ if (ret < 0)
+ return ret;
+
+ plca_cfg->to_tmr = ret & MDIO_OATC14_PLCA_TOT;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_BURST);
+ if (ret < 0)
+ return ret;
+
+ plca_cfg->burst_cnt = (ret & MDIO_OATC14_PLCA_MAXBC) >> 8;
+ plca_cfg->burst_tmr = (ret & MDIO_OATC14_PLCA_BTMR);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_plca_get_cfg);
+
+/**
+ * genphy_c45_plca_set_cfg - set PLCA configuration using standard registers
+ * @phydev: target phy_device struct
+ * @plca_cfg: structure containing the PLCA configuration. Fields set to -1 are
+ * not to be changed.
+ *
+ * Description: if the PHY complies to the Open Alliance TC14 10BASE-T1S PLCA
+ * Management Registers specifications, this function can be used to modify
+ * the PLCA configuration using the standard registers in MMD 31.
+ */
+int genphy_c45_plca_set_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg)
+{
+ u16 val = 0;
+ int ret;
+
+ // PLCA IDVER is read-only
+ if (plca_cfg->version >= 0)
+ return -EINVAL;
+
+ // first of all, disable PLCA if required
+ if (plca_cfg->enabled == 0) {
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_CTRL0,
+ MDIO_OATC14_PLCA_EN);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ // check if we need to set the PLCA node count, node ID, or both
+ if (plca_cfg->node_cnt >= 0 || plca_cfg->node_id >= 0) {
+ /* if one between node count and node ID is -not- to be
+ * changed, read the register to later perform merge/purge of
+ * the configuration as appropriate
+ */
+ if (plca_cfg->node_cnt < 0 || plca_cfg->node_id < 0) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_CTRL1);
+
+ if (ret < 0)
+ return ret;
+
+ val = ret;
+ }
+
+ if (plca_cfg->node_cnt >= 0)
+ val = (val & ~MDIO_OATC14_PLCA_NCNT) |
+ (plca_cfg->node_cnt << 8);
+
+ if (plca_cfg->node_id >= 0)
+ val = (val & ~MDIO_OATC14_PLCA_ID) |
+ (plca_cfg->node_id);
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_CTRL1, val);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ if (plca_cfg->to_tmr >= 0) {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_TOTMR,
+ plca_cfg->to_tmr);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ // check if we need to set the PLCA burst count, burst timer, or both
+ if (plca_cfg->burst_cnt >= 0 || plca_cfg->burst_tmr >= 0) {
+ /* if one between burst count and burst timer is -not- to be
+ * changed, read the register to later perform merge/purge of
+ * the configuration as appropriate
+ */
+ if (plca_cfg->burst_cnt < 0 || plca_cfg->burst_tmr < 0) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_BURST);
+
+ if (ret < 0)
+ return ret;
+
+ val = ret;
+ }
+
+ if (plca_cfg->burst_cnt >= 0)
+ val = (val & ~MDIO_OATC14_PLCA_MAXBC) |
+ (plca_cfg->burst_cnt << 8);
+
+ if (plca_cfg->burst_tmr >= 0)
+ val = (val & ~MDIO_OATC14_PLCA_BTMR) |
+ (plca_cfg->burst_tmr);
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_BURST, val);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ // if we need to enable PLCA, do it at the end
+ if (plca_cfg->enabled > 0) {
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_CTRL0,
+ MDIO_OATC14_PLCA_EN);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_plca_set_cfg);
+
+/**
+ * genphy_c45_plca_get_status - get PLCA status from standard registers
+ * @phydev: target phy_device struct
+ * @plca_st: output structure to store the PLCA status
+ *
+ * Description: if the PHY complies to the Open Alliance TC14 10BASE-T1S PLCA
+ * Management Registers specifications, this function can be used to retrieve
+ * the current PLCA status information from the standard registers in MMD 31.
+ */
+int genphy_c45_plca_get_status(struct phy_device *phydev,
+ struct phy_plca_status *plca_st)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_STATUS);
+ if (ret < 0)
+ return ret;
+
+ plca_st->pst = !!(ret & MDIO_OATC14_PLCA_PST);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_plca_get_status);
+
+/**
+ * genphy_c45_eee_is_active - get EEE status
+ * @phydev: target phy_device struct
+ * @adv: variable to store advertised linkmodes
+ * @lp: variable to store LP advertised linkmodes
+ * @is_enabled: variable to store EEE enabled/disabled configuration value
+ *
+ * Description: this function will read local and link partner PHY
+ * advertisements. Compare them return current EEE state.
+ */
+int genphy_c45_eee_is_active(struct phy_device *phydev, unsigned long *adv,
+ unsigned long *lp, bool *is_enabled)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp_adv) = {};
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp_lp) = {};
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
+ bool eee_enabled, eee_active;
+ int ret;
+
+ ret = genphy_c45_read_eee_adv(phydev, tmp_adv);
+ if (ret)
+ return ret;
+
+ ret = genphy_c45_read_eee_lpa(phydev, tmp_lp);
+ if (ret)
+ return ret;
+
+ eee_enabled = !linkmode_empty(tmp_adv);
+ linkmode_and(common, tmp_adv, tmp_lp);
+ if (eee_enabled && !linkmode_empty(common))
+ eee_active = phy_check_valid(phydev->speed, phydev->duplex,
+ common);
+ else
+ eee_active = false;
+
+ if (adv)
+ linkmode_copy(adv, tmp_adv);
+ if (lp)
+ linkmode_copy(lp, tmp_lp);
+ if (is_enabled)
+ *is_enabled = eee_enabled;
+
+ return eee_active;
+}
+EXPORT_SYMBOL(genphy_c45_eee_is_active);
+
+/**
+ * genphy_c45_ethtool_get_eee - get EEE supported and status
+ * @phydev: target phy_device struct
+ * @data: ethtool_eee data
+ *
+ * Description: it reports the Supported/Advertisement/LP Advertisement
+ * capabilities.
+ */
+int genphy_c45_ethtool_get_eee(struct phy_device *phydev,
+ struct ethtool_eee *data)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(adv) = {};
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(lp) = {};
+ bool overflow = false, is_enabled;
+ int ret;
+
+ ret = genphy_c45_eee_is_active(phydev, adv, lp, &is_enabled);
+ if (ret < 0)
+ return ret;
+
+ data->eee_enabled = is_enabled;
+ data->eee_active = ret;
+
+ if (!ethtool_convert_link_mode_to_legacy_u32(&data->supported,
+ phydev->supported_eee))
+ overflow = true;
+ if (!ethtool_convert_link_mode_to_legacy_u32(&data->advertised, adv))
+ overflow = true;
+ if (!ethtool_convert_link_mode_to_legacy_u32(&data->lp_advertised, lp))
+ overflow = true;
+
+ if (overflow)
+ phydev_warn(phydev, "Not all supported or advertised EEE link modes were passed to the user space\n");
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_c45_ethtool_get_eee);
+
+/**
+ * genphy_c45_ethtool_set_eee - set EEE supported and status
+ * @phydev: target phy_device struct
+ * @data: ethtool_eee data
+ *
+ * Description: sets the Supported/Advertisement/LP Advertisement
+ * capabilities. If eee_enabled is false, no links modes are
+ * advertised, but the previously advertised link modes are
+ * retained. This allows EEE to be enabled/disabled in a
+ * non-destructive way.
+ */
+int genphy_c45_ethtool_set_eee(struct phy_device *phydev,
+ struct ethtool_eee *data)
+{
+ int ret;
+
+ if (data->eee_enabled) {
+ if (data->advertised) {
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(adv);
+
+ ethtool_convert_legacy_u32_to_link_mode(adv,
+ data->advertised);
+ linkmode_andnot(adv, adv, phydev->supported_eee);
+ if (!linkmode_empty(adv)) {
+ phydev_warn(phydev, "At least some EEE link modes are not supported.\n");
+ return -EINVAL;
+ }
+
+ ethtool_convert_legacy_u32_to_link_mode(phydev->advertising_eee,
+ data->advertised);
+ } else {
+ linkmode_copy(phydev->advertising_eee,
+ phydev->supported_eee);
+ }
+
+ phydev->eee_enabled = true;
+ } else {
+ phydev->eee_enabled = false;
+ }
+
+ ret = genphy_c45_an_config_eee_aneg(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return phy_restart_aneg(phydev);
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_c45_ethtool_set_eee);
+
+struct phy_driver genphy_c45_driver = {
+ .phy_id = 0xffffffff,
+ .phy_id_mask = 0xffffffff,
+ .name = "Generic Clause 45 PHY",
+ .read_status = genphy_c45_read_status,
+};
diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c
new file mode 100644
index 000000000..966c93cbe
--- /dev/null
+++ b/drivers/net/phy/phy-core.c
@@ -0,0 +1,1013 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Core PHY library, taken from phy.c
+ */
+#include <linux/export.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+/**
+ * phy_speed_to_str - Return a string representing the PHY link speed
+ *
+ * @speed: Speed of the link
+ */
+const char *phy_speed_to_str(int speed)
+{
+ BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 102,
+ "Enum ethtool_link_mode_bit_indices and phylib are out of sync. "
+ "If a speed or mode has been added please update phy_speed_to_str "
+ "and the PHY settings array.\n");
+
+ 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_200000:
+ return "200Gbps";
+ case SPEED_400000:
+ return "400Gbps";
+ case SPEED_800000:
+ return "800Gbps";
+ case SPEED_UNKNOWN:
+ return "Unknown";
+ default:
+ return "Unsupported (update phy-core.c)";
+ }
+}
+EXPORT_SYMBOL_GPL(phy_speed_to_str);
+
+/**
+ * phy_duplex_to_str - Return string describing the duplex
+ *
+ * @duplex: Duplex setting to describe
+ */
+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);
+
+/**
+ * phy_rate_matching_to_str - Return a string describing the rate matching
+ *
+ * @rate_matching: Type of rate matching to describe
+ */
+const char *phy_rate_matching_to_str(int rate_matching)
+{
+ switch (rate_matching) {
+ case RATE_MATCH_NONE:
+ return "none";
+ case RATE_MATCH_PAUSE:
+ return "pause";
+ case RATE_MATCH_CRS:
+ return "crs";
+ case RATE_MATCH_OPEN_LOOP:
+ return "open-loop";
+ }
+ return "Unsupported (update phy-core.c)";
+}
+EXPORT_SYMBOL_GPL(phy_rate_matching_to_str);
+
+/**
+ * phy_interface_num_ports - Return the number of links that can be carried by
+ * a given MAC-PHY physical link. Returns 0 if this is
+ * unknown, the number of links else.
+ *
+ * @interface: The interface mode we want to get the number of ports
+ */
+int phy_interface_num_ports(phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_NA:
+ return 0;
+ case PHY_INTERFACE_MODE_INTERNAL:
+ case PHY_INTERFACE_MODE_MII:
+ case PHY_INTERFACE_MODE_GMII:
+ case PHY_INTERFACE_MODE_TBI:
+ case PHY_INTERFACE_MODE_REVMII:
+ case PHY_INTERFACE_MODE_RMII:
+ case PHY_INTERFACE_MODE_REVRMII:
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RTBI:
+ case PHY_INTERFACE_MODE_XGMII:
+ case PHY_INTERFACE_MODE_XLGMII:
+ case PHY_INTERFACE_MODE_MOCA:
+ case PHY_INTERFACE_MODE_TRGMII:
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_SMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_5GBASER:
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_25GBASER:
+ case PHY_INTERFACE_MODE_10GKR:
+ case PHY_INTERFACE_MODE_100BASEX:
+ case PHY_INTERFACE_MODE_RXAUI:
+ case PHY_INTERFACE_MODE_XAUI:
+ case PHY_INTERFACE_MODE_1000BASEKX:
+ return 1;
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ return 4;
+ case PHY_INTERFACE_MODE_PSGMII:
+ return 5;
+ case PHY_INTERFACE_MODE_MAX:
+ WARN_ONCE(1, "PHY_INTERFACE_MODE_MAX isn't a valid interface mode");
+ return 0;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(phy_interface_num_ports);
+
+/* 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.
+ */
+
+#define PHY_SETTING(s, d, b) { .speed = SPEED_ ## s, .duplex = DUPLEX_ ## d, \
+ .bit = ETHTOOL_LINK_MODE_ ## b ## _BIT}
+
+static const struct phy_setting settings[] = {
+ /* 800G */
+ PHY_SETTING( 800000, FULL, 800000baseCR8_Full ),
+ PHY_SETTING( 800000, FULL, 800000baseKR8_Full ),
+ PHY_SETTING( 800000, FULL, 800000baseDR8_Full ),
+ PHY_SETTING( 800000, FULL, 800000baseDR8_2_Full ),
+ PHY_SETTING( 800000, FULL, 800000baseSR8_Full ),
+ PHY_SETTING( 800000, FULL, 800000baseVR8_Full ),
+ /* 400G */
+ PHY_SETTING( 400000, FULL, 400000baseCR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseKR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseLR8_ER8_FR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseDR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseSR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseCR4_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseKR4_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseLR4_ER4_FR4_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseDR4_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseSR4_Full ),
+ /* 200G */
+ PHY_SETTING( 200000, FULL, 200000baseCR4_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseKR4_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseLR4_ER4_FR4_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseDR4_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseSR4_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseCR2_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseKR2_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseLR2_ER2_FR2_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseDR2_Full ),
+ PHY_SETTING( 200000, FULL, 200000baseSR2_Full ),
+ /* 100G */
+ PHY_SETTING( 100000, FULL, 100000baseCR4_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseKR4_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseLR4_ER4_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseSR4_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseCR2_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseKR2_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseLR2_ER2_FR2_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseDR2_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseSR2_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseCR_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseKR_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseLR_ER_FR_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseDR_Full ),
+ PHY_SETTING( 100000, FULL, 100000baseSR_Full ),
+ /* 56G */
+ PHY_SETTING( 56000, FULL, 56000baseCR4_Full ),
+ PHY_SETTING( 56000, FULL, 56000baseKR4_Full ),
+ PHY_SETTING( 56000, FULL, 56000baseLR4_Full ),
+ PHY_SETTING( 56000, FULL, 56000baseSR4_Full ),
+ /* 50G */
+ PHY_SETTING( 50000, FULL, 50000baseCR2_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseKR2_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseSR2_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseCR_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseKR_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseLR_ER_FR_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseDR_Full ),
+ PHY_SETTING( 50000, FULL, 50000baseSR_Full ),
+ /* 40G */
+ PHY_SETTING( 40000, FULL, 40000baseCR4_Full ),
+ PHY_SETTING( 40000, FULL, 40000baseKR4_Full ),
+ PHY_SETTING( 40000, FULL, 40000baseLR4_Full ),
+ PHY_SETTING( 40000, FULL, 40000baseSR4_Full ),
+ /* 25G */
+ PHY_SETTING( 25000, FULL, 25000baseCR_Full ),
+ PHY_SETTING( 25000, FULL, 25000baseKR_Full ),
+ PHY_SETTING( 25000, FULL, 25000baseSR_Full ),
+ /* 20G */
+ PHY_SETTING( 20000, FULL, 20000baseKR2_Full ),
+ PHY_SETTING( 20000, FULL, 20000baseMLD2_Full ),
+ /* 10G */
+ PHY_SETTING( 10000, FULL, 10000baseCR_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseER_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseKR_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseKX4_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseLR_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseLRM_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseR_FEC ),
+ PHY_SETTING( 10000, FULL, 10000baseSR_Full ),
+ PHY_SETTING( 10000, FULL, 10000baseT_Full ),
+ /* 5G */
+ PHY_SETTING( 5000, FULL, 5000baseT_Full ),
+ /* 2.5G */
+ PHY_SETTING( 2500, FULL, 2500baseT_Full ),
+ PHY_SETTING( 2500, FULL, 2500baseX_Full ),
+ /* 1G */
+ PHY_SETTING( 1000, FULL, 1000baseT_Full ),
+ PHY_SETTING( 1000, HALF, 1000baseT_Half ),
+ PHY_SETTING( 1000, FULL, 1000baseT1_Full ),
+ PHY_SETTING( 1000, FULL, 1000baseX_Full ),
+ PHY_SETTING( 1000, FULL, 1000baseKX_Full ),
+ /* 100M */
+ PHY_SETTING( 100, FULL, 100baseT_Full ),
+ PHY_SETTING( 100, FULL, 100baseT1_Full ),
+ PHY_SETTING( 100, HALF, 100baseT_Half ),
+ PHY_SETTING( 100, HALF, 100baseFX_Half ),
+ PHY_SETTING( 100, FULL, 100baseFX_Full ),
+ /* 10M */
+ PHY_SETTING( 10, FULL, 10baseT_Full ),
+ PHY_SETTING( 10, HALF, 10baseT_Half ),
+ PHY_SETTING( 10, FULL, 10baseT1L_Full ),
+ PHY_SETTING( 10, FULL, 10baseT1S_Full ),
+ PHY_SETTING( 10, HALF, 10baseT1S_Half ),
+ PHY_SETTING( 10, HALF, 10baseT1S_P2MP_Half ),
+};
+#undef PHY_SETTING
+
+/**
+ * phy_lookup_setting - lookup a PHY setting
+ * @speed: speed to match
+ * @duplex: duplex to match
+ * @mask: allowed 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, 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 < __ETHTOOL_LINK_MODE_MASK_NBITS &&
+ 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 count;
+ int i;
+
+ for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++)
+ if (settings[i].bit < __ETHTOOL_LINK_MODE_MASK_NBITS &&
+ test_bit(settings[i].bit, mask) &&
+ (count == 0 || speeds[count - 1] != settings[i].speed))
+ speeds[count++] = settings[i].speed;
+
+ return count;
+}
+
+static void __set_linkmode_max_speed(u32 max_speed, unsigned long *addr)
+{
+ const struct phy_setting *p;
+ int i;
+
+ for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
+ if (p->speed > max_speed)
+ linkmode_clear_bit(p->bit, addr);
+ else
+ break;
+ }
+}
+
+static void __set_phy_supported(struct phy_device *phydev, u32 max_speed)
+{
+ __set_linkmode_max_speed(max_speed, phydev->supported);
+}
+
+/**
+ * phy_set_max_speed - Set the maximum speed the PHY should support
+ *
+ * @phydev: The phy_device struct
+ * @max_speed: Maximum speed
+ *
+ * The PHY might be more capable than the MAC. For example a Fast Ethernet
+ * is connected to a 1G PHY. This function allows the MAC to indicate its
+ * maximum speed, and so limit what the PHY will advertise.
+ */
+void phy_set_max_speed(struct phy_device *phydev, u32 max_speed)
+{
+ __set_phy_supported(phydev, max_speed);
+
+ phy_advertise_supported(phydev);
+}
+EXPORT_SYMBOL(phy_set_max_speed);
+
+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);
+}
+
+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_resolve_aneg_pause - Determine pause autoneg results
+ *
+ * @phydev: The phy_device struct
+ *
+ * Once autoneg has completed the local pause settings can be
+ * resolved. Determine if pause and asymmetric pause should be used
+ * by the MAC.
+ */
+
+void phy_resolve_aneg_pause(struct phy_device *phydev)
+{
+ if (phydev->duplex == DUPLEX_FULL) {
+ phydev->pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->lp_advertising);
+ phydev->asym_pause = linkmode_test_bit(
+ ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->lp_advertising);
+ }
+}
+EXPORT_SYMBOL_GPL(phy_resolve_aneg_pause);
+
+/**
+ * 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)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
+ int i;
+
+ linkmode_and(common, phydev->lp_advertising, phydev->advertising);
+
+ for (i = 0; i < ARRAY_SIZE(settings); i++)
+ if (test_bit(settings[i].bit, common)) {
+ phydev->speed = settings[i].speed;
+ phydev->duplex = settings[i].duplex;
+ break;
+ }
+
+ phy_resolve_aneg_pause(phydev);
+}
+EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);
+
+/**
+ * phy_check_downshift - check whether downshift occurred
+ * @phydev: The phy_device struct
+ *
+ * Check whether a downshift to a lower speed occurred. If this should be the
+ * case warn the user.
+ * Prerequisite for detecting downshift is that PHY driver implements the
+ * read_status callback and sets phydev->speed to the actual link speed.
+ */
+void phy_check_downshift(struct phy_device *phydev)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
+ int i, speed = SPEED_UNKNOWN;
+
+ phydev->downshifted_rate = 0;
+
+ if (phydev->autoneg == AUTONEG_DISABLE ||
+ phydev->speed == SPEED_UNKNOWN)
+ return;
+
+ linkmode_and(common, phydev->lp_advertising, phydev->advertising);
+
+ for (i = 0; i < ARRAY_SIZE(settings); i++)
+ if (test_bit(settings[i].bit, common)) {
+ speed = settings[i].speed;
+ break;
+ }
+
+ if (speed == SPEED_UNKNOWN || phydev->speed >= speed)
+ return;
+
+ phydev_warn(phydev, "Downshift occurred from negotiated speed %s to actual speed %s, check cabling!\n",
+ phy_speed_to_str(speed), phy_speed_to_str(phydev->speed));
+
+ phydev->downshifted_rate = 1;
+}
+EXPORT_SYMBOL_GPL(phy_check_downshift);
+
+static int phy_resolve_min_speed(struct phy_device *phydev, bool fdx_only)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
+ int i = ARRAY_SIZE(settings);
+
+ linkmode_and(common, phydev->lp_advertising, phydev->advertising);
+
+ while (--i >= 0) {
+ if (test_bit(settings[i].bit, common)) {
+ if (fdx_only && settings[i].duplex != DUPLEX_FULL)
+ continue;
+ return settings[i].speed;
+ }
+ }
+
+ return SPEED_UNKNOWN;
+}
+
+int phy_speed_down_core(struct phy_device *phydev)
+{
+ int min_common_speed = phy_resolve_min_speed(phydev, true);
+
+ if (min_common_speed == SPEED_UNKNOWN)
+ return -EINVAL;
+
+ __set_linkmode_max_speed(min_common_speed, phydev->advertising);
+
+ return 0;
+}
+
+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 && phydev->drv->read_mmd) {
+ val = phydev->drv->read_mmd(phydev, devad, regnum);
+ } else if (phydev->is_c45) {
+ val = __mdiobus_c45_read(phydev->mdio.bus, phydev->mdio.addr,
+ devad, regnum);
+ } else {
+ struct mii_bus *bus = phydev->mdio.bus;
+ int phy_addr = phydev->mdio.addr;
+
+ mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+ /* Read the content of the MMD's selected register */
+ val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+ }
+ return val;
+}
+EXPORT_SYMBOL(__phy_read_mmd);
+
+/**
+ * phy_read_mmd - Convenience function for reading a register
+ * from an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from
+ * @regnum: The register on the MMD to read
+ *
+ * Same rules as for phy_read();
+ */
+int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_read_mmd(phydev, devad, regnum);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL(phy_read_mmd);
+
+/**
+ * __phy_write_mmd - Convenience function for writing a register
+ * on an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from
+ * @regnum: The register on the MMD to read
+ * @val: value to write to @regnum
+ *
+ * Same rules as for __phy_write();
+ */
+int __phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+{
+ int ret;
+
+ if (regnum > (u16)~0 || devad > 32)
+ return -EINVAL;
+
+ if (phydev->drv && phydev->drv->write_mmd) {
+ ret = phydev->drv->write_mmd(phydev, devad, regnum, val);
+ } else if (phydev->is_c45) {
+ ret = __mdiobus_c45_write(phydev->mdio.bus, phydev->mdio.addr,
+ devad, regnum, val);
+ } else {
+ struct mii_bus *bus = phydev->mdio.bus;
+ int phy_addr = phydev->mdio.addr;
+
+ mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+ /* Write the data into MMD's selected register */
+ __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+
+ ret = 0;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(__phy_write_mmd);
+
+/**
+ * phy_write_mmd - Convenience function for writing a register
+ * on an MMD on a given PHY.
+ * @phydev: The phy_device struct
+ * @devad: The MMD to read from
+ * @regnum: The register on the MMD to read
+ * @val: value to write to @regnum
+ *
+ * Same rules as for phy_write();
+ */
+int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_write_mmd(phydev, devad, regnum, val);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL(phy_write_mmd);
+
+/**
+ * phy_modify_changed - Function for modifying a PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int phy_modify_changed(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_modify_changed(phydev, regnum, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify_changed);
+
+/**
+ * __phy_modify - Convenience function for modifying a PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int __phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+ int ret;
+
+ ret = __phy_modify_changed(phydev, regnum, mask, set);
+
+ return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(__phy_modify);
+
+/**
+ * phy_modify - Convenience function for modifying a given PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_modify(phydev, regnum, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify);
+
+/**
+ * __phy_modify_mmd_changed - Function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * Unlocked helper function which allows a MMD register to be modified as
+ * new register value = (old register value & ~mask) | set
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int __phy_modify_mmd_changed(struct phy_device *phydev, int devad, u32 regnum,
+ u16 mask, u16 set)
+{
+ int new, ret;
+
+ ret = __phy_read_mmd(phydev, devad, regnum);
+ if (ret < 0)
+ return ret;
+
+ new = (ret & ~mask) | set;
+ if (new == ret)
+ return 0;
+
+ ret = __phy_write_mmd(phydev, devad, regnum, new);
+
+ return ret < 0 ? ret : 1;
+}
+EXPORT_SYMBOL_GPL(__phy_modify_mmd_changed);
+
+/**
+ * phy_modify_mmd_changed - Function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int phy_modify_mmd_changed(struct phy_device *phydev, int devad, u32 regnum,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify_mmd_changed);
+
+/**
+ * __phy_modify_mmd - Convenience function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int __phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set);
+
+ return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(__phy_modify_mmd);
+
+/**
+ * phy_modify_mmd - Convenience function for modifying a register on MMD
+ * @phydev: the phy_device struct
+ * @devad: the MMD containing register to modify
+ * @regnum: register number to modify
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ * NOTE: MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation.
+ */
+int phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_modify_mmd(phydev, devad, regnum, mask, set);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_modify_mmd);
+
+static int __phy_read_page(struct phy_device *phydev)
+{
+ if (WARN_ONCE(!phydev->drv->read_page, "read_page callback not available, PHY driver not loaded?\n"))
+ return -EOPNOTSUPP;
+
+ return phydev->drv->read_page(phydev);
+}
+
+static int __phy_write_page(struct phy_device *phydev, int page)
+{
+ if (WARN_ONCE(!phydev->drv->write_page, "write_page callback not available, PHY driver not loaded?\n"))
+ return -EOPNOTSUPP;
+
+ 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)
+{
+ phy_lock_mdio_bus(phydev);
+ 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;
+ }
+
+ phy_unlock_mdio_bus(phydev);
+
+ 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_changed() - 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
+ *
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
+ */
+int phy_modify_paged_changed(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_changed(phydev, regnum, mask, set);
+
+ return phy_restore_page(phydev, oldpage, ret);
+}
+EXPORT_SYMBOL(phy_modify_paged_changed);
+
+/**
+ * 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 = phy_modify_paged_changed(phydev, page, regnum, mask, set);
+
+ return ret < 0 ? ret : 0;
+}
+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..df54c137c
--- /dev/null
+++ b/drivers/net/phy/phy.c
@@ -0,0 +1,1725 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* 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
+ */
+
+#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/netlink.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/ethtool_netlink.h>
+#include <linux/phy.h>
+#include <linux/phy_led_triggers.h>
+#include <linux/sfp.h>
+#include <linux/workqueue.h>
+#include <linux/mdio.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/atomic.h>
+#include <linux/suspend.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include <net/sock.h>
+
+#define PHY_STATE_TIME HZ
+
+#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(READY)
+ PHY_STATE_STR(UP)
+ PHY_STATE_STR(RUNNING)
+ PHY_STATE_STR(NOLINK)
+ PHY_STATE_STR(CABLETEST)
+ PHY_STATE_STR(HALTED)
+ PHY_STATE_STR(ERROR)
+ }
+
+ return NULL;
+}
+
+static void phy_process_state_change(struct phy_device *phydev,
+ enum phy_state old_state)
+{
+ 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));
+ if (phydev->drv && phydev->drv->link_change_notify)
+ phydev->drv->link_change_notify(phydev);
+ }
+}
+
+static void phy_link_up(struct phy_device *phydev)
+{
+ phydev->phy_link_change(phydev, true);
+ phy_led_trigger_change_speed(phydev);
+}
+
+static void phy_link_down(struct phy_device *phydev)
+{
+ phydev->phy_link_change(phydev, false);
+ phy_led_trigger_change_speed(phydev);
+ WRITE_ONCE(phydev->link_down_events, phydev->link_down_events + 1);
+}
+
+static const char *phy_pause_str(struct phy_device *phydev)
+{
+ bool local_pause, local_asym_pause;
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ goto no_pause;
+
+ local_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->advertising);
+ local_asym_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->advertising);
+
+ if (local_pause && phydev->pause)
+ return "rx/tx";
+
+ if (local_asym_pause && phydev->asym_pause) {
+ if (local_pause)
+ return "rx";
+ if (phydev->pause)
+ return "tx";
+ }
+
+no_pause:
+ return "off";
+}
+
+/**
+ * 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 %s- flow control %s\n",
+ phy_speed_to_str(phydev->speed),
+ phy_duplex_to_str(phydev->duplex),
+ phydev->downshifted_rate ? "(downshifted) " : "",
+ phy_pause_str(phydev));
+ } else {
+ netdev_info(phydev->attached_dev, "Link is Down\n");
+ }
+}
+EXPORT_SYMBOL(phy_print_status);
+
+/**
+ * phy_get_rate_matching - determine if rate matching is supported
+ * @phydev: The phy device to return rate matching for
+ * @iface: The interface mode to use
+ *
+ * This determines the type of rate matching (if any) that @phy supports
+ * using @iface. @iface may be %PHY_INTERFACE_MODE_NA to determine if any
+ * interface supports rate matching.
+ *
+ * Return: The type of rate matching @phy supports for @iface, or
+ * %RATE_MATCH_NONE.
+ */
+int phy_get_rate_matching(struct phy_device *phydev,
+ phy_interface_t iface)
+{
+ int ret = RATE_MATCH_NONE;
+
+ if (phydev->drv->get_rate_matching) {
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->get_rate_matching(phydev, iface);
+ mutex_unlock(&phydev->lock);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_get_rate_matching);
+
+/**
+ * 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, bool interrupts)
+{
+ phydev->interrupts = interrupts ? 1 : 0;
+ 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);
+ else if (phydev->is_c45)
+ return genphy_c45_aneg_done(phydev);
+ else
+ 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, unsigned long *supported)
+{
+ return phy_lookup_setting(speed, duplex, supported, 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)
+{
+ return phy_speeds(speeds, size, phy->supported);
+}
+
+/**
+ * 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.
+ */
+bool phy_check_valid(int speed, int duplex, unsigned long *features)
+{
+ return !!phy_lookup_setting(speed, duplex, features, true);
+}
+EXPORT_SYMBOL(phy_check_valid);
+
+/**
+ * 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;
+
+ setting = phy_find_valid(phydev->speed, phydev->duplex,
+ phydev->supported);
+ 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;
+ }
+}
+
+void phy_ethtool_ksettings_get(struct phy_device *phydev,
+ struct ethtool_link_ksettings *cmd)
+{
+ mutex_lock(&phydev->lock);
+ linkmode_copy(cmd->link_modes.supported, phydev->supported);
+ linkmode_copy(cmd->link_modes.advertising, phydev->advertising);
+ linkmode_copy(cmd->link_modes.lp_advertising, phydev->lp_advertising);
+
+ cmd->base.speed = phydev->speed;
+ cmd->base.duplex = phydev->duplex;
+ cmd->base.master_slave_cfg = phydev->master_slave_get;
+ cmd->base.master_slave_state = phydev->master_slave_state;
+ cmd->base.rate_matching = phydev->rate_matching;
+ if (phydev->interface == PHY_INTERFACE_MODE_MOCA)
+ cmd->base.port = PORT_BNC;
+ else
+ cmd->base.port = phydev->port;
+ 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;
+ mutex_unlock(&phydev->lock);
+}
+EXPORT_SYMBOL(phy_ethtool_ksettings_get);
+
+/**
+ * phy_mii_ioctl - generic PHY MII ioctl interface
+ * @phydev: the phy_device struct
+ * @ifr: &struct ifreq for socket ioctl's
+ * @cmd: ioctl cmd to execute
+ *
+ * Note that this function is currently incompatible with the
+ * PHYCONTROL layer. It changes registers without regard to
+ * current state. Use at own risk.
+ */
+int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
+{
+ struct mii_ioctl_data *mii_data = if_mii(ifr);
+ u16 val = mii_data->val_in;
+ bool change_autoneg = false;
+ int prtad, devad;
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ mii_data->phy_id = phydev->mdio.addr;
+ fallthrough;
+
+ case SIOCGMIIREG:
+ if (mdio_phy_id_is_c45(mii_data->phy_id)) {
+ prtad = mdio_phy_id_prtad(mii_data->phy_id);
+ devad = mdio_phy_id_devad(mii_data->phy_id);
+ mii_data->val_out = mdiobus_c45_read(
+ phydev->mdio.bus, prtad, devad,
+ mii_data->reg_num);
+ } else {
+ mii_data->val_out = mdiobus_read(
+ phydev->mdio.bus, mii_data->phy_id,
+ mii_data->reg_num);
+ }
+ return 0;
+
+ case SIOCSMIIREG:
+ if (mdio_phy_id_is_c45(mii_data->phy_id)) {
+ prtad = mdio_phy_id_prtad(mii_data->phy_id);
+ devad = mdio_phy_id_devad(mii_data->phy_id);
+ } else {
+ prtad = mii_data->phy_id;
+ devad = mii_data->reg_num;
+ }
+ if (prtad == phydev->mdio.addr) {
+ switch (devad) {
+ case MII_BMCR:
+ if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) {
+ if (phydev->autoneg == AUTONEG_ENABLE)
+ change_autoneg = true;
+ phydev->autoneg = AUTONEG_DISABLE;
+ if (val & BMCR_FULLDPLX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+ if (val & BMCR_SPEED1000)
+ phydev->speed = SPEED_1000;
+ else if (val & BMCR_SPEED100)
+ phydev->speed = SPEED_100;
+ else phydev->speed = SPEED_10;
+ } else {
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ change_autoneg = true;
+ phydev->autoneg = AUTONEG_ENABLE;
+ }
+ break;
+ case MII_ADVERTISE:
+ mii_adv_mod_linkmode_adv_t(phydev->advertising,
+ val);
+ change_autoneg = true;
+ break;
+ case MII_CTRL1000:
+ mii_ctrl1000_mod_linkmode_adv_t(phydev->advertising,
+ val);
+ change_autoneg = true;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+
+ if (mdio_phy_id_is_c45(mii_data->phy_id))
+ mdiobus_c45_write(phydev->mdio.bus, prtad, devad,
+ mii_data->reg_num, val);
+ else
+ mdiobus_write(phydev->mdio.bus, prtad, devad, val);
+
+ if (prtad == phydev->mdio.addr &&
+ devad == MII_BMCR &&
+ val & BMCR_RESET)
+ return phy_init_hw(phydev);
+
+ if (change_autoneg)
+ return phy_start_aneg(phydev);
+
+ return 0;
+
+ case SIOCSHWTSTAMP:
+ if (phydev->mii_ts && phydev->mii_ts->hwtstamp)
+ return phydev->mii_ts->hwtstamp(phydev->mii_ts, ifr);
+ fallthrough;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(phy_mii_ioctl);
+
+/**
+ * phy_do_ioctl - generic ndo_eth_ioctl implementation
+ * @dev: the net_device struct
+ * @ifr: &struct ifreq for socket ioctl's
+ * @cmd: ioctl cmd to execute
+ */
+int phy_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ if (!dev->phydev)
+ return -ENODEV;
+
+ return phy_mii_ioctl(dev->phydev, ifr, cmd);
+}
+EXPORT_SYMBOL(phy_do_ioctl);
+
+/**
+ * phy_do_ioctl_running - generic ndo_eth_ioctl implementation but test first
+ *
+ * @dev: the net_device struct
+ * @ifr: &struct ifreq for socket ioctl's
+ * @cmd: ioctl cmd to execute
+ *
+ * Same as phy_do_ioctl, but ensures that net_device is running before
+ * handling the ioctl.
+ */
+int phy_do_ioctl_running(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ if (!netif_running(dev))
+ return -ENODEV;
+
+ return phy_do_ioctl(dev, ifr, cmd);
+}
+EXPORT_SYMBOL(phy_do_ioctl_running);
+
+/**
+ * __phy_hwtstamp_get - Get hardware timestamping configuration from PHY
+ *
+ * @phydev: the PHY device structure
+ * @config: structure holding the timestamping configuration
+ *
+ * Query the PHY device for its current hardware timestamping configuration.
+ */
+int __phy_hwtstamp_get(struct phy_device *phydev,
+ struct kernel_hwtstamp_config *config)
+{
+ if (!phydev)
+ return -ENODEV;
+
+ return phy_mii_ioctl(phydev, config->ifr, SIOCGHWTSTAMP);
+}
+
+/**
+ * __phy_hwtstamp_set - Modify PHY hardware timestamping configuration
+ *
+ * @phydev: the PHY device structure
+ * @config: structure holding the timestamping configuration
+ * @extack: netlink extended ack structure, for error reporting
+ */
+int __phy_hwtstamp_set(struct phy_device *phydev,
+ struct kernel_hwtstamp_config *config,
+ struct netlink_ext_ack *extack)
+{
+ if (!phydev)
+ return -ENODEV;
+
+ return phy_mii_ioctl(phydev, config->ifr, SIOCSHWTSTAMP);
+}
+
+/**
+ * phy_queue_state_machine - Trigger the state machine to run soon
+ *
+ * @phydev: the phy_device struct
+ * @jiffies: Run the state machine after these jiffies
+ */
+void phy_queue_state_machine(struct phy_device *phydev, unsigned long jiffies)
+{
+ mod_delayed_work(system_power_efficient_wq, &phydev->state_queue,
+ jiffies);
+}
+EXPORT_SYMBOL(phy_queue_state_machine);
+
+/**
+ * phy_trigger_machine - Trigger the state machine to run now
+ *
+ * @phydev: the phy_device struct
+ */
+void phy_trigger_machine(struct phy_device *phydev)
+{
+ phy_queue_state_machine(phydev, 0);
+}
+EXPORT_SYMBOL(phy_trigger_machine);
+
+static void phy_abort_cable_test(struct phy_device *phydev)
+{
+ int err;
+
+ ethnl_cable_test_finished(phydev);
+
+ err = phy_init_hw(phydev);
+ if (err)
+ phydev_err(phydev, "Error while aborting cable test");
+}
+
+/**
+ * phy_ethtool_get_strings - Get the statistic counter names
+ *
+ * @phydev: the phy_device struct
+ * @data: Where to put the strings
+ */
+int phy_ethtool_get_strings(struct phy_device *phydev, u8 *data)
+{
+ if (!phydev->drv)
+ return -EIO;
+
+ mutex_lock(&phydev->lock);
+ phydev->drv->get_strings(phydev, data);
+ mutex_unlock(&phydev->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_get_strings);
+
+/**
+ * phy_ethtool_get_sset_count - Get the number of statistic counters
+ *
+ * @phydev: the phy_device struct
+ */
+int phy_ethtool_get_sset_count(struct phy_device *phydev)
+{
+ int ret;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ if (phydev->drv->get_sset_count &&
+ phydev->drv->get_strings &&
+ phydev->drv->get_stats) {
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->get_sset_count(phydev);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+ }
+
+ return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(phy_ethtool_get_sset_count);
+
+/**
+ * phy_ethtool_get_stats - Get the statistic counters
+ *
+ * @phydev: the phy_device struct
+ * @stats: What counters to get
+ * @data: Where to store the counters
+ */
+int phy_ethtool_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ if (!phydev->drv)
+ return -EIO;
+
+ mutex_lock(&phydev->lock);
+ phydev->drv->get_stats(phydev, stats, data);
+ mutex_unlock(&phydev->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_get_stats);
+
+/**
+ * phy_ethtool_get_plca_cfg - Get PLCA RS configuration
+ * @phydev: the phy_device struct
+ * @plca_cfg: where to store the retrieved configuration
+ *
+ * Retrieve the PLCA configuration from the PHY. Return 0 on success or a
+ * negative value if an error occurred.
+ */
+int phy_ethtool_get_plca_cfg(struct phy_device *phydev,
+ struct phy_plca_cfg *plca_cfg)
+{
+ int ret;
+
+ if (!phydev->drv) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (!phydev->drv->get_plca_cfg) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->get_plca_cfg(phydev, plca_cfg);
+
+ mutex_unlock(&phydev->lock);
+out:
+ return ret;
+}
+
+/**
+ * plca_check_valid - Check PLCA configuration before enabling
+ * @phydev: the phy_device struct
+ * @plca_cfg: current PLCA configuration
+ * @extack: extack for reporting useful error messages
+ *
+ * Checks whether the PLCA and PHY configuration are consistent and it is safe
+ * to enable PLCA. Returns 0 on success or a negative value if the PLCA or PHY
+ * configuration is not consistent.
+ */
+static int plca_check_valid(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg,
+ struct netlink_ext_ack *extack)
+{
+ int ret = 0;
+
+ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
+ phydev->advertising)) {
+ ret = -EOPNOTSUPP;
+ NL_SET_ERR_MSG(extack,
+ "Point to Multi-Point mode is not enabled");
+ } else if (plca_cfg->node_id >= 255) {
+ NL_SET_ERR_MSG(extack, "PLCA node ID is not set");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * phy_ethtool_set_plca_cfg - Set PLCA RS configuration
+ * @phydev: the phy_device struct
+ * @plca_cfg: new PLCA configuration to apply
+ * @extack: extack for reporting useful error messages
+ *
+ * Sets the PLCA configuration in the PHY. Return 0 on success or a
+ * negative value if an error occurred.
+ */
+int phy_ethtool_set_plca_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct phy_plca_cfg *curr_plca_cfg;
+ int ret;
+
+ if (!phydev->drv) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (!phydev->drv->set_plca_cfg ||
+ !phydev->drv->get_plca_cfg) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ curr_plca_cfg = kmalloc(sizeof(*curr_plca_cfg), GFP_KERNEL);
+ if (!curr_plca_cfg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mutex_lock(&phydev->lock);
+
+ ret = phydev->drv->get_plca_cfg(phydev, curr_plca_cfg);
+ if (ret)
+ goto out_drv;
+
+ if (curr_plca_cfg->enabled < 0 && plca_cfg->enabled >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'enable' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->node_id < 0 && plca_cfg->node_id >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'local node ID' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->node_cnt < 0 && plca_cfg->node_cnt >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'node count' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->to_tmr < 0 && plca_cfg->to_tmr >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'TO timer' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->burst_cnt < 0 && plca_cfg->burst_cnt >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'burst count' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ if (curr_plca_cfg->burst_tmr < 0 && plca_cfg->burst_tmr >= 0) {
+ NL_SET_ERR_MSG(extack,
+ "PHY does not support changing the PLCA 'burst timer' attribute");
+ ret = -EINVAL;
+ goto out_drv;
+ }
+
+ // if enabling PLCA, perform a few sanity checks
+ if (plca_cfg->enabled > 0) {
+ // allow setting node_id concurrently with enabled
+ if (plca_cfg->node_id >= 0)
+ curr_plca_cfg->node_id = plca_cfg->node_id;
+
+ ret = plca_check_valid(phydev, curr_plca_cfg, extack);
+ if (ret)
+ goto out_drv;
+ }
+
+ ret = phydev->drv->set_plca_cfg(phydev, plca_cfg);
+
+out_drv:
+ kfree(curr_plca_cfg);
+ mutex_unlock(&phydev->lock);
+out:
+ return ret;
+}
+
+/**
+ * phy_ethtool_get_plca_status - Get PLCA RS status information
+ * @phydev: the phy_device struct
+ * @plca_st: where to store the retrieved status information
+ *
+ * Retrieve the PLCA status information from the PHY. Return 0 on success or a
+ * negative value if an error occurred.
+ */
+int phy_ethtool_get_plca_status(struct phy_device *phydev,
+ struct phy_plca_status *plca_st)
+{
+ int ret;
+
+ if (!phydev->drv) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (!phydev->drv->get_plca_status) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->get_plca_status(phydev, plca_st);
+
+ mutex_unlock(&phydev->lock);
+out:
+ return ret;
+}
+
+/**
+ * phy_start_cable_test - Start a cable test
+ *
+ * @phydev: the phy_device struct
+ * @extack: extack for reporting useful error messages
+ */
+int phy_start_cable_test(struct phy_device *phydev,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *dev = phydev->attached_dev;
+ int err = -ENOMEM;
+
+ if (!(phydev->drv &&
+ phydev->drv->cable_test_start &&
+ phydev->drv->cable_test_get_status)) {
+ NL_SET_ERR_MSG(extack,
+ "PHY driver does not support cable testing");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&phydev->lock);
+ if (phydev->state == PHY_CABLETEST) {
+ NL_SET_ERR_MSG(extack,
+ "PHY already performing a test");
+ err = -EBUSY;
+ goto out;
+ }
+
+ if (phydev->state < PHY_UP ||
+ phydev->state > PHY_CABLETEST) {
+ NL_SET_ERR_MSG(extack,
+ "PHY not configured. Try setting interface up");
+ err = -EBUSY;
+ goto out;
+ }
+
+ err = ethnl_cable_test_alloc(phydev, ETHTOOL_MSG_CABLE_TEST_NTF);
+ if (err)
+ goto out;
+
+ /* Mark the carrier down until the test is complete */
+ phy_link_down(phydev);
+
+ netif_testing_on(dev);
+ err = phydev->drv->cable_test_start(phydev);
+ if (err) {
+ netif_testing_off(dev);
+ phy_link_up(phydev);
+ goto out_free;
+ }
+
+ phydev->state = PHY_CABLETEST;
+
+ if (phy_polling_mode(phydev))
+ phy_trigger_machine(phydev);
+
+ mutex_unlock(&phydev->lock);
+
+ return 0;
+
+out_free:
+ ethnl_cable_test_free(phydev);
+out:
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+EXPORT_SYMBOL(phy_start_cable_test);
+
+/**
+ * phy_start_cable_test_tdr - Start a raw TDR cable test
+ *
+ * @phydev: the phy_device struct
+ * @extack: extack for reporting useful error messages
+ * @config: Configuration of the test to run
+ */
+int phy_start_cable_test_tdr(struct phy_device *phydev,
+ struct netlink_ext_ack *extack,
+ const struct phy_tdr_config *config)
+{
+ struct net_device *dev = phydev->attached_dev;
+ int err = -ENOMEM;
+
+ if (!(phydev->drv &&
+ phydev->drv->cable_test_tdr_start &&
+ phydev->drv->cable_test_get_status)) {
+ NL_SET_ERR_MSG(extack,
+ "PHY driver does not support cable test TDR");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&phydev->lock);
+ if (phydev->state == PHY_CABLETEST) {
+ NL_SET_ERR_MSG(extack,
+ "PHY already performing a test");
+ err = -EBUSY;
+ goto out;
+ }
+
+ if (phydev->state < PHY_UP ||
+ phydev->state > PHY_CABLETEST) {
+ NL_SET_ERR_MSG(extack,
+ "PHY not configured. Try setting interface up");
+ err = -EBUSY;
+ goto out;
+ }
+
+ err = ethnl_cable_test_alloc(phydev, ETHTOOL_MSG_CABLE_TEST_TDR_NTF);
+ if (err)
+ goto out;
+
+ /* Mark the carrier down until the test is complete */
+ phy_link_down(phydev);
+
+ netif_testing_on(dev);
+ err = phydev->drv->cable_test_tdr_start(phydev, config);
+ if (err) {
+ netif_testing_off(dev);
+ phy_link_up(phydev);
+ goto out_free;
+ }
+
+ phydev->state = PHY_CABLETEST;
+
+ if (phy_polling_mode(phydev))
+ phy_trigger_machine(phydev);
+
+ mutex_unlock(&phydev->lock);
+
+ return 0;
+
+out_free:
+ ethnl_cable_test_free(phydev);
+out:
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+EXPORT_SYMBOL(phy_start_cable_test_tdr);
+
+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 genphy_c45_config_aneg(phydev);
+
+ return genphy_config_aneg(phydev);
+}
+EXPORT_SYMBOL(phy_config_aneg);
+
+/**
+ * phy_check_link_status - check link status and set state accordingly
+ * @phydev: the phy_device struct
+ *
+ * Description: Check for link and whether autoneg was triggered / is running
+ * and set state accordingly
+ */
+static int phy_check_link_status(struct phy_device *phydev)
+{
+ int err;
+
+ lockdep_assert_held(&phydev->lock);
+
+ /* Keep previous state if loopback is enabled because some PHYs
+ * report that Link is Down when loopback is enabled.
+ */
+ if (phydev->loopback_enabled)
+ return 0;
+
+ err = phy_read_status(phydev);
+ if (err)
+ return err;
+
+ if (phydev->link && phydev->state != PHY_RUNNING) {
+ phy_check_downshift(phydev);
+ phydev->state = PHY_RUNNING;
+ phy_link_up(phydev);
+ } else if (!phydev->link && phydev->state != PHY_NOLINK) {
+ phydev->state = PHY_NOLINK;
+ phy_link_down(phydev);
+ }
+
+ return 0;
+}
+
+/**
+ * _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.
+ */
+static int _phy_start_aneg(struct phy_device *phydev)
+{
+ int err;
+
+ lockdep_assert_held(&phydev->lock);
+
+ if (!phydev->drv)
+ return -EIO;
+
+ if (AUTONEG_DISABLE == phydev->autoneg)
+ phy_sanitize_settings(phydev);
+
+ err = phy_config_aneg(phydev);
+ if (err < 0)
+ return err;
+
+ if (phy_is_started(phydev))
+ err = phy_check_link_status(phydev);
+
+ 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)
+{
+ int err;
+
+ mutex_lock(&phydev->lock);
+ err = _phy_start_aneg(phydev);
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+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;
+}
+
+int phy_ethtool_ksettings_set(struct phy_device *phydev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
+ u8 autoneg = cmd->base.autoneg;
+ u8 duplex = cmd->base.duplex;
+ u32 speed = cmd->base.speed;
+
+ if (cmd->base.phy_address != phydev->mdio.addr)
+ return -EINVAL;
+
+ linkmode_copy(advertising, cmd->link_modes.advertising);
+
+ /* We make sure that we don't pass unsupported values in to the PHY */
+ linkmode_and(advertising, advertising, phydev->supported);
+
+ /* Verify the settings we care about. */
+ if (autoneg != AUTONEG_ENABLE && autoneg != AUTONEG_DISABLE)
+ return -EINVAL;
+
+ if (autoneg == AUTONEG_ENABLE && linkmode_empty(advertising))
+ return -EINVAL;
+
+ if (autoneg == AUTONEG_DISABLE &&
+ ((speed != SPEED_1000 &&
+ speed != SPEED_100 &&
+ speed != SPEED_10) ||
+ (duplex != DUPLEX_HALF &&
+ duplex != DUPLEX_FULL)))
+ return -EINVAL;
+
+ mutex_lock(&phydev->lock);
+ phydev->autoneg = autoneg;
+
+ if (autoneg == AUTONEG_DISABLE) {
+ phydev->speed = speed;
+ phydev->duplex = duplex;
+ }
+
+ linkmode_copy(phydev->advertising, advertising);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->advertising, autoneg == AUTONEG_ENABLE);
+
+ phydev->master_slave_set = cmd->base.master_slave_cfg;
+ phydev->mdix_ctrl = cmd->base.eth_tp_mdix_ctrl;
+
+ /* Restart the PHY */
+ if (phy_is_started(phydev)) {
+ phydev->state = PHY_UP;
+ phy_trigger_machine(phydev);
+ } else {
+ _phy_start_aneg(phydev);
+ }
+
+ mutex_unlock(&phydev->lock);
+ return 0;
+}
+EXPORT_SYMBOL(phy_ethtool_ksettings_set);
+
+/**
+ * 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)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(adv_tmp);
+ int ret = 0;
+
+ mutex_lock(&phydev->lock);
+
+ if (phydev->autoneg != AUTONEG_ENABLE)
+ goto out;
+
+ linkmode_copy(adv_tmp, phydev->advertising);
+
+ ret = phy_speed_down_core(phydev);
+ if (ret)
+ goto out;
+
+ linkmode_copy(phydev->adv_old, adv_tmp);
+
+ if (linkmode_equal(phydev->advertising, adv_tmp)) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = phy_config_aneg(phydev);
+ if (ret)
+ goto out;
+
+ ret = sync ? phy_poll_aneg_done(phydev) : 0;
+out:
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+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)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(adv_tmp);
+ int ret = 0;
+
+ mutex_lock(&phydev->lock);
+
+ if (phydev->autoneg != AUTONEG_ENABLE)
+ goto out;
+
+ if (linkmode_empty(phydev->adv_old))
+ goto out;
+
+ linkmode_copy(adv_tmp, phydev->advertising);
+ linkmode_copy(phydev->advertising, phydev->adv_old);
+ linkmode_zero(phydev->adv_old);
+
+ if (linkmode_equal(phydev->advertising, adv_tmp))
+ goto out;
+
+ ret = phy_config_aneg(phydev);
+out:
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+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)
+{
+ phy_trigger_machine(phydev);
+}
+EXPORT_SYMBOL_GPL(phy_start_machine);
+
+/**
+ * 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 (phy_is_started(phydev))
+ phydev->state = PHY_UP;
+ mutex_unlock(&phydev->lock);
+}
+
+static void phy_process_error(struct phy_device *phydev)
+{
+ /* phydev->lock must be held for the state change to be safe */
+ if (!mutex_is_locked(&phydev->lock))
+ phydev_err(phydev, "PHY-device data unsafe context\n");
+
+ phydev->state = PHY_ERROR;
+
+ phy_trigger_machine(phydev);
+}
+
+static void phy_error_precise(struct phy_device *phydev,
+ const void *func, int err)
+{
+ WARN(1, "%pS: returned: %d\n", func, err);
+ mutex_lock(&phydev->lock);
+ phy_process_error(phydev);
+ mutex_unlock(&phydev->lock);
+}
+
+/**
+ * phy_error - enter ERROR state for this PHY device
+ * @phydev: target phy_device struct
+ *
+ * Moves the PHY to the ERROR state in response to a read
+ * or write error, and tells the controller the link is down.
+ * Must be called with phydev->lock held.
+ */
+void phy_error(struct phy_device *phydev)
+{
+ WARN_ON(1);
+ phy_process_error(phydev);
+}
+EXPORT_SYMBOL(phy_error);
+
+/**
+ * phy_disable_interrupts - Disable the PHY interrupts from the PHY side
+ * @phydev: target phy_device struct
+ */
+int phy_disable_interrupts(struct phy_device *phydev)
+{
+ /* Disable PHY interrupts */
+ return phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
+}
+
+/**
+ * phy_interrupt - PHY interrupt handler
+ * @irq: interrupt line
+ * @phy_dat: phy_device pointer
+ *
+ * Description: Handle PHY interrupt
+ */
+static irqreturn_t phy_interrupt(int irq, void *phy_dat)
+{
+ struct phy_device *phydev = phy_dat;
+ struct phy_driver *drv = phydev->drv;
+ irqreturn_t ret;
+
+ /* Wakeup interrupts may occur during a system sleep transition.
+ * Postpone handling until the PHY has resumed.
+ */
+ if (IS_ENABLED(CONFIG_PM_SLEEP) && phydev->irq_suspended) {
+ struct net_device *netdev = phydev->attached_dev;
+
+ if (netdev) {
+ struct device *parent = netdev->dev.parent;
+
+ if (netdev->wol_enabled)
+ pm_system_wakeup();
+ else if (device_may_wakeup(&netdev->dev))
+ pm_wakeup_dev_event(&netdev->dev, 0, true);
+ else if (parent && device_may_wakeup(parent))
+ pm_wakeup_dev_event(parent, 0, true);
+ }
+
+ phydev->irq_rerun = 1;
+ disable_irq_nosync(irq);
+ return IRQ_HANDLED;
+ }
+
+ mutex_lock(&phydev->lock);
+ ret = drv->handle_interrupt(phydev);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+
+/**
+ * phy_enable_interrupts - Enable the interrupts from the PHY side
+ * @phydev: target phy_device struct
+ */
+static int phy_enable_interrupts(struct phy_device *phydev)
+{
+ return phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
+}
+
+/**
+ * phy_request_interrupt - request and enable interrupt for a PHY device
+ * @phydev: target phy_device struct
+ *
+ * Description: Request and enable the interrupt for the given PHY.
+ * If this fails, then we set irq to PHY_POLL.
+ * This should only be called with a valid IRQ number.
+ */
+void phy_request_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ err = request_threaded_irq(phydev->irq, NULL, phy_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED,
+ phydev_name(phydev), phydev);
+ if (err) {
+ phydev_warn(phydev, "Error %d requesting IRQ %d, falling back to polling\n",
+ err, phydev->irq);
+ phydev->irq = PHY_POLL;
+ } else {
+ if (phy_enable_interrupts(phydev)) {
+ phydev_warn(phydev, "Can't enable interrupt, falling back to polling\n");
+ phy_free_interrupt(phydev);
+ phydev->irq = PHY_POLL;
+ }
+ }
+}
+EXPORT_SYMBOL(phy_request_interrupt);
+
+/**
+ * phy_free_interrupt - disable and free interrupt for a PHY device
+ * @phydev: target phy_device struct
+ *
+ * Description: Disable and free the interrupt for the given PHY.
+ * This should only be called with a valid IRQ number.
+ */
+void phy_free_interrupt(struct phy_device *phydev)
+{
+ phy_disable_interrupts(phydev);
+ free_irq(phydev->irq, phydev);
+}
+EXPORT_SYMBOL(phy_free_interrupt);
+
+/**
+ * phy_stop - Bring down the PHY link, and stop checking the status
+ * @phydev: target phy_device struct
+ */
+void phy_stop(struct phy_device *phydev)
+{
+ struct net_device *dev = phydev->attached_dev;
+ enum phy_state old_state;
+
+ if (!phy_is_started(phydev) && phydev->state != PHY_DOWN &&
+ phydev->state != PHY_ERROR) {
+ WARN(1, "called from state %s\n",
+ phy_state_to_str(phydev->state));
+ return;
+ }
+
+ mutex_lock(&phydev->lock);
+ old_state = phydev->state;
+
+ if (phydev->state == PHY_CABLETEST) {
+ phy_abort_cable_test(phydev);
+ netif_testing_off(dev);
+ }
+
+ if (phydev->sfp_bus)
+ sfp_upstream_stop(phydev->sfp_bus);
+
+ phydev->state = PHY_HALTED;
+ phy_process_state_change(phydev, old_state);
+
+ mutex_unlock(&phydev->lock);
+
+ phy_state_machine(&phydev->state_queue.work);
+ phy_stop_machine(phydev);
+
+ /* Cannot call flush_scheduled_work() here as desired because
+ * of rtnl_lock(), but PHY_HALTED shall guarantee irq handler
+ * 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)
+{
+ mutex_lock(&phydev->lock);
+
+ if (phydev->state != PHY_READY && phydev->state != PHY_HALTED) {
+ WARN(1, "called from state %s\n",
+ phy_state_to_str(phydev->state));
+ goto out;
+ }
+
+ if (phydev->sfp_bus)
+ sfp_upstream_start(phydev->sfp_bus);
+
+ /* if phy was suspended, bring the physical link up again */
+ __phy_resume(phydev);
+
+ phydev->state = PHY_UP;
+
+ phy_start_machine(phydev);
+out:
+ mutex_unlock(&phydev->lock);
+}
+EXPORT_SYMBOL(phy_start);
+
+/**
+ * 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);
+ struct net_device *dev = phydev->attached_dev;
+ bool needs_aneg = false, do_suspend = false;
+ enum phy_state old_state;
+ const void *func = NULL;
+ bool finished = false;
+ int err = 0;
+
+ mutex_lock(&phydev->lock);
+
+ old_state = phydev->state;
+
+ switch (phydev->state) {
+ case PHY_DOWN:
+ case PHY_READY:
+ break;
+ case PHY_UP:
+ needs_aneg = true;
+
+ break;
+ case PHY_NOLINK:
+ case PHY_RUNNING:
+ err = phy_check_link_status(phydev);
+ func = &phy_check_link_status;
+ break;
+ case PHY_CABLETEST:
+ err = phydev->drv->cable_test_get_status(phydev, &finished);
+ if (err) {
+ phy_abort_cable_test(phydev);
+ netif_testing_off(dev);
+ needs_aneg = true;
+ phydev->state = PHY_UP;
+ break;
+ }
+
+ if (finished) {
+ ethnl_cable_test_finished(phydev);
+ netif_testing_off(dev);
+ needs_aneg = true;
+ phydev->state = PHY_UP;
+ }
+ break;
+ case PHY_HALTED:
+ case PHY_ERROR:
+ if (phydev->link) {
+ phydev->link = 0;
+ phy_link_down(phydev);
+ }
+ do_suspend = true;
+ break;
+ }
+
+ mutex_unlock(&phydev->lock);
+
+ if (needs_aneg) {
+ err = phy_start_aneg(phydev);
+ func = &phy_start_aneg;
+ } else if (do_suspend) {
+ phy_suspend(phydev);
+ }
+
+ if (err == -ENODEV)
+ return;
+
+ if (err < 0)
+ phy_error_precise(phydev, func, err);
+
+ phy_process_state_change(phydev, old_state);
+
+ /* Only re-schedule a PHY state machine change if we are polling the
+ * PHY, if PHY_MAC_INTERRUPT is set, then we will be moving
+ * between states from phy_mac_interrupt().
+ *
+ * In state PHY_HALTED the PHY gets suspended, so rescheduling the
+ * state machine would be pointless and possibly error prone when
+ * called from phy_disconnect() synchronously.
+ */
+ mutex_lock(&phydev->lock);
+ if (phy_polling_mode(phydev) && phy_is_started(phydev))
+ phy_queue_state_machine(phydev, PHY_STATE_TIME);
+ mutex_unlock(&phydev->lock);
+}
+
+/**
+ * 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 */
+ phy_trigger_machine(phydev);
+}
+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)
+{
+ int ret;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ ret = genphy_c45_eee_is_active(phydev, NULL, NULL, NULL);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -EPROTONOSUPPORT;
+
+ if (clk_stop_enable)
+ /* Configure the PHY to stop receiving xMII
+ * clock while it is signaling LPI.
+ */
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1,
+ MDIO_PCS_CTRL1_CLKSTOP_EN);
+
+ return ret < 0 ? ret : 0;
+}
+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)
+{
+ int ret;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ mutex_lock(&phydev->lock);
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+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 ret;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ mutex_lock(&phydev->lock);
+ ret = genphy_c45_ethtool_get_eee(phydev, data);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+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 ret;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ mutex_lock(&phydev->lock);
+ ret = genphy_c45_ethtool_set_eee(phydev, data);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(phy_ethtool_set_eee);
+
+/**
+ * phy_ethtool_set_wol - Configure Wake On LAN
+ *
+ * @phydev: target phy_device struct
+ * @wol: Configuration requested
+ */
+int phy_ethtool_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ int ret;
+
+ if (phydev->drv && phydev->drv->set_wol) {
+ mutex_lock(&phydev->lock);
+ ret = phydev->drv->set_wol(phydev, wol);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+ }
+
+ return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(phy_ethtool_set_wol);
+
+/**
+ * phy_ethtool_get_wol - Get the current Wake On LAN configuration
+ *
+ * @phydev: target phy_device struct
+ * @wol: Store the current configuration here
+ */
+void phy_ethtool_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
+{
+ if (phydev->drv && phydev->drv->get_wol) {
+ mutex_lock(&phydev->lock);
+ phydev->drv->get_wol(phydev, wol);
+ mutex_unlock(&phydev->lock);
+ }
+}
+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);
+
+/**
+ * phy_ethtool_nway_reset - Restart auto negotiation
+ * @ndev: Network device to restart autoneg for
+ */
+int phy_ethtool_nway_reset(struct net_device *ndev)
+{
+ struct phy_device *phydev = ndev->phydev;
+ int ret;
+
+ if (!phydev)
+ return -ENODEV;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ mutex_lock(&phydev->lock);
+ ret = phy_restart_aneg(phydev);
+ mutex_unlock(&phydev->lock);
+
+ return ret;
+}
+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..a42df2c1b
--- /dev/null
+++ b/drivers/net/phy/phy_device.c
@@ -0,0 +1,3576 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Framework for finding and configuring PHYs.
+ * Also contains generic PHY driver
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitmap.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/phylib_stubs.h>
+#include <linux/phy_led_triggers.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/property.h>
+#include <linux/rtnetlink.h>
+#include <linux/sfp.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/unistd.h>
+
+MODULE_DESCRIPTION("PHY library");
+MODULE_AUTHOR("Andy Fleming");
+MODULE_LICENSE("GPL");
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_basic_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_basic_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_basic_t1_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_basic_t1_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_basic_t1s_p2mp_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_basic_t1s_p2mp_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_gbit_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_gbit_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_gbit_fibre_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_gbit_fibre_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_gbit_all_ports_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_gbit_all_ports_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_10gbit_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_10gbit_features);
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_10gbit_fec_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_10gbit_fec_features);
+
+const int phy_basic_ports_array[3] = {
+ ETHTOOL_LINK_MODE_Autoneg_BIT,
+ ETHTOOL_LINK_MODE_TP_BIT,
+ ETHTOOL_LINK_MODE_MII_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_basic_ports_array);
+
+const int phy_fibre_port_array[1] = {
+ ETHTOOL_LINK_MODE_FIBRE_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_fibre_port_array);
+
+const int phy_all_ports_features_array[7] = {
+ ETHTOOL_LINK_MODE_Autoneg_BIT,
+ ETHTOOL_LINK_MODE_TP_BIT,
+ ETHTOOL_LINK_MODE_MII_BIT,
+ ETHTOOL_LINK_MODE_FIBRE_BIT,
+ ETHTOOL_LINK_MODE_AUI_BIT,
+ ETHTOOL_LINK_MODE_BNC_BIT,
+ ETHTOOL_LINK_MODE_Backplane_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_all_ports_features_array);
+
+const int phy_10_100_features_array[4] = {
+ ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+ ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_10_100_features_array);
+
+const int phy_basic_t1_features_array[3] = {
+ ETHTOOL_LINK_MODE_TP_BIT,
+ ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_basic_t1_features_array);
+
+const int phy_basic_t1s_p2mp_features_array[2] = {
+ ETHTOOL_LINK_MODE_TP_BIT,
+ ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_basic_t1s_p2mp_features_array);
+
+const int phy_gbit_features_array[2] = {
+ ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_gbit_features_array);
+
+const int phy_10gbit_features_array[1] = {
+ ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+};
+EXPORT_SYMBOL_GPL(phy_10gbit_features_array);
+
+static const int phy_10gbit_fec_features_array[1] = {
+ ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
+};
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_10gbit_full_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_10gbit_full_features);
+
+static const int phy_10gbit_full_features_array[] = {
+ ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+};
+
+static const int phy_eee_cap1_features_array[] = {
+ ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+ ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+};
+
+__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_eee_cap1_features) __ro_after_init;
+EXPORT_SYMBOL_GPL(phy_eee_cap1_features);
+
+static void features_init(void)
+{
+ /* 10/100 half/full*/
+ linkmode_set_bit_array(phy_basic_ports_array,
+ ARRAY_SIZE(phy_basic_ports_array),
+ phy_basic_features);
+ linkmode_set_bit_array(phy_10_100_features_array,
+ ARRAY_SIZE(phy_10_100_features_array),
+ phy_basic_features);
+
+ /* 100 full, TP */
+ linkmode_set_bit_array(phy_basic_t1_features_array,
+ ARRAY_SIZE(phy_basic_t1_features_array),
+ phy_basic_t1_features);
+
+ /* 10 half, P2MP, TP */
+ linkmode_set_bit_array(phy_basic_t1s_p2mp_features_array,
+ ARRAY_SIZE(phy_basic_t1s_p2mp_features_array),
+ phy_basic_t1s_p2mp_features);
+
+ /* 10/100 half/full + 1000 half/full */
+ linkmode_set_bit_array(phy_basic_ports_array,
+ ARRAY_SIZE(phy_basic_ports_array),
+ phy_gbit_features);
+ linkmode_set_bit_array(phy_10_100_features_array,
+ ARRAY_SIZE(phy_10_100_features_array),
+ phy_gbit_features);
+ linkmode_set_bit_array(phy_gbit_features_array,
+ ARRAY_SIZE(phy_gbit_features_array),
+ phy_gbit_features);
+
+ /* 10/100 half/full + 1000 half/full + fibre*/
+ linkmode_set_bit_array(phy_basic_ports_array,
+ ARRAY_SIZE(phy_basic_ports_array),
+ phy_gbit_fibre_features);
+ linkmode_set_bit_array(phy_10_100_features_array,
+ ARRAY_SIZE(phy_10_100_features_array),
+ phy_gbit_fibre_features);
+ linkmode_set_bit_array(phy_gbit_features_array,
+ ARRAY_SIZE(phy_gbit_features_array),
+ phy_gbit_fibre_features);
+ linkmode_set_bit_array(phy_fibre_port_array,
+ ARRAY_SIZE(phy_fibre_port_array),
+ phy_gbit_fibre_features);
+
+ /* 10/100 half/full + 1000 half/full + TP/MII/FIBRE/AUI/BNC/Backplane*/
+ linkmode_set_bit_array(phy_all_ports_features_array,
+ ARRAY_SIZE(phy_all_ports_features_array),
+ phy_gbit_all_ports_features);
+ linkmode_set_bit_array(phy_10_100_features_array,
+ ARRAY_SIZE(phy_10_100_features_array),
+ phy_gbit_all_ports_features);
+ linkmode_set_bit_array(phy_gbit_features_array,
+ ARRAY_SIZE(phy_gbit_features_array),
+ phy_gbit_all_ports_features);
+
+ /* 10/100 half/full + 1000 half/full + 10G full*/
+ linkmode_set_bit_array(phy_all_ports_features_array,
+ ARRAY_SIZE(phy_all_ports_features_array),
+ phy_10gbit_features);
+ linkmode_set_bit_array(phy_10_100_features_array,
+ ARRAY_SIZE(phy_10_100_features_array),
+ phy_10gbit_features);
+ linkmode_set_bit_array(phy_gbit_features_array,
+ ARRAY_SIZE(phy_gbit_features_array),
+ phy_10gbit_features);
+ linkmode_set_bit_array(phy_10gbit_features_array,
+ ARRAY_SIZE(phy_10gbit_features_array),
+ phy_10gbit_features);
+
+ /* 10/100/1000/10G full */
+ linkmode_set_bit_array(phy_all_ports_features_array,
+ ARRAY_SIZE(phy_all_ports_features_array),
+ phy_10gbit_full_features);
+ linkmode_set_bit_array(phy_10gbit_full_features_array,
+ ARRAY_SIZE(phy_10gbit_full_features_array),
+ phy_10gbit_full_features);
+ /* 10G FEC only */
+ linkmode_set_bit_array(phy_10gbit_fec_features_array,
+ ARRAY_SIZE(phy_10gbit_fec_features_array),
+ phy_10gbit_fec_features);
+ linkmode_set_bit_array(phy_eee_cap1_features_array,
+ ARRAY_SIZE(phy_eee_cap1_features_array),
+ phy_eee_cap1_features);
+
+}
+
+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)
+{
+ fwnode_handle_put(dev->fwnode);
+ 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;
+
+static LIST_HEAD(phy_fixup_list);
+static DEFINE_MUTEX(phy_fixup_lock);
+
+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 __maybe_unused int mdio_bus_phy_suspend(struct device *dev)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ if (phydev->mac_managed_pm)
+ return 0;
+
+ /* Wakeup interrupts may occur during the system sleep transition when
+ * the PHY is inaccessible. Set flag to postpone handling until the PHY
+ * has resumed. Wait for concurrent interrupt handler to complete.
+ */
+ if (phy_interrupt_is_valid(phydev)) {
+ phydev->irq_suspended = 1;
+ synchronize_irq(phydev->irq);
+ }
+
+ /* 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 __maybe_unused int mdio_bus_phy_resume(struct device *dev)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ int ret;
+
+ if (phydev->mac_managed_pm)
+ return 0;
+
+ if (!phydev->suspended_by_mdio_bus)
+ goto no_resume;
+
+ phydev->suspended_by_mdio_bus = 0;
+
+ /* If we managed to get here with the PHY state machine in a state
+ * neither PHY_HALTED, PHY_READY nor PHY_UP, this is an indication
+ * that something went wrong and we should most likely be using
+ * MAC managed PM, but we are not.
+ */
+ WARN_ON(phydev->state != PHY_HALTED && phydev->state != PHY_READY &&
+ phydev->state != PHY_UP);
+
+ ret = phy_init_hw(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_resume(phydev);
+ if (ret < 0)
+ return ret;
+no_resume:
+ if (phy_interrupt_is_valid(phydev)) {
+ phydev->irq_suspended = 0;
+ synchronize_irq(phydev->irq);
+
+ /* Rerun interrupts which were postponed by phy_interrupt()
+ * because they occurred during the system sleep transition.
+ */
+ if (phydev->irq_rerun) {
+ phydev->irq_rerun = 0;
+ enable_irq(phydev->irq);
+ irq_wake_thread(phydev->irq, phydev);
+ }
+ }
+
+ if (phydev->attached_dev && phydev->adjust_link)
+ phy_start_machine(phydev);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mdio_bus_phy_pm_ops, mdio_bus_phy_suspend,
+ mdio_bus_phy_resume);
+
+/**
+ * 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;
+
+ strscpy(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)) &&
+ phy_id_compare(fixup->phy_uid, 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 (!phy_id_compare(phydev->phy_id, fixup->phy_uid,
+ fixup->phy_uid_mask))
+ if (fixup->phy_uid != PHY_ANY_UID)
+ return 0;
+
+ return 1;
+}
+
+/* Runs any matching fixups for this phydev */
+static int phy_scan_fixups(struct phy_device *phydev)
+{
+ struct phy_fixup *fixup;
+
+ mutex_lock(&phy_fixup_lock);
+ list_for_each_entry(fixup, &phy_fixup_list, list) {
+ if (phy_needs_fixup(phydev, fixup)) {
+ int err = fixup->run(phydev);
+
+ if (err < 0) {
+ mutex_unlock(&phy_fixup_lock);
+ return err;
+ }
+ phydev->has_fixups = true;
+ }
+ }
+ mutex_unlock(&phy_fixup_lock);
+
+ return 0;
+}
+
+static int phy_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ struct phy_driver *phydrv = to_phy_driver(drv);
+ const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
+ int i;
+
+ if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
+ return 0;
+
+ if (phydrv->match_phy_device)
+ return phydrv->match_phy_device(phydev);
+
+ if (phydev->is_c45) {
+ for (i = 1; i < num_ids; i++) {
+ if (phydev->c45_ids.device_ids[i] == 0xffffffff)
+ continue;
+
+ if (phy_id_compare(phydev->c45_ids.device_ids[i],
+ phydrv->phy_id, phydrv->phy_id_mask))
+ return 1;
+ }
+ return 0;
+ } else {
+ return phy_id_compare(phydev->phy_id, phydrv->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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(buf, "%d\n", phydev->has_fixups);
+}
+static DEVICE_ATTR_RO(phy_has_fixups);
+
+static ssize_t phy_dev_flags_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return sysfs_emit(buf, "0x%08x\n", phydev->dev_flags);
+}
+static DEVICE_ATTR_RO(phy_dev_flags);
+
+static struct attribute *phy_dev_attrs[] = {
+ &dev_attr_phy_id.attr,
+ &dev_attr_phy_interface.attr,
+ &dev_attr_phy_has_fixups.attr,
+ &dev_attr_phy_dev_flags.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 = pm_ptr(&mdio_bus_phy_pm_ops),
+};
+
+static int phy_request_driver_module(struct phy_device *dev, u32 phy_id)
+{
+ int ret;
+
+ ret = request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT,
+ MDIO_ID_ARGS(phy_id));
+ /* We only check for failures in executing the usermode binary,
+ * not whether a PHY driver module exists for the PHY ID.
+ * Accept -ENOENT because this may occur in case no initramfs exists,
+ * then modprobe isn't available.
+ */
+ if (IS_ENABLED(CONFIG_MODULES) && ret < 0 && ret != -ENOENT) {
+ phydev_err(dev, "error %d loading PHY driver module for ID 0x%08lx\n",
+ ret, (unsigned long)phy_id);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
+ bool is_c45,
+ struct phy_c45_device_ids *c45_ids)
+{
+ struct phy_device *dev;
+ struct mdio_device *mdiodev;
+ int ret = 0;
+
+ /* 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->port = PORT_TP;
+ dev->interface = PHY_INTERFACE_MODE_GMII;
+
+ dev->autoneg = AUTONEG_ENABLE;
+
+ dev->pma_extable = -ENODATA;
+ 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);
+ device_initialize(&mdiodev->dev);
+
+ dev->state = PHY_DOWN;
+ INIT_LIST_HEAD(&dev->leds);
+
+ mutex_init(&dev->lock);
+ INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
+
+ /* 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.
+ */
+ if (is_c45 && c45_ids) {
+ const int num_ids = ARRAY_SIZE(c45_ids->device_ids);
+ int i;
+
+ for (i = 1; i < num_ids; i++) {
+ if (c45_ids->device_ids[i] == 0xffffffff)
+ continue;
+
+ ret = phy_request_driver_module(dev,
+ c45_ids->device_ids[i]);
+ if (ret)
+ break;
+ }
+ } else {
+ ret = phy_request_driver_module(dev, phy_id);
+ }
+
+ if (ret) {
+ put_device(&mdiodev->dev);
+ dev = ERR_PTR(ret);
+ }
+
+ return dev;
+}
+EXPORT_SYMBOL(phy_device_create);
+
+/* phy_c45_probe_present - checks to see if a MMD is present in the package
+ * @bus: the target MII bus
+ * @prtad: PHY package address on the MII bus
+ * @devad: PHY device (MMD) address
+ *
+ * Read the MDIO_STAT2 register, and check whether a device is responding
+ * at this address.
+ *
+ * Returns: negative error number on bus access error, zero if no device
+ * is responding, or positive if a device is present.
+ */
+static int phy_c45_probe_present(struct mii_bus *bus, int prtad, int devad)
+{
+ int stat2;
+
+ stat2 = mdiobus_c45_read(bus, prtad, devad, MDIO_STAT2);
+ if (stat2 < 0)
+ return stat2;
+
+ return (stat2 & MDIO_STAT2_DEVPRST) == MDIO_STAT2_DEVPRST_VAL;
+}
+
+/* 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;
+
+ phy_reg = mdiobus_c45_read(bus, addr, dev_addr, MDIO_DEVS2);
+ if (phy_reg < 0)
+ return -EIO;
+ *devices_in_package = phy_reg << 16;
+
+ phy_reg = mdiobus_c45_read(bus, addr, dev_addr, MDIO_DEVS1);
+ if (phy_reg < 0)
+ return -EIO;
+ *devices_in_package |= phy_reg;
+
+ 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
+ * @c45_ids: where to store the c45 ID information.
+ *
+ * Read the PHY "devices in package". If this appears to be valid, read
+ * the PHY identifiers for each device. Return the "devices in package"
+ * and identifiers in @c45_ids.
+ *
+ * Returns zero on success, %-EIO on bus access error, or %-ENODEV if
+ * the "devices in package" is invalid.
+ */
+static int get_phy_c45_ids(struct mii_bus *bus, int addr,
+ struct phy_c45_device_ids *c45_ids)
+{
+ const int num_ids = ARRAY_SIZE(c45_ids->device_ids);
+ u32 devs_in_pkg = 0;
+ int i, ret, phy_reg;
+
+ /* 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 < MDIO_MMD_NUM && (devs_in_pkg == 0 ||
+ (devs_in_pkg & 0x1fffffff) == 0x1fffffff); i++) {
+ if (i == MDIO_MMD_VEND1 || i == MDIO_MMD_VEND2) {
+ /* Check that there is a device present at this
+ * address before reading the devices-in-package
+ * register to avoid reading garbage from the PHY.
+ * Some PHYs (88x3310) vendor space is not IEEE802.3
+ * compliant.
+ */
+ ret = phy_c45_probe_present(bus, addr, i);
+ if (ret < 0)
+ return -EIO;
+
+ if (!ret)
+ continue;
+ }
+ phy_reg = get_phy_c45_devs_in_pkg(bus, addr, i, &devs_in_pkg);
+ if (phy_reg < 0)
+ return -EIO;
+ }
+
+ if ((devs_in_pkg & 0x1fffffff) == 0x1fffffff) {
+ /* If mostly Fs, there is no device there, then let's probe
+ * MMD 0, 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_in_pkg);
+ if (phy_reg < 0)
+ return -EIO;
+
+ /* no device there, let's get out of here */
+ if ((devs_in_pkg & 0x1fffffff) == 0x1fffffff)
+ return -ENODEV;
+ }
+
+ /* Now probe Device Identifiers for each device present. */
+ for (i = 1; i < num_ids; i++) {
+ if (!(devs_in_pkg & (1 << i)))
+ continue;
+
+ if (i == MDIO_MMD_VEND1 || i == MDIO_MMD_VEND2) {
+ /* Probe the "Device Present" bits for the vendor MMDs
+ * to ignore these if they do not contain IEEE 802.3
+ * registers.
+ */
+ ret = phy_c45_probe_present(bus, addr, i);
+ if (ret < 0)
+ return ret;
+
+ if (!ret)
+ continue;
+ }
+
+ phy_reg = mdiobus_c45_read(bus, addr, i, MII_PHYSID1);
+ if (phy_reg < 0)
+ return -EIO;
+ c45_ids->device_ids[i] = phy_reg << 16;
+
+ phy_reg = mdiobus_c45_read(bus, addr, i, MII_PHYSID2);
+ if (phy_reg < 0)
+ return -EIO;
+ c45_ids->device_ids[i] |= phy_reg;
+ }
+
+ c45_ids->devices_in_package = devs_in_pkg;
+ /* Bit 0 doesn't represent a device, it indicates c22 regs presence */
+ c45_ids->mmds_present = devs_in_pkg & ~BIT(0);
+
+ return 0;
+}
+
+/**
+ * get_phy_c22_id - reads the specified addr for its clause 22 ID.
+ * @bus: the target MII bus
+ * @addr: PHY address on the MII bus
+ * @phy_id: where to store the ID retrieved.
+ *
+ * Read the 802.3 clause 22 PHY ID from the PHY at @addr on the @bus,
+ * placing it in @phy_id. Return zero on successful read and the ID is
+ * valid, %-EIO on bus access error, or %-ENODEV if no device responds
+ * or invalid ID.
+ */
+static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id)
+{
+ int phy_reg;
+
+ /* Grab the bits from PHYIR1, and put them in the upper half */
+ phy_reg = mdiobus_read(bus, addr, MII_PHYSID1);
+ if (phy_reg < 0) {
+ /* returning -ENODEV doesn't stop bus scanning */
+ return (phy_reg == -EIO || phy_reg == -ENODEV) ? -ENODEV : -EIO;
+ }
+
+ *phy_id = phy_reg << 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;
+
+ /* If the phy_id is mostly Fs, there is no device there */
+ if ((*phy_id & 0x1fffffff) == 0x1fffffff)
+ return -ENODEV;
+
+ return 0;
+}
+
+/* Extract the phy ID from the compatible string of the form
+ * ethernet-phy-idAAAA.BBBB.
+ */
+int fwnode_get_phy_id(struct fwnode_handle *fwnode, u32 *phy_id)
+{
+ unsigned int upper, lower;
+ const char *cp;
+ int ret;
+
+ ret = fwnode_property_read_string(fwnode, "compatible", &cp);
+ if (ret)
+ return ret;
+
+ if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) != 2)
+ return -EINVAL;
+
+ *phy_id = ((upper & GENMASK(15, 0)) << 16) | (lower & GENMASK(15, 0));
+ return 0;
+}
+EXPORT_SYMBOL(fwnode_get_phy_id);
+
+/**
+ * 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
+ *
+ * Probe for a PHY at @addr on @bus.
+ *
+ * When probing for a clause 22 PHY, then read the ID registers. If we find
+ * a valid ID, allocate and return a &struct phy_device.
+ *
+ * When probing for a clause 45 PHY, read the "devices in package" registers.
+ * If the "devices in package" appears valid, read the ID registers for each
+ * MMD, allocate and return a &struct phy_device.
+ *
+ * Returns an allocated &struct phy_device on success, %-ENODEV if there is
+ * no PHY present, or %-EIO on bus access error.
+ */
+struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
+{
+ struct phy_c45_device_ids c45_ids;
+ u32 phy_id = 0;
+ int r;
+
+ c45_ids.devices_in_package = 0;
+ c45_ids.mmds_present = 0;
+ memset(c45_ids.device_ids, 0xff, sizeof(c45_ids.device_ids));
+
+ if (is_c45)
+ r = get_phy_c45_ids(bus, addr, &c45_ids);
+ else
+ r = get_phy_c22_id(bus, addr, &phy_id);
+
+ if (r)
+ return ERR_PTR(r);
+
+ /* PHY device such as the Marvell Alaska 88E2110 will return a PHY ID
+ * of 0 when probed using get_phy_c22_id() with no error. Proceed to
+ * probe with C45 to see if we're able to get a valid PHY ID in the C45
+ * space, if successful, create the C45 PHY device.
+ */
+ if (!is_c45 && phy_id == 0 && bus->read_c45) {
+ r = get_phy_c45_ids(bus, addr, &c45_ids);
+ if (!r)
+ return phy_device_create(bus, addr, phy_id,
+ true, &c45_ids);
+ }
+
+ 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) {
+ phydev_err(phydev, "failed to initialize\n");
+ goto out;
+ }
+
+ err = device_add(&phydev->mdio.dev);
+ if (err) {
+ phydev_err(phydev, "failed to add\n");
+ 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)
+{
+ unregister_mii_timestamper(phydev->mii_ts);
+ pse_control_put(phydev->psec);
+
+ 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_get_c45_ids - Read 802.3-c45 IDs for phy device.
+ * @phydev: phy_device structure to read 802.3-c45 IDs
+ *
+ * Returns zero on success, %-EIO on bus access error, or %-ENODEV if
+ * the "devices in package" is invalid.
+ */
+int phy_get_c45_ids(struct phy_device *phydev)
+{
+ return get_phy_c45_ids(phydev->mdio.bus, phydev->mdio.addr,
+ &phydev->c45_ids);
+}
+EXPORT_SYMBOL(phy_get_c45_ids);
+
+/**
+ * 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)
+{
+ struct net_device *netdev = phydev->attached_dev;
+
+ if (up)
+ netif_carrier_on(netdev);
+ else
+ netif_carrier_off(netdev);
+ phydev->adjust_link(netdev);
+ if (phydev->mii_ts && phydev->mii_ts->link_state)
+ phydev->mii_ts->link_state(phydev->mii_ts, phydev);
+}
+
+/**
+ * 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);
+ if (phy_interrupt_is_valid(phydev))
+ phy_request_interrupt(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 (phy_is_started(phydev))
+ phy_stop(phydev);
+
+ if (phy_interrupt_is_valid(phydev))
+ phy_free_interrupt(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) */
+ int ret, val;
+
+ ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
+ 50000, 600000, true);
+ if (ret)
+ return ret;
+ /* 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)
+ return 0;
+
+ if (phydev->drv->soft_reset) {
+ ret = phydev->drv->soft_reset(phydev);
+ /* see comment in genphy_soft_reset for an explanation */
+ if (!ret)
+ phydev->suspended = 0;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ ret = phy_scan_fixups(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->drv->config_init) {
+ ret = phydev->drv->config_init(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (phydev->drv->config_intr) {
+ ret = phydev->drv->config_intr(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+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)"
+char *phy_attached_info_irq(struct phy_device *phydev)
+{
+ char *irq_str;
+ char irq_num[8];
+
+ switch(phydev->irq) {
+ case PHY_POLL:
+ irq_str = "POLL";
+ break;
+ case PHY_MAC_INTERRUPT:
+ irq_str = "MAC";
+ break;
+ default:
+ snprintf(irq_num, sizeof(irq_num), "%d", phydev->irq);
+ irq_str = irq_num;
+ break;
+ }
+
+ return kasprintf(GFP_KERNEL, "%s", irq_str);
+}
+EXPORT_SYMBOL(phy_attached_info_irq);
+
+void phy_attached_print(struct phy_device *phydev, const char *fmt, ...)
+{
+ const char *unbound = phydev->drv ? "" : "[unbound] ";
+ char *irq_str = phy_attached_info_irq(phydev);
+
+ if (!fmt) {
+ phydev_info(phydev, ATTACHED_FMT "\n", unbound,
+ phydev_name(phydev), irq_str);
+ } else {
+ va_list ap;
+
+ phydev_info(phydev, ATTACHED_FMT, unbound,
+ phydev_name(phydev), irq_str);
+
+ va_start(ap, fmt);
+ vprintk(fmt, ap);
+ va_end(ap);
+ }
+ kfree(irq_str);
+}
+EXPORT_SYMBOL(phy_attached_print);
+
+static void phy_sysfs_create_links(struct phy_device *phydev)
+{
+ struct net_device *dev = phydev->attached_dev;
+ int err;
+
+ if (!dev)
+ return;
+
+ err = sysfs_create_link(&phydev->mdio.dev.kobj, &dev->dev.kobj,
+ "attached_dev");
+ if (err)
+ return;
+
+ 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;
+}
+
+static ssize_t
+phy_standalone_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return sysfs_emit(buf, "%d\n", !phydev->attached_dev);
+}
+static DEVICE_ATTR_RO(phy_standalone);
+
+/**
+ * phy_sfp_attach - attach the SFP bus to the PHY upstream network device
+ * @upstream: pointer to the phy device
+ * @bus: sfp bus representing cage being attached
+ *
+ * This is used to fill in the sfp_upstream_ops .attach member.
+ */
+void phy_sfp_attach(void *upstream, struct sfp_bus *bus)
+{
+ struct phy_device *phydev = upstream;
+
+ if (phydev->attached_dev)
+ phydev->attached_dev->sfp_bus = bus;
+ phydev->sfp_bus_attached = true;
+}
+EXPORT_SYMBOL(phy_sfp_attach);
+
+/**
+ * phy_sfp_detach - detach the SFP bus from the PHY upstream network device
+ * @upstream: pointer to the phy device
+ * @bus: sfp bus representing cage being attached
+ *
+ * This is used to fill in the sfp_upstream_ops .detach member.
+ */
+void phy_sfp_detach(void *upstream, struct sfp_bus *bus)
+{
+ struct phy_device *phydev = upstream;
+
+ if (phydev->attached_dev)
+ phydev->attached_dev->sfp_bus = NULL;
+ phydev->sfp_bus_attached = false;
+}
+EXPORT_SYMBOL(phy_sfp_detach);
+
+/**
+ * phy_sfp_probe - probe for a SFP cage attached to this PHY device
+ * @phydev: Pointer to phy_device
+ * @ops: SFP's upstream operations
+ */
+int phy_sfp_probe(struct phy_device *phydev,
+ const struct sfp_upstream_ops *ops)
+{
+ struct sfp_bus *bus;
+ int ret = 0;
+
+ if (phydev->mdio.dev.fwnode) {
+ bus = sfp_bus_find_fwnode(phydev->mdio.dev.fwnode);
+ if (IS_ERR(bus))
+ return PTR_ERR(bus);
+
+ phydev->sfp_bus = bus;
+
+ ret = sfp_bus_add_upstream(bus, phydev, ops);
+ sfp_bus_put(bus);
+ }
+ return ret;
+}
+EXPORT_SYMBOL(phy_sfp_probe);
+
+/**
+ * phy_attach_direct - attach a network device to a given PHY device pointer
+ * @dev: network device to attach
+ * @phydev: Pointer to phy_device to attach
+ * @flags: PHY device's dev_flags
+ * @interface: PHY device's interface
+ *
+ * Description: Called by drivers to attach to a particular PHY
+ * device. The phy_device is found, and properly hooked up
+ * to the phy_driver. If no driver is attached, then a
+ * generic driver is used. The phy_device is given a ptr to
+ * the attaching device, and given a callback for link status
+ * change. The phy_device is returned to the attaching driver.
+ * This function takes a reference on the phy device.
+ */
+int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
+ u32 flags, phy_interface_t interface)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ struct device *d = &phydev->mdio.dev;
+ struct module *ndev_owner = NULL;
+ 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 (dev)
+ ndev_owner = dev->dev.parent->driver->owner;
+ if (ndev_owner != bus->owner && !try_module_get(bus->owner)) {
+ phydev_err(phydev, "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_c45_driver.mdiodrv.driver;
+ else
+ d->driver = &genphy_driver.mdiodrv.driver;
+
+ using_genphy = true;
+ }
+
+ if (!try_module_get(d->driver->owner)) {
+ phydev_err(phydev, "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;
+ if (dev) {
+ phydev->attached_dev = dev;
+ dev->phydev = phydev;
+
+ if (phydev->sfp_bus_attached)
+ dev->sfp_bus = phydev->sfp_bus;
+ }
+
+ /* Some Ethernet drivers try to connect to a PHY device before
+ * calling register_netdevice() -> netdev_register_kobject() and
+ * does the dev->dev.kobj initialization. Here we only check for
+ * success which indicates that the network device kobject is
+ * ready. Once we do that we still need to keep track of whether
+ * links were successfully set up or not for phy_detach() to
+ * remove them accordingly.
+ */
+ phydev->sysfs_links = false;
+
+ phy_sysfs_create_links(phydev);
+
+ if (!phydev->attached_dev) {
+ err = sysfs_create_file(&phydev->mdio.dev.kobj,
+ &dev_attr_phy_standalone.attr);
+ if (err)
+ phydev_err(phydev, "error creating 'phy_standalone' sysfs entry\n");
+ }
+
+ phydev->dev_flags |= flags;
+
+ phydev->interface = interface;
+
+ phydev->state = PHY_READY;
+
+ phydev->interrupts = PHY_INTERRUPT_DISABLED;
+
+ /* PHYs can request to use poll mode even though they have an
+ * associated interrupt line. This could be the case if they
+ * detect a broken interrupt handling.
+ */
+ if (phydev->dev_flags & PHY_F_NO_IRQ)
+ phydev->irq = PHY_POLL;
+
+ /* Port is set to PORT_TP by default and the actual PHY driver will set
+ * it to different value depending on the PHY configuration. If we have
+ * the generic PHY driver we can't figure it out, thus set the old
+ * legacy PORT_MII value.
+ */
+ if (using_genphy)
+ phydev->port = PORT_MII;
+
+ /* Initial carrier state is off as the phy is about to be
+ * (re)initialized.
+ */
+ if (dev)
+ 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);
+ if (!phydev->is_on_sfp_module)
+ phy_led_triggers_register(phydev);
+
+ /**
+ * If the external phy used by current mac interface is managed by
+ * another mac interface, so we should create a device link between
+ * phy dev and mac dev.
+ */
+ if (dev && phydev->mdio.bus->parent && dev->dev.parent != phydev->mdio.bus->parent)
+ phydev->devlink = device_link_add(dev->dev.parent, &phydev->mdio.dev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
+
+ return err;
+
+error:
+ /* phy_detach() does all of the cleanup below */
+ phy_detach(phydev);
+ return err;
+
+error_module_put:
+ module_put(d->driver->owner);
+ d->driver = NULL;
+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);
+
+static bool phy_driver_is_genphy_kind(struct phy_device *phydev,
+ struct device_driver *driver)
+{
+ struct device *d = &phydev->mdio.dev;
+ bool ret = false;
+
+ if (!phydev->drv)
+ return ret;
+
+ get_device(d);
+ ret = d->driver == driver;
+ put_device(d);
+
+ return ret;
+}
+
+bool phy_driver_is_genphy(struct phy_device *phydev)
+{
+ return phy_driver_is_genphy_kind(phydev,
+ &genphy_driver.mdiodrv.driver);
+}
+EXPORT_SYMBOL_GPL(phy_driver_is_genphy);
+
+bool phy_driver_is_genphy_10g(struct phy_device *phydev)
+{
+ return phy_driver_is_genphy_kind(phydev,
+ &genphy_c45_driver.mdiodrv.driver);
+}
+EXPORT_SYMBOL_GPL(phy_driver_is_genphy_10g);
+
+/**
+ * phy_package_join - join a common PHY group
+ * @phydev: target phy_device struct
+ * @addr: cookie and PHY address for global register access
+ * @priv_size: if non-zero allocate this amount of bytes for private data
+ *
+ * This joins a PHY group and provides a shared storage for all phydevs in
+ * this group. This is intended to be used for packages which contain
+ * more than one PHY, for example a quad PHY transceiver.
+ *
+ * The addr parameter serves as a cookie which has to have the same value
+ * for all members of one group and as a PHY address to access generic
+ * registers of a PHY package. Usually, one of the PHY addresses of the
+ * different PHYs in the package provides access to these global registers.
+ * The address which is given here, will be used in the phy_package_read()
+ * and phy_package_write() convenience functions. If your PHY doesn't have
+ * global registers you can just pick any of the PHY addresses.
+ *
+ * This will set the shared pointer of the phydev to the shared storage.
+ * If this is the first call for a this cookie the shared storage will be
+ * allocated. If priv_size is non-zero, the given amount of bytes are
+ * allocated for the priv member.
+ *
+ * Returns < 1 on error, 0 on success. Esp. calling phy_package_join()
+ * with the same cookie but a different priv_size is an error.
+ */
+int phy_package_join(struct phy_device *phydev, int addr, size_t priv_size)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ struct phy_package_shared *shared;
+ int ret;
+
+ if (addr < 0 || addr >= PHY_MAX_ADDR)
+ return -EINVAL;
+
+ mutex_lock(&bus->shared_lock);
+ shared = bus->shared[addr];
+ if (!shared) {
+ ret = -ENOMEM;
+ shared = kzalloc(sizeof(*shared), GFP_KERNEL);
+ if (!shared)
+ goto err_unlock;
+ if (priv_size) {
+ shared->priv = kzalloc(priv_size, GFP_KERNEL);
+ if (!shared->priv)
+ goto err_free;
+ shared->priv_size = priv_size;
+ }
+ shared->addr = addr;
+ refcount_set(&shared->refcnt, 1);
+ bus->shared[addr] = shared;
+ } else {
+ ret = -EINVAL;
+ if (priv_size && priv_size != shared->priv_size)
+ goto err_unlock;
+ refcount_inc(&shared->refcnt);
+ }
+ mutex_unlock(&bus->shared_lock);
+
+ phydev->shared = shared;
+
+ return 0;
+
+err_free:
+ kfree(shared);
+err_unlock:
+ mutex_unlock(&bus->shared_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_package_join);
+
+/**
+ * phy_package_leave - leave a common PHY group
+ * @phydev: target phy_device struct
+ *
+ * This leaves a PHY group created by phy_package_join(). If this phydev
+ * was the last user of the shared data between the group, this data is
+ * freed. Resets the phydev->shared pointer to NULL.
+ */
+void phy_package_leave(struct phy_device *phydev)
+{
+ struct phy_package_shared *shared = phydev->shared;
+ struct mii_bus *bus = phydev->mdio.bus;
+
+ if (!shared)
+ return;
+
+ if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) {
+ bus->shared[shared->addr] = NULL;
+ mutex_unlock(&bus->shared_lock);
+ kfree(shared->priv);
+ kfree(shared);
+ }
+
+ phydev->shared = NULL;
+}
+EXPORT_SYMBOL_GPL(phy_package_leave);
+
+static void devm_phy_package_leave(struct device *dev, void *res)
+{
+ phy_package_leave(*(struct phy_device **)res);
+}
+
+/**
+ * devm_phy_package_join - resource managed phy_package_join()
+ * @dev: device that is registering this PHY package
+ * @phydev: target phy_device struct
+ * @addr: cookie and PHY address for global register access
+ * @priv_size: if non-zero allocate this amount of bytes for private data
+ *
+ * Managed phy_package_join(). Shared storage fetched by this function,
+ * phy_package_leave() is automatically called on driver detach. See
+ * phy_package_join() for more information.
+ */
+int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
+ int addr, size_t priv_size)
+{
+ struct phy_device **ptr;
+ int ret;
+
+ ptr = devres_alloc(devm_phy_package_leave, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ ret = phy_package_join(phydev, addr, priv_size);
+
+ if (!ret) {
+ *ptr = phydev;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(devm_phy_package_join);
+
+/**
+ * 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 = NULL;
+ struct mii_bus *bus;
+
+ if (phydev->devlink)
+ device_link_del(phydev->devlink);
+
+ if (phydev->sysfs_links) {
+ if (dev)
+ sysfs_remove_link(&dev->dev.kobj, "phydev");
+ sysfs_remove_link(&phydev->mdio.dev.kobj, "attached_dev");
+ }
+
+ if (!phydev->attached_dev)
+ sysfs_remove_file(&phydev->mdio.dev.kobj,
+ &dev_attr_phy_standalone.attr);
+
+ phy_suspend(phydev);
+ if (dev) {
+ phydev->attached_dev->phydev = NULL;
+ phydev->attached_dev = NULL;
+ }
+ phydev->phylink = NULL;
+
+ if (!phydev->is_on_sfp_module)
+ 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 (phy_driver_is_genphy(phydev) ||
+ phy_driver_is_genphy_10g(phydev))
+ 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 (dev)
+ ndev_owner = dev->dev.parent->driver->owner;
+ if (ndev_owner != bus->owner)
+ module_put(bus->owner);
+}
+EXPORT_SYMBOL(phy_detach);
+
+int phy_suspend(struct phy_device *phydev)
+{
+ struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
+ struct net_device *netdev = phydev->attached_dev;
+ struct phy_driver *phydrv = phydev->drv;
+ int ret;
+
+ if (phydev->suspended)
+ return 0;
+
+ phy_ethtool_get_wol(phydev, &wol);
+ phydev->wol_enabled = wol.wolopts || (netdev && netdev->wol_enabled);
+ /* If the device has WOL enabled, we cannot suspend the PHY */
+ if (phydev->wol_enabled && !(phydrv->flags & PHY_ALWAYS_CALL_SUSPEND))
+ return -EBUSY;
+
+ if (!phydrv || !phydrv->suspend)
+ return 0;
+
+ ret = phydrv->suspend(phydev);
+ if (!ret)
+ phydev->suspended = true;
+
+ return ret;
+}
+EXPORT_SYMBOL(phy_suspend);
+
+int __phy_resume(struct phy_device *phydev)
+{
+ struct phy_driver *phydrv = phydev->drv;
+ int ret;
+
+ lockdep_assert_held(&phydev->lock);
+
+ if (!phydrv || !phydrv->resume)
+ return 0;
+
+ ret = phydrv->resume(phydev);
+ if (!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)
+{
+ int ret = 0;
+
+ if (!phydev->drv)
+ return -EIO;
+
+ 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->set_loopback)
+ ret = phydev->drv->set_loopback(phydev, enable);
+ else
+ ret = genphy_loopback(phydev, enable);
+
+ 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)
+{
+ int err, bmsr, changed = 0;
+ u32 adv;
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ adv = linkmode_adv_to_mii_adv_t(phydev->advertising);
+
+ /* Setup standard advertisement */
+ err = phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_100BASE4 |
+ ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ 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;
+
+ adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+
+ err = phy_modify_changed(phydev, MII_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ adv);
+ if (err < 0)
+ return err;
+ if (err > 0)
+ changed = 1;
+
+ return changed;
+}
+
+/**
+ * genphy_c37_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. This function is intended
+ * for Clause 37 1000Base-X mode.
+ */
+static int genphy_c37_config_advert(struct phy_device *phydev)
+{
+ u16 adv = 0;
+
+ /* Only allow advertising what this PHY supports */
+ linkmode_and(phydev->advertising, phydev->advertising,
+ phydev->supported);
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->advertising))
+ adv |= ADVERTISE_1000XFULL;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->advertising))
+ adv |= ADVERTISE_1000XPAUSE;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->advertising))
+ adv |= ADVERTISE_1000XPSE_ASYM;
+
+ return phy_modify_changed(phydev, MII_ADVERTISE,
+ ADVERTISE_1000XFULL | ADVERTISE_1000XPAUSE |
+ ADVERTISE_1000XHALF | ADVERTISE_1000XPSE_ASYM,
+ adv);
+}
+
+/**
+ * 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.
+ */
+int genphy_config_eee_advert(struct phy_device *phydev)
+{
+ int err;
+
+ /* Nothing to disable */
+ if (!phydev->eee_broken_modes)
+ return 0;
+
+ err = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV,
+ phydev->eee_broken_modes, 0);
+ /* If the call failed, we assume that EEE is not supported */
+ return err < 0 ? 0 : err;
+}
+EXPORT_SYMBOL(genphy_config_eee_advert);
+
+/**
+ * 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;
+
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
+
+ return phy_modify(phydev, MII_BMCR,
+ ~(BMCR_LOOPBACK | BMCR_ISOLATE | BMCR_PDOWN), ctl);
+}
+EXPORT_SYMBOL(genphy_setup_forced);
+
+static int genphy_setup_master_slave(struct phy_device *phydev)
+{
+ u16 ctl = 0;
+
+ if (!phydev->is_gigabit_capable)
+ return 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ ctl |= CTL1000_PREFER_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ break;
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl |= CTL1000_AS_MASTER;
+ fallthrough;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ ctl |= CTL1000_ENABLE_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ return phy_modify_changed(phydev, MII_CTRL1000,
+ (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER |
+ CTL1000_PREFER_MASTER), ctl);
+}
+
+int genphy_read_master_slave(struct phy_device *phydev)
+{
+ int cfg, state;
+ int val;
+
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+
+ val = phy_read(phydev, MII_CTRL1000);
+ if (val < 0)
+ return val;
+
+ if (val & CTL1000_ENABLE_MASTER) {
+ if (val & CTL1000_AS_MASTER)
+ cfg = MASTER_SLAVE_CFG_MASTER_FORCE;
+ else
+ cfg = MASTER_SLAVE_CFG_SLAVE_FORCE;
+ } else {
+ if (val & CTL1000_PREFER_MASTER)
+ cfg = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+ else
+ cfg = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+ }
+
+ val = phy_read(phydev, MII_STAT1000);
+ if (val < 0)
+ return val;
+
+ if (val & LPA_1000MSFAIL) {
+ state = MASTER_SLAVE_STATE_ERR;
+ } else if (phydev->link) {
+ /* this bits are valid only for active link */
+ if (val & LPA_1000MSRES)
+ state = MASTER_SLAVE_STATE_MASTER;
+ else
+ state = MASTER_SLAVE_STATE_SLAVE;
+ } else {
+ state = MASTER_SLAVE_STATE_UNKNOWN;
+ }
+
+ phydev->master_slave_get = cfg;
+ phydev->master_slave_state = state;
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_read_master_slave);
+
+/**
+ * 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_check_and_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: target phy_device struct
+ * @restart: whether aneg restart is requested
+ *
+ * Check, and restart auto-negotiation if needed.
+ */
+int genphy_check_and_restart_aneg(struct phy_device *phydev, bool restart)
+{
+ int ret;
+
+ if (!restart) {
+ /* Advertisement hasn't changed, but maybe aneg was never on to
+ * begin with? Or maybe phy was isolated?
+ */
+ ret = phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ return ret;
+
+ if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE))
+ restart = true;
+ }
+
+ if (restart)
+ return genphy_restart_aneg(phydev);
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_check_and_restart_aneg);
+
+/**
+ * __genphy_config_aneg - restart auto-negotiation or write BMCR
+ * @phydev: target phy_device struct
+ * @changed: whether autoneg is requested
+ *
+ * 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, bool changed)
+{
+ int err;
+
+ err = genphy_c45_an_config_eee_aneg(phydev);
+ if (err < 0)
+ return err;
+ else if (err)
+ changed = true;
+
+ err = genphy_setup_master_slave(phydev);
+ if (err < 0)
+ return err;
+ else if (err)
+ changed = true;
+
+ if (AUTONEG_ENABLE != phydev->autoneg)
+ return genphy_setup_forced(phydev);
+
+ err = genphy_config_advert(phydev);
+ if (err < 0) /* error */
+ return err;
+ else if (err)
+ changed = true;
+
+ return genphy_check_and_restart_aneg(phydev, changed);
+}
+EXPORT_SYMBOL(__genphy_config_aneg);
+
+/**
+ * genphy_c37_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. This function is intended
+ * for use with Clause 37 1000Base-X mode.
+ */
+int genphy_c37_config_aneg(struct phy_device *phydev)
+{
+ int err, changed;
+
+ if (phydev->autoneg != AUTONEG_ENABLE)
+ return genphy_setup_forced(phydev);
+
+ err = phy_modify(phydev, MII_BMCR, BMCR_SPEED1000 | BMCR_SPEED100,
+ BMCR_SPEED1000);
+ if (err)
+ return err;
+
+ changed = genphy_c37_config_advert(phydev);
+ if (changed < 0) /* error */
+ return changed;
+
+ if (!changed) {
+ /* 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_c37_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 = 0, bmcr;
+
+ bmcr = phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ /* Autoneg is being started, therefore disregard BMSR value and
+ * report link as down.
+ */
+ if (bmcr & BMCR_ANRESTART)
+ goto done;
+
+ /* 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 except
+ * the link was already down.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ status = phy_read(phydev, MII_BMSR);
+ if (status < 0)
+ return status;
+ else if (status & BMSR_LSTATUS)
+ goto done;
+ }
+
+ /* Read link and autonegotiation status */
+ status = phy_read(phydev, MII_BMSR);
+ if (status < 0)
+ return status;
+done:
+ phydev->link = status & BMSR_LSTATUS ? 1 : 0;
+ phydev->autoneg_complete = status & BMSR_ANEGCOMPLETE ? 1 : 0;
+
+ /* Consider the case that autoneg was started and "aneg complete"
+ * bit has been reset, but "link up" bit not yet.
+ */
+ if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete)
+ phydev->link = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_update_link);
+
+int genphy_read_lpa(struct phy_device *phydev)
+{
+ int lpa, lpagb;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ if (!phydev->autoneg_complete) {
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
+ 0);
+ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+ return 0;
+ }
+
+ if (phydev->is_gigabit_capable) {
+ lpagb = phy_read(phydev, MII_STAT1000);
+ if (lpagb < 0)
+ return lpagb;
+
+ if (lpagb & LPA_1000MSFAIL) {
+ int adv = phy_read(phydev, MII_CTRL1000);
+
+ if (adv < 0)
+ return adv;
+
+ 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;
+ }
+
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
+ lpagb);
+ }
+
+ lpa = phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa);
+ } else {
+ linkmode_zero(phydev->lp_advertising);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_read_lpa);
+
+/**
+ * genphy_read_status_fixed - read the link parameters for !aneg mode
+ * @phydev: target phy_device struct
+ *
+ * Read the current duplex and speed state for a PHY operating with
+ * autonegotiation disabled.
+ */
+int genphy_read_status_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;
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_read_status_fixed);
+
+/**
+ * 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 err, old_link = phydev->link;
+
+ /* Update the link, but return if there was an error */
+ err = genphy_update_link(phydev);
+ if (err)
+ return err;
+
+ /* why bother the PHY if nothing can have changed */
+ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+ return 0;
+
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNSUPPORTED;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ if (phydev->is_gigabit_capable) {
+ err = genphy_read_master_slave(phydev);
+ if (err < 0)
+ return err;
+ }
+
+ err = genphy_read_lpa(phydev);
+ if (err < 0)
+ return err;
+
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
+ phy_resolve_aneg_linkmode(phydev);
+ } else if (phydev->autoneg == AUTONEG_DISABLE) {
+ err = genphy_read_status_fixed(phydev);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_read_status);
+
+/**
+ * genphy_c37_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. This function is for Clause 37 1000Base-X mode.
+ */
+int genphy_c37_read_status(struct phy_device *phydev)
+{
+ int lpa, err, old_link = phydev->link;
+
+ /* Update the link, but return if there was an error */
+ err = genphy_update_link(phydev);
+ if (err)
+ return err;
+
+ /* why bother the PHY if nothing can have changed */
+ if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+ return 0;
+
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
+ lpa = phy_read(phydev, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->lp_advertising, lpa & LPA_LPACK);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->lp_advertising, lpa & LPA_1000XFULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->lp_advertising, lpa & LPA_1000XPAUSE);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->lp_advertising,
+ lpa & LPA_1000XPAUSE_ASYM);
+
+ phy_resolve_aneg_linkmode(phydev);
+ } else if (phydev->autoneg == AUTONEG_DISABLE) {
+ 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;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_c37_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)
+{
+ u16 res = BMCR_RESET;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_ENABLE)
+ res |= BMCR_ANRESTART;
+
+ ret = phy_modify(phydev, MII_BMCR, BMCR_ISOLATE, res);
+ if (ret < 0)
+ return ret;
+
+ /* Clause 22 states that setting bit BMCR_RESET sets control registers
+ * to their default value. Therefore the POWER DOWN bit is supposed to
+ * be cleared after soft reset.
+ */
+ phydev->suspended = 0;
+
+ ret = phy_poll_reset(phydev);
+ if (ret)
+ return ret;
+
+ /* BMCR may be reset to defaults */
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ ret = genphy_setup_forced(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL(genphy_soft_reset);
+
+irqreturn_t genphy_handle_interrupt_no_ack(struct phy_device *phydev)
+{
+ /* It seems there are cases where the interrupts are handled by another
+ * entity (ie an IRQ controller embedded inside the PHY) and do not
+ * need any other interraction from phylib. In this case, just trigger
+ * the state machine directly.
+ */
+ phy_trigger_machine(phydev);
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_handle_interrupt_no_ack);
+
+/**
+ * genphy_read_abilities - read PHY abilities from Clause 22 registers
+ * @phydev: target phy_device struct
+ *
+ * Description: Reads the PHY's abilities and populates
+ * phydev->supported accordingly.
+ *
+ * Returns: 0 on success, < 0 on failure
+ */
+int genphy_read_abilities(struct phy_device *phydev)
+{
+ int val;
+
+ linkmode_set_bit_array(phy_basic_ports_array,
+ ARRAY_SIZE(phy_basic_ports_array),
+ phydev->supported);
+
+ val = phy_read(phydev, MII_BMSR);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported,
+ val & BMSR_ANEGCAPABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported,
+ val & BMSR_100FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported,
+ val & BMSR_100HALF);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported,
+ val & BMSR_10FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported,
+ val & BMSR_10HALF);
+
+ if (val & BMSR_ESTATEN) {
+ val = phy_read(phydev, MII_ESTATUS);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported, val & ESTATUS_1000_TFULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->supported, val & ESTATUS_1000_THALF);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+ phydev->supported, val & ESTATUS_1000_XFULL);
+ }
+
+ /* This is optional functionality. If not supported, we may get an error
+ * which should be ignored.
+ */
+ genphy_c45_read_eee_abilities(phydev);
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_read_abilities);
+
+/* 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)
+{
+ if (enable) {
+ u16 val, ctl = BMCR_LOOPBACK;
+ int ret;
+
+ ctl |= mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
+
+ phy_modify(phydev, MII_BMCR, ~0, ctl);
+
+ ret = phy_read_poll_timeout(phydev, MII_BMSR, val,
+ val & BMSR_LSTATUS,
+ 5000, 500000, true);
+ if (ret)
+ return ret;
+ } else {
+ phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, 0);
+
+ phy_config_aneg(phydev);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(genphy_loopback);
+
+/**
+ * phy_remove_link_mode - Remove a supported link mode
+ * @phydev: phy_device structure to remove link mode from
+ * @link_mode: Link mode to be removed
+ *
+ * Description: Some MACs don't support all link modes which the PHY
+ * does. e.g. a 1G MAC often does not support 1000Half. Add a helper
+ * to remove a link mode.
+ */
+void phy_remove_link_mode(struct phy_device *phydev, u32 link_mode)
+{
+ linkmode_clear_bit(link_mode, phydev->supported);
+ phy_advertise_supported(phydev);
+}
+EXPORT_SYMBOL(phy_remove_link_mode);
+
+static void phy_copy_pause_bits(unsigned long *dst, unsigned long *src)
+{
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, dst,
+ linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, src));
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, dst,
+ linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, src));
+}
+
+/**
+ * phy_advertise_supported - Advertise all supported modes
+ * @phydev: target phy_device struct
+ *
+ * Description: Called to advertise all supported modes, doesn't touch
+ * pause mode advertising.
+ */
+void phy_advertise_supported(struct phy_device *phydev)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(new);
+
+ linkmode_copy(new, phydev->supported);
+ phy_copy_pause_bits(new, phydev->advertising);
+ linkmode_copy(phydev->advertising, new);
+}
+EXPORT_SYMBOL(phy_advertise_supported);
+
+/**
+ * phy_support_sym_pause - Enable support of symmetrical pause
+ * @phydev: target phy_device struct
+ *
+ * Description: Called by the MAC to indicate is supports symmetrical
+ * Pause, but not asym pause.
+ */
+void phy_support_sym_pause(struct phy_device *phydev)
+{
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);
+ phy_copy_pause_bits(phydev->advertising, phydev->supported);
+}
+EXPORT_SYMBOL(phy_support_sym_pause);
+
+/**
+ * phy_support_asym_pause - Enable support of asym pause
+ * @phydev: target phy_device struct
+ *
+ * Description: Called by the MAC to indicate is supports Asym Pause.
+ */
+void phy_support_asym_pause(struct phy_device *phydev)
+{
+ phy_copy_pause_bits(phydev->advertising, phydev->supported);
+}
+EXPORT_SYMBOL(phy_support_asym_pause);
+
+/**
+ * phy_set_sym_pause - Configure symmetric Pause
+ * @phydev: target phy_device struct
+ * @rx: Receiver Pause is supported
+ * @tx: Transmit Pause is supported
+ * @autoneg: Auto neg should be used
+ *
+ * Description: Configure advertised Pause support depending on if
+ * receiver pause and pause auto neg is supported. Generally called
+ * from the set_pauseparam .ndo.
+ */
+void phy_set_sym_pause(struct phy_device *phydev, bool rx, bool tx,
+ bool autoneg)
+{
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+
+ if (rx && tx && autoneg)
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->supported);
+
+ linkmode_copy(phydev->advertising, phydev->supported);
+}
+EXPORT_SYMBOL(phy_set_sym_pause);
+
+/**
+ * phy_set_asym_pause - Configure Pause and Asym Pause
+ * @phydev: target phy_device struct
+ * @rx: Receiver Pause is supported
+ * @tx: Transmit Pause is supported
+ *
+ * Description: Configure advertised Pause support depending on if
+ * transmit and receiver pause is supported. If there has been a
+ * change in adverting, trigger a new autoneg. Generally called from
+ * the set_pauseparam .ndo.
+ */
+void phy_set_asym_pause(struct phy_device *phydev, bool rx, bool tx)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(oldadv);
+
+ linkmode_copy(oldadv, phydev->advertising);
+ linkmode_set_pause(phydev->advertising, tx, rx);
+
+ if (!linkmode_equal(oldadv, phydev->advertising) &&
+ phydev->autoneg)
+ phy_start_aneg(phydev);
+}
+EXPORT_SYMBOL(phy_set_asym_pause);
+
+/**
+ * phy_validate_pause - Test if the PHY/MAC support the pause configuration
+ * @phydev: phy_device struct
+ * @pp: requested pause configuration
+ *
+ * Description: Test if the PHY/MAC combination supports the Pause
+ * configuration the user is requesting. Returns True if it is
+ * supported, false otherwise.
+ */
+bool phy_validate_pause(struct phy_device *phydev,
+ struct ethtool_pauseparam *pp)
+{
+ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->supported) && pp->rx_pause)
+ return false;
+
+ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->supported) &&
+ pp->rx_pause != pp->tx_pause)
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL(phy_validate_pause);
+
+/**
+ * phy_get_pause - resolve negotiated pause modes
+ * @phydev: phy_device struct
+ * @tx_pause: pointer to bool to indicate whether transmit pause should be
+ * enabled.
+ * @rx_pause: pointer to bool to indicate whether receive pause should be
+ * enabled.
+ *
+ * Resolve and return the flow control modes according to the negotiation
+ * result. This includes checking that we are operating in full duplex mode.
+ * See linkmode_resolve_pause() for further details.
+ */
+void phy_get_pause(struct phy_device *phydev, bool *tx_pause, bool *rx_pause)
+{
+ if (phydev->duplex != DUPLEX_FULL) {
+ *tx_pause = false;
+ *rx_pause = false;
+ return;
+ }
+
+ return linkmode_resolve_pause(phydev->advertising,
+ phydev->lp_advertising,
+ tx_pause, rx_pause);
+}
+EXPORT_SYMBOL(phy_get_pause);
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+static int phy_get_int_delay_property(struct device *dev, const char *name)
+{
+ s32 int_delay;
+ int ret;
+
+ ret = device_property_read_u32(dev, name, &int_delay);
+ if (ret)
+ return ret;
+
+ return int_delay;
+}
+#else
+static int phy_get_int_delay_property(struct device *dev, const char *name)
+{
+ return -EINVAL;
+}
+#endif
+
+/**
+ * phy_get_internal_delay - returns the index of the internal delay
+ * @phydev: phy_device struct
+ * @dev: pointer to the devices device struct
+ * @delay_values: array of delays the PHY supports
+ * @size: the size of the delay array
+ * @is_rx: boolean to indicate to get the rx internal delay
+ *
+ * Returns the index within the array of internal delay passed in.
+ * If the device property is not present then the interface type is checked
+ * if the interface defines use of internal delay then a 1 is returned otherwise
+ * a 0 is returned.
+ * The array must be in ascending order. If PHY does not have an ascending order
+ * array then size = 0 and the value of the delay property is returned.
+ * Return -EINVAL if the delay is invalid or cannot be found.
+ */
+s32 phy_get_internal_delay(struct phy_device *phydev, struct device *dev,
+ const int *delay_values, int size, bool is_rx)
+{
+ s32 delay;
+ int i;
+
+ if (is_rx) {
+ delay = phy_get_int_delay_property(dev, "rx-internal-delay-ps");
+ if (delay < 0 && size == 0) {
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+ return 1;
+ else
+ return 0;
+ }
+
+ } else {
+ delay = phy_get_int_delay_property(dev, "tx-internal-delay-ps");
+ if (delay < 0 && size == 0) {
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+ phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ if (delay < 0)
+ return delay;
+
+ if (delay && size == 0)
+ return delay;
+
+ if (delay < delay_values[0] || delay > delay_values[size - 1]) {
+ phydev_err(phydev, "Delay %d is out of range\n", delay);
+ return -EINVAL;
+ }
+
+ if (delay == delay_values[0])
+ return 0;
+
+ for (i = 1; i < size; i++) {
+ if (delay == delay_values[i])
+ return i;
+
+ /* Find an approximate index by looking up the table */
+ if (delay > delay_values[i - 1] &&
+ delay < delay_values[i]) {
+ if (delay - delay_values[i - 1] <
+ delay_values[i] - delay)
+ return i - 1;
+ else
+ return i;
+ }
+ }
+
+ phydev_err(phydev, "error finding internal delay index for %d\n",
+ delay);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL(phy_get_internal_delay);
+
+static bool phy_drv_supports_irq(struct phy_driver *phydrv)
+{
+ return phydrv->config_intr && phydrv->handle_interrupt;
+}
+
+static int phy_led_set_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct phy_led *phyled = to_phy_led(led_cdev);
+ struct phy_device *phydev = phyled->phydev;
+ int err;
+
+ mutex_lock(&phydev->lock);
+ err = phydev->drv->led_brightness_set(phydev, phyled->index, value);
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+
+static int phy_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct phy_led *phyled = to_phy_led(led_cdev);
+ struct phy_device *phydev = phyled->phydev;
+ int err;
+
+ mutex_lock(&phydev->lock);
+ err = phydev->drv->led_blink_set(phydev, phyled->index,
+ delay_on, delay_off);
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+
+static __maybe_unused struct device *
+phy_led_hw_control_get_device(struct led_classdev *led_cdev)
+{
+ struct phy_led *phyled = to_phy_led(led_cdev);
+ struct phy_device *phydev = phyled->phydev;
+
+ if (phydev->attached_dev)
+ return &phydev->attached_dev->dev;
+ return NULL;
+}
+
+static int __maybe_unused
+phy_led_hw_control_get(struct led_classdev *led_cdev,
+ unsigned long *rules)
+{
+ struct phy_led *phyled = to_phy_led(led_cdev);
+ struct phy_device *phydev = phyled->phydev;
+ int err;
+
+ mutex_lock(&phydev->lock);
+ err = phydev->drv->led_hw_control_get(phydev, phyled->index, rules);
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+
+static int __maybe_unused
+phy_led_hw_control_set(struct led_classdev *led_cdev,
+ unsigned long rules)
+{
+ struct phy_led *phyled = to_phy_led(led_cdev);
+ struct phy_device *phydev = phyled->phydev;
+ int err;
+
+ mutex_lock(&phydev->lock);
+ err = phydev->drv->led_hw_control_set(phydev, phyled->index, rules);
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+
+static __maybe_unused int phy_led_hw_is_supported(struct led_classdev *led_cdev,
+ unsigned long rules)
+{
+ struct phy_led *phyled = to_phy_led(led_cdev);
+ struct phy_device *phydev = phyled->phydev;
+ int err;
+
+ mutex_lock(&phydev->lock);
+ err = phydev->drv->led_hw_is_supported(phydev, phyled->index, rules);
+ mutex_unlock(&phydev->lock);
+
+ return err;
+}
+
+static void phy_leds_unregister(struct phy_device *phydev)
+{
+ struct phy_led *phyled;
+
+ list_for_each_entry(phyled, &phydev->leds, list) {
+ led_classdev_unregister(&phyled->led_cdev);
+ }
+}
+
+static int of_phy_led(struct phy_device *phydev,
+ struct device_node *led)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ struct phy_led *phyled;
+ u32 index;
+ int err;
+
+ phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
+ if (!phyled)
+ return -ENOMEM;
+
+ cdev = &phyled->led_cdev;
+ phyled->phydev = phydev;
+
+ err = of_property_read_u32(led, "reg", &index);
+ if (err)
+ return err;
+ if (index > U8_MAX)
+ return -EINVAL;
+
+ phyled->index = index;
+ if (phydev->drv->led_brightness_set)
+ cdev->brightness_set_blocking = phy_led_set_brightness;
+ if (phydev->drv->led_blink_set)
+ cdev->blink_set = phy_led_blink_set;
+
+#ifdef CONFIG_LEDS_TRIGGERS
+ if (phydev->drv->led_hw_is_supported &&
+ phydev->drv->led_hw_control_set &&
+ phydev->drv->led_hw_control_get) {
+ cdev->hw_control_is_supported = phy_led_hw_is_supported;
+ cdev->hw_control_set = phy_led_hw_control_set;
+ cdev->hw_control_get = phy_led_hw_control_get;
+ cdev->hw_control_trigger = "netdev";
+ }
+
+ cdev->hw_control_get_device = phy_led_hw_control_get_device;
+#endif
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+ init_data.devname_mandatory = true;
+
+ err = led_classdev_register_ext(dev, cdev, &init_data);
+ if (err)
+ return err;
+
+ list_add(&phyled->list, &phydev->leds);
+
+ return 0;
+}
+
+static int of_phy_leds(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ struct device_node *leds, *led;
+ int err;
+
+ if (!IS_ENABLED(CONFIG_OF_MDIO))
+ return 0;
+
+ if (!node)
+ return 0;
+
+ leds = of_get_child_by_name(node, "leds");
+ if (!leds)
+ return 0;
+
+ for_each_available_child_of_node(leds, led) {
+ err = of_phy_led(phydev, led);
+ if (err) {
+ of_node_put(led);
+ phy_leds_unregister(phydev);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * fwnode_mdio_find_device - Given a fwnode, find the mdio_device
+ * @fwnode: pointer to the mdio_device's fwnode
+ *
+ * If successful, returns a pointer to the mdio_device with the embedded
+ * struct device refcount incremented by one, or NULL on failure.
+ * The caller should call put_device() on the mdio_device after its use.
+ */
+struct mdio_device *fwnode_mdio_find_device(struct fwnode_handle *fwnode)
+{
+ struct device *d;
+
+ if (!fwnode)
+ return NULL;
+
+ d = bus_find_device_by_fwnode(&mdio_bus_type, fwnode);
+ if (!d)
+ return NULL;
+
+ return to_mdio_device(d);
+}
+EXPORT_SYMBOL(fwnode_mdio_find_device);
+
+/**
+ * fwnode_phy_find_device - For provided phy_fwnode, find phy_device.
+ *
+ * @phy_fwnode: Pointer to the phy's fwnode.
+ *
+ * If successful, returns a pointer to the phy_device with the embedded
+ * struct device refcount incremented by one, or NULL on failure.
+ */
+struct phy_device *fwnode_phy_find_device(struct fwnode_handle *phy_fwnode)
+{
+ struct mdio_device *mdiodev;
+
+ mdiodev = fwnode_mdio_find_device(phy_fwnode);
+ if (!mdiodev)
+ return NULL;
+
+ if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY)
+ return to_phy_device(&mdiodev->dev);
+
+ put_device(&mdiodev->dev);
+
+ return NULL;
+}
+EXPORT_SYMBOL(fwnode_phy_find_device);
+
+/**
+ * device_phy_find_device - For the given device, get the phy_device
+ * @dev: Pointer to the given device
+ *
+ * Refer return conditions of fwnode_phy_find_device().
+ */
+struct phy_device *device_phy_find_device(struct device *dev)
+{
+ return fwnode_phy_find_device(dev_fwnode(dev));
+}
+EXPORT_SYMBOL_GPL(device_phy_find_device);
+
+/**
+ * fwnode_get_phy_node - Get the phy_node using the named reference.
+ * @fwnode: Pointer to fwnode from which phy_node has to be obtained.
+ *
+ * Refer return conditions of fwnode_find_reference().
+ * For ACPI, only "phy-handle" is supported. Legacy DT properties "phy"
+ * and "phy-device" are not supported in ACPI. DT supports all the three
+ * named references to the phy node.
+ */
+struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwnode)
+{
+ struct fwnode_handle *phy_node;
+
+ /* Only phy-handle is used for ACPI */
+ phy_node = fwnode_find_reference(fwnode, "phy-handle", 0);
+ if (is_acpi_node(fwnode) || !IS_ERR(phy_node))
+ return phy_node;
+ phy_node = fwnode_find_reference(fwnode, "phy", 0);
+ if (IS_ERR(phy_node))
+ phy_node = fwnode_find_reference(fwnode, "phy-device", 0);
+ return phy_node;
+}
+EXPORT_SYMBOL_GPL(fwnode_get_phy_node);
+
+/**
+ * phy_probe - probe and init a PHY device
+ * @dev: device to probe and init
+ *
+ * Take care of setting up the phy_device structure, set the state to READY.
+ */
+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 (!phy_drv_supports_irq(phydrv) && phy_interrupt_is_valid(phydev))
+ phydev->irq = PHY_POLL;
+
+ if (phydrv->flags & PHY_IS_INTERNAL)
+ phydev->is_internal = true;
+
+ /* Deassert the reset signal */
+ phy_device_reset(phydev, 0);
+
+ if (phydev->drv->probe) {
+ err = phydev->drv->probe(phydev);
+ if (err)
+ goto out;
+ }
+
+ phy_disable_interrupts(phydev);
+
+ /* Start out supporting everything. Eventually,
+ * a controller will attach, and may modify one
+ * or both of these values
+ */
+ if (phydrv->features) {
+ linkmode_copy(phydev->supported, phydrv->features);
+ genphy_c45_read_eee_abilities(phydev);
+ }
+ else if (phydrv->get_features)
+ err = phydrv->get_features(phydev);
+ else if (phydev->is_c45)
+ err = genphy_c45_pma_read_abilities(phydev);
+ else
+ err = genphy_read_abilities(phydev);
+
+ if (err)
+ goto out;
+
+ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->supported))
+ phydev->autoneg = 0;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->supported))
+ phydev->is_gigabit_capable = 1;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->supported))
+ phydev->is_gigabit_capable = 1;
+
+ of_set_phy_supported(phydev);
+ phy_advertise_supported(phydev);
+
+ /* Get PHY default EEE advertising modes and handle them as potentially
+ * safe initial configuration.
+ */
+ err = genphy_c45_read_eee_adv(phydev, phydev->advertising_eee);
+ if (err)
+ goto out;
+
+ /* There is no "enabled" flag. If PHY is advertising, assume it is
+ * kind of enabled.
+ */
+ phydev->eee_enabled = !linkmode_empty(phydev->advertising_eee);
+
+ /* Some PHYs may advertise, by default, not support EEE modes. So,
+ * we need to clean them.
+ */
+ if (phydev->eee_enabled)
+ linkmode_and(phydev->advertising_eee, phydev->supported_eee,
+ phydev->advertising_eee);
+
+ /* 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 (!test_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported) &&
+ !test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported)) {
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->supported);
+ }
+
+ /* Set the state to READY by default */
+ phydev->state = PHY_READY;
+
+ /* Get the LEDs from the device tree, and instantiate standard
+ * LEDs for them.
+ */
+ if (IS_ENABLED(CONFIG_PHYLIB_LEDS))
+ err = of_phy_leds(phydev);
+
+out:
+ /* Re-assert the reset signal on error */
+ if (err)
+ phy_device_reset(phydev, 1);
+
+ return err;
+}
+
+static int phy_remove(struct device *dev)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ cancel_delayed_work_sync(&phydev->state_queue);
+
+ if (IS_ENABLED(CONFIG_PHYLIB_LEDS))
+ phy_leds_unregister(phydev);
+
+ phydev->state = PHY_DOWN;
+
+ sfp_bus_del_upstream(phydev->sfp_bus);
+ phydev->sfp_bus = NULL;
+
+ if (phydev->drv && phydev->drv->remove)
+ phydev->drv->remove(phydev);
+
+ /* Assert the reset signal */
+ phy_device_reset(phydev, 1);
+
+ phydev->drv = NULL;
+
+ return 0;
+}
+
+/**
+ * phy_driver_register - register a phy_driver with the PHY layer
+ * @new_driver: new phy_driver to register
+ * @owner: module owning this PHY
+ */
+int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
+{
+ int retval;
+
+ /* Either the features are hard coded, or dynamically
+ * determined. It cannot be both.
+ */
+ if (WARN_ON(new_driver->features && new_driver->get_features)) {
+ pr_err("%s: features and get_features must not both be set\n",
+ new_driver->name);
+ return -EINVAL;
+ }
+
+ /* PHYLIB device drivers must not match using a DT compatible table
+ * as this bypasses our checks that the mdiodev that is being matched
+ * is backed by a struct phy_device. If such a case happens, we will
+ * make out-of-bounds accesses and lockup in phydev->lock.
+ */
+ if (WARN(new_driver->mdiodrv.driver.of_match_table,
+ "%s: driver must not provide a DT match table\n",
+ new_driver->name))
+ return -EINVAL;
+
+ 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;
+ 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",
+ .get_features = genphy_read_abilities,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .set_loopback = genphy_loopback,
+};
+
+static const struct ethtool_phy_ops phy_ethtool_phy_ops = {
+ .get_sset_count = phy_ethtool_get_sset_count,
+ .get_strings = phy_ethtool_get_strings,
+ .get_stats = phy_ethtool_get_stats,
+ .get_plca_cfg = phy_ethtool_get_plca_cfg,
+ .set_plca_cfg = phy_ethtool_set_plca_cfg,
+ .get_plca_status = phy_ethtool_get_plca_status,
+ .start_cable_test = phy_start_cable_test,
+ .start_cable_test_tdr = phy_start_cable_test_tdr,
+};
+
+static const struct phylib_stubs __phylib_stubs = {
+ .hwtstamp_get = __phy_hwtstamp_get,
+ .hwtstamp_set = __phy_hwtstamp_set,
+};
+
+static void phylib_register_stubs(void)
+{
+ phylib_stubs = &__phylib_stubs;
+}
+
+static void phylib_unregister_stubs(void)
+{
+ phylib_stubs = NULL;
+}
+
+static int __init phy_init(void)
+{
+ int rc;
+
+ rtnl_lock();
+ ethtool_set_ethtool_phy_ops(&phy_ethtool_phy_ops);
+ phylib_register_stubs();
+ rtnl_unlock();
+
+ rc = mdio_bus_init();
+ if (rc)
+ goto err_ethtool_phy_ops;
+
+ features_init();
+
+ rc = phy_driver_register(&genphy_c45_driver, THIS_MODULE);
+ if (rc)
+ goto err_mdio_bus;
+
+ rc = phy_driver_register(&genphy_driver, THIS_MODULE);
+ if (rc)
+ goto err_c45;
+
+ return 0;
+
+err_c45:
+ phy_driver_unregister(&genphy_c45_driver);
+err_mdio_bus:
+ mdio_bus_exit();
+err_ethtool_phy_ops:
+ rtnl_lock();
+ phylib_unregister_stubs();
+ ethtool_set_ethtool_phy_ops(NULL);
+ rtnl_unlock();
+
+ return rc;
+}
+
+static void __exit phy_exit(void)
+{
+ phy_driver_unregister(&genphy_c45_driver);
+ phy_driver_unregister(&genphy_driver);
+ mdio_bus_exit();
+ rtnl_lock();
+ phylib_unregister_stubs();
+ ethtool_set_ethtool_phy_ops(NULL);
+ rtnl_unlock();
+}
+
+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..f550576eb
--- /dev/null
+++ b/drivers/net/phy/phy_led_triggers.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2016 National Instruments Corp. */
+#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, const 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,
+ const char *suffix)
+{
+ plt->speed = speed;
+ phy_led_trigger_format_name(phy, plt->name, sizeof(plt->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;
+ }
+
+ err = phy_led_trigger_register(phy, phy->led_link_trigger, 0, "link");
+ 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],
+ phy_speed_to_str(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..b5f012619
--- /dev/null
+++ b/drivers/net/phy/phylink.c
@@ -0,0 +1,3744 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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
+ */
+#include <linux/acpi.h>
+#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,
+ PHYLINK_DISABLE_MAC_WOL,
+
+ PCS_STATE_DOWN = 0,
+ PCS_STATE_STARTING,
+ PCS_STATE_STARTED,
+};
+
+/**
+ * struct phylink - internal data type for phylink
+ */
+struct phylink {
+ /* private: */
+ struct net_device *netdev;
+ const struct phylink_mac_ops *mac_ops;
+ struct phylink_config *config;
+ struct phylink_pcs *pcs;
+ struct device *dev;
+ unsigned int old_link_state:1;
+
+ unsigned long phylink_disable_state; /* bitmask of disables */
+ struct phy_device *phydev;
+ phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
+ u8 cfg_link_an_mode; /* MLO_AN_xxx */
+ u8 cur_link_an_mode;
+ u8 link_port; /* The current non-phy ethtool port */
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+
+ /* The link configuration settings */
+ struct phylink_link_state link_config;
+
+ /* The current settings */
+ phy_interface_t cur_interface;
+
+ struct gpio_desc *link_gpio;
+ unsigned int link_irq;
+ struct timer_list link_poll;
+ void (*get_fixed_state)(struct net_device *dev,
+ struct phylink_link_state *s);
+
+ struct mutex state_mutex;
+ struct phylink_link_state phy_state;
+ struct work_struct resolve;
+ unsigned int pcs_neg_mode;
+ unsigned int pcs_state;
+
+ bool mac_link_dropped;
+ bool using_mac_select_pcs;
+
+ struct sfp_bus *sfp_bus;
+ bool sfp_may_have_phy;
+ DECLARE_PHY_INTERFACE_MASK(sfp_interfaces);
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+ u8 sfp_port;
+};
+
+#define phylink_printk(level, pl, fmt, ...) \
+ do { \
+ if ((pl)->config->type == PHYLINK_NETDEV) \
+ netdev_printk(level, (pl)->netdev, fmt, ##__VA_ARGS__); \
+ else if ((pl)->config->type == PHYLINK_DEV) \
+ dev_printk(level, (pl)->dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define phylink_err(pl, fmt, ...) \
+ phylink_printk(KERN_ERR, pl, fmt, ##__VA_ARGS__)
+#define phylink_warn(pl, fmt, ...) \
+ phylink_printk(KERN_WARNING, pl, fmt, ##__VA_ARGS__)
+#define phylink_info(pl, fmt, ...) \
+ phylink_printk(KERN_INFO, pl, fmt, ##__VA_ARGS__)
+#if defined(CONFIG_DYNAMIC_DEBUG)
+#define phylink_dbg(pl, fmt, ...) \
+do { \
+ if ((pl)->config->type == PHYLINK_NETDEV) \
+ netdev_dbg((pl)->netdev, fmt, ##__VA_ARGS__); \
+ else if ((pl)->config->type == PHYLINK_DEV) \
+ dev_dbg((pl)->dev, fmt, ##__VA_ARGS__); \
+} while (0)
+#elif defined(DEBUG)
+#define phylink_dbg(pl, fmt, ...) \
+ phylink_printk(KERN_DEBUG, pl, fmt, ##__VA_ARGS__)
+#else
+#define phylink_dbg(pl, fmt, ...) \
+({ \
+ if (0) \
+ phylink_printk(KERN_DEBUG, pl, fmt, ##__VA_ARGS__); \
+})
+#endif
+
+/**
+ * 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);
+
+ return linkmode_subset(linkmode, 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 unsigned int phylink_interface_signal_rate(phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX: /* 1.25Mbd */
+ return 1250;
+ case PHY_INTERFACE_MODE_2500BASEX: /* 3.125Mbd */
+ return 3125;
+ case PHY_INTERFACE_MODE_5GBASER: /* 5.15625Mbd */
+ return 5156;
+ case PHY_INTERFACE_MODE_10GBASER: /* 10.3125Mbd */
+ return 10313;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * phylink_interface_max_speed() - get the maximum speed of a phy interface
+ * @interface: phy interface mode defined by &typedef phy_interface_t
+ *
+ * Determine the maximum speed of a phy interface. This is intended to help
+ * determine the correct speed to pass to the MAC when the phy is performing
+ * rate matching.
+ *
+ * Return: The maximum speed of @interface
+ */
+static int phylink_interface_max_speed(phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_100BASEX:
+ case PHY_INTERFACE_MODE_REVRMII:
+ case PHY_INTERFACE_MODE_RMII:
+ case PHY_INTERFACE_MODE_SMII:
+ case PHY_INTERFACE_MODE_REVMII:
+ case PHY_INTERFACE_MODE_MII:
+ return SPEED_100;
+
+ case PHY_INTERFACE_MODE_TBI:
+ case PHY_INTERFACE_MODE_MOCA:
+ case PHY_INTERFACE_MODE_RTBI:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_1000BASEKX:
+ case PHY_INTERFACE_MODE_TRGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_GMII:
+ return SPEED_1000;
+
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return SPEED_2500;
+
+ case PHY_INTERFACE_MODE_5GBASER:
+ return SPEED_5000;
+
+ case PHY_INTERFACE_MODE_XGMII:
+ case PHY_INTERFACE_MODE_RXAUI:
+ case PHY_INTERFACE_MODE_XAUI:
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_10GKR:
+ case PHY_INTERFACE_MODE_USXGMII:
+ return SPEED_10000;
+
+ case PHY_INTERFACE_MODE_25GBASER:
+ return SPEED_25000;
+
+ case PHY_INTERFACE_MODE_XLGMII:
+ return SPEED_40000;
+
+ case PHY_INTERFACE_MODE_INTERNAL:
+ case PHY_INTERFACE_MODE_NA:
+ case PHY_INTERFACE_MODE_MAX:
+ /* No idea! Garbage in, unknown out */
+ return SPEED_UNKNOWN;
+ }
+
+ /* If we get here, someone forgot to add an interface mode above */
+ WARN_ON_ONCE(1);
+ return SPEED_UNKNOWN;
+}
+
+/**
+ * phylink_caps_to_linkmodes() - Convert capabilities to ethtool link modes
+ * @linkmodes: ethtool linkmode mask (must be already initialised)
+ * @caps: bitmask of MAC capabilities
+ *
+ * Set all possible pause, speed and duplex linkmodes in @linkmodes that are
+ * supported by the @caps. @linkmodes must have been initialised previously.
+ */
+void phylink_caps_to_linkmodes(unsigned long *linkmodes, unsigned long caps)
+{
+ if (caps & MAC_SYM_PAUSE)
+ __set_bit(ETHTOOL_LINK_MODE_Pause_BIT, linkmodes);
+
+ if (caps & MAC_ASYM_PAUSE)
+ __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, linkmodes);
+
+ if (caps & MAC_10HD) {
+ __set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10baseT1S_Half_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT, linkmodes);
+ }
+
+ if (caps & MAC_10FD) {
+ __set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10baseT1S_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_100HD) {
+ __set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT, linkmodes);
+ }
+
+ if (caps & MAC_100FD) {
+ __set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_1000HD)
+ __set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, linkmodes);
+
+ if (caps & MAC_1000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_1000baseT1_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_2500FD) {
+ __set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_5000FD)
+ __set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, linkmodes);
+
+ if (caps & MAC_10000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_25000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_40000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_50000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_56000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_100000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseKR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseSR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseCR_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_100000baseDR_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_200000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT, linkmodes);
+ }
+
+ if (caps & MAC_400000FD) {
+ __set_bit(ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT,
+ linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT, linkmodes);
+ __set_bit(ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT, linkmodes);
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_caps_to_linkmodes);
+
+static struct {
+ unsigned long mask;
+ int speed;
+ unsigned int duplex;
+} phylink_caps_params[] = {
+ { MAC_400000FD, SPEED_400000, DUPLEX_FULL },
+ { MAC_200000FD, SPEED_200000, DUPLEX_FULL },
+ { MAC_100000FD, SPEED_100000, DUPLEX_FULL },
+ { MAC_56000FD, SPEED_56000, DUPLEX_FULL },
+ { MAC_50000FD, SPEED_50000, DUPLEX_FULL },
+ { MAC_40000FD, SPEED_40000, DUPLEX_FULL },
+ { MAC_25000FD, SPEED_25000, DUPLEX_FULL },
+ { MAC_20000FD, SPEED_20000, DUPLEX_FULL },
+ { MAC_10000FD, SPEED_10000, DUPLEX_FULL },
+ { MAC_5000FD, SPEED_5000, DUPLEX_FULL },
+ { MAC_2500FD, SPEED_2500, DUPLEX_FULL },
+ { MAC_1000FD, SPEED_1000, DUPLEX_FULL },
+ { MAC_1000HD, SPEED_1000, DUPLEX_HALF },
+ { MAC_100FD, SPEED_100, DUPLEX_FULL },
+ { MAC_100HD, SPEED_100, DUPLEX_HALF },
+ { MAC_10FD, SPEED_10, DUPLEX_FULL },
+ { MAC_10HD, SPEED_10, DUPLEX_HALF },
+};
+
+/**
+ * phylink_limit_mac_speed - limit the phylink_config to a maximum speed
+ * @config: pointer to a &struct phylink_config
+ * @max_speed: maximum speed
+ *
+ * Mask off MAC capabilities for speeds higher than the @max_speed parameter.
+ * Any further motifications of config.mac_capabilities will override this.
+ */
+void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(phylink_caps_params) &&
+ phylink_caps_params[i].speed > max_speed; i++)
+ config->mac_capabilities &= ~phylink_caps_params[i].mask;
+}
+EXPORT_SYMBOL_GPL(phylink_limit_mac_speed);
+
+/**
+ * phylink_cap_from_speed_duplex - Get mac capability from speed/duplex
+ * @speed: the speed to search for
+ * @duplex: the duplex to search for
+ *
+ * Find the mac capability for a given speed and duplex.
+ *
+ * Return: A mask with the mac capability patching @speed and @duplex, or 0 if
+ * there were no matches.
+ */
+static unsigned long phylink_cap_from_speed_duplex(int speed,
+ unsigned int duplex)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(phylink_caps_params); i++) {
+ if (speed == phylink_caps_params[i].speed &&
+ duplex == phylink_caps_params[i].duplex)
+ return phylink_caps_params[i].mask;
+ }
+
+ return 0;
+}
+
+/**
+ * phylink_get_capabilities() - get capabilities for a given MAC
+ * @interface: phy interface mode defined by &typedef phy_interface_t
+ * @mac_capabilities: bitmask of MAC capabilities
+ * @rate_matching: type of rate matching being performed
+ *
+ * Get the MAC capabilities that are supported by the @interface mode and
+ * @mac_capabilities.
+ */
+unsigned long phylink_get_capabilities(phy_interface_t interface,
+ unsigned long mac_capabilities,
+ int rate_matching)
+{
+ int max_speed = phylink_interface_max_speed(interface);
+ unsigned long caps = MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
+ unsigned long matched_caps = 0;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_USXGMII:
+ caps |= MAC_10000FD | MAC_5000FD | MAC_2500FD;
+ fallthrough;
+
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_GMII:
+ caps |= MAC_1000HD | MAC_1000FD;
+ fallthrough;
+
+ case PHY_INTERFACE_MODE_REVRMII:
+ case PHY_INTERFACE_MODE_RMII:
+ case PHY_INTERFACE_MODE_SMII:
+ case PHY_INTERFACE_MODE_REVMII:
+ case PHY_INTERFACE_MODE_MII:
+ caps |= MAC_10HD | MAC_10FD;
+ fallthrough;
+
+ case PHY_INTERFACE_MODE_100BASEX:
+ caps |= MAC_100HD | MAC_100FD;
+ break;
+
+ case PHY_INTERFACE_MODE_TBI:
+ case PHY_INTERFACE_MODE_MOCA:
+ case PHY_INTERFACE_MODE_RTBI:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ caps |= MAC_1000HD;
+ fallthrough;
+ case PHY_INTERFACE_MODE_1000BASEKX:
+ case PHY_INTERFACE_MODE_TRGMII:
+ caps |= MAC_1000FD;
+ break;
+
+ case PHY_INTERFACE_MODE_2500BASEX:
+ caps |= MAC_2500FD;
+ break;
+
+ case PHY_INTERFACE_MODE_5GBASER:
+ caps |= MAC_5000FD;
+ break;
+
+ case PHY_INTERFACE_MODE_XGMII:
+ case PHY_INTERFACE_MODE_RXAUI:
+ case PHY_INTERFACE_MODE_XAUI:
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_10GKR:
+ caps |= MAC_10000FD;
+ break;
+
+ case PHY_INTERFACE_MODE_25GBASER:
+ caps |= MAC_25000FD;
+ break;
+
+ case PHY_INTERFACE_MODE_XLGMII:
+ caps |= MAC_40000FD;
+ break;
+
+ case PHY_INTERFACE_MODE_INTERNAL:
+ caps |= ~0;
+ break;
+
+ case PHY_INTERFACE_MODE_NA:
+ case PHY_INTERFACE_MODE_MAX:
+ break;
+ }
+
+ switch (rate_matching) {
+ case RATE_MATCH_OPEN_LOOP:
+ /* TODO */
+ fallthrough;
+ case RATE_MATCH_NONE:
+ matched_caps = 0;
+ break;
+ case RATE_MATCH_PAUSE: {
+ /* The MAC must support asymmetric pause towards the local
+ * device for this. We could allow just symmetric pause, but
+ * then we might have to renegotiate if the link partner
+ * doesn't support pause. This is because there's no way to
+ * accept pause frames without transmitting them if we only
+ * support symmetric pause.
+ */
+ if (!(mac_capabilities & MAC_SYM_PAUSE) ||
+ !(mac_capabilities & MAC_ASYM_PAUSE))
+ break;
+
+ /* We can't adapt if the MAC doesn't support the interface's
+ * max speed at full duplex.
+ */
+ if (mac_capabilities &
+ phylink_cap_from_speed_duplex(max_speed, DUPLEX_FULL)) {
+ /* Although a duplex-matching phy might exist, we
+ * conservatively remove these modes because the MAC
+ * will not be aware of the half-duplex nature of the
+ * link.
+ */
+ matched_caps = GENMASK(__fls(caps), __fls(MAC_10HD));
+ matched_caps &= ~(MAC_1000HD | MAC_100HD | MAC_10HD);
+ }
+ break;
+ }
+ case RATE_MATCH_CRS:
+ /* The MAC must support half duplex at the interface's max
+ * speed.
+ */
+ if (mac_capabilities &
+ phylink_cap_from_speed_duplex(max_speed, DUPLEX_HALF)) {
+ matched_caps = GENMASK(__fls(caps), __fls(MAC_10HD));
+ matched_caps &= mac_capabilities;
+ }
+ break;
+ }
+
+ return (caps & mac_capabilities) | matched_caps;
+}
+EXPORT_SYMBOL_GPL(phylink_get_capabilities);
+
+/**
+ * phylink_validate_mask_caps() - Restrict link modes based on caps
+ * @supported: ethtool bitmask for supported link modes.
+ * @state: pointer to a &struct phylink_link_state.
+ * @mac_capabilities: bitmask of MAC capabilities
+ *
+ * Calculate the supported link modes based on @mac_capabilities, and restrict
+ * @supported and @state based on that. Use this function if your capabiliies
+ * aren't constant, such as if they vary depending on the interface.
+ */
+void phylink_validate_mask_caps(unsigned long *supported,
+ struct phylink_link_state *state,
+ unsigned long mac_capabilities)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
+ unsigned long caps;
+
+ phylink_set_port_modes(mask);
+ phylink_set(mask, Autoneg);
+ caps = phylink_get_capabilities(state->interface, mac_capabilities,
+ state->rate_matching);
+ phylink_caps_to_linkmodes(mask, caps);
+
+ linkmode_and(supported, supported, mask);
+ linkmode_and(state->advertising, state->advertising, mask);
+}
+EXPORT_SYMBOL_GPL(phylink_validate_mask_caps);
+
+/**
+ * phylink_generic_validate() - generic validate() callback implementation
+ * @config: a pointer to a &struct phylink_config.
+ * @supported: ethtool bitmask for supported link modes.
+ * @state: a pointer to a &struct phylink_link_state.
+ *
+ * Generic implementation of the validate() callback that MAC drivers can
+ * use when they pass the range of supported interfaces and MAC capabilities.
+ */
+void phylink_generic_validate(struct phylink_config *config,
+ unsigned long *supported,
+ struct phylink_link_state *state)
+{
+ phylink_validate_mask_caps(supported, state, config->mac_capabilities);
+}
+EXPORT_SYMBOL_GPL(phylink_generic_validate);
+
+static int phylink_validate_mac_and_pcs(struct phylink *pl,
+ unsigned long *supported,
+ struct phylink_link_state *state)
+{
+ struct phylink_pcs *pcs;
+ int ret;
+
+ /* Get the PCS for this interface mode */
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+ } else {
+ pcs = pl->pcs;
+ }
+
+ if (pcs) {
+ /* The PCS, if present, must be setup before phylink_create()
+ * has been called. If the ops is not initialised, print an
+ * error and backtrace rather than oopsing the kernel.
+ */
+ if (!pcs->ops) {
+ phylink_err(pl, "interface %s: uninitialised PCS\n",
+ phy_modes(state->interface));
+ dump_stack();
+ return -EINVAL;
+ }
+
+ /* Validate the link parameters with the PCS */
+ if (pcs->ops->pcs_validate) {
+ ret = pcs->ops->pcs_validate(pcs, supported, state);
+ if (ret < 0 || phylink_is_empty_linkmode(supported))
+ return -EINVAL;
+
+ /* Ensure the advertising mask is a subset of the
+ * supported mask.
+ */
+ linkmode_and(state->advertising, state->advertising,
+ supported);
+ }
+ }
+
+ /* Then validate the link parameters with the MAC */
+ if (pl->mac_ops->validate)
+ pl->mac_ops->validate(pl->config, supported, state);
+ else
+ phylink_generic_validate(pl->config, supported, state);
+
+ return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+}
+
+static int phylink_validate_mask(struct phylink *pl, unsigned long *supported,
+ struct phylink_link_state *state,
+ const unsigned long *interfaces)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(all_adv) = { 0, };
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(all_s) = { 0, };
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(s);
+ struct phylink_link_state t;
+ int intf;
+
+ for (intf = 0; intf < PHY_INTERFACE_MODE_MAX; intf++) {
+ if (test_bit(intf, interfaces)) {
+ linkmode_copy(s, supported);
+
+ t = *state;
+ t.interface = intf;
+ if (!phylink_validate_mac_and_pcs(pl, s, &t)) {
+ linkmode_or(all_s, all_s, s);
+ linkmode_or(all_adv, all_adv, t.advertising);
+ }
+ }
+ }
+
+ linkmode_copy(supported, all_s);
+ linkmode_copy(state->advertising, all_adv);
+
+ return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
+}
+
+static int phylink_validate(struct phylink *pl, unsigned long *supported,
+ struct phylink_link_state *state)
+{
+ const unsigned long *interfaces = pl->config->supported_interfaces;
+
+ if (state->interface == PHY_INTERFACE_MODE_NA)
+ return phylink_validate_mask(pl, supported, state, interfaces);
+
+ if (!test_bit(state->interface, interfaces))
+ return -EINVAL;
+
+ return phylink_validate_mac_and_pcs(pl, supported, state);
+}
+
+static int phylink_parse_fixedlink(struct phylink *pl,
+ const struct fwnode_handle *fwnode)
+{
+ struct fwnode_handle *fixed_node;
+ bool pause, asym_pause, autoneg;
+ 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"))
+ __set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ pl->link_config.lp_advertising);
+ if (fwnode_property_read_bool(fixed_node, "asym-pause"))
+ __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ pl->link_config.lp_advertising);
+
+ if (ret == 0) {
+ desc = fwnode_gpiod_get_index(fixed_node, "link", 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)) {
+ phylink_err(pl, "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])
+ __set_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ pl->link_config.lp_advertising);
+ if (prop[4])
+ __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ pl->link_config.lp_advertising);
+ }
+ }
+
+ if (pl->link_config.speed > SPEED_1000 &&
+ pl->link_config.duplex != DUPLEX_FULL)
+ phylink_warn(pl, "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);
+
+ pause = phylink_test(pl->supported, Pause);
+ asym_pause = phylink_test(pl->supported, Asym_Pause);
+ autoneg = phylink_test(pl->supported, Autoneg);
+ s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex,
+ pl->supported, true);
+ linkmode_zero(pl->supported);
+ phylink_set(pl->supported, MII);
+
+ if (pause)
+ phylink_set(pl->supported, Pause);
+
+ if (asym_pause)
+ phylink_set(pl->supported, Asym_Pause);
+
+ if (autoneg)
+ phylink_set(pl->supported, Autoneg);
+
+ if (s) {
+ __set_bit(s->bit, pl->supported);
+ __set_bit(s->bit, pl->link_config.lp_advertising);
+ } else {
+ phylink_warn(pl, "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,
+ const struct fwnode_handle *fwnode)
+{
+ struct fwnode_handle *dn;
+ const char *managed;
+
+ dn = fwnode_get_named_child_node(fwnode, "fixed-link");
+ if (dn || fwnode_property_present(fwnode, "fixed-link"))
+ pl->cfg_link_an_mode = MLO_AN_FIXED;
+ fwnode_handle_put(dn);
+
+ if ((fwnode_property_read_string(fwnode, "managed", &managed) == 0 &&
+ strcmp(managed, "in-band-status") == 0) ||
+ pl->config->ovr_an_inband) {
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+ phylink_err(pl,
+ "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->cfg_link_an_mode = MLO_AN_INBAND;
+
+ switch (pl->link_config.interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_PSGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_QUSGMII:
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RTBI:
+ 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_5GBASER:
+ phylink_set(pl->supported, 5000baseT_Full);
+ break;
+
+ case PHY_INTERFACE_MODE_25GBASER:
+ phylink_set(pl->supported, 25000baseCR_Full);
+ phylink_set(pl->supported, 25000baseKR_Full);
+ phylink_set(pl->supported, 25000baseSR_Full);
+ fallthrough;
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10GKR:
+ case PHY_INTERFACE_MODE_10GBASER:
+ 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, 1000baseKX_Full);
+ phylink_set(pl->supported, 2500baseT_Full);
+ phylink_set(pl->supported, 2500baseX_Full);
+ phylink_set(pl->supported, 5000baseT_Full);
+ phylink_set(pl->supported, 10000baseT_Full);
+ phylink_set(pl->supported, 10000baseKR_Full);
+ phylink_set(pl->supported, 10000baseKX4_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;
+
+ case PHY_INTERFACE_MODE_XLGMII:
+ phylink_set(pl->supported, 25000baseCR_Full);
+ phylink_set(pl->supported, 25000baseKR_Full);
+ phylink_set(pl->supported, 25000baseSR_Full);
+ phylink_set(pl->supported, 40000baseKR4_Full);
+ phylink_set(pl->supported, 40000baseCR4_Full);
+ phylink_set(pl->supported, 40000baseSR4_Full);
+ phylink_set(pl->supported, 40000baseLR4_Full);
+ phylink_set(pl->supported, 50000baseCR2_Full);
+ phylink_set(pl->supported, 50000baseKR2_Full);
+ phylink_set(pl->supported, 50000baseSR2_Full);
+ phylink_set(pl->supported, 50000baseKR_Full);
+ phylink_set(pl->supported, 50000baseSR_Full);
+ phylink_set(pl->supported, 50000baseCR_Full);
+ phylink_set(pl->supported, 50000baseLR_ER_FR_Full);
+ phylink_set(pl->supported, 50000baseDR_Full);
+ phylink_set(pl->supported, 100000baseKR4_Full);
+ phylink_set(pl->supported, 100000baseSR4_Full);
+ phylink_set(pl->supported, 100000baseCR4_Full);
+ phylink_set(pl->supported, 100000baseLR4_ER4_Full);
+ phylink_set(pl->supported, 100000baseKR2_Full);
+ phylink_set(pl->supported, 100000baseSR2_Full);
+ phylink_set(pl->supported, 100000baseCR2_Full);
+ phylink_set(pl->supported, 100000baseLR2_ER2_FR2_Full);
+ phylink_set(pl->supported, 100000baseDR2_Full);
+ break;
+
+ default:
+ phylink_err(pl,
+ "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)) {
+ phylink_err(pl,
+ "failed to validate link configuration for in-band status\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void phylink_apply_manual_flow(struct phylink *pl,
+ struct phylink_link_state *state)
+{
+ /* If autoneg is disabled, pause AN is also disabled */
+ if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ state->advertising))
+ state->pause &= ~MLO_PAUSE_AN;
+
+ /* Manual configuration of pause modes */
+ if (!(pl->link_config.pause & MLO_PAUSE_AN))
+ state->pause = pl->link_config.pause;
+}
+
+static void phylink_resolve_an_pause(struct phylink_link_state *state)
+{
+ bool tx_pause, rx_pause;
+
+ if (state->duplex == DUPLEX_FULL) {
+ linkmode_resolve_pause(state->advertising,
+ state->lp_advertising,
+ &tx_pause, &rx_pause);
+ if (tx_pause)
+ state->pause |= MLO_PAUSE_TX;
+ if (rx_pause)
+ state->pause |= MLO_PAUSE_RX;
+ }
+}
+
+static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ if (pcs && pcs->ops->pcs_pre_config)
+ pcs->ops->pcs_pre_config(pcs, interface);
+}
+
+static int phylink_pcs_post_config(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ int err = 0;
+
+ if (pcs && pcs->ops->pcs_post_config)
+ err = pcs->ops->pcs_post_config(pcs, interface);
+
+ return err;
+}
+
+static void phylink_pcs_disable(struct phylink_pcs *pcs)
+{
+ if (pcs && pcs->ops->pcs_disable)
+ pcs->ops->pcs_disable(pcs);
+}
+
+static int phylink_pcs_enable(struct phylink_pcs *pcs)
+{
+ int err = 0;
+
+ if (pcs && pcs->ops->pcs_enable)
+ err = pcs->ops->pcs_enable(pcs);
+
+ return err;
+}
+
+static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ const struct phylink_link_state *state,
+ bool permit_pause_to_mac)
+{
+ if (!pcs)
+ return 0;
+
+ return pcs->ops->pcs_config(pcs, neg_mode, state->interface,
+ state->advertising, permit_pause_to_mac);
+}
+
+static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+{
+ if (pcs && pcs->ops->pcs_link_up)
+ pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+}
+
+static void phylink_pcs_poll_stop(struct phylink *pl)
+{
+ if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+ del_timer(&pl->link_poll);
+}
+
+static void phylink_pcs_poll_start(struct phylink *pl)
+{
+ if (pl->pcs && pl->pcs->poll && pl->cfg_link_an_mode == MLO_AN_INBAND)
+ mod_timer(&pl->link_poll, jiffies + HZ);
+}
+
+static void phylink_mac_config(struct phylink *pl,
+ const struct phylink_link_state *state)
+{
+ struct phylink_link_state st = *state;
+
+ /* Stop drivers incorrectly using these */
+ linkmode_zero(st.lp_advertising);
+ st.speed = SPEED_UNKNOWN;
+ st.duplex = DUPLEX_UNKNOWN;
+ st.an_complete = false;
+ st.link = false;
+
+ phylink_dbg(pl,
+ "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
+ __func__, phylink_an_mode_str(pl->cur_link_an_mode),
+ phy_modes(st.interface),
+ phy_rate_matching_to_str(st.rate_matching),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
+ st.pause);
+
+ pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st);
+}
+
+static void phylink_pcs_an_restart(struct phylink *pl)
+{
+ if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ pl->link_config.advertising) &&
+ phy_interface_mode_is_8023z(pl->link_config.interface) &&
+ phylink_autoneg_inband(pl->cur_link_an_mode))
+ pl->pcs->ops->pcs_an_restart(pl->pcs);
+}
+
+static void phylink_major_config(struct phylink *pl, bool restart,
+ const struct phylink_link_state *state)
+{
+ struct phylink_pcs *pcs = NULL;
+ bool pcs_changed = false;
+ unsigned int rate_kbd;
+ unsigned int neg_mode;
+ int err;
+
+ phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+
+ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+ state->interface,
+ state->advertising);
+
+ if (pl->using_mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+ if (IS_ERR(pcs)) {
+ phylink_err(pl,
+ "mac_select_pcs unexpectedly failed: %pe\n",
+ pcs);
+ return;
+ }
+
+ pcs_changed = pcs && pl->pcs != pcs;
+ }
+
+ phylink_pcs_poll_stop(pl);
+
+ if (pl->mac_ops->mac_prepare) {
+ err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode,
+ state->interface);
+ if (err < 0) {
+ phylink_err(pl, "mac_prepare failed: %pe\n",
+ ERR_PTR(err));
+ return;
+ }
+ }
+
+ /* If we have a new PCS, switch to the new PCS after preparing the MAC
+ * for the change.
+ */
+ if (pcs_changed) {
+ phylink_pcs_disable(pl->pcs);
+
+ if (pl->pcs)
+ pl->pcs->phylink = NULL;
+
+ pcs->phylink = pl;
+
+ pl->pcs = pcs;
+ }
+
+ if (pl->pcs)
+ phylink_pcs_pre_config(pl->pcs, state->interface);
+
+ phylink_mac_config(pl, state);
+
+ if (pl->pcs)
+ phylink_pcs_post_config(pl->pcs, state->interface);
+
+ if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
+ phylink_pcs_enable(pl->pcs);
+
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs && pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+ err = phylink_pcs_config(pl->pcs, neg_mode, state,
+ !!(pl->link_config.pause & MLO_PAUSE_AN));
+ if (err < 0)
+ phylink_err(pl, "pcs_config failed: %pe\n",
+ ERR_PTR(err));
+ else if (err > 0)
+ restart = true;
+
+ if (restart)
+ phylink_pcs_an_restart(pl);
+
+ if (pl->mac_ops->mac_finish) {
+ err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode,
+ state->interface);
+ if (err < 0)
+ phylink_err(pl, "mac_finish failed: %pe\n",
+ ERR_PTR(err));
+ }
+
+ if (pl->sfp_bus) {
+ rate_kbd = phylink_interface_signal_rate(state->interface);
+ if (rate_kbd)
+ sfp_upstream_set_signal_rate(pl->sfp_bus, rate_kbd);
+ }
+
+ phylink_pcs_poll_start(pl);
+}
+
+/*
+ * Reconfigure for a change of inband advertisement.
+ * If we have a separate PCS, we only need to call its pcs_config() method,
+ * and then restart AN if it indicates something changed. Otherwise, we do
+ * the full MAC reconfiguration.
+ */
+static int phylink_change_inband_advert(struct phylink *pl)
+{
+ unsigned int neg_mode;
+ int ret;
+
+ if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
+ return 0;
+
+ phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
+ phylink_an_mode_str(pl->cur_link_an_mode),
+ phy_modes(pl->link_config.interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
+ pl->link_config.pause);
+
+ /* Recompute the PCS neg mode */
+ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+ pl->link_config.interface,
+ pl->link_config.advertising);
+
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+ /* Modern PCS-based method; update the advert at the PCS, and
+ * restart negotiation if the pcs_config() helper indicates that
+ * the programmed advertisement has changed.
+ */
+ ret = phylink_pcs_config(pl->pcs, neg_mode, &pl->link_config,
+ !!(pl->link_config.pause & MLO_PAUSE_AN));
+ if (ret < 0)
+ return ret;
+
+ if (ret > 0)
+ phylink_pcs_an_restart(pl);
+
+ return 0;
+}
+
+static void phylink_mac_pcs_get_state(struct phylink *pl,
+ struct phylink_link_state *state)
+{
+ linkmode_copy(state->advertising, pl->link_config.advertising);
+ linkmode_zero(state->lp_advertising);
+ state->interface = pl->link_config.interface;
+ state->rate_matching = pl->link_config.rate_matching;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ state->advertising)) {
+ state->speed = SPEED_UNKNOWN;
+ state->duplex = DUPLEX_UNKNOWN;
+ state->pause = MLO_PAUSE_NONE;
+ } else {
+ state->speed = pl->link_config.speed;
+ state->duplex = pl->link_config.duplex;
+ state->pause = pl->link_config.pause;
+ }
+ state->an_complete = 0;
+ state->link = 1;
+
+ if (pl->pcs)
+ pl->pcs->ops->pcs_get_state(pl->pcs, state);
+ else
+ state->link = 0;
+}
+
+/* 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->config->get_fixed_state)
+ pl->config->get_fixed_state(pl->config, state);
+ else if (pl->link_gpio)
+ state->link = !!gpiod_get_value_cansleep(pl->link_gpio);
+
+ state->pause = MLO_PAUSE_NONE;
+ phylink_resolve_an_pause(state);
+}
+
+static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
+{
+ struct phylink_link_state link_state;
+
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_PHY:
+ link_state = pl->phy_state;
+ break;
+
+ case MLO_AN_FIXED:
+ phylink_get_fixed_state(pl, &link_state);
+ break;
+
+ case MLO_AN_INBAND:
+ link_state = pl->link_config;
+ if (link_state.interface == PHY_INTERFACE_MODE_SGMII)
+ link_state.pause = MLO_PAUSE_NONE;
+ break;
+
+ default: /* can't happen */
+ return;
+ }
+
+ link_state.link = false;
+
+ phylink_apply_manual_flow(pl, &link_state);
+ phylink_major_config(pl, force_restart, &link_state);
+}
+
+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_link_up(struct phylink *pl,
+ struct phylink_link_state link_state)
+{
+ struct net_device *ndev = pl->netdev;
+ unsigned int neg_mode;
+ int speed, duplex;
+ bool rx_pause;
+
+ speed = link_state.speed;
+ duplex = link_state.duplex;
+ rx_pause = !!(link_state.pause & MLO_PAUSE_RX);
+
+ switch (link_state.rate_matching) {
+ case RATE_MATCH_PAUSE:
+ /* The PHY is doing rate matchion from the media rate (in
+ * the link_state) to the interface speed, and will send
+ * pause frames to the MAC to limit its transmission speed.
+ */
+ speed = phylink_interface_max_speed(link_state.interface);
+ duplex = DUPLEX_FULL;
+ rx_pause = true;
+ break;
+
+ case RATE_MATCH_CRS:
+ /* The PHY is doing rate matchion from the media rate (in
+ * the link_state) to the interface speed, and will cause
+ * collisions to the MAC to limit its transmission speed.
+ */
+ speed = phylink_interface_max_speed(link_state.interface);
+ duplex = DUPLEX_HALF;
+ break;
+ }
+
+ pl->cur_interface = link_state.interface;
+
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs && pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+ phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
+ duplex);
+
+ pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
+ pl->cur_interface, speed, duplex,
+ !!(link_state.pause & MLO_PAUSE_TX), rx_pause);
+
+ if (ndev)
+ netif_carrier_on(ndev);
+
+ phylink_info(pl,
+ "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));
+}
+
+static void phylink_link_down(struct phylink *pl)
+{
+ struct net_device *ndev = pl->netdev;
+
+ if (ndev)
+ netif_carrier_off(ndev);
+ pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
+ pl->cur_interface);
+ phylink_info(pl, "Link is Down\n");
+}
+
+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;
+ bool mac_config = false;
+ bool retrigger = false;
+ bool cur_link_state;
+
+ mutex_lock(&pl->state_mutex);
+ if (pl->netdev)
+ cur_link_state = netif_carrier_ok(ndev);
+ else
+ cur_link_state = pl->old_link_state;
+
+ if (pl->phylink_disable_state) {
+ pl->mac_link_dropped = false;
+ link_state.link = false;
+ } else if (pl->mac_link_dropped) {
+ link_state.link = false;
+ retrigger = true;
+ } else {
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_PHY:
+ link_state = pl->phy_state;
+ phylink_apply_manual_flow(pl, &link_state);
+ mac_config = link_state.link;
+ break;
+
+ case MLO_AN_FIXED:
+ phylink_get_fixed_state(pl, &link_state);
+ mac_config = link_state.link;
+ break;
+
+ case MLO_AN_INBAND:
+ phylink_mac_pcs_get_state(pl, &link_state);
+
+ /* The PCS may have a latching link-fail indicator.
+ * If the link was up, bring the link down and
+ * re-trigger the resolve. Otherwise, re-read the
+ * PCS state to get the current status of the link.
+ */
+ if (!link_state.link) {
+ if (cur_link_state)
+ retrigger = true;
+ else
+ phylink_mac_pcs_get_state(pl,
+ &link_state);
+ }
+
+ /* If we have a phy, the "up" state is the union of
+ * both the PHY and the MAC
+ */
+ if (pl->phydev)
+ link_state.link &= pl->phy_state.link;
+
+ /* Only update if the PHY link is up */
+ if (pl->phydev && pl->phy_state.link) {
+ /* If the interface has changed, force a
+ * link down event if the link isn't already
+ * down, and re-resolve.
+ */
+ if (link_state.interface !=
+ pl->phy_state.interface) {
+ retrigger = true;
+ link_state.link = false;
+ }
+ link_state.interface = pl->phy_state.interface;
+
+ /* If we are doing rate matching, then the
+ * link speed/duplex comes from the PHY
+ */
+ if (pl->phy_state.rate_matching) {
+ link_state.rate_matching =
+ pl->phy_state.rate_matching;
+ link_state.speed = pl->phy_state.speed;
+ link_state.duplex =
+ pl->phy_state.duplex;
+ }
+
+ /* If we have a PHY, we need to update with
+ * the PHY flow control bits.
+ */
+ link_state.pause = pl->phy_state.pause;
+ mac_config = true;
+ }
+ phylink_apply_manual_flow(pl, &link_state);
+ break;
+ }
+ }
+
+ if (mac_config) {
+ if (link_state.interface != pl->link_config.interface) {
+ /* The interface has changed, force the link down and
+ * then reconfigure.
+ */
+ if (cur_link_state) {
+ phylink_link_down(pl);
+ cur_link_state = false;
+ }
+ phylink_major_config(pl, false, &link_state);
+ pl->link_config.interface = link_state.interface;
+ }
+ }
+
+ if (link_state.link != cur_link_state) {
+ pl->old_link_state = link_state.link;
+ if (!link_state.link)
+ phylink_link_down(pl);
+ else
+ phylink_link_up(pl, link_state);
+ }
+ if (!link_state.link && retrigger) {
+ 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_enable_and_run_resolve(struct phylink *pl, int bit)
+{
+ clear_bit(bit, &pl->phylink_disable_state);
+ phylink_run_resolve(pl);
+}
+
+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,
+ const struct fwnode_handle *fwnode)
+{
+ struct sfp_bus *bus;
+ int ret;
+
+ if (!fwnode)
+ return 0;
+
+ bus = sfp_bus_find_fwnode(fwnode);
+ if (IS_ERR(bus)) {
+ phylink_err(pl, "unable to attach SFP bus: %pe\n", bus);
+ return PTR_ERR(bus);
+ }
+
+ pl->sfp_bus = bus;
+
+ ret = sfp_bus_add_upstream(bus, pl, &sfp_phylink_ops);
+ sfp_bus_put(bus);
+
+ return ret;
+}
+
+/**
+ * phylink_create() - create a phylink instance
+ * @config: a pointer to the target &struct phylink_config
+ * @fwnode: a pointer to a &struct fwnode_handle describing the network
+ * interface
+ * @iface: the desired link mode defined by &typedef phy_interface_t
+ * @mac_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.
+ *
+ * Note: the rtnl lock must not be held when calling this function.
+ *
+ * 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 phylink_config *config,
+ const struct fwnode_handle *fwnode,
+ phy_interface_t iface,
+ const struct phylink_mac_ops *mac_ops)
+{
+ bool using_mac_select_pcs = false;
+ struct phylink *pl;
+ int ret;
+
+ /* Validate the supplied configuration */
+ if (phy_interface_empty(config->supported_interfaces)) {
+ dev_err(config->dev,
+ "phylink: error: empty supported_interfaces\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (mac_ops->mac_select_pcs &&
+ mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
+ ERR_PTR(-EOPNOTSUPP))
+ using_mac_select_pcs = true;
+
+ pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+ if (!pl)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&pl->state_mutex);
+ INIT_WORK(&pl->resolve, phylink_resolve);
+
+ pl->config = config;
+ if (config->type == PHYLINK_NETDEV) {
+ pl->netdev = to_net_dev(config->dev);
+ netif_carrier_off(pl->netdev);
+ } else if (config->type == PHYLINK_DEV) {
+ pl->dev = config->dev;
+ } else {
+ kfree(pl);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pl->using_mac_select_pcs = using_mac_select_pcs;
+ 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->pcs_state = PCS_STATE_DOWN;
+ pl->mac_ops = mac_ops;
+ __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+ timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
+
+ bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
+ linkmode_copy(pl->link_config.advertising, pl->supported);
+ phylink_validate(pl, pl->supported, &pl->link_config);
+
+ ret = phylink_parse_mode(pl, fwnode);
+ if (ret < 0) {
+ kfree(pl);
+ return ERR_PTR(ret);
+ }
+
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+ ret = phylink_parse_fixedlink(pl, fwnode);
+ if (ret < 0) {
+ kfree(pl);
+ return ERR_PTR(ret);
+ }
+ }
+
+ pl->cur_link_an_mode = pl->cfg_link_an_mode;
+
+ ret = phylink_register_sfp(pl, fwnode);
+ if (ret < 0) {
+ kfree(pl);
+ return ERR_PTR(ret);
+ }
+
+ return pl;
+}
+EXPORT_SYMBOL_GPL(phylink_create);
+
+/**
+ * phylink_destroy() - cleanup and destroy the phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Destroy a phylink instance. Any PHY that has been attached must have been
+ * cleaned up via phylink_disconnect_phy() prior to calling this function.
+ *
+ * Note: the rtnl lock must not be held when calling this function.
+ */
+void phylink_destroy(struct phylink *pl)
+{
+ sfp_bus_del_upstream(pl->sfp_bus);
+ if (pl->link_gpio)
+ gpiod_put(pl->link_gpio);
+
+ cancel_work_sync(&pl->resolve);
+ kfree(pl);
+}
+EXPORT_SYMBOL_GPL(phylink_destroy);
+
+/**
+ * phylink_expects_phy() - Determine if phylink expects a phy to be attached
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * When using fixed-link mode, or in-band mode with 1000base-X or 2500base-X,
+ * no PHY is needed.
+ *
+ * Returns true if phylink will be expecting a PHY.
+ */
+bool phylink_expects_phy(struct phylink *pl)
+{
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
+ (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+ phy_interface_mode_is_8023z(pl->link_config.interface)))
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(phylink_expects_phy);
+
+static void phylink_phy_change(struct phy_device *phydev, bool up)
+{
+ struct phylink *pl = phydev->phylink;
+ bool tx_pause, rx_pause;
+
+ phy_get_pause(phydev, &tx_pause, &rx_pause);
+
+ mutex_lock(&pl->state_mutex);
+ pl->phy_state.speed = phydev->speed;
+ pl->phy_state.duplex = phydev->duplex;
+ pl->phy_state.rate_matching = phydev->rate_matching;
+ pl->phy_state.pause = MLO_PAUSE_NONE;
+ if (tx_pause)
+ pl->phy_state.pause |= MLO_PAUSE_TX;
+ if (rx_pause)
+ pl->phy_state.pause |= MLO_PAUSE_RX;
+ pl->phy_state.interface = phydev->interface;
+ pl->phy_state.link = up;
+ mutex_unlock(&pl->state_mutex);
+
+ phylink_run_resolve(pl);
+
+ phylink_dbg(pl, "phy link %s %s/%s/%s/%s/%s\n", up ? "up" : "down",
+ phy_modes(phydev->interface),
+ phy_speed_to_str(phydev->speed),
+ phy_duplex_to_str(phydev->duplex),
+ phy_rate_matching_to_str(phydev->rate_matching),
+ phylink_pause_to_str(pl->phy_state.pause));
+}
+
+static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
+ phy_interface_t interface)
+{
+ struct phylink_link_state config;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+ char *irq_str;
+ int ret;
+
+ /*
+ * This is the new way of dealing with flow control for PHYs,
+ * as described by Timur Tabi in commit 529ed1275263 ("net: phy:
+ * phy drivers should not set SUPPORTED_[Asym_]Pause") except
+ * using our validate call to the MAC, we rely upon the MAC
+ * clearing the bits from both supported and advertising fields.
+ */
+ phy_support_asym_pause(phy);
+
+ memset(&config, 0, sizeof(config));
+ linkmode_copy(supported, phy->supported);
+ linkmode_copy(config.advertising, phy->advertising);
+
+ /* Check whether we would use rate matching for the proposed interface
+ * mode.
+ */
+ config.rate_matching = phy_get_rate_matching(phy, interface);
+
+ /* Clause 45 PHYs may switch their Serdes lane between, e.g. 10GBASE-R,
+ * 5GBASE-R, 2500BASE-X and SGMII if they are not using rate matching.
+ * For some interface modes (e.g. RXAUI, XAUI and USXGMII) switching
+ * their Serdes is either unnecessary or not reasonable.
+ *
+ * For these which switch interface modes, we really need to know which
+ * interface modes the PHY supports to properly work out which ethtool
+ * linkmodes can be supported. For now, as a work-around, we validate
+ * against all interface modes, which may lead to more ethtool link
+ * modes being advertised than are actually supported.
+ */
+ if (phy->is_c45 && config.rate_matching == RATE_MATCH_NONE &&
+ interface != PHY_INTERFACE_MODE_RXAUI &&
+ interface != PHY_INTERFACE_MODE_XAUI &&
+ interface != PHY_INTERFACE_MODE_USXGMII)
+ config.interface = PHY_INTERFACE_MODE_NA;
+ else
+ config.interface = interface;
+
+ ret = phylink_validate(pl, supported, &config);
+ if (ret) {
+ phylink_warn(pl, "validation of %s with support %*pb and advertisement %*pb failed: %pe\n",
+ phy_modes(config.interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, phy->supported,
+ __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising,
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ phy->phylink = pl;
+ phy->phy_link_change = phylink_phy_change;
+
+ irq_str = phy_attached_info_irq(phy);
+ phylink_info(pl,
+ "PHY [%s] driver [%s] (irq=%s)\n",
+ dev_name(&phy->mdio.dev), phy->drv->name, irq_str);
+ kfree(irq_str);
+
+ mutex_lock(&phy->lock);
+ mutex_lock(&pl->state_mutex);
+ pl->phydev = phy;
+ pl->phy_state.interface = interface;
+ pl->phy_state.pause = MLO_PAUSE_NONE;
+ pl->phy_state.speed = SPEED_UNKNOWN;
+ pl->phy_state.duplex = DUPLEX_UNKNOWN;
+ pl->phy_state.rate_matching = RATE_MATCH_NONE;
+ linkmode_copy(pl->supported, supported);
+ linkmode_copy(pl->link_config.advertising, config.advertising);
+
+ /* Restrict the phy advertisement according to the MAC support. */
+ linkmode_copy(phy->advertising, config.advertising);
+ mutex_unlock(&pl->state_mutex);
+ mutex_unlock(&phy->lock);
+
+ phylink_dbg(pl,
+ "phy: %s setting supported %*pb advertising %*pb\n",
+ phy_modes(interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, pl->supported,
+ __ETHTOOL_LINK_MODE_MASK_NBITS, phy->advertising);
+
+ if (phy_interrupt_is_valid(phy))
+ phy_request_interrupt(phy);
+
+ if (pl->config->mac_managed_pm)
+ phy->mac_managed_pm = true;
+
+ return 0;
+}
+
+static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
+ phy_interface_t interface)
+{
+ if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED ||
+ (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+ phy_interface_mode_is_8023z(interface) && !pl->sfp_bus)))
+ return -EINVAL;
+
+ if (pl->phydev)
+ return -EBUSY;
+
+ return phy_attach_direct(pl->netdev, phy, 0, interface);
+}
+
+/**
+ * phylink_connect_phy() - connect a PHY to the phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @phy: a pointer to a &struct phy_device.
+ *
+ * Connect @phy to the phylink instance specified by @pl by calling
+ * phy_attach_direct(). Configure the @phy according to the MAC driver's
+ * capabilities, start the PHYLIB state machine and enable any interrupts
+ * that the PHY supports.
+ *
+ * This updates the phylink's ethtool supported and advertising link mode
+ * masks.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
+{
+ int ret;
+
+ /* Use PHY device/driver interface */
+ if (pl->link_interface == PHY_INTERFACE_MODE_NA) {
+ pl->link_interface = phy->interface;
+ pl->link_config.interface = pl->link_interface;
+ }
+
+ ret = phylink_attach_phy(pl, phy, pl->link_interface);
+ if (ret < 0)
+ return ret;
+
+ ret = phylink_bringup_phy(pl, phy, pl->link_config.interface);
+ if (ret)
+ phy_detach(phy);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_connect_phy);
+
+/**
+ * phylink_of_phy_connect() - connect the PHY specified in the DT mode.
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @dn: a pointer to a &struct device_node.
+ * @flags: PHY-specific flags to communicate to the PHY device driver
+ *
+ * Connect the phy specified in the device node @dn to the phylink instance
+ * specified by @pl. Actions specified in phylink_connect_phy() will be
+ * performed.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn,
+ u32 flags)
+{
+ return phylink_fwnode_phy_connect(pl, of_fwnode_handle(dn), flags);
+}
+EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
+
+/**
+ * phylink_fwnode_phy_connect() - connect the PHY specified in the fwnode.
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @fwnode: a pointer to a &struct fwnode_handle.
+ * @flags: PHY-specific flags to communicate to the PHY device driver
+ *
+ * Connect the phy specified @fwnode to the phylink instance specified
+ * by @pl.
+ *
+ * Returns 0 on success or a negative errno.
+ */
+int phylink_fwnode_phy_connect(struct phylink *pl,
+ const struct fwnode_handle *fwnode,
+ u32 flags)
+{
+ struct fwnode_handle *phy_fwnode;
+ struct phy_device *phy_dev;
+ int ret;
+
+ /* Fixed links and 802.3z are handled without needing a PHY */
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
+ (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+ phy_interface_mode_is_8023z(pl->link_interface)))
+ return 0;
+
+ phy_fwnode = fwnode_get_phy_node(fwnode);
+ if (IS_ERR(phy_fwnode)) {
+ if (pl->cfg_link_an_mode == MLO_AN_PHY)
+ return -ENODEV;
+ return 0;
+ }
+
+ phy_dev = fwnode_phy_find_device(phy_fwnode);
+ /* We're done with the phy_node handle */
+ fwnode_handle_put(phy_fwnode);
+ if (!phy_dev)
+ return -ENODEV;
+
+ /* Use PHY device/driver interface */
+ if (pl->link_interface == PHY_INTERFACE_MODE_NA) {
+ pl->link_interface = phy_dev->interface;
+ pl->link_config.interface = pl->link_interface;
+ }
+
+ ret = phy_attach_direct(pl->netdev, phy_dev, flags,
+ pl->link_interface);
+ phy_device_free(phy_dev);
+ if (ret)
+ return ret;
+
+ ret = phylink_bringup_phy(pl, phy_dev, pl->link_config.interface);
+ if (ret)
+ phy_detach(phy_dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_fwnode_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);
+
+static void phylink_link_changed(struct phylink *pl, bool up, const char *what)
+{
+ if (!up)
+ pl->mac_link_dropped = true;
+ phylink_run_resolve(pl);
+ phylink_dbg(pl, "%s link %s\n", what, up ? "up" : "down");
+}
+
+/**
+ * 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)
+{
+ phylink_link_changed(pl, up, "mac");
+}
+EXPORT_SYMBOL_GPL(phylink_mac_change);
+
+/**
+ * phylink_pcs_change() - notify phylink of a change to PCS link state
+ * @pcs: pointer to &struct phylink_pcs
+ * @up: indicates whether the link is currently up.
+ *
+ * The PCS driver should call this when the state of its link changes
+ * (e.g. link failure, new negotiation results, etc.) Note: it should
+ * not determine "up" by reading the BMSR. If in doubt about the link
+ * state at interrupt time, then pass true if pcs_get_state() returns
+ * the latched link-down state, otherwise pass false.
+ */
+void phylink_pcs_change(struct phylink_pcs *pcs, bool up)
+{
+ struct phylink *pl = pcs->phylink;
+
+ if (pl)
+ phylink_link_changed(pl, up, "pcs");
+}
+EXPORT_SYMBOL_GPL(phylink_pcs_change);
+
+static irqreturn_t phylink_link_handler(int irq, void *data)
+{
+ struct phylink *pl = data;
+
+ phylink_run_resolve(pl);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * phylink_start() - start a phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Start the phylink instance specified by @pl, configuring the MAC for the
+ * desired link mode(s) and negotiation style. This should be called from the
+ * network device driver's &struct net_device_ops ndo_open() method.
+ */
+void phylink_start(struct phylink *pl)
+{
+ bool poll = false;
+
+ ASSERT_RTNL();
+
+ phylink_info(pl, "configuring for %s/%s link mode\n",
+ phylink_an_mode_str(pl->cur_link_an_mode),
+ phy_modes(pl->link_config.interface));
+
+ /* Always set the carrier off */
+ if (pl->netdev)
+ netif_carrier_off(pl->netdev);
+
+ pl->pcs_state = PCS_STATE_STARTING;
+
+ /* 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.
+ *
+ * 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_initial_config(pl, true);
+
+ pl->pcs_state = PCS_STATE_STARTED;
+
+ phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
+
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+ int irq = gpiod_to_irq(pl->link_gpio);
+
+ if (irq > 0) {
+ if (!request_irq(irq, phylink_link_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "netdev link", pl))
+ pl->link_irq = irq;
+ else
+ irq = 0;
+ }
+ if (irq <= 0)
+ poll = true;
+ }
+
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED)
+ poll |= pl->config->poll_fixed_state;
+
+ if (poll)
+ mod_timer(&pl->link_poll, jiffies + HZ);
+ if (pl->phydev)
+ phy_start(pl->phydev);
+ if (pl->sfp_bus)
+ sfp_upstream_start(pl->sfp_bus);
+}
+EXPORT_SYMBOL_GPL(phylink_start);
+
+/**
+ * phylink_stop() - stop a phylink instance
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Stop the phylink instance specified by @pl. This should be called from the
+ * network device driver's &struct net_device_ops ndo_stop() method. The
+ * network device's carrier state should not be changed prior to calling this
+ * function.
+ *
+ * This will synchronously bring down the link if the link is not already
+ * down (in other words, it will trigger a mac_link_down() method call.)
+ */
+void phylink_stop(struct phylink *pl)
+{
+ ASSERT_RTNL();
+
+ if (pl->sfp_bus)
+ sfp_upstream_stop(pl->sfp_bus);
+ if (pl->phydev)
+ phy_stop(pl->phydev);
+ del_timer_sync(&pl->link_poll);
+ if (pl->link_irq) {
+ free_irq(pl->link_irq, pl);
+ pl->link_irq = 0;
+ }
+
+ phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_STOPPED);
+
+ pl->pcs_state = PCS_STATE_DOWN;
+
+ phylink_pcs_disable(pl->pcs);
+}
+EXPORT_SYMBOL_GPL(phylink_stop);
+
+/**
+ * phylink_suspend() - handle a network device suspend event
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @mac_wol: true if the MAC needs to receive packets for Wake-on-Lan
+ *
+ * Handle a network device suspend event. There are several cases:
+ *
+ * - If Wake-on-Lan is not active, we can bring down the link between
+ * the MAC and PHY by calling phylink_stop().
+ * - If Wake-on-Lan is active, and being handled only by the PHY, we
+ * can also bring down the link between the MAC and PHY.
+ * - If Wake-on-Lan is active, but being handled by the MAC, the MAC
+ * still needs to receive packets, so we can not bring the link down.
+ */
+void phylink_suspend(struct phylink *pl, bool mac_wol)
+{
+ ASSERT_RTNL();
+
+ if (mac_wol && (!pl->netdev || pl->netdev->wol_enabled)) {
+ /* Wake-on-Lan enabled, MAC handling */
+ mutex_lock(&pl->state_mutex);
+
+ /* Stop the resolver bringing the link up */
+ __set_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state);
+
+ /* Disable the carrier, to prevent transmit timeouts,
+ * but one would hope all packets have been sent. This
+ * also means phylink_resolve() will do nothing.
+ */
+ if (pl->netdev)
+ netif_carrier_off(pl->netdev);
+ else
+ pl->old_link_state = false;
+
+ /* We do not call mac_link_down() here as we want the
+ * link to remain up to receive the WoL packets.
+ */
+ mutex_unlock(&pl->state_mutex);
+ } else {
+ phylink_stop(pl);
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_suspend);
+
+/**
+ * phylink_resume() - handle a network device resume event
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Undo the effects of phylink_suspend(), returning the link to an
+ * operational state.
+ */
+void phylink_resume(struct phylink *pl)
+{
+ ASSERT_RTNL();
+
+ if (test_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state)) {
+ /* Wake-on-Lan enabled, MAC handling */
+
+ /* Call mac_link_down() so we keep the overall state balanced.
+ * Do this under the state_mutex lock for consistency. This
+ * will cause a "Link Down" message to be printed during
+ * resume, which is harmless - the true link state will be
+ * printed when we run a resolve.
+ */
+ mutex_lock(&pl->state_mutex);
+ phylink_link_down(pl);
+ mutex_unlock(&pl->state_mutex);
+
+ /* Re-apply the link parameters so that all the settings get
+ * restored to the MAC.
+ */
+ phylink_mac_initial_config(pl, true);
+
+ /* Re-enable and re-resolve the link parameters */
+ phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_MAC_WOL);
+ } else {
+ phylink_start(pl);
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_resume);
+
+/**
+ * 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);
+ if (kset->base.rate_matching == RATE_MATCH_NONE) {
+ kset->base.speed = state->speed;
+ kset->base.duplex = state->duplex;
+ }
+ kset->base.autoneg = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ state->advertising) ?
+ AUTONEG_ENABLE : AUTONEG_DISABLE;
+}
+
+/**
+ * phylink_ethtool_ksettings_get() - get the current link settings
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @kset: a pointer to a &struct ethtool_link_ksettings to hold link settings
+ *
+ * Read the current link settings for the phylink instance specified by @pl.
+ * This will be the link settings read from the MAC, PHY or fixed link
+ * settings depending on the current negotiation mode.
+ */
+int phylink_ethtool_ksettings_get(struct phylink *pl,
+ struct ethtool_link_ksettings *kset)
+{
+ struct phylink_link_state link_state;
+
+ ASSERT_RTNL();
+
+ if (pl->phydev)
+ phy_ethtool_ksettings_get(pl->phydev, kset);
+ else
+ kset->base.port = pl->link_port;
+
+ linkmode_copy(kset->link_modes.supported, pl->supported);
+
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_FIXED:
+ /* We are using fixed settings. Report these as the
+ * current link settings - and note that these also
+ * represent the supported speeds/duplex/pause modes.
+ */
+ phylink_get_fixed_state(pl, &link_state);
+ phylink_get_ksettings(&link_state, kset);
+ break;
+
+ case MLO_AN_INBAND:
+ /* If there is a phy attached, then use the reported
+ * settings from the phy with no modification.
+ */
+ if (pl->phydev)
+ break;
+
+ phylink_mac_pcs_get_state(pl, &link_state);
+
+ /* The MAC is reporting the link results from its own PCS
+ * layer via in-band status. Report these as the current
+ * link settings.
+ */
+ phylink_get_ksettings(&link_state, kset);
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
+
+/**
+ * phylink_ethtool_ksettings_set() - set the link settings
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @kset: a pointer to a &struct ethtool_link_ksettings for the desired modes
+ */
+int phylink_ethtool_ksettings_set(struct phylink *pl,
+ const struct ethtool_link_ksettings *kset)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+ struct phylink_link_state config;
+ const struct phy_setting *s;
+
+ ASSERT_RTNL();
+
+ if (pl->phydev) {
+ struct ethtool_link_ksettings phy_kset = *kset;
+
+ linkmode_and(phy_kset.link_modes.advertising,
+ phy_kset.link_modes.advertising,
+ pl->supported);
+
+ /* We can rely on phylib for this update; we also do not need
+ * to update the pl->link_config settings:
+ * - the configuration returned via ksettings_get() will come
+ * from phylib whenever a PHY is present.
+ * - link_config.interface will be updated by the PHY calling
+ * back via phylink_phy_change() and a subsequent resolve.
+ * - initial link configuration for PHY mode comes from the
+ * last phy state updated via phylink_phy_change().
+ * - other configuration changes (e.g. pause modes) are
+ * performed directly via phylib.
+ * - if in in-band mode with a PHY, the link configuration
+ * is passed on the link from the PHY, and all of
+ * link_config.{speed,duplex,an_enabled,pause} are not used.
+ * - the only possible use would be link_config.advertising
+ * pause modes when in 1000base-X mode with a PHY, but in
+ * the presence of a PHY, this should not be changed as that
+ * should be determined from the media side advertisement.
+ */
+ return phy_ethtool_ksettings_set(pl->phydev, &phy_kset);
+ }
+
+ 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? */
+ switch (kset->base.autoneg) {
+ case AUTONEG_DISABLE:
+ /* Autonegotiation disabled, select a suitable speed and
+ * duplex.
+ */
+ s = phy_lookup_setting(kset->base.speed, kset->base.duplex,
+ pl->supported, false);
+ if (!s)
+ return -EINVAL;
+
+ /* If we have a fixed link, refuse to change link parameters.
+ * If the link parameters match, accept them but do nothing.
+ */
+ if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ if (s->speed != pl->link_config.speed ||
+ s->duplex != pl->link_config.duplex)
+ return -EINVAL;
+ return 0;
+ }
+
+ config.speed = s->speed;
+ config.duplex = s->duplex;
+ break;
+
+ case AUTONEG_ENABLE:
+ /* If we have a fixed link, allow autonegotiation (since that
+ * is our default case) but do not allow the advertisement to
+ * be changed. If the advertisement matches, simply return.
+ */
+ if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ if (!linkmode_equal(config.advertising,
+ pl->link_config.advertising))
+ return -EINVAL;
+ return 0;
+ }
+
+ config.speed = SPEED_UNKNOWN;
+ config.duplex = DUPLEX_UNKNOWN;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* We have ruled out the case with a PHY attached, and the
+ * fixed-link cases. All that is left are in-band links.
+ */
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising,
+ kset->base.autoneg == AUTONEG_ENABLE);
+
+ /* If this link is with an SFP, ensure that changes to advertised modes
+ * also cause the associated interface to be selected such that the
+ * link can be configured correctly.
+ */
+ if (pl->sfp_bus) {
+ config.interface = sfp_select_interface(pl->sfp_bus,
+ config.advertising);
+ if (config.interface == PHY_INTERFACE_MODE_NA) {
+ phylink_err(pl,
+ "selection of interface failed, advertisement %*pb\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS,
+ config.advertising);
+ return -EINVAL;
+ }
+
+ /* Revalidate with the selected interface */
+ linkmode_copy(support, pl->supported);
+ if (phylink_validate(pl, support, &config)) {
+ phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
+ phylink_an_mode_str(pl->cur_link_an_mode),
+ phy_modes(config.interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support);
+ return -EINVAL;
+ }
+ } else {
+ /* Validate without changing the current supported mask. */
+ linkmode_copy(support, pl->supported);
+ if (phylink_validate(pl, support, &config))
+ return -EINVAL;
+ }
+
+ /* If autonegotiation is enabled, we must have an advertisement */
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ config.advertising) &&
+ phylink_is_empty_linkmode(config.advertising))
+ return -EINVAL;
+
+ mutex_lock(&pl->state_mutex);
+ pl->link_config.speed = config.speed;
+ pl->link_config.duplex = config.duplex;
+
+ if (pl->link_config.interface != config.interface) {
+ /* The interface changed, e.g. 1000base-X <-> 2500base-X */
+ /* We need to force the link down, then change the interface */
+ if (pl->old_link_state) {
+ phylink_link_down(pl);
+ pl->old_link_state = false;
+ }
+ if (!test_bit(PHYLINK_DISABLE_STOPPED,
+ &pl->phylink_disable_state))
+ phylink_major_config(pl, false, &config);
+ pl->link_config.interface = config.interface;
+ linkmode_copy(pl->link_config.advertising, config.advertising);
+ } else if (!linkmode_equal(pl->link_config.advertising,
+ config.advertising)) {
+ linkmode_copy(pl->link_config.advertising, config.advertising);
+ phylink_change_inband_advert(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_pcs_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;
+ bool manual_changed;
+ int pause_state;
+
+ ASSERT_RTNL();
+
+ if (pl->cur_link_an_mode == MLO_AN_FIXED)
+ return -EOPNOTSUPP;
+
+ 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;
+
+ pause_state = 0;
+ if (pause->autoneg)
+ pause_state |= MLO_PAUSE_AN;
+ if (pause->rx_pause)
+ pause_state |= MLO_PAUSE_RX;
+ if (pause->tx_pause)
+ pause_state |= MLO_PAUSE_TX;
+
+ mutex_lock(&pl->state_mutex);
+ /*
+ * See the comments for linkmode_set_pause(), wrt the deficiencies
+ * with the current implementation. A solution to this issue would
+ * be:
+ * ethtool Local device
+ * rx tx Pause AsymDir
+ * 0 0 0 0
+ * 1 0 1 1
+ * 0 1 0 1
+ * 1 1 1 1
+ * and then use the ethtool rx/tx enablement status to mask the
+ * rx/tx pause resolution.
+ */
+ linkmode_set_pause(config->advertising, pause->tx_pause,
+ pause->rx_pause);
+
+ manual_changed = (config->pause ^ pause_state) & MLO_PAUSE_AN ||
+ (!(pause_state & MLO_PAUSE_AN) &&
+ (config->pause ^ pause_state) & MLO_PAUSE_TXRX_MASK);
+
+ config->pause = pause_state;
+
+ /* Update our in-band advertisement, triggering a renegotiation if
+ * the advertisement changed.
+ */
+ if (!pl->phydev)
+ phylink_change_inband_advert(pl);
+
+ mutex_unlock(&pl->state_mutex);
+
+ /* If we have a PHY, a change of the pause frame advertisement will
+ * cause phylib to renegotiate (if AN is enabled) which will in turn
+ * call our phylink_phy_change() and trigger a resolve. Note that
+ * we can't hold our state mutex while calling phy_set_asym_pause().
+ */
+ if (pl->phydev)
+ phy_set_asym_pause(pl->phydev, pause->rx_pause,
+ pause->tx_pause);
+
+ /* If the manual pause settings changed, make sure we trigger a
+ * resolve to update their state; we can not guarantee that the
+ * link will cycle.
+ */
+ if (manual_changed) {
+ pl->mac_link_dropped = true;
+ phylink_run_resolve(pl);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
+
+/**
+ * phylink_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_init_eee() - init and check the EEE features
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @clk_stop_enable: allow PHY to stop receive clock
+ *
+ * Must be called either with RTNL held or within mac_link_up()
+ */
+int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
+{
+ int ret = -EOPNOTSUPP;
+
+ if (pl->phydev)
+ ret = phy_init_eee(pl->phydev, clk_stop_enable);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_init_eee);
+
+/**
+ * phylink_ethtool_get_eee() - read the energy efficient ethernet parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @eee: a pointer to a &struct ethtool_eee for the read parameters
+ */
+int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee)
+{
+ int ret = -EOPNOTSUPP;
+
+ ASSERT_RTNL();
+
+ if (pl->phydev)
+ ret = phy_ethtool_get_eee(pl->phydev, eee);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
+
+/**
+ * phylink_ethtool_set_eee() - set the energy efficient ethernet parameters
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @eee: a pointer to a &struct ethtool_eee for the desired parameters
+ */
+int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee)
+{
+ int ret = -EOPNOTSUPP;
+
+ ASSERT_RTNL();
+
+ if (pl->phydev)
+ ret = phy_ethtool_set_eee(pl->phydev, eee);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee);
+
+/* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+ * FIXME: should deal with negotiation state too.
+ */
+static int phylink_mii_emul_read(unsigned int reg,
+ struct phylink_link_state *state)
+{
+ struct fixed_phy_status fs;
+ unsigned long *lpa = state->lp_advertising;
+ int val;
+
+ fs.link = state->link;
+ fs.speed = state->speed;
+ fs.duplex = state->duplex;
+ fs.pause = test_bit(ETHTOOL_LINK_MODE_Pause_BIT, lpa);
+ fs.asym_pause = test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, lpa);
+
+ val = swphy_read_reg(reg, &fs);
+ if (reg == MII_BMSR) {
+ if (!state->an_complete)
+ val &= ~BMSR_ANEGCOMPLETE;
+ }
+ return val;
+}
+
+static int phylink_phy_read(struct phylink *pl, unsigned int phy_id,
+ unsigned int reg)
+{
+ struct phy_device *phydev = pl->phydev;
+ int prtad, devad;
+
+ if (mdio_phy_id_is_c45(phy_id)) {
+ prtad = mdio_phy_id_prtad(phy_id);
+ devad = mdio_phy_id_devad(phy_id);
+ return mdiobus_c45_read(pl->phydev->mdio.bus, prtad, devad,
+ reg);
+ }
+
+ if (phydev->is_c45) {
+ switch (reg) {
+ case MII_BMCR:
+ case MII_BMSR:
+ case MII_PHYSID1:
+ case MII_PHYSID2:
+ devad = __ffs(phydev->c45_ids.mmds_present);
+ break;
+ case MII_ADVERTISE:
+ case MII_LPA:
+ if (!(phydev->c45_ids.mmds_present & 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;
+ return mdiobus_c45_read(pl->phydev->mdio.bus, prtad, devad,
+ reg);
+ }
+
+ return mdiobus_read(pl->phydev->mdio.bus, phy_id, reg);
+}
+
+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);
+ return mdiobus_c45_write(pl->phydev->mdio.bus, prtad, devad,
+ reg, val);
+ }
+
+ if (phydev->is_c45) {
+ switch (reg) {
+ case MII_BMCR:
+ case MII_BMSR:
+ case MII_PHYSID1:
+ case MII_PHYSID2:
+ devad = __ffs(phydev->c45_ids.mmds_present);
+ break;
+ case MII_ADVERTISE:
+ case MII_LPA:
+ if (!(phydev->c45_ids.mmds_present & 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;
+ }
+ return mdiobus_c45_write(pl->phydev->mdio.bus, phy_id, devad,
+ reg, val);
+ }
+
+ return mdiobus_write(phydev->mdio.bus, phy_id, reg, val);
+}
+
+static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
+ unsigned int reg)
+{
+ struct phylink_link_state state;
+ int val = 0xffff;
+
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_FIXED:
+ if (phy_id == 0) {
+ phylink_get_fixed_state(pl, &state);
+ val = phylink_mii_emul_read(reg, &state);
+ }
+ break;
+
+ case MLO_AN_PHY:
+ return -EOPNOTSUPP;
+
+ case MLO_AN_INBAND:
+ if (phy_id == 0) {
+ phylink_mac_pcs_get_state(pl, &state);
+ val = phylink_mii_emul_read(reg, &state);
+ }
+ break;
+ }
+
+ return val & 0xffff;
+}
+
+static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
+ unsigned int reg, unsigned int val)
+{
+ switch (pl->cur_link_an_mode) {
+ case MLO_AN_FIXED:
+ break;
+
+ case MLO_AN_PHY:
+ return -EOPNOTSUPP;
+
+ case MLO_AN_INBAND:
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * phylink_mii_ioctl() - generic mii ioctl interface
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @ifr: a pointer to a &struct ifreq for socket ioctls
+ * @cmd: ioctl cmd to execute
+ *
+ * Perform the specified MII ioctl on the PHY attached to the phylink instance
+ * specified by @pl. If no PHY is attached, emulate the presence of the PHY.
+ *
+ * Returns: zero on success or negative error code.
+ *
+ * %SIOCGMIIPHY:
+ * read register from the current PHY.
+ * %SIOCGMIIREG:
+ * read register from the specified PHY.
+ * %SIOCSMIIREG:
+ * set a register on the specified PHY.
+ */
+int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
+{
+ struct mii_ioctl_data *mii = if_mii(ifr);
+ int ret;
+
+ ASSERT_RTNL();
+
+ if (pl->phydev) {
+ /* PHYs only exist for MLO_AN_PHY and SGMII */
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ mii->phy_id = pl->phydev->mdio.addr;
+ fallthrough;
+
+ 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;
+ fallthrough;
+
+ 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);
+
+/**
+ * phylink_speed_down() - set the non-SFP PHY to lowest speed supported by both
+ * link partners
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @sync: perform action synchronously
+ *
+ * If we have a PHY that is not part of a SFP module, then set the speed
+ * as described in the phy_speed_down() function. Please see this function
+ * for a description of the @sync parameter.
+ *
+ * Returns zero if there is no PHY, otherwise as per phy_speed_down().
+ */
+int phylink_speed_down(struct phylink *pl, bool sync)
+{
+ int ret = 0;
+
+ ASSERT_RTNL();
+
+ if (!pl->sfp_bus && pl->phydev)
+ ret = phy_speed_down(pl->phydev, sync);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_speed_down);
+
+/**
+ * phylink_speed_up() - restore the advertised speeds prior to the call to
+ * phylink_speed_down()
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * If we have a PHY that is not part of a SFP module, then restore the
+ * PHY speeds as per phy_speed_up().
+ *
+ * Returns zero if there is no PHY, otherwise as per phy_speed_up().
+ */
+int phylink_speed_up(struct phylink *pl)
+{
+ int ret = 0;
+
+ ASSERT_RTNL();
+
+ if (!pl->sfp_bus && pl->phydev)
+ ret = phy_speed_up(pl->phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_speed_up);
+
+static void phylink_sfp_attach(void *upstream, struct sfp_bus *bus)
+{
+ struct phylink *pl = upstream;
+
+ pl->netdev->sfp_bus = bus;
+}
+
+static void phylink_sfp_detach(void *upstream, struct sfp_bus *bus)
+{
+ struct phylink *pl = upstream;
+
+ pl->netdev->sfp_bus = NULL;
+}
+
+static const phy_interface_t phylink_sfp_interface_preference[] = {
+ PHY_INTERFACE_MODE_25GBASER,
+ PHY_INTERFACE_MODE_USXGMII,
+ PHY_INTERFACE_MODE_10GBASER,
+ PHY_INTERFACE_MODE_5GBASER,
+ PHY_INTERFACE_MODE_2500BASEX,
+ PHY_INTERFACE_MODE_SGMII,
+ PHY_INTERFACE_MODE_1000BASEX,
+ PHY_INTERFACE_MODE_100BASEX,
+};
+
+static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
+
+static phy_interface_t phylink_choose_sfp_interface(struct phylink *pl,
+ const unsigned long *intf)
+{
+ phy_interface_t interface;
+ size_t i;
+
+ interface = PHY_INTERFACE_MODE_NA;
+ for (i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); i++)
+ if (test_bit(phylink_sfp_interface_preference[i], intf)) {
+ interface = phylink_sfp_interface_preference[i];
+ break;
+ }
+
+ return interface;
+}
+
+static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
+ unsigned long *supported,
+ struct phylink_link_state *state)
+{
+ bool changed = false;
+
+ phylink_dbg(pl, "requesting link mode %s/%s with support %*pb\n",
+ phylink_an_mode_str(mode), phy_modes(state->interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, supported);
+
+ if (!linkmode_equal(pl->supported, supported)) {
+ linkmode_copy(pl->supported, supported);
+ changed = true;
+ }
+
+ if (!linkmode_equal(pl->link_config.advertising, state->advertising)) {
+ linkmode_copy(pl->link_config.advertising, state->advertising);
+ changed = true;
+ }
+
+ if (pl->cur_link_an_mode != mode ||
+ pl->link_config.interface != state->interface) {
+ pl->cur_link_an_mode = mode;
+ pl->link_config.interface = state->interface;
+
+ changed = true;
+
+ phylink_info(pl, "switched to %s/%s link mode\n",
+ phylink_an_mode_str(mode),
+ phy_modes(state->interface));
+ }
+
+ if (changed && !test_bit(PHYLINK_DISABLE_STOPPED,
+ &pl->phylink_disable_state))
+ phylink_mac_initial_config(pl, false);
+}
+
+static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
+ struct phy_device *phy)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support1);
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+ struct phylink_link_state config;
+ phy_interface_t iface;
+ int ret;
+
+ linkmode_copy(support, phy->supported);
+
+ memset(&config, 0, sizeof(config));
+ linkmode_copy(config.advertising, phy->advertising);
+ config.interface = PHY_INTERFACE_MODE_NA;
+ config.speed = SPEED_UNKNOWN;
+ config.duplex = DUPLEX_UNKNOWN;
+ config.pause = MLO_PAUSE_AN;
+
+ /* Ignore errors if we're expecting a PHY to attach later */
+ ret = phylink_validate(pl, support, &config);
+ if (ret) {
+ phylink_err(pl, "validation with support %*pb failed: %pe\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ iface = sfp_select_interface(pl->sfp_bus, config.advertising);
+ if (iface == PHY_INTERFACE_MODE_NA) {
+ phylink_err(pl,
+ "selection of interface failed, advertisement %*pb\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, config.advertising);
+ return -EINVAL;
+ }
+
+ config.interface = iface;
+ linkmode_copy(support1, support);
+ ret = phylink_validate(pl, support1, &config);
+ if (ret) {
+ phylink_err(pl,
+ "validation of %s/%s with support %*pb failed: %pe\n",
+ phylink_an_mode_str(mode),
+ phy_modes(config.interface),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ pl->link_port = pl->sfp_port;
+
+ phylink_sfp_set_config(pl, mode, support, &config);
+
+ return 0;
+}
+
+static int phylink_sfp_config_optical(struct phylink *pl)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
+ struct phylink_link_state config;
+ phy_interface_t interface;
+ int ret;
+
+ phylink_dbg(pl, "optical SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n",
+ (int)PHY_INTERFACE_MODE_MAX,
+ pl->config->supported_interfaces,
+ (int)PHY_INTERFACE_MODE_MAX,
+ pl->sfp_interfaces);
+
+ /* Find the union of the supported interfaces by the PCS/MAC and
+ * the SFP module.
+ */
+ phy_interface_and(interfaces, pl->config->supported_interfaces,
+ pl->sfp_interfaces);
+ if (phy_interface_empty(interfaces)) {
+ phylink_err(pl, "unsupported SFP module: no common interface modes\n");
+ return -EINVAL;
+ }
+
+ memset(&config, 0, sizeof(config));
+ linkmode_copy(support, pl->sfp_support);
+ linkmode_copy(config.advertising, pl->sfp_support);
+ config.speed = SPEED_UNKNOWN;
+ config.duplex = DUPLEX_UNKNOWN;
+ config.pause = MLO_PAUSE_AN;
+
+ /* For all the interfaces that are supported, reduce the sfp_support
+ * mask to only those link modes that can be supported.
+ */
+ ret = phylink_validate_mask(pl, pl->sfp_support, &config, interfaces);
+ if (ret) {
+ phylink_err(pl, "unsupported SFP module: validation with support %*pb failed\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support);
+ return ret;
+ }
+
+ interface = phylink_choose_sfp_interface(pl, interfaces);
+ if (interface == PHY_INTERFACE_MODE_NA) {
+ phylink_err(pl, "failed to select SFP interface\n");
+ return -EINVAL;
+ }
+
+ phylink_dbg(pl, "optical SFP: chosen %s interface\n",
+ phy_modes(interface));
+
+ config.interface = interface;
+
+ /* Ignore errors if we're expecting a PHY to attach later */
+ ret = phylink_validate(pl, support, &config);
+ if (ret) {
+ phylink_err(pl, "validation with support %*pb failed: %pe\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ pl->link_port = pl->sfp_port;
+
+ phylink_sfp_set_config(pl, MLO_AN_INBAND, pl->sfp_support, &config);
+
+ return 0;
+}
+
+static int phylink_sfp_module_insert(void *upstream,
+ const struct sfp_eeprom_id *id)
+{
+ struct phylink *pl = upstream;
+
+ ASSERT_RTNL();
+
+ linkmode_zero(pl->sfp_support);
+ phy_interface_zero(pl->sfp_interfaces);
+ sfp_parse_support(pl->sfp_bus, id, pl->sfp_support, pl->sfp_interfaces);
+ pl->sfp_port = sfp_parse_port(pl->sfp_bus, id, pl->sfp_support);
+
+ /* If this module may have a PHY connecting later, defer until later */
+ pl->sfp_may_have_phy = sfp_may_have_phy(pl->sfp_bus, id);
+ if (pl->sfp_may_have_phy)
+ return 0;
+
+ return phylink_sfp_config_optical(pl);
+}
+
+static int phylink_sfp_module_start(void *upstream)
+{
+ struct phylink *pl = upstream;
+
+ /* If this SFP module has a PHY, start the PHY now. */
+ if (pl->phydev) {
+ phy_start(pl->phydev);
+ return 0;
+ }
+
+ /* If the module may have a PHY but we didn't detect one we
+ * need to configure the MAC here.
+ */
+ if (!pl->sfp_may_have_phy)
+ return 0;
+
+ return phylink_sfp_config_optical(pl);
+}
+
+static void phylink_sfp_module_stop(void *upstream)
+{
+ struct phylink *pl = upstream;
+
+ /* If this SFP module has a PHY, stop it. */
+ if (pl->phydev)
+ phy_stop(pl->phydev);
+}
+
+static void phylink_sfp_link_down(void *upstream)
+{
+ struct phylink *pl = upstream;
+
+ ASSERT_RTNL();
+
+ phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_LINK);
+}
+
+static void phylink_sfp_link_up(void *upstream)
+{
+ struct phylink *pl = upstream;
+
+ ASSERT_RTNL();
+
+ phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK);
+}
+
+/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
+ * or 802.3z control word, so inband will not work.
+ */
+static bool phylink_phy_no_inband(struct phy_device *phy)
+{
+ return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
+ 0xae025150, 0xfffffff0);
+}
+
+static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+{
+ struct phylink *pl = upstream;
+ phy_interface_t interface;
+ u8 mode;
+ int ret;
+
+ /*
+ * This is the new way of dealing with flow control for PHYs,
+ * as described by Timur Tabi in commit 529ed1275263 ("net: phy:
+ * phy drivers should not set SUPPORTED_[Asym_]Pause") except
+ * using our validate call to the MAC, we rely upon the MAC
+ * clearing the bits from both supported and advertising fields.
+ */
+ phy_support_asym_pause(phy);
+
+ if (phylink_phy_no_inband(phy))
+ mode = MLO_AN_PHY;
+ else
+ mode = MLO_AN_INBAND;
+
+ /* Set the PHY's host supported interfaces */
+ phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
+ pl->config->supported_interfaces);
+
+ /* Do the initial configuration */
+ ret = phylink_sfp_config_phy(pl, mode, phy);
+ if (ret < 0)
+ return ret;
+
+ interface = pl->link_config.interface;
+ ret = phylink_attach_phy(pl, phy, interface);
+ if (ret < 0)
+ return ret;
+
+ ret = phylink_bringup_phy(pl, phy, interface);
+ if (ret)
+ phy_detach(phy);
+
+ return ret;
+}
+
+static void phylink_sfp_disconnect_phy(void *upstream)
+{
+ phylink_disconnect_phy(upstream);
+}
+
+static const struct sfp_upstream_ops sfp_phylink_ops = {
+ .attach = phylink_sfp_attach,
+ .detach = phylink_sfp_detach,
+ .module_insert = phylink_sfp_module_insert,
+ .module_start = phylink_sfp_module_start,
+ .module_stop = phylink_sfp_module_stop,
+ .link_up = phylink_sfp_link_up,
+ .link_down = phylink_sfp_link_down,
+ .connect_phy = phylink_sfp_connect_phy,
+ .disconnect_phy = phylink_sfp_disconnect_phy,
+};
+
+/* Helpers for MAC drivers */
+
+static struct {
+ int bit;
+ int speed;
+} phylink_c73_priority_resolution[] = {
+ { ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, SPEED_100000 },
+ { ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, SPEED_100000 },
+ /* 100GBASE-KP4 and 100GBASE-CR10 not supported */
+ { ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, SPEED_40000 },
+ { ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, SPEED_40000 },
+ { ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, SPEED_10000 },
+ { ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, SPEED_10000 },
+ /* 5GBASE-KR not supported */
+ { ETHTOOL_LINK_MODE_2500baseX_Full_BIT, SPEED_2500 },
+ { ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, SPEED_1000 },
+};
+
+void phylink_resolve_c73(struct phylink_link_state *state)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(phylink_c73_priority_resolution); i++) {
+ int bit = phylink_c73_priority_resolution[i].bit;
+ if (linkmode_test_bit(bit, state->advertising) &&
+ linkmode_test_bit(bit, state->lp_advertising))
+ break;
+ }
+
+ if (i < ARRAY_SIZE(phylink_c73_priority_resolution)) {
+ state->speed = phylink_c73_priority_resolution[i].speed;
+ state->duplex = DUPLEX_FULL;
+ } else {
+ /* negotiation failure */
+ state->link = false;
+ }
+
+ phylink_resolve_an_pause(state);
+}
+EXPORT_SYMBOL_GPL(phylink_resolve_c73);
+
+static void phylink_decode_c37_word(struct phylink_link_state *state,
+ uint16_t config_reg, int speed)
+{
+ int fd_bit;
+
+ if (speed == SPEED_2500)
+ fd_bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT;
+ else
+ fd_bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT;
+
+ mii_lpa_mod_linkmode_x(state->lp_advertising, config_reg, fd_bit);
+
+ if (linkmode_test_bit(fd_bit, state->advertising) &&
+ linkmode_test_bit(fd_bit, state->lp_advertising)) {
+ state->speed = speed;
+ state->duplex = DUPLEX_FULL;
+ } else {
+ /* negotiation failure */
+ state->link = false;
+ }
+
+ phylink_resolve_an_pause(state);
+}
+
+static void phylink_decode_sgmii_word(struct phylink_link_state *state,
+ uint16_t config_reg)
+{
+ if (!(config_reg & LPA_SGMII_LINK)) {
+ state->link = false;
+ return;
+ }
+
+ switch (config_reg & LPA_SGMII_SPD_MASK) {
+ case LPA_SGMII_10:
+ state->speed = SPEED_10;
+ break;
+ case LPA_SGMII_100:
+ state->speed = SPEED_100;
+ break;
+ case LPA_SGMII_1000:
+ state->speed = SPEED_1000;
+ break;
+ default:
+ state->link = false;
+ return;
+ }
+ if (config_reg & LPA_SGMII_FULL_DUPLEX)
+ state->duplex = DUPLEX_FULL;
+ else
+ state->duplex = DUPLEX_HALF;
+}
+
+/**
+ * phylink_decode_usxgmii_word() - decode the USXGMII word from a MAC PCS
+ * @state: a pointer to a struct phylink_link_state.
+ * @lpa: a 16 bit value which stores the USXGMII auto-negotiation word
+ *
+ * Helper for MAC PCS supporting the USXGMII protocol and the auto-negotiation
+ * code word. Decode the USXGMII code word and populate the corresponding fields
+ * (speed, duplex) into the phylink_link_state structure.
+ */
+void phylink_decode_usxgmii_word(struct phylink_link_state *state,
+ uint16_t lpa)
+{
+ switch (lpa & MDIO_USXGMII_SPD_MASK) {
+ case MDIO_USXGMII_10:
+ state->speed = SPEED_10;
+ break;
+ case MDIO_USXGMII_100:
+ state->speed = SPEED_100;
+ break;
+ case MDIO_USXGMII_1000:
+ state->speed = SPEED_1000;
+ break;
+ case MDIO_USXGMII_2500:
+ state->speed = SPEED_2500;
+ break;
+ case MDIO_USXGMII_5000:
+ state->speed = SPEED_5000;
+ break;
+ case MDIO_USXGMII_10G:
+ state->speed = SPEED_10000;
+ break;
+ default:
+ state->link = false;
+ return;
+ }
+
+ if (lpa & MDIO_USXGMII_FULL_DUPLEX)
+ state->duplex = DUPLEX_FULL;
+ else
+ state->duplex = DUPLEX_HALF;
+}
+EXPORT_SYMBOL_GPL(phylink_decode_usxgmii_word);
+
+/**
+ * phylink_decode_usgmii_word() - decode the USGMII word from a MAC PCS
+ * @state: a pointer to a struct phylink_link_state.
+ * @lpa: a 16 bit value which stores the USGMII auto-negotiation word
+ *
+ * Helper for MAC PCS supporting the USGMII protocol and the auto-negotiation
+ * code word. Decode the USGMII code word and populate the corresponding fields
+ * (speed, duplex) into the phylink_link_state structure. The structure for this
+ * word is the same as the USXGMII word, except it only supports speeds up to
+ * 1Gbps.
+ */
+static void phylink_decode_usgmii_word(struct phylink_link_state *state,
+ uint16_t lpa)
+{
+ switch (lpa & MDIO_USXGMII_SPD_MASK) {
+ case MDIO_USXGMII_10:
+ state->speed = SPEED_10;
+ break;
+ case MDIO_USXGMII_100:
+ state->speed = SPEED_100;
+ break;
+ case MDIO_USXGMII_1000:
+ state->speed = SPEED_1000;
+ break;
+ default:
+ state->link = false;
+ return;
+ }
+
+ if (lpa & MDIO_USXGMII_FULL_DUPLEX)
+ state->duplex = DUPLEX_FULL;
+ else
+ state->duplex = DUPLEX_HALF;
+}
+
+/**
+ * phylink_mii_c22_pcs_decode_state() - Decode MAC PCS state from MII registers
+ * @state: a pointer to a &struct phylink_link_state.
+ * @bmsr: The value of the %MII_BMSR register
+ * @lpa: The value of the %MII_LPA register
+ *
+ * Helper for MAC PCS supporting the 802.3 clause 22 register set for
+ * clause 37 negotiation and/or SGMII control.
+ *
+ * Parse the Clause 37 or Cisco SGMII link partner negotiation word into
+ * the phylink @state structure. This is suitable to be used for implementing
+ * the pcs_get_state() member of the struct phylink_pcs_ops structure if
+ * accessing @bmsr and @lpa cannot be done with MDIO directly.
+ */
+void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state,
+ u16 bmsr, u16 lpa)
+{
+ state->link = !!(bmsr & BMSR_LSTATUS);
+ state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
+ /* If there is no link or autonegotiation is disabled, the LP advertisement
+ * data is not meaningful, so don't go any further.
+ */
+ if (!state->link || !linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ state->advertising))
+ return;
+
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ phylink_decode_c37_word(state, lpa, SPEED_1000);
+ break;
+
+ case PHY_INTERFACE_MODE_2500BASEX:
+ phylink_decode_c37_word(state, lpa, SPEED_2500);
+ break;
+
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ phylink_decode_sgmii_word(state, lpa);
+ break;
+ case PHY_INTERFACE_MODE_QUSGMII:
+ phylink_decode_usgmii_word(state, lpa);
+ break;
+
+ default:
+ state->link = false;
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_decode_state);
+
+/**
+ * phylink_mii_c22_pcs_get_state() - read the MAC PCS state
+ * @pcs: a pointer to a &struct mdio_device.
+ * @state: a pointer to a &struct phylink_link_state.
+ *
+ * Helper for MAC PCS supporting the 802.3 clause 22 register set for
+ * clause 37 negotiation and/or SGMII control.
+ *
+ * Read the MAC PCS state from the MII device configured in @config and
+ * parse the Clause 37 or Cisco SGMII link partner negotiation word into
+ * the phylink @state structure. This is suitable to be directly plugged
+ * into the pcs_get_state() member of the struct phylink_pcs_ops
+ * structure.
+ */
+void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs,
+ struct phylink_link_state *state)
+{
+ int bmsr, lpa;
+
+ bmsr = mdiodev_read(pcs, MII_BMSR);
+ lpa = mdiodev_read(pcs, MII_LPA);
+ if (bmsr < 0 || lpa < 0) {
+ state->link = false;
+ return;
+ }
+
+ phylink_mii_c22_pcs_decode_state(state, bmsr, lpa);
+}
+EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_get_state);
+
+/**
+ * phylink_mii_c22_pcs_encode_advertisement() - configure the clause 37 PCS
+ * advertisement
+ * @interface: the PHY interface mode being configured
+ * @advertising: the ethtool advertisement mask
+ *
+ * Helper for MAC PCS supporting the 802.3 clause 22 register set for
+ * clause 37 negotiation and/or SGMII control.
+ *
+ * Encode the clause 37 PCS advertisement as specified by @interface and
+ * @advertising.
+ *
+ * Return: The new value for @adv, or ``-EINVAL`` if it should not be changed.
+ */
+int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface,
+ const unsigned long *advertising)
+{
+ u16 adv;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ adv = ADVERTISE_1000XFULL;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ advertising))
+ adv |= ADVERTISE_1000XPAUSE;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ advertising))
+ adv |= ADVERTISE_1000XPSE_ASYM;
+ return adv;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ return 0x0001;
+ default:
+ /* Nothing to do for other modes */
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_encode_advertisement);
+
+/**
+ * phylink_mii_c22_pcs_config() - configure clause 22 PCS
+ * @pcs: a pointer to a &struct mdio_device.
+ * @interface: the PHY interface mode being configured
+ * @advertising: the ethtool advertisement mask
+ * @neg_mode: PCS negotiation mode
+ *
+ * Configure a Clause 22 PCS PHY with the appropriate negotiation
+ * parameters for the @mode, @interface and @advertising parameters.
+ * Returns negative error number on failure, zero if the advertisement
+ * has not changed, or positive if there is a change.
+ */
+int phylink_mii_c22_pcs_config(struct mdio_device *pcs,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ unsigned int neg_mode)
+{
+ bool changed = 0;
+ u16 bmcr;
+ int ret, adv;
+
+ adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
+ if (adv >= 0) {
+ ret = mdiobus_modify_changed(pcs->bus, pcs->addr,
+ MII_ADVERTISE, 0xffff, adv);
+ if (ret < 0)
+ return ret;
+ changed = ret;
+ }
+
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ bmcr = BMCR_ANENABLE;
+ else
+ bmcr = 0;
+
+ /* Configure the inband state. Ensure ISOLATE bit is disabled */
+ ret = mdiodev_modify(pcs, MII_BMCR, BMCR_ANENABLE | BMCR_ISOLATE, bmcr);
+ if (ret < 0)
+ return ret;
+
+ return changed;
+}
+EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_config);
+
+/**
+ * phylink_mii_c22_pcs_an_restart() - restart 802.3z autonegotiation
+ * @pcs: a pointer to a &struct mdio_device.
+ *
+ * Helper for MAC PCS supporting the 802.3 clause 22 register set for
+ * clause 37 negotiation.
+ *
+ * Restart the clause 37 negotiation with the link partner. This is
+ * suitable to be directly plugged into the pcs_get_state() member
+ * of the struct phylink_pcs_ops structure.
+ */
+void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs)
+{
+ int val = mdiodev_read(pcs, MII_BMCR);
+
+ if (val >= 0) {
+ val |= BMCR_ANRESTART;
+
+ mdiodev_write(pcs, MII_BMCR, val);
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_an_restart);
+
+void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs,
+ struct phylink_link_state *state)
+{
+ struct mii_bus *bus = pcs->bus;
+ int addr = pcs->addr;
+ int stat;
+
+ stat = mdiobus_c45_read(bus, addr, MDIO_MMD_PCS, MDIO_STAT1);
+ if (stat < 0) {
+ state->link = false;
+ return;
+ }
+
+ state->link = !!(stat & MDIO_STAT1_LSTATUS);
+ if (!state->link)
+ return;
+
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ state->speed = SPEED_10000;
+ state->duplex = DUPLEX_FULL;
+ break;
+
+ default:
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state);
+
+static int __init phylink_init(void)
+{
+ for (int i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); ++i)
+ __set_bit(phylink_sfp_interface_preference[i],
+ phylink_sfp_interfaces);
+
+ return 0;
+}
+
+module_init(phylink_init);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/qsemi.c b/drivers/net/phy/qsemi.c
new file mode 100644
index 000000000..30d15f7c9
--- /dev/null
+++ b/drivers/net/phy/qsemi.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/qsemi.c
+ *
+ * Driver for Quality Semiconductor PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+#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;
+
+ /* The Interrupt Source register is not self-clearing, bits 4 and 5 are
+ * cleared when MII_BMSR is read and bits 1 and 3 are cleared when
+ * MII_EXPANSION is read
+ */
+ 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) {
+ /* clear any interrupts before enabling them */
+ err = qs6612_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, MII_QS6612_IMR,
+ MII_QS6612_IMR_INIT);
+ } else {
+ err = phy_write(phydev, MII_QS6612_IMR, 0);
+ if (err)
+ return err;
+
+ /* clear any leftover interrupts */
+ err = qs6612_ack_interrupt(phydev);
+ }
+
+ return err;
+
+}
+
+static irqreturn_t qs6612_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_QS6612_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_QS6612_IMR_INIT))
+ return IRQ_NONE;
+
+ /* the interrupt source register is not self-clearing */
+ qs6612_ack_interrupt(phydev);
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static struct phy_driver qs6612_driver[] = { {
+ .phy_id = 0x00181440,
+ .name = "QS6612",
+ .phy_id_mask = 0xfffffff0,
+ /* PHY_BASIC_FEATURES */
+ .config_init = qs6612_config_init,
+ .config_intr = qs6612_config_intr,
+ .handle_interrupt = qs6612_handle_interrupt,
+} };
+
+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..894172a3e
--- /dev/null
+++ b/drivers/net/phy/realtek.c
@@ -0,0 +1,1094 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* drivers/net/phy/realtek.c
+ *
+ * Driver for Realtek PHYs
+ *
+ * Author: Johnson Leung <r58129@freescale.com>
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, Inc.
+ */
+#include <linux/bitops.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/clk.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_EXT_PAGE_SELECT 0x1e
+#define RTL821x_PAGE_SELECT 0x1f
+
+#define RTL8211F_PHYCR1 0x18
+#define RTL8211F_PHYCR2 0x19
+#define RTL8211F_INSR 0x1d
+
+#define RTL8211F_TX_DELAY BIT(8)
+#define RTL8211F_RX_DELAY BIT(3)
+
+#define RTL8211F_ALDPS_PLL_OFF BIT(1)
+#define RTL8211F_ALDPS_ENABLE BIT(2)
+#define RTL8211F_ALDPS_XTAL_OFF BIT(12)
+
+#define RTL8211E_CTRL_DELAY BIT(13)
+#define RTL8211E_TX_DELAY BIT(12)
+#define RTL8211E_RX_DELAY BIT(11)
+
+#define RTL8211F_CLKOUT_EN BIT(0)
+
+#define RTL8201F_ISR 0x1e
+#define RTL8201F_ISR_ANERR BIT(15)
+#define RTL8201F_ISR_DUPLEX BIT(13)
+#define RTL8201F_ISR_LINK BIT(11)
+#define RTL8201F_ISR_MASK (RTL8201F_ISR_ANERR | \
+ RTL8201F_ISR_DUPLEX | \
+ RTL8201F_ISR_LINK)
+#define RTL8201F_IER 0x13
+
+#define RTL8366RB_POWER_SAVE 0x15
+#define RTL8366RB_POWER_SAVE_ON BIT(12)
+
+#define RTL_SUPPORTS_5000FULL BIT(14)
+#define RTL_SUPPORTS_2500FULL BIT(13)
+#define RTL_SUPPORTS_10000FULL BIT(0)
+#define RTL_ADV_2500FULL BIT(7)
+#define RTL_LPADV_10000FULL BIT(11)
+#define RTL_LPADV_5000FULL BIT(6)
+#define RTL_LPADV_2500FULL BIT(5)
+
+#define RTL9000A_GINMR 0x14
+#define RTL9000A_GINMR_LINK_STATUS BIT(4)
+
+#define RTLGEN_SPEED_MASK 0x0630
+
+#define RTL_GENERIC_PHYID 0x001cc800
+#define RTL_8211FVD_PHYID 0x001cc878
+
+MODULE_DESCRIPTION("Realtek PHY driver");
+MODULE_AUTHOR("Johnson Leung");
+MODULE_LICENSE("GPL");
+
+struct rtl821x_priv {
+ u16 phycr1;
+ u16 phycr2;
+ bool has_phycr2;
+ struct clk *clk;
+};
+
+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 rtl821x_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct rtl821x_priv *priv;
+ u32 phy_id = phydev->drv->phy_id;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "failed to get phy clock\n");
+
+ ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR1);
+ if (ret < 0)
+ return ret;
+
+ priv->phycr1 = ret & (RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF);
+ if (of_property_read_bool(dev->of_node, "realtek,aldps-enable"))
+ priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF;
+
+ priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID);
+ if (priv->has_phycr2) {
+ ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR2);
+ if (ret < 0)
+ return ret;
+
+ priv->phycr2 = ret & RTL8211F_CLKOUT_EN;
+ if (of_property_read_bool(dev->of_node, "realtek,clkout-disable"))
+ priv->phycr2 &= ~RTL8211F_CLKOUT_EN;
+ }
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
+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;
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = rtl8201_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ val = BIT(13) | BIT(12) | BIT(11);
+ err = phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
+ } else {
+ val = 0;
+ err = phy_write_paged(phydev, 0x7, RTL8201F_IER, val);
+ if (err)
+ return err;
+
+ err = rtl8201_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static int rtl8211b_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = rtl821x_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, RTL821x_INER,
+ RTL8211B_INER_INIT);
+ } else {
+ err = phy_write(phydev, RTL821x_INER, 0);
+ if (err)
+ return err;
+
+ err = rtl821x_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static int rtl8211e_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = rtl821x_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ err = phy_write(phydev, RTL821x_INER,
+ RTL8211E_INER_LINK_STATUS);
+ } else {
+ err = phy_write(phydev, RTL821x_INER, 0);
+ if (err)
+ return err;
+
+ err = rtl821x_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static int rtl8211f_config_intr(struct phy_device *phydev)
+{
+ u16 val;
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = rtl8211f_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ val = RTL8211F_INER_LINK_STATUS;
+ err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
+ } else {
+ val = 0;
+ err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val);
+ if (err)
+ return err;
+
+ err = rtl8211f_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t rtl8201_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, RTL8201F_ISR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & RTL8201F_ISR_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rtl821x_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, irq_enabled;
+
+ irq_status = phy_read(phydev, RTL821x_INSR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ irq_enabled = phy_read(phydev, RTL821x_INER);
+ if (irq_enabled < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & irq_enabled))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read_paged(phydev, 0xa43, RTL8211F_INSR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & RTL8211F_INER_LINK_STATUS))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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 */
+ return phy_set_bits(phydev, MII_CTRL1000,
+ CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
+}
+
+static int rtl8211f_config_init(struct phy_device *phydev)
+{
+ struct rtl821x_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ u16 val_txdly, val_rxdly;
+ int ret;
+
+ ret = phy_modify_paged_changed(phydev, 0xa43, RTL8211F_PHYCR1,
+ RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
+ priv->phycr1);
+ if (ret < 0) {
+ dev_err(dev, "aldps mode configuration failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ val_txdly = 0;
+ val_rxdly = 0;
+ break;
+
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ val_txdly = 0;
+ val_rxdly = RTL8211F_RX_DELAY;
+ break;
+
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val_txdly = RTL8211F_TX_DELAY;
+ val_rxdly = 0;
+ break;
+
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ val_txdly = RTL8211F_TX_DELAY;
+ val_rxdly = RTL8211F_RX_DELAY;
+ break;
+
+ default: /* the rest of the modes imply leaving delay as is. */
+ return 0;
+ }
+
+ ret = phy_modify_paged_changed(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY,
+ val_txdly);
+ if (ret < 0) {
+ dev_err(dev, "Failed to update the TX delay register\n");
+ return ret;
+ } else if (ret) {
+ dev_dbg(dev,
+ "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n",
+ val_txdly ? "Enabling" : "Disabling");
+ } else {
+ dev_dbg(dev,
+ "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
+ val_txdly ? "enabled" : "disabled");
+ }
+
+ ret = phy_modify_paged_changed(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY,
+ val_rxdly);
+ if (ret < 0) {
+ dev_err(dev, "Failed to update the RX delay register\n");
+ return ret;
+ } else if (ret) {
+ dev_dbg(dev,
+ "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n",
+ val_rxdly ? "Enabling" : "Disabling");
+ } else {
+ dev_dbg(dev,
+ "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n",
+ val_rxdly ? "enabled" : "disabled");
+ }
+
+ if (priv->has_phycr2) {
+ ret = phy_modify_paged(phydev, 0xa43, RTL8211F_PHYCR2,
+ RTL8211F_CLKOUT_EN, priv->phycr2);
+ if (ret < 0) {
+ dev_err(dev, "clkout configuration failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+
+ return genphy_soft_reset(phydev);
+}
+
+static int rtl821x_suspend(struct phy_device *phydev)
+{
+ struct rtl821x_priv *priv = phydev->priv;
+ int ret = 0;
+
+ if (!phydev->wol_enabled) {
+ ret = genphy_suspend(phydev);
+
+ if (ret)
+ return ret;
+
+ clk_disable_unprepare(priv->clk);
+ }
+
+ return ret;
+}
+
+static int rtl821x_resume(struct phy_device *phydev)
+{
+ struct rtl821x_priv *priv = phydev->priv;
+ int ret;
+
+ if (!phydev->wol_enabled)
+ clk_prepare_enable(priv->clk);
+
+ ret = genphy_resume(phydev);
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+
+ return 0;
+}
+
+static int rtl8211e_config_init(struct phy_device *phydev)
+{
+ int ret = 0, oldpage;
+ u16 val;
+
+ /* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ val = RTL8211E_CTRL_DELAY | 0;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ val = RTL8211E_CTRL_DELAY | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ val = RTL8211E_CTRL_DELAY | RTL8211E_RX_DELAY;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val = RTL8211E_CTRL_DELAY | RTL8211E_TX_DELAY;
+ break;
+ default: /* the rest of the modes imply leaving delays as is. */
+ return 0;
+ }
+
+ /* According to a sample driver there is a 0x1c config register on the
+ * 0xa4 extension page (0x7) layout. It can be used to disable/enable
+ * the RX/TX delays otherwise controlled by RXDLY/TXDLY pins.
+ * The configuration register definition:
+ * 14 = reserved
+ * 13 = Force Tx RX Delay controlled by bit12 bit11,
+ * 12 = RX Delay, 11 = TX Delay
+ * 10:0 = Test && debug settings reserved by realtek
+ */
+ oldpage = phy_select_page(phydev, 0x7);
+ if (oldpage < 0)
+ goto err_restore_page;
+
+ ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0xa4);
+ if (ret)
+ goto err_restore_page;
+
+ ret = __phy_modify(phydev, 0x1c, RTL8211E_CTRL_DELAY
+ | RTL8211E_TX_DELAY | RTL8211E_RX_DELAY,
+ val);
+
+err_restore_page:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+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 = 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;
+}
+
+/* get actual speed to cover the downshift case */
+static int rtlgen_get_speed(struct phy_device *phydev)
+{
+ int val;
+
+ if (!phydev->link)
+ return 0;
+
+ val = phy_read_paged(phydev, 0xa43, 0x12);
+ if (val < 0)
+ return val;
+
+ switch (val & RTLGEN_SPEED_MASK) {
+ case 0x0000:
+ phydev->speed = SPEED_10;
+ break;
+ case 0x0010:
+ phydev->speed = SPEED_100;
+ break;
+ case 0x0020:
+ phydev->speed = SPEED_1000;
+ break;
+ case 0x0200:
+ phydev->speed = SPEED_10000;
+ break;
+ case 0x0210:
+ phydev->speed = SPEED_2500;
+ break;
+ case 0x0220:
+ phydev->speed = SPEED_5000;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int rtlgen_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_read_status(phydev);
+ if (ret < 0)
+ return ret;
+
+ return rtlgen_get_speed(phydev);
+}
+
+static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
+{
+ int ret;
+
+ if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) {
+ rtl821x_write_page(phydev, 0xa5c);
+ ret = __phy_read(phydev, 0x12);
+ rtl821x_write_page(phydev, 0);
+ } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+ rtl821x_write_page(phydev, 0xa5d);
+ ret = __phy_read(phydev, 0x10);
+ rtl821x_write_page(phydev, 0);
+ } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) {
+ rtl821x_write_page(phydev, 0xa5d);
+ ret = __phy_read(phydev, 0x11);
+ rtl821x_write_page(phydev, 0);
+ } else {
+ ret = -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int rtlgen_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
+ u16 val)
+{
+ int ret;
+
+ if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) {
+ rtl821x_write_page(phydev, 0xa5d);
+ ret = __phy_write(phydev, 0x10, val);
+ rtl821x_write_page(phydev, 0);
+ } else {
+ ret = -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int rtl822x_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
+{
+ int ret = rtlgen_read_mmd(phydev, devnum, regnum);
+
+ if (ret != -EOPNOTSUPP)
+ return ret;
+
+ if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE2) {
+ rtl821x_write_page(phydev, 0xa6e);
+ ret = __phy_read(phydev, 0x16);
+ rtl821x_write_page(phydev, 0);
+ } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
+ rtl821x_write_page(phydev, 0xa6d);
+ ret = __phy_read(phydev, 0x12);
+ rtl821x_write_page(phydev, 0);
+ } else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2) {
+ rtl821x_write_page(phydev, 0xa6d);
+ ret = __phy_read(phydev, 0x10);
+ rtl821x_write_page(phydev, 0);
+ }
+
+ return ret;
+}
+
+static int rtl822x_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
+ u16 val)
+{
+ int ret = rtlgen_write_mmd(phydev, devnum, regnum, val);
+
+ if (ret != -EOPNOTSUPP)
+ return ret;
+
+ if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) {
+ rtl821x_write_page(phydev, 0xa6d);
+ ret = __phy_write(phydev, 0x12, val);
+ rtl821x_write_page(phydev, 0);
+ }
+
+ return ret;
+}
+
+static int rtl822x_get_features(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_paged(phydev, 0xa61, 0x13);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->supported, val & RTL_SUPPORTS_2500FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ phydev->supported, val & RTL_SUPPORTS_5000FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ phydev->supported, val & RTL_SUPPORTS_10000FULL);
+
+ return genphy_read_abilities(phydev);
+}
+
+static int rtl822x_config_aneg(struct phy_device *phydev)
+{
+ int ret = 0;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ u16 adv2500 = 0;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->advertising))
+ adv2500 = RTL_ADV_2500FULL;
+
+ ret = phy_modify_paged_changed(phydev, 0xa5d, 0x12,
+ RTL_ADV_2500FULL, adv2500);
+ if (ret < 0)
+ return ret;
+ }
+
+ return __genphy_config_aneg(phydev, ret);
+}
+
+static int rtl822x_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
+
+ if (lpadv < 0)
+ return lpadv;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ phydev->lp_advertising, lpadv & RTL_LPADV_10000FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ phydev->lp_advertising, lpadv & RTL_LPADV_5000FULL);
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->lp_advertising, lpadv & RTL_LPADV_2500FULL);
+ }
+
+ ret = genphy_read_status(phydev);
+ if (ret < 0)
+ return ret;
+
+ return rtlgen_get_speed(phydev);
+}
+
+static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
+{
+ int val;
+
+ phy_write(phydev, RTL821x_PAGE_SELECT, 0xa61);
+ val = phy_read(phydev, 0x13);
+ phy_write(phydev, RTL821x_PAGE_SELECT, 0);
+
+ return val >= 0 && val & RTL_SUPPORTS_2500FULL;
+}
+
+static int rtlgen_match_phy_device(struct phy_device *phydev)
+{
+ return phydev->phy_id == RTL_GENERIC_PHYID &&
+ !rtlgen_supports_2_5gbps(phydev);
+}
+
+static int rtl8226_match_phy_device(struct phy_device *phydev)
+{
+ return phydev->phy_id == RTL_GENERIC_PHYID &&
+ rtlgen_supports_2_5gbps(phydev);
+}
+
+static int rtlgen_resume(struct phy_device *phydev)
+{
+ int ret = genphy_resume(phydev);
+
+ /* Internal PHY's from RTL8168h up may not be instantly ready */
+ msleep(20);
+
+ return ret;
+}
+
+static int rtl9000a_config_init(struct phy_device *phydev)
+{
+ phydev->autoneg = AUTONEG_DISABLE;
+ phydev->speed = SPEED_100;
+ phydev->duplex = DUPLEX_FULL;
+
+ return 0;
+}
+
+static int rtl9000a_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+ u16 ctl = 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl |= CTL1000_AS_MASTER;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = phy_modify_changed(phydev, MII_CTRL1000, CTL1000_AS_MASTER, ctl);
+ if (ret == 1)
+ ret = genphy_soft_reset(phydev);
+
+ return ret;
+}
+
+static int rtl9000a_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+
+ ret = genphy_update_link(phydev);
+ if (ret)
+ return ret;
+
+ ret = phy_read(phydev, MII_CTRL1000);
+ if (ret < 0)
+ return ret;
+ if (ret & CTL1000_AS_MASTER)
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+ else
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+
+ ret = phy_read(phydev, MII_STAT1000);
+ if (ret < 0)
+ return ret;
+ if (ret & LPA_1000MSRES)
+ phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+ else
+ phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+
+ return 0;
+}
+
+static int rtl9000a_ack_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_read(phydev, RTL8211F_INSR);
+
+ return (err < 0) ? err : 0;
+}
+
+static int rtl9000a_config_intr(struct phy_device *phydev)
+{
+ u16 val;
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = rtl9000a_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ val = (u16)~RTL9000A_GINMR_LINK_STATUS;
+ err = phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
+ } else {
+ val = ~0;
+ err = phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
+ if (err)
+ return err;
+
+ err = rtl9000a_ack_interrupt(phydev);
+ }
+
+ return phy_write_paged(phydev, 0xa42, RTL9000A_GINMR, val);
+}
+
+static irqreturn_t rtl9000a_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, RTL8211F_INSR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & RTL8211F_INER_LINK_STATUS))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static struct phy_driver realtek_drvs[] = {
+ {
+ PHY_ID_MATCH_EXACT(0x00008201),
+ .name = "RTL8201CP Ethernet",
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc816),
+ .name = "RTL8201F Fast Ethernet",
+ .config_intr = &rtl8201_config_intr,
+ .handle_interrupt = rtl8201_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_MODEL(0x001cc880),
+ .name = "RTL8208 Fast Ethernet",
+ .read_mmd = genphy_read_mmd_unsupported,
+ .write_mmd = genphy_write_mmd_unsupported,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc910),
+ .name = "RTL8211 Gigabit Ethernet",
+ .config_aneg = rtl8211_config_aneg,
+ .read_mmd = &genphy_read_mmd_unsupported,
+ .write_mmd = &genphy_write_mmd_unsupported,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc912),
+ .name = "RTL8211B Gigabit Ethernet",
+ .config_intr = &rtl8211b_config_intr,
+ .handle_interrupt = rtl821x_handle_interrupt,
+ .read_mmd = &genphy_read_mmd_unsupported,
+ .write_mmd = &genphy_write_mmd_unsupported,
+ .suspend = rtl8211b_suspend,
+ .resume = rtl8211b_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc913),
+ .name = "RTL8211C Gigabit Ethernet",
+ .config_init = rtl8211c_config_init,
+ .read_mmd = &genphy_read_mmd_unsupported,
+ .write_mmd = &genphy_write_mmd_unsupported,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc914),
+ .name = "RTL8211DN Gigabit Ethernet",
+ .config_intr = rtl8211e_config_intr,
+ .handle_interrupt = rtl821x_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc915),
+ .name = "RTL8211E Gigabit Ethernet",
+ .config_init = &rtl8211e_config_init,
+ .config_intr = &rtl8211e_config_intr,
+ .handle_interrupt = rtl821x_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc916),
+ .name = "RTL8211F Gigabit Ethernet",
+ .probe = rtl821x_probe,
+ .config_init = &rtl8211f_config_init,
+ .read_status = rtlgen_read_status,
+ .config_intr = &rtl8211f_config_intr,
+ .handle_interrupt = rtl8211f_handle_interrupt,
+ .suspend = rtl821x_suspend,
+ .resume = rtl821x_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .flags = PHY_ALWAYS_CALL_SUSPEND,
+ }, {
+ PHY_ID_MATCH_EXACT(RTL_8211FVD_PHYID),
+ .name = "RTL8211F-VD Gigabit Ethernet",
+ .probe = rtl821x_probe,
+ .config_init = &rtl8211f_config_init,
+ .read_status = rtlgen_read_status,
+ .config_intr = &rtl8211f_config_intr,
+ .handle_interrupt = rtl8211f_handle_interrupt,
+ .suspend = rtl821x_suspend,
+ .resume = rtl821x_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .flags = PHY_ALWAYS_CALL_SUSPEND,
+ }, {
+ .name = "Generic FE-GE Realtek PHY",
+ .match_phy_device = rtlgen_match_phy_device,
+ .read_status = rtlgen_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .read_mmd = rtlgen_read_mmd,
+ .write_mmd = rtlgen_write_mmd,
+ }, {
+ .name = "RTL8226 2.5Gbps PHY",
+ .match_phy_device = rtl8226_match_phy_device,
+ .get_features = rtl822x_get_features,
+ .config_aneg = rtl822x_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .read_mmd = rtl822x_read_mmd,
+ .write_mmd = rtl822x_write_mmd,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc840),
+ .name = "RTL8226B_RTL8221B 2.5Gbps PHY",
+ .get_features = rtl822x_get_features,
+ .config_aneg = rtl822x_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .read_mmd = rtl822x_read_mmd,
+ .write_mmd = rtl822x_write_mmd,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc838),
+ .name = "RTL8226-CG 2.5Gbps PHY",
+ .get_features = rtl822x_get_features,
+ .config_aneg = rtl822x_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc848),
+ .name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
+ .get_features = rtl822x_get_features,
+ .config_aneg = rtl822x_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc849),
+ .name = "RTL8221B-VB-CG 2.5Gbps PHY",
+ .get_features = rtl822x_get_features,
+ .config_aneg = rtl822x_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc84a),
+ .name = "RTL8221B-VM-CG 2.5Gbps PHY",
+ .get_features = rtl822x_get_features,
+ .config_aneg = rtl822x_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc961),
+ .name = "RTL8366RB Gigabit Ethernet",
+ .config_init = &rtl8366rb_config_init,
+ /* These interrupts are handled by the irq controller
+ * embedded inside the RTL8366RB, they get unmasked when the
+ * irq is requested and ACKed by reading the status register,
+ * which is done by the irqchip code.
+ */
+ .config_intr = genphy_no_config_intr,
+ .handle_interrupt = genphy_handle_interrupt_no_ack,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001ccb00),
+ .name = "RTL9000AA_RTL9000AN Ethernet",
+ .features = PHY_BASIC_T1_FEATURES,
+ .config_init = rtl9000a_config_init,
+ .config_aneg = rtl9000a_config_aneg,
+ .read_status = rtl9000a_read_status,
+ .config_intr = rtl9000a_config_intr,
+ .handle_interrupt = rtl9000a_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc942),
+ .name = "RTL8365MB-VC Gigabit Ethernet",
+ /* Interrupt handling analogous to RTL8366RB */
+ .config_intr = genphy_no_config_intr,
+ .handle_interrupt = genphy_handle_interrupt_no_ack,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ },
+};
+
+module_phy_driver(realtek_drvs);
+
+static const struct mdio_device_id __maybe_unused realtek_tbl[] = {
+ { PHY_ID_MATCH_VENDOR(0x001cc800) },
+ { }
+};
+
+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..bb13e7518
--- /dev/null
+++ b/drivers/net/phy/rockchip.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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>
+ */
+
+#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)
+{
+ /*
+ * 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->state == PHY_RUNNING && phydev->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",
+ /* 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");
diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c
new file mode 100644
index 000000000..208a9393c
--- /dev/null
+++ b/drivers/net/phy/sfp-bus.c
@@ -0,0 +1,861 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/export.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/phylink.h>
+#include <linux/property.h>
+#include <linux/rtnetlink.h>
+#include <linux/slab.h>
+
+#include "sfp.h"
+
+/**
+ * struct sfp_bus - internal representation of a sfp bus
+ */
+struct sfp_bus {
+ /* private: */
+ struct kref kref;
+ struct list_head node;
+ const struct fwnode_handle *fwnode;
+
+ const struct sfp_socket_ops *socket_ops;
+ struct device *sfp_dev;
+ struct sfp *sfp;
+ const struct sfp_quirk *sfp_quirk;
+
+ const struct sfp_upstream_ops *upstream_ops;
+ void *upstream;
+ struct phy_device *phydev;
+
+ bool registered;
+ bool started;
+};
+
+/**
+ * sfp_parse_port() - Parse the EEPROM base ID, setting the port type
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ * @support: optional pointer to an array of unsigned long for the
+ * ethtool support mask
+ *
+ * Parse the EEPROM identification given in @id, and return one of
+ * %PORT_TP, %PORT_FIBRE or %PORT_OTHER. If @support is non-%NULL,
+ * also set the ethtool %ETHTOOL_LINK_MODE_xxx_BIT corresponding with
+ * the connector type.
+ *
+ * If the port type is not known, returns %PORT_OTHER.
+ */
+int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+ unsigned long *support)
+{
+ int port;
+
+ /* port is the physical connector, set this from the connector field. */
+ switch (id->base.connector) {
+ case SFF8024_CONNECTOR_SC:
+ case SFF8024_CONNECTOR_FIBERJACK:
+ case SFF8024_CONNECTOR_LC:
+ case SFF8024_CONNECTOR_MT_RJ:
+ case SFF8024_CONNECTOR_MU:
+ case SFF8024_CONNECTOR_OPTICAL_PIGTAIL:
+ case SFF8024_CONNECTOR_MPO_1X12:
+ case SFF8024_CONNECTOR_MPO_2X16:
+ port = PORT_FIBRE;
+ break;
+
+ case SFF8024_CONNECTOR_RJ45:
+ port = PORT_TP;
+ break;
+
+ case SFF8024_CONNECTOR_COPPER_PIGTAIL:
+ port = PORT_DA;
+ break;
+
+ case SFF8024_CONNECTOR_UNSPEC:
+ if (id->base.e1000_base_t) {
+ port = PORT_TP;
+ break;
+ }
+ fallthrough;
+ case SFF8024_CONNECTOR_SG: /* guess */
+ case SFF8024_CONNECTOR_HSSDC_II:
+ case SFF8024_CONNECTOR_NOSEPARATE:
+ case SFF8024_CONNECTOR_MXC_2X16:
+ port = PORT_OTHER;
+ break;
+ default:
+ dev_warn(bus->sfp_dev, "SFP: unknown connector id 0x%02x\n",
+ id->base.connector);
+ port = PORT_OTHER;
+ break;
+ }
+
+ if (support) {
+ switch (port) {
+ case PORT_FIBRE:
+ phylink_set(support, FIBRE);
+ break;
+
+ case PORT_TP:
+ phylink_set(support, TP);
+ break;
+ }
+ }
+
+ return port;
+}
+EXPORT_SYMBOL_GPL(sfp_parse_port);
+
+/**
+ * sfp_may_have_phy() - indicate whether the module may have a PHY
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ *
+ * Parse the EEPROM identification given in @id, and return whether
+ * this module may have a PHY.
+ */
+bool sfp_may_have_phy(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
+{
+ if (id->base.e1000_base_t)
+ return true;
+
+ if (id->base.phys_id != SFF8024_ID_DWDM_SFP) {
+ switch (id->base.extended_cc) {
+ case SFF8024_ECC_10GBASE_T_SFI:
+ case SFF8024_ECC_10GBASE_T_SR:
+ case SFF8024_ECC_5GBASE_T:
+ case SFF8024_ECC_2_5GBASE_T:
+ return true;
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(sfp_may_have_phy);
+
+/**
+ * sfp_parse_support() - Parse the eeprom id for supported link modes
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @id: a pointer to the module's &struct sfp_eeprom_id
+ * @support: pointer to an array of unsigned long for the ethtool support mask
+ * @interfaces: pointer to an array of unsigned long for phy interface modes
+ * 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 long *interfaces)
+{
+ unsigned int br_min, br_nom, br_max;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { 0, };
+
+ phylink_set(modes, Autoneg);
+ phylink_set(modes, Pause);
+ phylink_set(modes, Asym_Pause);
+
+ /* 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);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ if (id->base.e10g_base_lr) {
+ phylink_set(modes, 10000baseLR_Full);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ if (id->base.e10g_base_lrm) {
+ phylink_set(modes, 10000baseLRM_Full);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ if (id->base.e10g_base_er) {
+ phylink_set(modes, 10000baseER_Full);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ if (id->base.e1000_base_sx ||
+ id->base.e1000_base_lx ||
+ id->base.e1000_base_cx) {
+ phylink_set(modes, 1000baseX_Full);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+ }
+ if (id->base.e1000_base_t) {
+ phylink_set(modes, 1000baseT_Half);
+ phylink_set(modes, 1000baseT_Full);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+ __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
+ }
+
+ /* 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);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+ }
+
+ /* 100Base-FX, 100Base-LX, 100Base-PX, 100Base-BX10 */
+ if (id->base.e100_base_fx || id->base.e100_base_lx) {
+ phylink_set(modes, 100baseFX_Full);
+ __set_bit(PHY_INTERFACE_MODE_100BASEX, interfaces);
+ }
+ if ((id->base.e_base_px || id->base.e_base_bx10) && br_nom == 100) {
+ phylink_set(modes, 100baseFX_Full);
+ __set_bit(PHY_INTERFACE_MODE_100BASEX, interfaces);
+ }
+
+ /* 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);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ if (br_min <= 3200 && br_max >= 3100) {
+ phylink_set(modes, 2500baseX_Full);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+ }
+ if (br_min <= 1300 && br_max >= 1200) {
+ phylink_set(modes, 1000baseX_Full);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+ }
+ }
+ if (id->base.sfp_ct_passive) {
+ if (id->base.passive.sff8431_app_e) {
+ phylink_set(modes, 10000baseCR_Full);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ }
+ if (id->base.sfp_ct_active) {
+ if (id->base.active.sff8431_app_e ||
+ id->base.active.sff8431_lim) {
+ phylink_set(modes, 10000baseCR_Full);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ }
+ }
+
+ switch (id->base.extended_cc) {
+ case SFF8024_ECC_UNSPEC:
+ break;
+ case SFF8024_ECC_100G_25GAUI_C2M_AOC:
+ if (br_min <= 28000 && br_max >= 25000) {
+ /* 25GBASE-R, possibly with FEC */
+ __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
+ /* There is currently no link mode for 25000base
+ * with unspecified range, reuse SR.
+ */
+ phylink_set(modes, 25000baseSR_Full);
+ }
+ break;
+ case SFF8024_ECC_100GBASE_SR4_25GBASE_SR:
+ phylink_set(modes, 100000baseSR4_Full);
+ phylink_set(modes, 25000baseSR_Full);
+ __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
+ break;
+ case SFF8024_ECC_100GBASE_LR4_25GBASE_LR:
+ case SFF8024_ECC_100GBASE_ER4_25GBASE_ER:
+ phylink_set(modes, 100000baseLR4_ER4_Full);
+ break;
+ case SFF8024_ECC_100GBASE_CR4:
+ phylink_set(modes, 100000baseCR4_Full);
+ fallthrough;
+ case SFF8024_ECC_25GBASE_CR_S:
+ case SFF8024_ECC_25GBASE_CR_N:
+ phylink_set(modes, 25000baseCR_Full);
+ __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
+ break;
+ case SFF8024_ECC_10GBASE_T_SFI:
+ case SFF8024_ECC_10GBASE_T_SR:
+ phylink_set(modes, 10000baseT_Full);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
+ break;
+ case SFF8024_ECC_5GBASE_T:
+ phylink_set(modes, 5000baseT_Full);
+ __set_bit(PHY_INTERFACE_MODE_5GBASER, interfaces);
+ break;
+ case SFF8024_ECC_2_5GBASE_T:
+ phylink_set(modes, 2500baseT_Full);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+ 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);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+ }
+ if (id->base.br_nominal >= 12) {
+ phylink_set(modes, 1000baseX_Full);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+ }
+ }
+
+ /* If we haven't discovered any modes that this module supports, try
+ * the 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.
+ *
+ * Do the same for modules supporting 2500BASE-X. Note that some
+ * modules use 2500Mbaud rather than 3100 or 3200Mbaud for
+ * 2500BASE-X, so we allow some slack here.
+ */
+ if (bitmap_empty(modes, __ETHTOOL_LINK_MODE_MASK_NBITS) && br_nom) {
+ if (br_min <= 1300 && br_max >= 1200) {
+ phylink_set(modes, 1000baseX_Full);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, interfaces);
+ }
+ if (br_min <= 3200 && br_max >= 2500) {
+ phylink_set(modes, 2500baseX_Full);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+ }
+ }
+
+ if (bus->sfp_quirk && bus->sfp_quirk->modes)
+ bus->sfp_quirk->modes(id, modes, interfaces);
+
+ linkmode_or(support, support, modes);
+}
+EXPORT_SYMBOL_GPL(sfp_parse_support);
+
+/**
+ * sfp_select_interface() - Select appropriate phy_interface_t mode
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @link_modes: ethtool link modes mask
+ *
+ * Derive the phy_interface_t mode for the SFP module from the link
+ * modes mask.
+ */
+phy_interface_t sfp_select_interface(struct sfp_bus *bus,
+ unsigned long *link_modes)
+{
+ if (phylink_test(link_modes, 25000baseCR_Full) ||
+ phylink_test(link_modes, 25000baseKR_Full) ||
+ phylink_test(link_modes, 25000baseSR_Full))
+ return PHY_INTERFACE_MODE_25GBASER;
+
+ if (phylink_test(link_modes, 10000baseCR_Full) ||
+ phylink_test(link_modes, 10000baseSR_Full) ||
+ phylink_test(link_modes, 10000baseLR_Full) ||
+ phylink_test(link_modes, 10000baseLRM_Full) ||
+ phylink_test(link_modes, 10000baseER_Full) ||
+ phylink_test(link_modes, 10000baseT_Full))
+ return PHY_INTERFACE_MODE_10GBASER;
+
+ if (phylink_test(link_modes, 5000baseT_Full))
+ return PHY_INTERFACE_MODE_5GBASER;
+
+ if (phylink_test(link_modes, 2500baseX_Full))
+ return PHY_INTERFACE_MODE_2500BASEX;
+
+ if (phylink_test(link_modes, 1000baseT_Half) ||
+ phylink_test(link_modes, 1000baseT_Full))
+ return PHY_INTERFACE_MODE_SGMII;
+
+ if (phylink_test(link_modes, 1000baseX_Full))
+ return PHY_INTERFACE_MODE_1000BASEX;
+
+ if (phylink_test(link_modes, 100baseFX_Full))
+ return PHY_INTERFACE_MODE_100BASEX;
+
+ 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(const struct fwnode_handle *fwnode)
+{
+ struct sfp_bus *sfp, *new, *found = NULL;
+
+ new = kzalloc(sizeof(*new), GFP_KERNEL);
+
+ mutex_lock(&sfp_mutex);
+
+ list_for_each_entry(sfp, &sfp_buses, node) {
+ if (sfp->fwnode == fwnode) {
+ kref_get(&sfp->kref);
+ found = sfp;
+ break;
+ }
+ }
+
+ if (!found && new) {
+ kref_init(&new->kref);
+ new->fwnode = fwnode;
+ list_add(&new->node, &sfp_buses);
+ found = new;
+ new = NULL;
+ }
+
+ mutex_unlock(&sfp_mutex);
+
+ kfree(new);
+
+ return found;
+}
+
+static void sfp_bus_release(struct kref *kref)
+{
+ struct sfp_bus *bus = container_of(kref, struct sfp_bus, kref);
+
+ list_del(&bus->node);
+ mutex_unlock(&sfp_mutex);
+ kfree(bus);
+}
+
+/**
+ * sfp_bus_put() - put a reference on the &struct sfp_bus
+ * @bus: the &struct sfp_bus found via sfp_bus_find_fwnode()
+ *
+ * Put a reference on the &struct sfp_bus and free the underlying structure
+ * if this was the last reference.
+ */
+void sfp_bus_put(struct sfp_bus *bus)
+{
+ if (bus)
+ kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex);
+}
+EXPORT_SYMBOL_GPL(sfp_bus_put);
+
+static int sfp_register_bus(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = bus->upstream_ops;
+ int ret;
+
+ if (ops) {
+ if (ops->link_down)
+ ops->link_down(bus->upstream);
+ if (ops->connect_phy && bus->phydev) {
+ ret = ops->connect_phy(bus->upstream, bus->phydev);
+ if (ret)
+ return ret;
+ }
+ }
+ bus->registered = true;
+ bus->socket_ops->attach(bus->sfp);
+ if (bus->started)
+ bus->socket_ops->start(bus->sfp);
+ bus->upstream_ops->attach(bus->upstream, bus);
+ return 0;
+}
+
+static void sfp_unregister_bus(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = bus->upstream_ops;
+
+ if (bus->registered) {
+ bus->upstream_ops->detach(bus->upstream, bus);
+ if (bus->started)
+ bus->socket_ops->stop(bus->sfp);
+ bus->socket_ops->detach(bus->sfp);
+ if (bus->phydev && ops && ops->disconnect_phy)
+ ops->disconnect_phy(bus->upstream);
+ }
+ bus->registered = false;
+}
+
+/**
+ * sfp_get_module_info() - Get the ethtool_modinfo for a SFP module
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @modinfo: a &struct ethtool_modinfo
+ *
+ * Fill in the type and eeprom_len parameters in @modinfo for a module on
+ * the sfp bus specified by @bus.
+ *
+ * Returns 0 on success or a negative errno number.
+ */
+int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo)
+{
+ return bus->socket_ops->module_info(bus->sfp, modinfo);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_info);
+
+/**
+ * sfp_get_module_eeprom() - Read the SFP module EEPROM
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @ee: a &struct ethtool_eeprom
+ * @data: buffer to contain the EEPROM data (must be at least @ee->len bytes)
+ *
+ * Read the EEPROM as specified by the supplied @ee. See the documentation
+ * for &struct ethtool_eeprom for the region to be read.
+ *
+ * Returns 0 on success or a negative errno number.
+ */
+int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
+ u8 *data)
+{
+ return bus->socket_ops->module_eeprom(bus->sfp, ee, data);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_eeprom);
+
+/**
+ * sfp_get_module_eeprom_by_page() - Read a page from the SFP module EEPROM
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @page: a &struct ethtool_module_eeprom
+ * @extack: extack for reporting problems
+ *
+ * Read an EEPROM page as specified by the supplied @page. See the
+ * documentation for &struct ethtool_module_eeprom for the page to be read.
+ *
+ * Returns 0 on success or a negative errno number. More error
+ * information might be provided via extack
+ */
+int sfp_get_module_eeprom_by_page(struct sfp_bus *bus,
+ const struct ethtool_module_eeprom *page,
+ struct netlink_ext_ack *extack)
+{
+ return bus->socket_ops->module_eeprom_by_page(bus->sfp, page, extack);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_eeprom_by_page);
+
+/**
+ * sfp_upstream_start() - Inform the SFP that the network device is up
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Inform the SFP socket that the network device is now up, so that the
+ * module can be enabled by allowing TX_DISABLE to be deasserted. This
+ * should be called from the network device driver's &struct net_device_ops
+ * ndo_open() method.
+ */
+void sfp_upstream_start(struct sfp_bus *bus)
+{
+ if (bus->registered)
+ bus->socket_ops->start(bus->sfp);
+ bus->started = true;
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_start);
+
+/**
+ * sfp_upstream_stop() - Inform the SFP that the network device is down
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Inform the SFP socket that the network device is now up, so that the
+ * module can be disabled by asserting TX_DISABLE, disabling the laser
+ * in optical modules. This should be called from the network device
+ * driver's &struct net_device_ops ndo_stop() method.
+ */
+void sfp_upstream_stop(struct sfp_bus *bus)
+{
+ if (bus->registered)
+ bus->socket_ops->stop(bus->sfp);
+ bus->started = false;
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_stop);
+
+static void sfp_upstream_clear(struct sfp_bus *bus)
+{
+ bus->upstream_ops = NULL;
+ bus->upstream = NULL;
+}
+
+/**
+ * sfp_upstream_set_signal_rate() - set data signalling rate
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ * @rate_kbd: signalling rate in units of 1000 baud
+ *
+ * Configure the rate select settings on the SFP module for the signalling
+ * rate (not the same as the data rate).
+ *
+ * Locks that may be held:
+ * Phylink's state_mutex
+ * rtnl lock
+ * SFP's sm_mutex
+ */
+void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd)
+{
+ if (bus->registered)
+ bus->socket_ops->set_signal_rate(bus->sfp, rate_kbd);
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_set_signal_rate);
+
+/**
+ * sfp_bus_find_fwnode() - parse and locate the SFP bus from fwnode
+ * @fwnode: firmware node for the parent device (MAC or PHY)
+ *
+ * Parse the parent device's firmware node for a SFP bus, and locate
+ * the sfp_bus structure, incrementing its reference count. This must
+ * be put via sfp_bus_put() when done.
+ *
+ * Returns:
+ * - on success, a pointer to the sfp_bus structure,
+ * - %NULL if no SFP is specified,
+ * - on failure, an error pointer value:
+ *
+ * - corresponding to the errors detailed for
+ * fwnode_property_get_reference_args().
+ * - %-ENOMEM if we failed to allocate the bus.
+ * - an error from the upstream's connect_phy() method.
+ */
+struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode)
+{
+ struct fwnode_reference_args ref;
+ struct sfp_bus *bus;
+ int ret;
+
+ ret = fwnode_property_get_reference_args(fwnode, "sfp", NULL,
+ 0, 0, &ref);
+ if (ret == -ENOENT)
+ return NULL;
+ else if (ret < 0)
+ return ERR_PTR(ret);
+
+ if (!fwnode_device_is_available(ref.fwnode)) {
+ fwnode_handle_put(ref.fwnode);
+ return NULL;
+ }
+
+ bus = sfp_bus_get(ref.fwnode);
+ fwnode_handle_put(ref.fwnode);
+ if (!bus)
+ return ERR_PTR(-ENOMEM);
+
+ return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_bus_find_fwnode);
+
+/**
+ * sfp_bus_add_upstream() - parse and register the neighbouring device
+ * @bus: the &struct sfp_bus found via sfp_bus_find_fwnode()
+ * @upstream: the upstream private data
+ * @ops: the upstream's &struct sfp_upstream_ops
+ *
+ * Add upstream driver for the SFP bus, and if the bus is complete, register
+ * the SFP bus using sfp_register_upstream(). This takes a reference on the
+ * bus, so it is safe to put the bus after this call.
+ *
+ * Returns:
+ * - on success, a pointer to the sfp_bus structure,
+ * - %NULL if no SFP is specified,
+ * - on failure, an error pointer value:
+ *
+ * - corresponding to the errors detailed for
+ * fwnode_property_get_reference_args().
+ * - %-ENOMEM if we failed to allocate the bus.
+ * - an error from the upstream's connect_phy() method.
+ */
+int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream,
+ const struct sfp_upstream_ops *ops)
+{
+ int ret;
+
+ /* If no bus, return success */
+ if (!bus)
+ return 0;
+
+ rtnl_lock();
+ kref_get(&bus->kref);
+ bus->upstream_ops = ops;
+ bus->upstream = upstream;
+
+ if (bus->sfp) {
+ ret = sfp_register_bus(bus);
+ if (ret)
+ sfp_upstream_clear(bus);
+ } else {
+ ret = 0;
+ }
+ rtnl_unlock();
+
+ if (ret)
+ sfp_bus_put(bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_bus_add_upstream);
+
+/**
+ * sfp_bus_del_upstream() - Delete a sfp bus
+ * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+ *
+ * Delete a previously registered upstream connection for the SFP
+ * module. @bus should have been added by sfp_bus_add_upstream().
+ */
+void sfp_bus_del_upstream(struct sfp_bus *bus)
+{
+ if (bus) {
+ rtnl_lock();
+ if (bus->sfp)
+ sfp_unregister_bus(bus);
+ sfp_upstream_clear(bus);
+ rtnl_unlock();
+
+ sfp_bus_put(bus);
+ }
+}
+EXPORT_SYMBOL_GPL(sfp_bus_del_upstream);
+
+/* Socket driver entry points */
+int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+ int ret = 0;
+
+ if (ops && ops->connect_phy)
+ ret = ops->connect_phy(bus->upstream, phydev);
+
+ if (ret == 0)
+ bus->phydev = phydev;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_add_phy);
+
+void sfp_remove_phy(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+ if (ops && ops->disconnect_phy)
+ ops->disconnect_phy(bus->upstream);
+ bus->phydev = NULL;
+}
+EXPORT_SYMBOL_GPL(sfp_remove_phy);
+
+void sfp_link_up(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+ if (ops && ops->link_up)
+ ops->link_up(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_link_up);
+
+void sfp_link_down(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+ if (ops && ops->link_down)
+ ops->link_down(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_link_down);
+
+int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+ const struct sfp_quirk *quirk)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+ int ret = 0;
+
+ bus->sfp_quirk = quirk;
+
+ if (ops && ops->module_insert)
+ ret = ops->module_insert(bus->upstream, id);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_module_insert);
+
+void sfp_module_remove(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+ if (ops && ops->module_remove)
+ ops->module_remove(bus->upstream);
+
+ bus->sfp_quirk = NULL;
+}
+EXPORT_SYMBOL_GPL(sfp_module_remove);
+
+int sfp_module_start(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+ int ret = 0;
+
+ if (ops && ops->module_start)
+ ret = ops->module_start(bus->upstream);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_module_start);
+
+void sfp_module_stop(struct sfp_bus *bus)
+{
+ const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+ if (ops && ops->module_stop)
+ ops->module_stop(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_module_stop);
+
+static void sfp_socket_clear(struct sfp_bus *bus)
+{
+ bus->sfp_dev = NULL;
+ bus->sfp = NULL;
+ bus->socket_ops = NULL;
+}
+
+struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
+ const struct sfp_socket_ops *ops)
+{
+ struct sfp_bus *bus = sfp_bus_get(dev->fwnode);
+ int ret = 0;
+
+ if (bus) {
+ rtnl_lock();
+ bus->sfp_dev = dev;
+ bus->sfp = sfp;
+ bus->socket_ops = ops;
+
+ if (bus->upstream_ops) {
+ ret = sfp_register_bus(bus);
+ if (ret)
+ sfp_socket_clear(bus);
+ }
+ rtnl_unlock();
+ }
+
+ if (ret) {
+ sfp_bus_put(bus);
+ bus = NULL;
+ }
+
+ return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_register_socket);
+
+void sfp_unregister_socket(struct sfp_bus *bus)
+{
+ rtnl_lock();
+ if (bus->upstream_ops)
+ sfp_unregister_bus(bus);
+ sfp_socket_clear(bus);
+ rtnl_unlock();
+
+ sfp_bus_put(bus);
+}
+EXPORT_SYMBOL_GPL(sfp_unregister_socket);
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
new file mode 100644
index 000000000..3679a43f4
--- /dev/null
+++ b/drivers/net/phy/sfp.c
@@ -0,0 +1,3140 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/debugfs.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/mdio/mdio-i2c.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 "sfp.h"
+#include "swphy.h"
+
+enum {
+ GPIO_MODDEF0,
+ GPIO_LOS,
+ GPIO_TX_FAULT,
+ GPIO_TX_DISABLE,
+ GPIO_RS0,
+ GPIO_RS1,
+ 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_RS0 = BIT(GPIO_RS0),
+ SFP_F_RS1 = BIT(GPIO_RS1),
+
+ SFP_F_OUTPUTS = SFP_F_TX_DISABLE | SFP_F_RS0 | SFP_F_RS1,
+
+ SFP_E_INSERT = 0,
+ SFP_E_REMOVE,
+ SFP_E_DEV_ATTACH,
+ SFP_E_DEV_DETACH,
+ SFP_E_DEV_DOWN,
+ SFP_E_DEV_UP,
+ SFP_E_TX_FAULT,
+ SFP_E_TX_CLEAR,
+ SFP_E_LOS_HIGH,
+ SFP_E_LOS_LOW,
+ SFP_E_TIMEOUT,
+
+ SFP_MOD_EMPTY = 0,
+ SFP_MOD_ERROR,
+ SFP_MOD_PROBE,
+ SFP_MOD_WAITDEV,
+ SFP_MOD_HPOWER,
+ SFP_MOD_WAITPWR,
+ SFP_MOD_PRESENT,
+
+ SFP_DEV_DETACHED = 0,
+ SFP_DEV_DOWN,
+ SFP_DEV_UP,
+
+ SFP_S_DOWN = 0,
+ SFP_S_FAIL,
+ SFP_S_WAIT,
+ SFP_S_INIT,
+ SFP_S_INIT_PHY,
+ SFP_S_INIT_TX_FAULT,
+ SFP_S_WAIT_LOS,
+ SFP_S_LINK_UP,
+ SFP_S_TX_FAULT,
+ SFP_S_REINIT,
+ SFP_S_TX_DISABLE,
+};
+
+static const char * const mod_state_strings[] = {
+ [SFP_MOD_EMPTY] = "empty",
+ [SFP_MOD_ERROR] = "error",
+ [SFP_MOD_PROBE] = "probe",
+ [SFP_MOD_WAITDEV] = "waitdev",
+ [SFP_MOD_HPOWER] = "hpower",
+ [SFP_MOD_WAITPWR] = "waitpwr",
+ [SFP_MOD_PRESENT] = "present",
+};
+
+static const char *mod_state_to_str(unsigned short mod_state)
+{
+ if (mod_state >= ARRAY_SIZE(mod_state_strings))
+ return "Unknown module state";
+ return mod_state_strings[mod_state];
+}
+
+static const char * const dev_state_strings[] = {
+ [SFP_DEV_DETACHED] = "detached",
+ [SFP_DEV_DOWN] = "down",
+ [SFP_DEV_UP] = "up",
+};
+
+static const char *dev_state_to_str(unsigned short dev_state)
+{
+ if (dev_state >= ARRAY_SIZE(dev_state_strings))
+ return "Unknown device state";
+ return dev_state_strings[dev_state];
+}
+
+static const char * const event_strings[] = {
+ [SFP_E_INSERT] = "insert",
+ [SFP_E_REMOVE] = "remove",
+ [SFP_E_DEV_ATTACH] = "dev_attach",
+ [SFP_E_DEV_DETACH] = "dev_detach",
+ [SFP_E_DEV_DOWN] = "dev_down",
+ [SFP_E_DEV_UP] = "dev_up",
+ [SFP_E_TX_FAULT] = "tx_fault",
+ [SFP_E_TX_CLEAR] = "tx_clear",
+ [SFP_E_LOS_HIGH] = "los_high",
+ [SFP_E_LOS_LOW] = "los_low",
+ [SFP_E_TIMEOUT] = "timeout",
+};
+
+static const char *event_to_str(unsigned short event)
+{
+ if (event >= ARRAY_SIZE(event_strings))
+ return "Unknown event";
+ return event_strings[event];
+}
+
+static const char * const sm_state_strings[] = {
+ [SFP_S_DOWN] = "down",
+ [SFP_S_FAIL] = "fail",
+ [SFP_S_WAIT] = "wait",
+ [SFP_S_INIT] = "init",
+ [SFP_S_INIT_PHY] = "init_phy",
+ [SFP_S_INIT_TX_FAULT] = "init_tx_fault",
+ [SFP_S_WAIT_LOS] = "wait_los",
+ [SFP_S_LINK_UP] = "link_up",
+ [SFP_S_TX_FAULT] = "tx_fault",
+ [SFP_S_REINIT] = "reinit",
+ [SFP_S_TX_DISABLE] = "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_names[] = {
+ "mod-def0",
+ "los",
+ "tx-fault",
+ "tx-disable",
+ "rate-select0",
+ "rate-select1",
+};
+
+static const enum gpiod_flags gpio_flags[] = {
+ GPIOD_IN,
+ GPIOD_IN,
+ GPIOD_IN,
+ GPIOD_ASIS,
+ GPIOD_ASIS,
+ GPIOD_ASIS,
+};
+
+/* t_start_up (SFF-8431) or t_init (SFF-8472) is the time required for a
+ * non-cooled module to initialise its laser safety circuitry. We wait
+ * an initial T_WAIT period before we check the tx fault to give any PHY
+ * on board (for a copper SFP) time to initialise.
+ */
+#define T_WAIT msecs_to_jiffies(50)
+#define T_START_UP msecs_to_jiffies(300)
+#define T_START_UP_BAD_GPON msecs_to_jiffies(60000)
+
+/* t_reset is the time required to assert the TX_DISABLE signal to reset
+ * an indicated TX_FAULT.
+ */
+#define T_RESET_US 10
+#define T_FAULT_RECOVER msecs_to_jiffies(1000)
+
+/* N_FAULT_INIT is the number of recovery attempts at module initialisation
+ * time. If the TX_FAULT signal is not deasserted after this number of
+ * attempts at clearing it, we decide that the module is faulty.
+ * N_FAULT is the same but after the module has initialised.
+ */
+#define N_FAULT_INIT 5
+#define N_FAULT 5
+
+/* T_PHY_RETRY is the time interval between attempts to probe the PHY.
+ * R_PHY_RETRY is the number of attempts.
+ */
+#define T_PHY_RETRY msecs_to_jiffies(50)
+#define R_PHY_RETRY 12
+
+/* SFP module presence detection is poor: the three MOD DEF signals are
+ * the same length on the PCB, which means it's possible for MOD DEF 0 to
+ * connect before the I2C bus on MOD DEF 1/2.
+ *
+ * The SFF-8472 specifies t_serial ("Time from power on until module is
+ * ready for data transmission over the two wire serial bus.") as 300ms.
+ */
+#define T_SERIAL msecs_to_jiffies(300)
+#define T_HPOWER_LEVEL msecs_to_jiffies(300)
+#define T_PROBE_RETRY_INIT msecs_to_jiffies(100)
+#define R_PROBE_RETRY_INIT 10
+#define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000)
+#define R_PROBE_RETRY_SLOW 12
+
+/* SFP modules appear to always have their PHY configured for bus address
+ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
+ * RollBall SFPs access phy via SFP Enhanced Digital Diagnostic Interface
+ * via address 0x51 (mdio-i2c will use RollBall protocol on this address).
+ */
+#define SFP_PHY_ADDR 22
+#define SFP_PHY_ADDR_ROLLBALL 17
+
+/* SFP_EEPROM_BLOCK_SIZE is the size of data chunk to read the EEPROM
+ * at a time. Some SFP modules and also some Linux I2C drivers do not like
+ * reads longer than 16 bytes.
+ */
+#define SFP_EEPROM_BLOCK_SIZE 16
+
+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;
+ enum mdio_i2c_proto mdio_protocol;
+ struct phy_device *mod_phy;
+ const struct sff_data *type;
+ size_t i2c_block_size;
+ u32 max_power_mW;
+
+ unsigned int (*get_state)(struct sfp *);
+ void (*set_state)(struct sfp *, unsigned int);
+ int (*read)(struct sfp *, bool, u8, void *, size_t);
+ int (*write)(struct sfp *, bool, u8, void *, size_t);
+
+ struct gpio_desc *gpio[GPIO_MAX];
+ int gpio_irq[GPIO_MAX];
+
+ bool need_poll;
+
+ /* Access rules:
+ * state_hw_drive: st_mutex held
+ * state_hw_mask: st_mutex held
+ * state_soft_mask: st_mutex held
+ * state: st_mutex held unless reading input bits
+ */
+ struct mutex st_mutex; /* Protects state */
+ unsigned int state_hw_drive;
+ unsigned int state_hw_mask;
+ unsigned int state_soft_mask;
+ unsigned int state;
+
+ struct delayed_work poll;
+ struct delayed_work timeout;
+ struct mutex sm_mutex; /* Protects state machine */
+ unsigned char sm_mod_state;
+ unsigned char sm_mod_tries_init;
+ unsigned char sm_mod_tries;
+ unsigned char sm_dev_state;
+ unsigned short sm_state;
+ unsigned char sm_fault_retries;
+ unsigned char sm_phy_retries;
+
+ struct sfp_eeprom_id id;
+ unsigned int module_power_mW;
+ unsigned int module_t_start_up;
+ unsigned int module_t_wait;
+
+ unsigned int rate_kbd;
+ unsigned int rs_threshold_kbd;
+ unsigned int rs_state_mask;
+
+ bool have_a2;
+ bool tx_fault_ignore;
+
+ const struct sfp_quirk *quirk;
+
+#if IS_ENABLED(CONFIG_HWMON)
+ struct sfp_diag diag;
+ struct delayed_work hwmon_probe;
+ unsigned int hwmon_tries;
+ struct device *hwmon_dev;
+ char *hwmon_name;
+#endif
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ struct dentry *debugfs_dir;
+#endif
+};
+
+static bool sff_module_supported(const struct sfp_eeprom_id *id)
+{
+ return id->base.phys_id == SFF8024_ID_SFF_8472 &&
+ id->base.phys_ext_id == SFP_PHYS_EXT_ID_SFP;
+}
+
+static const struct sff_data sff_data = {
+ .gpios = SFP_F_LOS | SFP_F_TX_FAULT | SFP_F_TX_DISABLE,
+ .module_supported = sff_module_supported,
+};
+
+static bool sfp_module_supported(const struct sfp_eeprom_id *id)
+{
+ if (id->base.phys_id == SFF8024_ID_SFP &&
+ id->base.phys_ext_id == SFP_PHYS_EXT_ID_SFP)
+ return true;
+
+ /* SFP GPON module Ubiquiti U-Fiber Instant has in its EEPROM stored
+ * phys id SFF instead of SFP. Therefore mark this module explicitly
+ * as supported based on vendor name and pn match.
+ */
+ if (id->base.phys_id == SFF8024_ID_SFF_8472 &&
+ id->base.phys_ext_id == SFP_PHYS_EXT_ID_SFP &&
+ !memcmp(id->base.vendor_name, "UBNT ", 16) &&
+ !memcmp(id->base.vendor_pn, "UF-INSTANT ", 16))
+ return true;
+
+ return false;
+}
+
+static const struct sff_data sfp_data = {
+ .gpios = SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT |
+ SFP_F_TX_DISABLE | SFP_F_RS0 | SFP_F_RS1,
+ .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 void sfp_fixup_long_startup(struct sfp *sfp)
+{
+ sfp->module_t_start_up = T_START_UP_BAD_GPON;
+}
+
+static void sfp_fixup_ignore_tx_fault(struct sfp *sfp)
+{
+ sfp->tx_fault_ignore = true;
+}
+
+// For 10GBASE-T short-reach modules
+static void sfp_fixup_10gbaset_30m(struct sfp *sfp)
+{
+ sfp->id.base.connector = SFF8024_CONNECTOR_RJ45;
+ sfp->id.base.extended_cc = SFF8024_ECC_10GBASE_T_SR;
+}
+
+static void sfp_fixup_rollball_proto(struct sfp *sfp, unsigned int secs)
+{
+ sfp->mdio_protocol = MDIO_I2C_ROLLBALL;
+ sfp->module_t_wait = msecs_to_jiffies(secs * 1000);
+}
+
+static void sfp_fixup_fs_10gt(struct sfp *sfp)
+{
+ sfp_fixup_10gbaset_30m(sfp);
+
+ // These SFPs need 4 seconds before the PHY can be accessed
+ sfp_fixup_rollball_proto(sfp, 4);
+}
+
+static void sfp_fixup_halny_gsfp(struct sfp *sfp)
+{
+ /* Ignore the TX_FAULT and LOS signals on this module.
+ * these are possibly used for other purposes on this
+ * module, e.g. a serial port.
+ */
+ sfp->state_hw_mask &= ~(SFP_F_TX_FAULT | SFP_F_LOS);
+}
+
+static void sfp_fixup_rollball(struct sfp *sfp)
+{
+ // Rollball SFPs need 25 seconds before the PHY can be accessed
+ sfp_fixup_rollball_proto(sfp, 25);
+}
+
+static void sfp_fixup_rollball_cc(struct sfp *sfp)
+{
+ sfp_fixup_rollball(sfp);
+
+ /* Some RollBall SFPs may have wrong (zero) extended compliance code
+ * burned in EEPROM. For PHY probing we need the correct one.
+ */
+ sfp->id.base.extended_cc = SFF8024_ECC_10GBASE_T_SFI;
+}
+
+static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+ unsigned long *modes,
+ unsigned long *interfaces)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, modes);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+}
+
+static void sfp_quirk_disable_autoneg(const struct sfp_eeprom_id *id,
+ unsigned long *modes,
+ unsigned long *interfaces)
+{
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, modes);
+}
+
+static void sfp_quirk_oem_2_5g(const struct sfp_eeprom_id *id,
+ unsigned long *modes,
+ unsigned long *interfaces)
+{
+ /* Copper 2.5G SFP */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, modes);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, interfaces);
+ sfp_quirk_disable_autoneg(id, modes, interfaces);
+}
+
+static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
+ unsigned long *modes,
+ unsigned long *interfaces)
+{
+ /* Ubiquiti U-Fiber Instant module claims that support all transceiver
+ * types including 10G Ethernet which is not truth. So clear all claimed
+ * modes and set only one mode which module supports: 1000baseX_Full.
+ */
+ linkmode_zero(modes);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, modes);
+}
+
+#define SFP_QUIRK(_v, _p, _m, _f) \
+ { .vendor = _v, .part = _p, .modes = _m, .fixup = _f, }
+#define SFP_QUIRK_M(_v, _p, _m) SFP_QUIRK(_v, _p, _m, NULL)
+#define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f)
+
+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
+ SFP_QUIRK_M("ALCATELLUCENT", "G010SP", sfp_quirk_2500basex),
+
+ // Alcatel Lucent G-010S-A can operate at 2500base-X, but report 3.2GBd
+ // NRZ in their EEPROM
+ SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex,
+ sfp_fixup_long_startup),
+
+ // Fiberstore SFP-10G-T doesn't identify as copper, and uses the
+ // Rollball protocol to talk to the PHY.
+ SFP_QUIRK_F("FS", "SFP-10G-T", sfp_fixup_fs_10gt),
+
+ // Fiberstore GPON-ONU-34-20BI can operate at 2500base-X, but report 1.2GBd
+ // NRZ in their EEPROM
+ SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex,
+ sfp_fixup_ignore_tx_fault),
+
+ SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
+
+ // HG MXPD-483II-F 2.5G supports 2500Base-X, but incorrectly reports
+ // 2600MBd in their EERPOM
+ SFP_QUIRK_M("HG GENUINE", "MXPD-483II", sfp_quirk_2500basex),
+
+ // Huawei MA5671A can operate at 2500base-X, but report 1.2GBd NRZ in
+ // their EEPROM
+ SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex,
+ sfp_fixup_ignore_tx_fault),
+
+ // FS 2.5G Base-T
+ SFP_QUIRK_M("FS", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
+
+ // Lantech 8330-262D-E can operate at 2500base-X, but incorrectly report
+ // 2500MBd NRZ in their EEPROM
+ SFP_QUIRK_M("Lantech", "8330-262D-E", sfp_quirk_2500basex),
+
+ SFP_QUIRK_M("UBNT", "UF-INSTANT", sfp_quirk_ubnt_uf_instant),
+
+ // Walsun HXSX-ATR[CI]-1 don't identify as copper, and use the
+ // Rollball protocol to talk to the PHY.
+ SFP_QUIRK_F("Walsun", "HXSX-ATRC-1", sfp_fixup_fs_10gt),
+ SFP_QUIRK_F("Walsun", "HXSX-ATRI-1", sfp_fixup_fs_10gt),
+
+ SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
+ SFP_QUIRK_M("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
+ SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
+ SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
+ SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
+ SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
+};
+
+static size_t sfp_strlen(const char *str, size_t maxlen)
+{
+ size_t size, i;
+
+ /* Trailing characters should be filled with space chars, but
+ * some manufacturers can't read SFF-8472 and use NUL.
+ */
+ for (i = 0, size = 0; i < maxlen; i++)
+ if (str[i] != ' ' && str[i] != '\0')
+ 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;
+}
+
+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)
+{
+ unsigned int drive;
+
+ if (state & SFP_F_PRESENT)
+ /* If the module is present, drive the requested signals */
+ drive = sfp->state_hw_drive;
+ else
+ /* Otherwise, let them float to the pull-ups */
+ drive = 0;
+
+ if (sfp->gpio[GPIO_TX_DISABLE]) {
+ if (drive & SFP_F_TX_DISABLE)
+ gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
+ state & SFP_F_TX_DISABLE);
+ else
+ gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
+ }
+
+ if (sfp->gpio[GPIO_RS0]) {
+ if (drive & SFP_F_RS0)
+ gpiod_direction_output(sfp->gpio[GPIO_RS0],
+ state & SFP_F_RS0);
+ else
+ gpiod_direction_input(sfp->gpio[GPIO_RS0]);
+ }
+
+ if (sfp->gpio[GPIO_RS1]) {
+ if (drive & SFP_F_RS1)
+ gpiod_direction_output(sfp->gpio[GPIO_RS1],
+ state & SFP_F_RS1);
+ else
+ gpiod_direction_input(sfp->gpio[GPIO_RS1]);
+ }
+}
+
+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 block_size = sfp->i2c_block_size;
+ 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 > block_size)
+ this_len = block_size;
+
+ 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)
+{
+ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+ return -EINVAL;
+
+ sfp->i2c = i2c;
+ sfp->read = sfp_i2c_read;
+ sfp->write = sfp_i2c_write;
+
+ return 0;
+}
+
+static int sfp_i2c_mdiobus_create(struct sfp *sfp)
+{
+ struct mii_bus *i2c_mii;
+ int ret;
+
+ i2c_mii = mdio_i2c_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
+ 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;
+}
+
+static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
+{
+ mdiobus_unregister(sfp->i2c_mii);
+ sfp->i2c_mii = NULL;
+}
+
+/* Interface */
+static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+{
+ return sfp->read(sfp, a2, addr, buf, len);
+}
+
+static int sfp_write(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+{
+ return sfp->write(sfp, a2, addr, buf, len);
+}
+
+static int sfp_modify_u8(struct sfp *sfp, bool a2, u8 addr, u8 mask, u8 val)
+{
+ int ret;
+ u8 old, v;
+
+ ret = sfp_read(sfp, a2, addr, &old, sizeof(old));
+ if (ret != sizeof(old))
+ return ret;
+
+ v = (old & ~mask) | (val & mask);
+ if (v == old)
+ return sizeof(v);
+
+ return sfp_write(sfp, a2, addr, &v, sizeof(v));
+}
+
+static unsigned int sfp_soft_get_state(struct sfp *sfp)
+{
+ unsigned int state = 0;
+ u8 status;
+ int ret;
+
+ ret = sfp_read(sfp, true, SFP_STATUS, &status, sizeof(status));
+ if (ret == sizeof(status)) {
+ if (status & SFP_STATUS_RX_LOS)
+ state |= SFP_F_LOS;
+ if (status & SFP_STATUS_TX_FAULT)
+ state |= SFP_F_TX_FAULT;
+ } else {
+ dev_err_ratelimited(sfp->dev,
+ "failed to read SFP soft status: %pe\n",
+ ERR_PTR(ret));
+ /* Preserve the current state */
+ state = sfp->state;
+ }
+
+ return state & sfp->state_soft_mask;
+}
+
+static void sfp_soft_set_state(struct sfp *sfp, unsigned int state,
+ unsigned int soft)
+{
+ u8 mask = 0;
+ u8 val = 0;
+
+ if (soft & SFP_F_TX_DISABLE)
+ mask |= SFP_STATUS_TX_DISABLE_FORCE;
+ if (state & SFP_F_TX_DISABLE)
+ val |= SFP_STATUS_TX_DISABLE_FORCE;
+
+ if (soft & SFP_F_RS0)
+ mask |= SFP_STATUS_RS0_SELECT;
+ if (state & SFP_F_RS0)
+ val |= SFP_STATUS_RS0_SELECT;
+
+ if (mask)
+ sfp_modify_u8(sfp, true, SFP_STATUS, mask, val);
+
+ val = mask = 0;
+ if (soft & SFP_F_RS1)
+ mask |= SFP_EXT_STATUS_RS1_SELECT;
+ if (state & SFP_F_RS1)
+ val |= SFP_EXT_STATUS_RS1_SELECT;
+
+ if (mask)
+ sfp_modify_u8(sfp, true, SFP_EXT_STATUS, mask, val);
+}
+
+static void sfp_soft_start_poll(struct sfp *sfp)
+{
+ const struct sfp_eeprom_id *id = &sfp->id;
+ unsigned int mask = 0;
+
+ if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE)
+ mask |= SFP_F_TX_DISABLE;
+ if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT)
+ mask |= SFP_F_TX_FAULT;
+ if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS)
+ mask |= SFP_F_LOS;
+ if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RATE_SELECT)
+ mask |= sfp->rs_state_mask;
+
+ mutex_lock(&sfp->st_mutex);
+ // Poll the soft state for hardware pins we want to ignore
+ sfp->state_soft_mask = ~sfp->state_hw_mask & mask;
+
+ if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) &&
+ !sfp->need_poll)
+ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+ mutex_unlock(&sfp->st_mutex);
+}
+
+static void sfp_soft_stop_poll(struct sfp *sfp)
+{
+ mutex_lock(&sfp->st_mutex);
+ sfp->state_soft_mask = 0;
+ mutex_unlock(&sfp->st_mutex);
+}
+
+/* sfp_get_state() - must be called with st_mutex held, or in the
+ * initialisation path.
+ */
+static unsigned int sfp_get_state(struct sfp *sfp)
+{
+ unsigned int soft = sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT);
+ unsigned int state;
+
+ state = sfp->get_state(sfp) & sfp->state_hw_mask;
+ if (state & SFP_F_PRESENT && soft)
+ state |= sfp_soft_get_state(sfp);
+
+ return state;
+}
+
+/* sfp_set_state() - must be called with st_mutex held, or in the
+ * initialisation path.
+ */
+static void sfp_set_state(struct sfp *sfp, unsigned int state)
+{
+ unsigned int soft;
+
+ sfp->set_state(sfp, state);
+
+ soft = sfp->state_soft_mask & SFP_F_OUTPUTS;
+ if (state & SFP_F_PRESENT && soft)
+ sfp_soft_set_state(sfp, state, soft);
+}
+
+static void sfp_mod_state(struct sfp *sfp, unsigned int mask, unsigned int set)
+{
+ mutex_lock(&sfp->st_mutex);
+ sfp->state = (sfp->state & ~mask) | set;
+ sfp_set_state(sfp, sfp->state);
+ mutex_unlock(&sfp->st_mutex);
+}
+
+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;
+ fallthrough;
+ case hwmon_temp_input:
+ case hwmon_temp_label:
+ 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;
+ fallthrough;
+ case hwmon_in_input:
+ case hwmon_in_label:
+ 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;
+ fallthrough;
+ case hwmon_curr_input:
+ case hwmon_curr_label:
+ 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;
+ fallthrough;
+ case hwmon_power_input:
+ case hwmon_power_label:
+ 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 char *const sfp_hwmon_power_labels[] = {
+ "TX_power",
+ "RX_power",
+};
+
+static int sfp_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_label:
+ *str = "bias";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ *str = "temperature";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_label:
+ *str = "VCC";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_label:
+ *str = sfp_hwmon_power_labels[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops sfp_hwmon_ops = {
+ .is_visible = sfp_hwmon_is_visible,
+ .read = sfp_hwmon_read,
+ .read_string = sfp_hwmon_read_string,
+};
+
+static const struct hwmon_channel_info * const sfp_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(in,
+ 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 |
+ HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ 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 |
+ HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ 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 |
+ HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(power,
+ /* 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 |
+ HWMON_P_LABEL,
+ /* 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 |
+ HWMON_P_LABEL),
+ NULL,
+};
+
+static const struct hwmon_chip_info sfp_hwmon_chip_info = {
+ .ops = &sfp_hwmon_ops,
+ .info = sfp_hwmon_info,
+};
+
+static void sfp_hwmon_probe(struct work_struct *work)
+{
+ struct sfp *sfp = container_of(work, struct sfp, hwmon_probe.work);
+ int err;
+
+ /* hwmon interface needs to access 16bit registers in atomic way to
+ * guarantee coherency of the diagnostic monitoring data. If it is not
+ * possible to guarantee coherency because EEPROM is broken in such way
+ * that does not support atomic 16bit read operation then we have to
+ * skip registration of hwmon device.
+ */
+ if (sfp->i2c_block_size < 2) {
+ dev_info(sfp->dev,
+ "skipping hwmon device registration due to broken EEPROM\n");
+ dev_info(sfp->dev,
+ "diagnostic EEPROM area cannot be read atomically to guarantee data coherency\n");
+ return;
+ }
+
+ err = sfp_read(sfp, true, 0, &sfp->diag, sizeof(sfp->diag));
+ if (err < 0) {
+ if (sfp->hwmon_tries--) {
+ mod_delayed_work(system_wq, &sfp->hwmon_probe,
+ T_PROBE_RETRY_SLOW);
+ } else {
+ dev_warn(sfp->dev, "hwmon probe failed: %pe\n",
+ ERR_PTR(err));
+ }
+ return;
+ }
+
+ sfp->hwmon_name = hwmon_sanitize_name(dev_name(sfp->dev));
+ if (IS_ERR(sfp->hwmon_name)) {
+ dev_err(sfp->dev, "out of memory for hwmon name\n");
+ return;
+ }
+
+ sfp->hwmon_dev = hwmon_device_register_with_info(sfp->dev,
+ sfp->hwmon_name, sfp,
+ &sfp_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(sfp->hwmon_dev))
+ dev_err(sfp->dev, "failed to register hwmon device: %ld\n",
+ PTR_ERR(sfp->hwmon_dev));
+}
+
+static int sfp_hwmon_insert(struct sfp *sfp)
+{
+ if (sfp->have_a2 && sfp->id.ext.diagmon & SFP_DIAGMON_DDM) {
+ mod_delayed_work(system_wq, &sfp->hwmon_probe, 1);
+ sfp->hwmon_tries = R_PROBE_RETRY_SLOW;
+ }
+
+ return 0;
+}
+
+static void sfp_hwmon_remove(struct sfp *sfp)
+{
+ cancel_delayed_work_sync(&sfp->hwmon_probe);
+ if (!IS_ERR_OR_NULL(sfp->hwmon_dev)) {
+ hwmon_device_unregister(sfp->hwmon_dev);
+ sfp->hwmon_dev = NULL;
+ kfree(sfp->hwmon_name);
+ }
+}
+
+static int sfp_hwmon_init(struct sfp *sfp)
+{
+ INIT_DELAYED_WORK(&sfp->hwmon_probe, sfp_hwmon_probe);
+
+ return 0;
+}
+
+static void sfp_hwmon_exit(struct sfp *sfp)
+{
+ cancel_delayed_work_sync(&sfp->hwmon_probe);
+}
+#else
+static int sfp_hwmon_insert(struct sfp *sfp)
+{
+ return 0;
+}
+
+static void sfp_hwmon_remove(struct sfp *sfp)
+{
+}
+
+static int sfp_hwmon_init(struct sfp *sfp)
+{
+ return 0;
+}
+
+static void sfp_hwmon_exit(struct sfp *sfp)
+{
+}
+#endif
+
+/* Helpers */
+static void sfp_module_tx_disable(struct sfp *sfp)
+{
+ dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+ sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
+ sfp_mod_state(sfp, SFP_F_TX_DISABLE, SFP_F_TX_DISABLE);
+}
+
+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_mod_state(sfp, SFP_F_TX_DISABLE, 0);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static int sfp_debug_state_show(struct seq_file *s, void *data)
+{
+ struct sfp *sfp = s->private;
+
+ seq_printf(s, "Module state: %s\n",
+ mod_state_to_str(sfp->sm_mod_state));
+ seq_printf(s, "Module probe attempts: %d %d\n",
+ R_PROBE_RETRY_INIT - sfp->sm_mod_tries_init,
+ R_PROBE_RETRY_SLOW - sfp->sm_mod_tries);
+ seq_printf(s, "Device state: %s\n",
+ dev_state_to_str(sfp->sm_dev_state));
+ seq_printf(s, "Main state: %s\n",
+ sm_state_to_str(sfp->sm_state));
+ seq_printf(s, "Fault recovery remaining retries: %d\n",
+ sfp->sm_fault_retries);
+ seq_printf(s, "PHY probe remaining retries: %d\n",
+ sfp->sm_phy_retries);
+ seq_printf(s, "Signalling rate: %u kBd\n", sfp->rate_kbd);
+ seq_printf(s, "Rate select threshold: %u kBd\n",
+ sfp->rs_threshold_kbd);
+ seq_printf(s, "moddef0: %d\n", !!(sfp->state & SFP_F_PRESENT));
+ seq_printf(s, "rx_los: %d\n", !!(sfp->state & SFP_F_LOS));
+ seq_printf(s, "tx_fault: %d\n", !!(sfp->state & SFP_F_TX_FAULT));
+ seq_printf(s, "tx_disable: %d\n", !!(sfp->state & SFP_F_TX_DISABLE));
+ seq_printf(s, "rs0: %d\n", !!(sfp->state & SFP_F_RS0));
+ seq_printf(s, "rs1: %d\n", !!(sfp->state & SFP_F_RS1));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(sfp_debug_state);
+
+static void sfp_debugfs_init(struct sfp *sfp)
+{
+ sfp->debugfs_dir = debugfs_create_dir(dev_name(sfp->dev), NULL);
+
+ debugfs_create_file("state", 0600, sfp->debugfs_dir, sfp,
+ &sfp_debug_state_fops);
+}
+
+static void sfp_debugfs_exit(struct sfp *sfp)
+{
+ debugfs_remove_recursive(sfp->debugfs_dir);
+}
+#else
+static void sfp_debugfs_init(struct sfp *sfp)
+{
+}
+
+static void sfp_debugfs_exit(struct sfp *sfp)
+{
+}
+#endif
+
+static void sfp_module_tx_fault_reset(struct sfp *sfp)
+{
+ unsigned int state;
+
+ mutex_lock(&sfp->st_mutex);
+ state = sfp->state;
+ if (!(state & SFP_F_TX_DISABLE)) {
+ sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
+
+ udelay(T_RESET_US);
+
+ sfp_set_state(sfp, state);
+ }
+ mutex_unlock(&sfp->st_mutex);
+}
+
+/* SFP state machine */
+static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
+{
+ if (timeout)
+ mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
+ timeout);
+ else
+ cancel_delayed_work(&sfp->timeout);
+}
+
+static void sfp_sm_next(struct sfp *sfp, unsigned int state,
+ unsigned int timeout)
+{
+ sfp->sm_state = state;
+ sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_mod_next(struct sfp *sfp, unsigned int state,
+ unsigned int timeout)
+{
+ sfp->sm_mod_state = state;
+ sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_phy_detach(struct sfp *sfp)
+{
+ sfp_remove_phy(sfp->sfp_bus);
+ phy_device_remove(sfp->mod_phy);
+ phy_device_free(sfp->mod_phy);
+ sfp->mod_phy = NULL;
+}
+
+static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45)
+{
+ struct phy_device *phy;
+ int err;
+
+ phy = get_phy_device(sfp->i2c_mii, addr, is_c45);
+ if (phy == ERR_PTR(-ENODEV))
+ return PTR_ERR(phy);
+ if (IS_ERR(phy)) {
+ dev_err(sfp->dev, "mdiobus scan returned %pe\n", phy);
+ return PTR_ERR(phy);
+ }
+
+ /* Mark this PHY as being on a SFP module */
+ phy->is_on_sfp_module = true;
+
+ err = phy_device_register(phy);
+ if (err) {
+ phy_device_free(phy);
+ dev_err(sfp->dev, "phy_device_register failed: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ err = sfp_add_phy(sfp->sfp_bus, phy);
+ if (err) {
+ phy_device_remove(phy);
+ phy_device_free(phy);
+ dev_err(sfp->dev, "sfp_add_phy failed: %pe\n", ERR_PTR(err));
+ return err;
+ }
+
+ sfp->mod_phy = phy;
+
+ return 0;
+}
+
+static void sfp_sm_link_up(struct sfp *sfp)
+{
+ sfp_link_up(sfp->sfp_bus);
+ sfp_sm_next(sfp, SFP_S_LINK_UP, 0);
+}
+
+static void sfp_sm_link_down(struct sfp *sfp)
+{
+ sfp_link_down(sfp->sfp_bus);
+}
+
+static void sfp_sm_link_check_los(struct sfp *sfp)
+{
+ const __be16 los_inverted = cpu_to_be16(SFP_OPTIONS_LOS_INVERTED);
+ const __be16 los_normal = cpu_to_be16(SFP_OPTIONS_LOS_NORMAL);
+ __be16 los_options = sfp->id.ext.options & (los_inverted | los_normal);
+ bool los = false;
+
+ /* If neither SFP_OPTIONS_LOS_INVERTED nor SFP_OPTIONS_LOS_NORMAL
+ * are set, we assume that no LOS signal is available. If both are
+ * set, we assume LOS is not implemented (and is meaningless.)
+ */
+ if (los_options == los_inverted)
+ los = !(sfp->state & SFP_F_LOS);
+ else if (los_options == los_normal)
+ los = !!(sfp->state & SFP_F_LOS);
+
+ 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)
+{
+ const __be16 los_inverted = cpu_to_be16(SFP_OPTIONS_LOS_INVERTED);
+ const __be16 los_normal = cpu_to_be16(SFP_OPTIONS_LOS_NORMAL);
+ __be16 los_options = sfp->id.ext.options & (los_inverted | los_normal);
+
+ return (los_options == los_inverted && event == SFP_E_LOS_LOW) ||
+ (los_options == los_normal && event == SFP_E_LOS_HIGH);
+}
+
+static bool sfp_los_event_inactive(struct sfp *sfp, unsigned int event)
+{
+ const __be16 los_inverted = cpu_to_be16(SFP_OPTIONS_LOS_INVERTED);
+ const __be16 los_normal = cpu_to_be16(SFP_OPTIONS_LOS_NORMAL);
+ __be16 los_options = sfp->id.ext.options & (los_inverted | los_normal);
+
+ return (los_options == los_inverted && event == SFP_E_LOS_HIGH) ||
+ (los_options == los_normal && event == SFP_E_LOS_LOW);
+}
+
+static void sfp_sm_fault(struct sfp *sfp, unsigned int next_state, bool warn)
+{
+ if (sfp->sm_fault_retries && !--sfp->sm_fault_retries) {
+ dev_err(sfp->dev,
+ "module persistently indicates fault, disabling\n");
+ sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
+ } else {
+ if (warn)
+ dev_err(sfp->dev, "module transmit fault indicated\n");
+
+ sfp_sm_next(sfp, next_state, T_FAULT_RECOVER);
+ }
+}
+
+static int sfp_sm_add_mdio_bus(struct sfp *sfp)
+{
+ if (sfp->mdio_protocol != MDIO_I2C_NONE)
+ return sfp_i2c_mdiobus_create(sfp);
+
+ return 0;
+}
+
+/* Probe a SFP for a PHY device if the module supports copper - the PHY
+ * normally sits at I2C bus address 0x56, and may either be a clause 22
+ * or clause 45 PHY.
+ *
+ * Clause 22 copper SFP modules normally operate in Cisco SGMII mode with
+ * negotiation enabled, but some may be in 1000base-X - which is for the
+ * PHY driver to determine.
+ *
+ * Clause 45 copper SFP+ modules (10G) appear to switch their interface
+ * mode according to the negotiated line speed.
+ */
+static int sfp_sm_probe_for_phy(struct sfp *sfp)
+{
+ int err = 0;
+
+ switch (sfp->mdio_protocol) {
+ case MDIO_I2C_NONE:
+ break;
+
+ case MDIO_I2C_MARVELL_C22:
+ err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR, false);
+ break;
+
+ case MDIO_I2C_C45:
+ err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR, true);
+ break;
+
+ case MDIO_I2C_ROLLBALL:
+ err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR_ROLLBALL, true);
+ break;
+ }
+
+ return err;
+}
+
+static int sfp_module_parse_power(struct sfp *sfp)
+{
+ u32 power_mW = 1000;
+ bool supports_a2;
+
+ if (sfp->id.ext.sff8472_compliance >= SFP_SFF8472_COMPLIANCE_REV10_2 &&
+ sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_POWER_DECL))
+ power_mW = 1500;
+ /* Added in Rev 11.9, but there is no compliance code for this */
+ if (sfp->id.ext.sff8472_compliance >= SFP_SFF8472_COMPLIANCE_REV11_4 &&
+ sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_HIGH_POWER_LEVEL))
+ power_mW = 2000;
+
+ /* Power level 1 modules (max. 1W) are always supported. */
+ if (power_mW <= 1000) {
+ sfp->module_power_mW = power_mW;
+ return 0;
+ }
+
+ supports_a2 = sfp->id.ext.sff8472_compliance !=
+ SFP_SFF8472_COMPLIANCE_NONE ||
+ sfp->id.ext.diagmon & SFP_DIAGMON_DDM;
+
+ if (power_mW > sfp->max_power_mW) {
+ /* Module power specification exceeds the allowed maximum. */
+ if (!supports_a2) {
+ /* The module appears not to implement bus address
+ * 0xa2, so assume that the module powers up in the
+ * indicated mode.
+ */
+ dev_err(sfp->dev,
+ "Host does not support %u.%uW modules\n",
+ power_mW / 1000, (power_mW / 100) % 10);
+ return -EINVAL;
+ } else {
+ dev_warn(sfp->dev,
+ "Host does not support %u.%uW modules, module left in power mode 1\n",
+ power_mW / 1000, (power_mW / 100) % 10);
+ return 0;
+ }
+ }
+
+ if (!supports_a2) {
+ /* The module power level is below the host maximum and the
+ * module appears not to implement bus address 0xa2, so assume
+ * that the module powers up in the indicated mode.
+ */
+ return 0;
+ }
+
+ /* If the module requires a higher power mode, but also requires
+ * an address change sequence, warn the user that the module may
+ * not be functional.
+ */
+ if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE) {
+ dev_warn(sfp->dev,
+ "Address Change Sequence not supported but module requires %u.%uW, module may not be functional\n",
+ power_mW / 1000, (power_mW / 100) % 10);
+ return 0;
+ }
+
+ sfp->module_power_mW = power_mW;
+
+ return 0;
+}
+
+static int sfp_sm_mod_hpower(struct sfp *sfp, bool enable)
+{
+ int err;
+
+ err = sfp_modify_u8(sfp, true, SFP_EXT_STATUS,
+ SFP_EXT_STATUS_PWRLVL_SELECT,
+ enable ? SFP_EXT_STATUS_PWRLVL_SELECT : 0);
+ if (err != sizeof(u8)) {
+ dev_err(sfp->dev, "failed to %sable high power: %pe\n",
+ enable ? "en" : "dis", ERR_PTR(err));
+ return -EAGAIN;
+ }
+
+ if (enable)
+ dev_info(sfp->dev, "Module switched to %u.%uW power level\n",
+ sfp->module_power_mW / 1000,
+ (sfp->module_power_mW / 100) % 10);
+
+ return 0;
+}
+
+static void sfp_module_parse_rate_select(struct sfp *sfp)
+{
+ u8 rate_id;
+
+ sfp->rs_threshold_kbd = 0;
+ sfp->rs_state_mask = 0;
+
+ if (!(sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_RATE_SELECT)))
+ /* No support for RateSelect */
+ return;
+
+ /* Default to INF-8074 RateSelect operation. The signalling threshold
+ * rate is not well specified, so always select "Full Bandwidth", but
+ * SFF-8079 reveals that it is understood that RS0 will be low for
+ * 1.0625Gb/s and high for 2.125Gb/s. Choose a value half-way between.
+ * This method exists prior to SFF-8472.
+ */
+ sfp->rs_state_mask = SFP_F_RS0;
+ sfp->rs_threshold_kbd = 1594;
+
+ /* Parse the rate identifier, which is complicated due to history:
+ * SFF-8472 rev 9.5 marks this field as reserved.
+ * SFF-8079 references SFF-8472 rev 9.5 and defines bit 0. SFF-8472
+ * compliance is not required.
+ * SFF-8472 rev 10.2 defines this field using values 0..4
+ * SFF-8472 rev 11.0 redefines this field with bit 0 for SFF-8079
+ * and even values.
+ */
+ rate_id = sfp->id.base.rate_id;
+ if (rate_id == 0)
+ /* Unspecified */
+ return;
+
+ /* SFF-8472 rev 10.0..10.4 did not account for SFF-8079 using bit 0,
+ * and allocated value 3 to SFF-8431 independent tx/rx rate select.
+ * Convert this to a SFF-8472 rev 11.0 rate identifier.
+ */
+ if (sfp->id.ext.sff8472_compliance >= SFP_SFF8472_COMPLIANCE_REV10_2 &&
+ sfp->id.ext.sff8472_compliance < SFP_SFF8472_COMPLIANCE_REV11_0 &&
+ rate_id == 3)
+ rate_id = SFF_RID_8431;
+
+ if (rate_id & SFF_RID_8079) {
+ /* SFF-8079 RateSelect / Application Select in conjunction with
+ * SFF-8472 rev 9.5. SFF-8079 defines rate_id as a bitfield
+ * with only bit 0 used, which takes precedence over SFF-8472.
+ */
+ if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_APP_SELECT_SFF8079)) {
+ /* SFF-8079 Part 1 - rate selection between Fibre
+ * Channel 1.0625/2.125/4.25 Gbd modes. Note that RS0
+ * is high for 2125, so we have to subtract 1 to
+ * include it.
+ */
+ sfp->rs_threshold_kbd = 2125 - 1;
+ sfp->rs_state_mask = SFP_F_RS0;
+ }
+ return;
+ }
+
+ /* SFF-8472 rev 9.5 does not define the rate identifier */
+ if (sfp->id.ext.sff8472_compliance <= SFP_SFF8472_COMPLIANCE_REV9_5)
+ return;
+
+ /* SFF-8472 rev 11.0 defines rate_id as a numerical value which will
+ * always have bit 0 clear due to SFF-8079's bitfield usage of rate_id.
+ */
+ switch (rate_id) {
+ case SFF_RID_8431_RX_ONLY:
+ sfp->rs_threshold_kbd = 4250;
+ sfp->rs_state_mask = SFP_F_RS0;
+ break;
+
+ case SFF_RID_8431_TX_ONLY:
+ sfp->rs_threshold_kbd = 4250;
+ sfp->rs_state_mask = SFP_F_RS1;
+ break;
+
+ case SFF_RID_8431:
+ sfp->rs_threshold_kbd = 4250;
+ sfp->rs_state_mask = SFP_F_RS0 | SFP_F_RS1;
+ break;
+
+ case SFF_RID_10G8G:
+ sfp->rs_threshold_kbd = 9000;
+ sfp->rs_state_mask = SFP_F_RS0 | SFP_F_RS1;
+ break;
+ }
+}
+
+/* GPON modules based on Realtek RTL8672 and RTL9601C chips (e.g. V-SOL
+ * V2801F, CarlitoxxPro CPGOS03-0490, Ubiquiti U-Fiber Instant, ...) do
+ * not support multibyte reads from the EEPROM. Each multi-byte read
+ * operation returns just one byte of EEPROM followed by zeros. There is
+ * no way to identify which modules are using Realtek RTL8672 and RTL9601C
+ * chips. Moreover every OEM of V-SOL V2801F module puts its own vendor
+ * name and vendor id into EEPROM, so there is even no way to detect if
+ * module is V-SOL V2801F. Therefore check for those zeros in the read
+ * data and then based on check switch to reading EEPROM to one byte
+ * at a time.
+ */
+static bool sfp_id_needs_byte_io(struct sfp *sfp, void *buf, size_t len)
+{
+ size_t i, block_size = sfp->i2c_block_size;
+
+ /* Already using byte IO */
+ if (block_size == 1)
+ return false;
+
+ for (i = 1; i < len; i += block_size) {
+ if (memchr_inv(buf + i, '\0', min(block_size - 1, len - i)))
+ return false;
+ }
+ return true;
+}
+
+static int sfp_cotsworks_fixup_check(struct sfp *sfp, struct sfp_eeprom_id *id)
+{
+ u8 check;
+ int err;
+
+ if (id->base.phys_id != SFF8024_ID_SFF_8472 ||
+ id->base.phys_ext_id != SFP_PHYS_EXT_ID_SFP ||
+ id->base.connector != SFF8024_CONNECTOR_LC) {
+ dev_warn(sfp->dev, "Rewriting fiber module EEPROM with corrected values\n");
+ id->base.phys_id = SFF8024_ID_SFF_8472;
+ id->base.phys_ext_id = SFP_PHYS_EXT_ID_SFP;
+ id->base.connector = SFF8024_CONNECTOR_LC;
+ err = sfp_write(sfp, false, SFP_PHYS_ID, &id->base, 3);
+ if (err != 3) {
+ dev_err(sfp->dev,
+ "Failed to rewrite module EEPROM: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ /* Cotsworks modules have been found to require a delay between write operations. */
+ mdelay(50);
+
+ /* Update base structure checksum */
+ check = sfp_check(&id->base, sizeof(id->base) - 1);
+ err = sfp_write(sfp, false, SFP_CC_BASE, &check, 1);
+ if (err != 1) {
+ dev_err(sfp->dev,
+ "Failed to update base structure checksum in fiber module EEPROM: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int sfp_module_parse_sff8472(struct sfp *sfp)
+{
+ /* 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");
+ else
+ sfp->have_a2 = true;
+
+ return 0;
+}
+
+static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+{
+ /* SFP module inserted - read I2C data */
+ struct sfp_eeprom_id id;
+ bool cotsworks_sfbg;
+ unsigned int mask;
+ bool cotsworks;
+ u8 check;
+ int ret;
+
+ sfp->i2c_block_size = SFP_EEPROM_BLOCK_SIZE;
+
+ ret = sfp_read(sfp, false, 0, &id.base, sizeof(id.base));
+ if (ret < 0) {
+ if (report)
+ dev_err(sfp->dev, "failed to read EEPROM: %pe\n",
+ ERR_PTR(ret));
+ return -EAGAIN;
+ }
+
+ if (ret != sizeof(id.base)) {
+ dev_err(sfp->dev, "EEPROM short read: %pe\n", ERR_PTR(ret));
+ return -EAGAIN;
+ }
+
+ /* Some SFP modules (e.g. Nokia 3FE46541AA) lock up if read from
+ * address 0x51 is just one byte at a time. Also SFF-8472 requires
+ * that EEPROM supports atomic 16bit read operation for diagnostic
+ * fields, so do not switch to one byte reading at a time unless it
+ * is really required and we have no other option.
+ */
+ if (sfp_id_needs_byte_io(sfp, &id.base, sizeof(id.base))) {
+ dev_info(sfp->dev,
+ "Detected broken RTL8672/RTL9601C emulated EEPROM\n");
+ dev_info(sfp->dev,
+ "Switching to reading EEPROM to one byte at a time\n");
+ sfp->i2c_block_size = 1;
+
+ ret = sfp_read(sfp, false, 0, &id.base, sizeof(id.base));
+ if (ret < 0) {
+ if (report)
+ dev_err(sfp->dev,
+ "failed to read EEPROM: %pe\n",
+ ERR_PTR(ret));
+ return -EAGAIN;
+ }
+
+ if (ret != sizeof(id.base)) {
+ dev_err(sfp->dev, "EEPROM short read: %pe\n",
+ ERR_PTR(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);
+ cotsworks_sfbg = !memcmp(id.base.vendor_pn, "SFBG", 4);
+
+ /* Cotsworks SFF module EEPROM do not always have valid phys_id,
+ * phys_ext_id, and connector bytes. Rewrite SFF EEPROM bytes if
+ * Cotsworks PN matches and bytes are not correct.
+ */
+ if (cotsworks && cotsworks_sfbg) {
+ ret = sfp_cotsworks_fixup_check(sfp, &id);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* 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;
+ }
+ }
+
+ ret = sfp_read(sfp, false, SFP_CC_BASE + 1, &id.ext, sizeof(id.ext));
+ if (ret < 0) {
+ if (report)
+ dev_err(sfp->dev, "failed to read EEPROM: %pe\n",
+ ERR_PTR(ret));
+ return -EAGAIN;
+ }
+
+ if (ret != sizeof(id.ext)) {
+ dev_err(sfp->dev, "EEPROM short read: %pe\n", ERR_PTR(ret));
+ return -EAGAIN;
+ }
+
+ check = sfp_check(&id.ext, sizeof(id.ext) - 1);
+ if (check != id.ext.cc_ext) {
+ if (cotsworks) {
+ dev_warn(sfp->dev,
+ "EEPROM extended structure checksum failure (0x%02x != 0x%02x)\n",
+ check, id.ext.cc_ext);
+ } else {
+ dev_err(sfp->dev,
+ "EEPROM extended structure checksum failure: 0x%02x != 0x%02x\n",
+ check, id.ext.cc_ext);
+ print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET,
+ 16, 1, &id, sizeof(id), true);
+ memset(&id.ext, 0, sizeof(id.ext));
+ }
+ }
+
+ sfp->id = id;
+
+ dev_info(sfp->dev, "module %.*s %.*s rev %.*s sn %.*s dc %.*s\n",
+ (int)sizeof(id.base.vendor_name), id.base.vendor_name,
+ (int)sizeof(id.base.vendor_pn), id.base.vendor_pn,
+ (int)sizeof(id.base.vendor_rev), id.base.vendor_rev,
+ (int)sizeof(id.ext.vendor_sn), id.ext.vendor_sn,
+ (int)sizeof(id.ext.datecode), id.ext.datecode);
+
+ /* Check whether we support this module */
+ if (!sfp->type->module_supported(&id)) {
+ dev_err(sfp->dev,
+ "module is not supported - phys id 0x%02x 0x%02x\n",
+ sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
+ return -EINVAL;
+ }
+
+ if (sfp->id.ext.sff8472_compliance != SFP_SFF8472_COMPLIANCE_NONE) {
+ ret = sfp_module_parse_sff8472(sfp);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Parse the module power requirement */
+ ret = sfp_module_parse_power(sfp);
+ if (ret < 0)
+ return ret;
+
+ sfp_module_parse_rate_select(sfp);
+
+ mask = SFP_F_PRESENT;
+ if (sfp->gpio[GPIO_TX_DISABLE])
+ mask |= SFP_F_TX_DISABLE;
+ if (sfp->gpio[GPIO_TX_FAULT])
+ mask |= SFP_F_TX_FAULT;
+ if (sfp->gpio[GPIO_LOS])
+ mask |= SFP_F_LOS;
+ if (sfp->gpio[GPIO_RS0])
+ mask |= SFP_F_RS0;
+ if (sfp->gpio[GPIO_RS1])
+ mask |= SFP_F_RS1;
+
+ sfp->module_t_start_up = T_START_UP;
+ sfp->module_t_wait = T_WAIT;
+
+ sfp->tx_fault_ignore = false;
+
+ if (sfp->id.base.extended_cc == SFF8024_ECC_10GBASE_T_SFI ||
+ sfp->id.base.extended_cc == SFF8024_ECC_10GBASE_T_SR ||
+ sfp->id.base.extended_cc == SFF8024_ECC_5GBASE_T ||
+ sfp->id.base.extended_cc == SFF8024_ECC_2_5GBASE_T)
+ sfp->mdio_protocol = MDIO_I2C_C45;
+ else if (sfp->id.base.e1000_base_t)
+ sfp->mdio_protocol = MDIO_I2C_MARVELL_C22;
+ else
+ sfp->mdio_protocol = MDIO_I2C_NONE;
+
+ sfp->quirk = sfp_lookup_quirk(&id);
+
+ mutex_lock(&sfp->st_mutex);
+ /* Initialise state bits to use from hardware */
+ sfp->state_hw_mask = mask;
+
+ /* We want to drive the rate select pins that the module is using */
+ sfp->state_hw_drive |= sfp->rs_state_mask;
+
+ if (sfp->quirk && sfp->quirk->fixup)
+ sfp->quirk->fixup(sfp);
+ mutex_unlock(&sfp->st_mutex);
+
+ return 0;
+}
+
+static void sfp_sm_mod_remove(struct sfp *sfp)
+{
+ if (sfp->sm_mod_state > SFP_MOD_WAITDEV)
+ sfp_module_remove(sfp->sfp_bus);
+
+ sfp_hwmon_remove(sfp);
+
+ memset(&sfp->id, 0, sizeof(sfp->id));
+ sfp->module_power_mW = 0;
+ sfp->state_hw_drive = SFP_F_TX_DISABLE;
+ sfp->have_a2 = false;
+
+ dev_info(sfp->dev, "module removed\n");
+}
+
+/* This state machine tracks the upstream's state */
+static void sfp_sm_device(struct sfp *sfp, unsigned int event)
+{
+ switch (sfp->sm_dev_state) {
+ default:
+ if (event == SFP_E_DEV_ATTACH)
+ sfp->sm_dev_state = SFP_DEV_DOWN;
+ break;
+
+ case SFP_DEV_DOWN:
+ if (event == SFP_E_DEV_DETACH)
+ sfp->sm_dev_state = SFP_DEV_DETACHED;
+ else if (event == SFP_E_DEV_UP)
+ sfp->sm_dev_state = SFP_DEV_UP;
+ break;
+
+ case SFP_DEV_UP:
+ if (event == SFP_E_DEV_DETACH)
+ sfp->sm_dev_state = SFP_DEV_DETACHED;
+ else if (event == SFP_E_DEV_DOWN)
+ sfp->sm_dev_state = SFP_DEV_DOWN;
+ break;
+ }
+}
+
+/* This state machine tracks the insert/remove state of the module, probes
+ * the on-board EEPROM, and sets up the power level.
+ */
+static void sfp_sm_module(struct sfp *sfp, unsigned int event)
+{
+ int err;
+
+ /* Handle remove event globally, it resets this state machine */
+ if (event == SFP_E_REMOVE) {
+ if (sfp->sm_mod_state > SFP_MOD_PROBE)
+ sfp_sm_mod_remove(sfp);
+ sfp_sm_mod_next(sfp, SFP_MOD_EMPTY, 0);
+ return;
+ }
+
+ /* Handle device detach globally */
+ if (sfp->sm_dev_state < SFP_DEV_DOWN &&
+ sfp->sm_mod_state > SFP_MOD_WAITDEV) {
+ if (sfp->module_power_mW > 1000 &&
+ sfp->sm_mod_state > SFP_MOD_HPOWER)
+ sfp_sm_mod_hpower(sfp, false);
+ sfp_sm_mod_next(sfp, SFP_MOD_WAITDEV, 0);
+ return;
+ }
+
+ switch (sfp->sm_mod_state) {
+ default:
+ if (event == SFP_E_INSERT) {
+ sfp_sm_mod_next(sfp, SFP_MOD_PROBE, T_SERIAL);
+ sfp->sm_mod_tries_init = R_PROBE_RETRY_INIT;
+ sfp->sm_mod_tries = R_PROBE_RETRY_SLOW;
+ }
+ break;
+
+ case SFP_MOD_PROBE:
+ /* Wait for T_PROBE_INIT to time out */
+ if (event != SFP_E_TIMEOUT)
+ break;
+
+ err = sfp_sm_mod_probe(sfp, sfp->sm_mod_tries == 1);
+ if (err == -EAGAIN) {
+ if (sfp->sm_mod_tries_init &&
+ --sfp->sm_mod_tries_init) {
+ sfp_sm_set_timer(sfp, T_PROBE_RETRY_INIT);
+ break;
+ } else if (sfp->sm_mod_tries && --sfp->sm_mod_tries) {
+ if (sfp->sm_mod_tries == R_PROBE_RETRY_SLOW - 1)
+ dev_warn(sfp->dev,
+ "please wait, module slow to respond\n");
+ sfp_sm_set_timer(sfp, T_PROBE_RETRY_SLOW);
+ break;
+ }
+ }
+ if (err < 0) {
+ sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+ break;
+ }
+
+ /* Force a poll to re-read the hardware signal state after
+ * sfp_sm_mod_probe() changed state_hw_mask.
+ */
+ mod_delayed_work(system_wq, &sfp->poll, 1);
+
+ err = sfp_hwmon_insert(sfp);
+ if (err)
+ dev_warn(sfp->dev, "hwmon probe failed: %pe\n",
+ ERR_PTR(err));
+
+ sfp_sm_mod_next(sfp, SFP_MOD_WAITDEV, 0);
+ fallthrough;
+ case SFP_MOD_WAITDEV:
+ /* Ensure that the device is attached before proceeding */
+ if (sfp->sm_dev_state < SFP_DEV_DOWN)
+ break;
+
+ /* Report the module insertion to the upstream device */
+ err = sfp_module_insert(sfp->sfp_bus, &sfp->id,
+ sfp->quirk);
+ if (err < 0) {
+ sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+ break;
+ }
+
+ /* If this is a power level 1 module, we are done */
+ if (sfp->module_power_mW <= 1000)
+ goto insert;
+
+ sfp_sm_mod_next(sfp, SFP_MOD_HPOWER, 0);
+ fallthrough;
+ case SFP_MOD_HPOWER:
+ /* Enable high power mode */
+ err = sfp_sm_mod_hpower(sfp, true);
+ if (err < 0) {
+ if (err != -EAGAIN) {
+ sfp_module_remove(sfp->sfp_bus);
+ sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+ } else {
+ sfp_sm_set_timer(sfp, T_PROBE_RETRY_INIT);
+ }
+ break;
+ }
+
+ sfp_sm_mod_next(sfp, SFP_MOD_WAITPWR, T_HPOWER_LEVEL);
+ break;
+
+ case SFP_MOD_WAITPWR:
+ /* Wait for T_HPOWER_LEVEL to time out */
+ if (event != SFP_E_TIMEOUT)
+ break;
+
+ insert:
+ sfp_sm_mod_next(sfp, SFP_MOD_PRESENT, 0);
+ break;
+
+ case SFP_MOD_PRESENT:
+ case SFP_MOD_ERROR:
+ break;
+ }
+}
+
+static void sfp_sm_main(struct sfp *sfp, unsigned int event)
+{
+ unsigned long timeout;
+ int ret;
+
+ /* Some events are global */
+ if (sfp->sm_state != SFP_S_DOWN &&
+ (sfp->sm_mod_state != SFP_MOD_PRESENT ||
+ sfp->sm_dev_state != SFP_DEV_UP)) {
+ if (sfp->sm_state == SFP_S_LINK_UP &&
+ sfp->sm_dev_state == SFP_DEV_UP)
+ sfp_sm_link_down(sfp);
+ if (sfp->sm_state > SFP_S_INIT)
+ sfp_module_stop(sfp->sfp_bus);
+ if (sfp->mod_phy)
+ sfp_sm_phy_detach(sfp);
+ if (sfp->i2c_mii)
+ sfp_i2c_mdiobus_destroy(sfp);
+ sfp_module_tx_disable(sfp);
+ sfp_soft_stop_poll(sfp);
+ sfp_sm_next(sfp, SFP_S_DOWN, 0);
+ return;
+ }
+
+ /* The main state machine */
+ switch (sfp->sm_state) {
+ case SFP_S_DOWN:
+ if (sfp->sm_mod_state != SFP_MOD_PRESENT ||
+ sfp->sm_dev_state != SFP_DEV_UP)
+ break;
+
+ /* Only use the soft state bits if we have access to the A2h
+ * memory, which implies that we have some level of SFF-8472
+ * compliance.
+ */
+ if (sfp->have_a2)
+ sfp_soft_start_poll(sfp);
+
+ sfp_module_tx_enable(sfp);
+
+ /* Initialise the fault clearance retries */
+ sfp->sm_fault_retries = N_FAULT_INIT;
+
+ /* We need to check the TX_FAULT state, which is not defined
+ * while TX_DISABLE is asserted. The earliest we want to do
+ * anything (such as probe for a PHY) is 50ms (or more on
+ * specific modules).
+ */
+ sfp_sm_next(sfp, SFP_S_WAIT, sfp->module_t_wait);
+ break;
+
+ case SFP_S_WAIT:
+ if (event != SFP_E_TIMEOUT)
+ break;
+
+ if (sfp->state & SFP_F_TX_FAULT) {
+ /* Wait up to t_init (SFF-8472) or t_start_up (SFF-8431)
+ * from the TX_DISABLE deassertion for the module to
+ * initialise, which is indicated by TX_FAULT
+ * deasserting.
+ */
+ timeout = sfp->module_t_start_up;
+ if (timeout > sfp->module_t_wait)
+ timeout -= sfp->module_t_wait;
+ else
+ timeout = 1;
+
+ sfp_sm_next(sfp, SFP_S_INIT, timeout);
+ } else {
+ /* TX_FAULT is not asserted, assume the module has
+ * finished initialising.
+ */
+ goto init_done;
+ }
+ break;
+
+ case SFP_S_INIT:
+ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+ /* TX_FAULT is still asserted after t_init
+ * or t_start_up, so assume there is a fault.
+ */
+ sfp_sm_fault(sfp, SFP_S_INIT_TX_FAULT,
+ sfp->sm_fault_retries == N_FAULT_INIT);
+ } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+ init_done:
+ /* Create mdiobus and start trying for PHY */
+ ret = sfp_sm_add_mdio_bus(sfp);
+ if (ret < 0) {
+ sfp_sm_next(sfp, SFP_S_FAIL, 0);
+ break;
+ }
+ sfp->sm_phy_retries = R_PHY_RETRY;
+ goto phy_probe;
+ }
+ break;
+
+ case SFP_S_INIT_PHY:
+ if (event != SFP_E_TIMEOUT)
+ break;
+ phy_probe:
+ /* TX_FAULT deasserted or we timed out with TX_FAULT
+ * clear. Probe for the PHY and check the LOS state.
+ */
+ ret = sfp_sm_probe_for_phy(sfp);
+ if (ret == -ENODEV) {
+ if (--sfp->sm_phy_retries) {
+ sfp_sm_next(sfp, SFP_S_INIT_PHY, T_PHY_RETRY);
+ break;
+ } else {
+ dev_info(sfp->dev, "no PHY detected\n");
+ }
+ } else if (ret) {
+ sfp_sm_next(sfp, SFP_S_FAIL, 0);
+ break;
+ }
+ if (sfp_module_start(sfp->sfp_bus)) {
+ sfp_sm_next(sfp, SFP_S_FAIL, 0);
+ break;
+ }
+ sfp_sm_link_check_los(sfp);
+
+ /* Reset the fault retry count */
+ sfp->sm_fault_retries = N_FAULT;
+ break;
+
+ case SFP_S_INIT_TX_FAULT:
+ if (event == SFP_E_TIMEOUT) {
+ sfp_module_tx_fault_reset(sfp);
+ sfp_sm_next(sfp, SFP_S_INIT, sfp->module_t_start_up);
+ }
+ break;
+
+ case SFP_S_WAIT_LOS:
+ if (event == SFP_E_TX_FAULT)
+ sfp_sm_fault(sfp, SFP_S_TX_FAULT, true);
+ else if (sfp_los_event_inactive(sfp, event))
+ sfp_sm_link_up(sfp);
+ break;
+
+ case SFP_S_LINK_UP:
+ if (event == SFP_E_TX_FAULT) {
+ sfp_sm_link_down(sfp);
+ sfp_sm_fault(sfp, SFP_S_TX_FAULT, true);
+ } else if (sfp_los_event_active(sfp, event)) {
+ sfp_sm_link_down(sfp);
+ sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
+ }
+ break;
+
+ case SFP_S_TX_FAULT:
+ if (event == SFP_E_TIMEOUT) {
+ sfp_module_tx_fault_reset(sfp);
+ sfp_sm_next(sfp, SFP_S_REINIT, sfp->module_t_start_up);
+ }
+ break;
+
+ case SFP_S_REINIT:
+ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+ sfp_sm_fault(sfp, SFP_S_TX_FAULT, false);
+ } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+ dev_info(sfp->dev, "module transmit fault recovered\n");
+ sfp_sm_link_check_los(sfp);
+ }
+ break;
+
+ case SFP_S_TX_DISABLE:
+ break;
+ }
+}
+
+static void __sfp_sm_event(struct sfp *sfp, unsigned int event)
+{
+ dev_dbg(sfp->dev, "SM: enter %s:%s:%s event %s\n",
+ mod_state_to_str(sfp->sm_mod_state),
+ dev_state_to_str(sfp->sm_dev_state),
+ sm_state_to_str(sfp->sm_state),
+ event_to_str(event));
+
+ sfp_sm_device(sfp, event);
+ sfp_sm_module(sfp, event);
+ sfp_sm_main(sfp, event);
+
+ dev_dbg(sfp->dev, "SM: exit %s:%s:%s\n",
+ mod_state_to_str(sfp->sm_mod_state),
+ dev_state_to_str(sfp->sm_dev_state),
+ sm_state_to_str(sfp->sm_state));
+}
+
+static void sfp_sm_event(struct sfp *sfp, unsigned int event)
+{
+ mutex_lock(&sfp->sm_mutex);
+ __sfp_sm_event(sfp, event);
+ mutex_unlock(&sfp->sm_mutex);
+}
+
+static void sfp_attach(struct sfp *sfp)
+{
+ sfp_sm_event(sfp, SFP_E_DEV_ATTACH);
+}
+
+static void sfp_detach(struct sfp *sfp)
+{
+ sfp_sm_event(sfp, SFP_E_DEV_DETACH);
+}
+
+static void sfp_start(struct sfp *sfp)
+{
+ sfp_sm_event(sfp, SFP_E_DEV_UP);
+}
+
+static void sfp_stop(struct sfp *sfp)
+{
+ sfp_sm_event(sfp, SFP_E_DEV_DOWN);
+}
+
+static void sfp_set_signal_rate(struct sfp *sfp, unsigned int rate_kbd)
+{
+ unsigned int set;
+
+ sfp->rate_kbd = rate_kbd;
+
+ if (rate_kbd > sfp->rs_threshold_kbd)
+ set = sfp->rs_state_mask;
+ else
+ set = 0;
+
+ sfp_mod_state(sfp, SFP_F_RS0 | SFP_F_RS1, set);
+}
+
+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 (!(sfp->state & SFP_F_PRESENT))
+ return -ENODEV;
+
+ 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 int sfp_module_eeprom_by_page(struct sfp *sfp,
+ const struct ethtool_module_eeprom *page,
+ struct netlink_ext_ack *extack)
+{
+ if (!(sfp->state & SFP_F_PRESENT))
+ return -ENODEV;
+
+ if (page->bank) {
+ NL_SET_ERR_MSG(extack, "Banks not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (page->page) {
+ NL_SET_ERR_MSG(extack, "Only page 0 supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (page->i2c_address != 0x50 &&
+ page->i2c_address != 0x51) {
+ NL_SET_ERR_MSG(extack, "Only address 0x50 and 0x51 supported");
+ return -EOPNOTSUPP;
+ }
+
+ return sfp_read(sfp, page->i2c_address == 0x51, page->offset,
+ page->data, page->length);
+};
+
+static const struct sfp_socket_ops sfp_module_ops = {
+ .attach = sfp_attach,
+ .detach = sfp_detach,
+ .start = sfp_start,
+ .stop = sfp_stop,
+ .set_signal_rate = sfp_set_signal_rate,
+ .module_info = sfp_module_info,
+ .module_eeprom = sfp_module_eeprom,
+ .module_eeprom_by_page = sfp_module_eeprom_by_page,
+};
+
+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;
+
+ rtnl_lock();
+ mutex_lock(&sfp->st_mutex);
+ state = sfp_get_state(sfp);
+ changed = state ^ sfp->state;
+ if (sfp->tx_fault_ignore)
+ changed &= SFP_F_PRESENT | SFP_F_LOS;
+ else
+ 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_names[i],
+ !!(sfp->state & BIT(i)), !!(state & BIT(i)));
+
+ state |= sfp->state & SFP_F_OUTPUTS;
+ sfp->state = state;
+ mutex_unlock(&sfp->st_mutex);
+
+ mutex_lock(&sfp->sm_mutex);
+ 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);
+ mutex_unlock(&sfp->sm_mutex);
+ rtnl_unlock();
+}
+
+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);
+
+ // st_mutex doesn't need to be held here for state_soft_mask,
+ // it's unimportant if we race while reading this.
+ if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) ||
+ sfp->need_poll)
+ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+}
+
+static struct sfp *sfp_alloc(struct device *dev)
+{
+ struct sfp *sfp;
+
+ sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
+ if (!sfp)
+ return ERR_PTR(-ENOMEM);
+
+ sfp->dev = dev;
+ sfp->i2c_block_size = SFP_EEPROM_BLOCK_SIZE;
+
+ mutex_init(&sfp->sm_mutex);
+ mutex_init(&sfp->st_mutex);
+ INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
+ INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
+
+ sfp_hwmon_init(sfp);
+
+ return sfp;
+}
+
+static void sfp_cleanup(void *data)
+{
+ struct sfp *sfp = data;
+
+ sfp_hwmon_exit(sfp);
+
+ cancel_delayed_work_sync(&sfp->poll);
+ cancel_delayed_work_sync(&sfp->timeout);
+ if (sfp->i2c_mii) {
+ mdiobus_unregister(sfp->i2c_mii);
+ mdiobus_free(sfp->i2c_mii);
+ }
+ if (sfp->i2c)
+ i2c_put_adapter(sfp->i2c);
+ kfree(sfp);
+}
+
+static int sfp_i2c_get(struct sfp *sfp)
+{
+ struct fwnode_handle *h;
+ struct i2c_adapter *i2c;
+ int err;
+
+ h = fwnode_find_reference(dev_fwnode(sfp->dev), "i2c-bus", 0);
+ if (IS_ERR(h)) {
+ dev_err(sfp->dev, "missing 'i2c-bus' property\n");
+ return -ENODEV;
+ }
+
+ i2c = i2c_get_adapter_by_fwnode(h);
+ if (!i2c) {
+ err = -EPROBE_DEFER;
+ goto put;
+ }
+
+ err = sfp_i2c_configure(sfp, i2c);
+ if (err)
+ i2c_put_adapter(i2c);
+put:
+ fwnode_handle_put(h);
+ return err;
+}
+
+static int sfp_probe(struct platform_device *pdev)
+{
+ const struct sff_data *sff;
+ char *sfp_irq_name;
+ struct sfp *sfp;
+ int err, i;
+
+ sfp = sfp_alloc(&pdev->dev);
+ if (IS_ERR(sfp))
+ return PTR_ERR(sfp);
+
+ platform_set_drvdata(pdev, sfp);
+
+ err = devm_add_action_or_reset(sfp->dev, sfp_cleanup, sfp);
+ if (err < 0)
+ return err;
+
+ sff = device_get_match_data(sfp->dev);
+ if (!sff)
+ sff = &sfp_data;
+
+ sfp->type = sff;
+
+ err = sfp_i2c_get(sfp);
+ if (err)
+ return err;
+
+ for (i = 0; i < GPIO_MAX; i++)
+ if (sff->gpios & BIT(i)) {
+ sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
+ gpio_names[i], gpio_flags[i]);
+ if (IS_ERR(sfp->gpio[i]))
+ return PTR_ERR(sfp->gpio[i]);
+ }
+
+ sfp->state_hw_mask = SFP_F_PRESENT;
+ sfp->state_hw_drive = SFP_F_TX_DISABLE;
+
+ 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 < 1000) {
+ if (sfp->max_power_mW)
+ dev_warn(sfp->dev,
+ "Firmware bug: host maximum power should be at least 1W\n");
+ 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_RS0] &&
+ gpiod_get_value_cansleep(sfp->gpio[GPIO_RS0]))
+ sfp->state |= SFP_F_RS0;
+ sfp_set_state(sfp, sfp->state);
+ sfp_module_tx_disable(sfp);
+ if (sfp->state & SFP_F_PRESENT) {
+ rtnl_lock();
+ sfp_sm_event(sfp, SFP_E_INSERT);
+ rtnl_unlock();
+ }
+
+ for (i = 0; i < GPIO_MAX; i++) {
+ if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+ continue;
+
+ sfp->gpio_irq[i] = gpiod_to_irq(sfp->gpio[i]);
+ if (sfp->gpio_irq[i] < 0) {
+ sfp->gpio_irq[i] = 0;
+ sfp->need_poll = true;
+ continue;
+ }
+
+ sfp_irq_name = devm_kasprintf(sfp->dev, GFP_KERNEL,
+ "%s-%s", dev_name(sfp->dev),
+ gpio_names[i]);
+
+ if (!sfp_irq_name)
+ return -ENOMEM;
+
+ err = devm_request_threaded_irq(sfp->dev, sfp->gpio_irq[i],
+ NULL, sfp_irq,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ sfp_irq_name, sfp);
+ if (err) {
+ sfp->gpio_irq[i] = 0;
+ sfp->need_poll = true;
+ }
+ }
+
+ if (sfp->need_poll)
+ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+
+ /* We could have an issue in cases no Tx disable pin is available or
+ * wired as modules using a laser as their light source will continue to
+ * be active when the fiber is removed. This could be a safety issue and
+ * we should at least warn the user about that.
+ */
+ if (!sfp->gpio[GPIO_TX_DISABLE])
+ dev_warn(sfp->dev,
+ "No tx_disable pin: SFP modules will always be emitting.\n");
+
+ sfp->sfp_bus = sfp_register_socket(sfp->dev, sfp, &sfp_module_ops);
+ if (!sfp->sfp_bus)
+ return -ENOMEM;
+
+ sfp_debugfs_init(sfp);
+
+ return 0;
+}
+
+static int sfp_remove(struct platform_device *pdev)
+{
+ struct sfp *sfp = platform_get_drvdata(pdev);
+
+ sfp_debugfs_exit(sfp);
+ sfp_unregister_socket(sfp->sfp_bus);
+
+ rtnl_lock();
+ sfp_sm_event(sfp, SFP_E_REMOVE);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static void sfp_shutdown(struct platform_device *pdev)
+{
+ struct sfp *sfp = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < GPIO_MAX; i++) {
+ if (!sfp->gpio_irq[i])
+ continue;
+
+ devm_free_irq(sfp->dev, sfp->gpio_irq[i], sfp);
+ }
+
+ cancel_delayed_work_sync(&sfp->poll);
+ cancel_delayed_work_sync(&sfp->timeout);
+}
+
+static struct platform_driver sfp_driver = {
+ .probe = sfp_probe,
+ .remove = sfp_remove,
+ .shutdown = sfp_shutdown,
+ .driver = {
+ .name = "sfp",
+ .of_match_table = sfp_of_match,
+ },
+};
+
+static int sfp_init(void)
+{
+ poll_jiffies = msecs_to_jiffies(100);
+
+ return platform_driver_register(&sfp_driver);
+}
+module_init(sfp_init);
+
+static void sfp_exit(void)
+{
+ platform_driver_unregister(&sfp_driver);
+}
+module_exit(sfp_exit);
+
+MODULE_ALIAS("platform:sfp");
+MODULE_AUTHOR("Russell King");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
new file mode 100644
index 000000000..1fd097dcc
--- /dev/null
+++ b/drivers/net/phy/sfp.h
@@ -0,0 +1,44 @@
+#ifndef SFP_H
+#define SFP_H
+
+#include <linux/ethtool.h>
+#include <linux/sfp.h>
+
+struct sfp;
+
+struct sfp_quirk {
+ const char *vendor;
+ const char *part;
+ void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes,
+ unsigned long *interfaces);
+ void (*fixup)(struct sfp *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);
+ void (*set_signal_rate)(struct sfp *sfp, unsigned int rate_kbd);
+ int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
+ int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
+ u8 *data);
+ int (*module_eeprom_by_page)(struct sfp *sfp,
+ const struct ethtool_module_eeprom *page,
+ struct netlink_ext_ack *extack);
+};
+
+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,
+ const struct sfp_quirk *quirk);
+void sfp_module_remove(struct sfp_bus *bus);
+int sfp_module_start(struct sfp_bus *bus);
+void sfp_module_stop(struct sfp_bus *bus);
+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..c88edb19d
--- /dev/null
+++ b/drivers/net/phy/smsc.c
@@ -0,0 +1,853 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/smsc.c
+ *
+ * Driver for SMSC PHYs
+ *
+ * Author: Herbert Valerio Riedel
+ *
+ * Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.org>
+ *
+ * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net
+ *
+ */
+
+#include <linux/clk.h>
+#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/crc16.h>
+#include <linux/etherdevice.h>
+#include <linux/smscphy.h>
+
+/* Vendor-specific PHY Definitions */
+/* EDPD NLP / crossover time configuration */
+#define PHY_EDPD_CONFIG 16
+#define PHY_EDPD_CONFIG_EXT_CROSSOVER_ 0x0001
+
+/* Control/Status Indication Register */
+#define SPECIAL_CTRL_STS 27
+#define SPECIAL_CTRL_STS_OVRRD_AMDIX_ 0x8000
+#define SPECIAL_CTRL_STS_AMDIX_ENABLE_ 0x4000
+#define SPECIAL_CTRL_STS_AMDIX_STATE_ 0x2000
+
+#define EDPD_MAX_WAIT_DFLT_MS 640
+/* interval between phylib state machine runs in ms */
+#define PHY_STATE_MACH_MS 1000
+
+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 {
+ unsigned int edpd_enable:1;
+ unsigned int edpd_mode_set_by_user:1;
+ unsigned int edpd_max_wait_ms;
+ bool wol_arp;
+};
+
+static int smsc_phy_ack_interrupt(struct phy_device *phydev)
+{
+ int rc = phy_read(phydev, MII_LAN83C185_ISF);
+
+ return rc < 0 ? rc : 0;
+}
+
+int smsc_phy_config_intr(struct phy_device *phydev)
+{
+ int rc;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ rc = smsc_phy_ack_interrupt(phydev);
+ if (rc)
+ return rc;
+
+ rc = phy_write(phydev, MII_LAN83C185_IM,
+ MII_LAN83C185_ISF_INT_PHYLIB_EVENTS);
+ } else {
+ rc = phy_write(phydev, MII_LAN83C185_IM, 0);
+ if (rc)
+ return rc;
+
+ rc = smsc_phy_ack_interrupt(phydev);
+ }
+
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL_GPL(smsc_phy_config_intr);
+
+static int smsc_phy_config_edpd(struct phy_device *phydev)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+
+ if (priv->edpd_enable)
+ return phy_set_bits(phydev, MII_LAN83C185_CTRL_STATUS,
+ MII_LAN83C185_EDPWRDOWN);
+ else
+ return phy_clear_bits(phydev, MII_LAN83C185_CTRL_STATUS,
+ MII_LAN83C185_EDPWRDOWN);
+}
+
+irqreturn_t smsc_phy_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_LAN83C185_ISF);
+ if (irq_status < 0) {
+ if (irq_status != -ENODEV)
+ phy_error(phydev);
+
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_LAN83C185_ISF_INT_PHYLIB_EVENTS))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(smsc_phy_handle_interrupt);
+
+int smsc_phy_config_init(struct phy_device *phydev)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+
+ if (!priv)
+ return 0;
+
+ /* don't use EDPD in irq mode except overridden by user */
+ if (!priv->edpd_mode_set_by_user && phydev->irq != PHY_POLL)
+ priv->edpd_enable = false;
+
+ return smsc_phy_config_edpd(phydev);
+}
+EXPORT_SYMBOL_GPL(smsc_phy_config_init);
+
+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 lan87xx_config_aneg(struct phy_device *phydev)
+{
+ int rc;
+ int val;
+
+ switch (phydev->mdix_ctrl) {
+ case ETH_TP_MDI:
+ val = SPECIAL_CTRL_STS_OVRRD_AMDIX_;
+ break;
+ case ETH_TP_MDI_X:
+ val = SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
+ SPECIAL_CTRL_STS_AMDIX_STATE_;
+ break;
+ case ETH_TP_MDI_AUTO:
+ val = SPECIAL_CTRL_STS_AMDIX_ENABLE_;
+ break;
+ default:
+ return genphy_config_aneg(phydev);
+ }
+
+ rc = phy_read(phydev, SPECIAL_CTRL_STS);
+ if (rc < 0)
+ return rc;
+
+ rc &= ~(SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
+ SPECIAL_CTRL_STS_AMDIX_ENABLE_ |
+ SPECIAL_CTRL_STS_AMDIX_STATE_);
+ rc |= val;
+ phy_write(phydev, SPECIAL_CTRL_STS, rc);
+
+ phydev->mdix = phydev->mdix_ctrl;
+ return genphy_config_aneg(phydev);
+}
+
+static int lan95xx_config_aneg_ext(struct phy_device *phydev)
+{
+ if (phydev->phy_id == 0x0007c0f0) { /* LAN9500A or LAN9505A */
+ /* Extend Manual AutoMDIX timer */
+ int rc = phy_set_bits(phydev, PHY_EDPD_CONFIG,
+ PHY_EDPD_CONFIG_EXT_CROSSOVER_);
+
+ if (rc < 0)
+ return rc;
+ }
+
+ return lan87xx_config_aneg(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.
+ * The workaround is only applicable to poll mode. Energy Detect Power-Down may
+ * not be used in interrupt mode lest link change detection becomes unreliable.
+ */
+int lan87xx_read_status(struct phy_device *phydev)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+ int err;
+
+ err = genphy_read_status(phydev);
+ if (err)
+ return err;
+
+ if (!phydev->link && priv && priv->edpd_enable &&
+ priv->edpd_max_wait_ms) {
+ unsigned int max_wait = priv->edpd_max_wait_ms * 1000;
+ int rc;
+
+ /* Disable EDPD to wake up PHY */
+ 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 and the timeout is not
+ * an actual error.
+ */
+ read_poll_timeout(phy_read, rc,
+ rc & MII_LAN83C185_ENERGYON || rc < 0,
+ 10000, max_wait, true, phydev,
+ MII_LAN83C185_CTRL_STATUS);
+ if (rc < 0)
+ return rc;
+
+ /* 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;
+}
+EXPORT_SYMBOL_GPL(lan87xx_read_status);
+
+static int lan874x_phy_config_init(struct phy_device *phydev)
+{
+ u16 val;
+ int rc;
+
+ /* Setup LED2/nINT/nPME pin to function as nPME. May need user option
+ * to use LED1/nINT/nPME.
+ */
+ val = MII_LAN874X_PHY_PME2_SET;
+
+ /* The bits MII_LAN874X_PHY_WOL_PFDA_FR, MII_LAN874X_PHY_WOL_WUFR,
+ * MII_LAN874X_PHY_WOL_MPR, and MII_LAN874X_PHY_WOL_BCAST_FR need to
+ * be cleared to de-assert PME signal after a WoL event happens, but
+ * using PME auto clear gets around that.
+ */
+ val |= MII_LAN874X_PHY_PME_SELF_CLEAR;
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val);
+ if (rc < 0)
+ return rc;
+
+ /* set nPME self clear delay time */
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_MCFGR,
+ MII_LAN874X_PHY_PME_SELF_CLEAR_DELAY);
+ if (rc < 0)
+ return rc;
+
+ return smsc_phy_config_init(phydev);
+}
+
+static void lan874x_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+ int rc;
+
+ wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC |
+ WAKE_ARP | WAKE_MCAST);
+ wol->wolopts = 0;
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR);
+ if (rc < 0)
+ return;
+
+ if (rc & MII_LAN874X_PHY_WOL_PFDAEN)
+ wol->wolopts |= WAKE_UCAST;
+
+ if (rc & MII_LAN874X_PHY_WOL_BCSTEN)
+ wol->wolopts |= WAKE_BCAST;
+
+ if (rc & MII_LAN874X_PHY_WOL_MPEN)
+ wol->wolopts |= WAKE_MAGIC;
+
+ if (rc & MII_LAN874X_PHY_WOL_WUEN) {
+ if (priv->wol_arp)
+ wol->wolopts |= WAKE_ARP;
+ else
+ wol->wolopts |= WAKE_MCAST;
+ }
+}
+
+static u16 smsc_crc16(const u8 *buffer, size_t len)
+{
+ return bitrev16(crc16(0xFFFF, buffer, len));
+}
+
+static int lan874x_chk_wol_pattern(const u8 pattern[], const u16 *mask,
+ u8 len, u8 *data, u8 *datalen)
+{
+ size_t i, j, k;
+ int ret = 0;
+ u16 bits;
+
+ /* Pattern filtering can match up to 128 bytes of frame data. There
+ * are 8 registers to program the 16-bit masks, where each bit means
+ * the byte will be compared. The frame data will then go through a
+ * CRC16 calculation for hardware comparison. This helper function
+ * makes sure only relevant frame data are included in this
+ * calculation. It provides a warning when the masks and expected
+ * data size do not match.
+ */
+ i = 0;
+ k = 0;
+ while (len > 0) {
+ bits = *mask;
+ for (j = 0; j < 16; j++, i++, len--) {
+ /* No more pattern. */
+ if (!len) {
+ /* The rest of bitmap is not empty. */
+ if (bits)
+ ret = i + 1;
+ break;
+ }
+ if (bits & 1)
+ data[k++] = pattern[i];
+ bits >>= 1;
+ }
+ mask++;
+ }
+ *datalen = k;
+ return ret;
+}
+
+static int lan874x_set_wol_pattern(struct phy_device *phydev, u16 val,
+ const u8 data[], u8 datalen,
+ const u16 *mask, u8 masklen)
+{
+ u16 crc, reg;
+ int rc;
+
+ /* Starting pattern offset is set before calling this function. */
+ val |= MII_LAN874X_PHY_WOL_FILTER_EN;
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_WUF_CFGA, val);
+ if (rc < 0)
+ return rc;
+
+ crc = smsc_crc16(data, datalen);
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_WUF_CFGB, crc);
+ if (rc < 0)
+ return rc;
+
+ masklen = (masklen + 15) & ~0xf;
+ reg = MII_LAN874X_PHY_MMD_WOL_WUF_MASK7;
+ while (masklen >= 16) {
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg, *mask);
+ if (rc < 0)
+ return rc;
+ reg--;
+ mask++;
+ masklen -= 16;
+ }
+
+ /* Clear out the rest of mask registers. */
+ while (reg != MII_LAN874X_PHY_MMD_WOL_WUF_MASK0) {
+ phy_write_mmd(phydev, MDIO_MMD_PCS, reg, 0);
+ reg--;
+ }
+ return rc;
+}
+
+static int lan874x_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ struct smsc_phy_priv *priv = phydev->priv;
+ u16 val, val_wucsr;
+ u8 data[128];
+ u8 datalen;
+ int rc;
+
+ /* lan874x has only one WoL filter pattern */
+ if ((wol->wolopts & (WAKE_ARP | WAKE_MCAST)) ==
+ (WAKE_ARP | WAKE_MCAST)) {
+ phydev_info(phydev,
+ "lan874x WoL supports one of ARP|MCAST at a time\n");
+ return -EOPNOTSUPP;
+ }
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR);
+ if (rc < 0)
+ return rc;
+
+ val_wucsr = rc;
+
+ if (wol->wolopts & WAKE_UCAST)
+ val_wucsr |= MII_LAN874X_PHY_WOL_PFDAEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_PFDAEN;
+
+ if (wol->wolopts & WAKE_BCAST)
+ val_wucsr |= MII_LAN874X_PHY_WOL_BCSTEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_BCSTEN;
+
+ if (wol->wolopts & WAKE_MAGIC)
+ val_wucsr |= MII_LAN874X_PHY_WOL_MPEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_MPEN;
+
+ /* Need to use pattern matching */
+ if (wol->wolopts & (WAKE_ARP | WAKE_MCAST))
+ val_wucsr |= MII_LAN874X_PHY_WOL_WUEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_WUEN;
+
+ if (wol->wolopts & WAKE_ARP) {
+ const u8 pattern[2] = { 0x08, 0x06 };
+ const u16 mask[1] = { 0x0003 };
+
+ rc = lan874x_chk_wol_pattern(pattern, mask, 2, data,
+ &datalen);
+ if (rc)
+ phydev_dbg(phydev, "pattern not valid at %d\n", rc);
+
+ /* Need to match broadcast destination address and provided
+ * data pattern at offset 12.
+ */
+ val = 12 | MII_LAN874X_PHY_WOL_FILTER_BCSTEN;
+ rc = lan874x_set_wol_pattern(phydev, val, data, datalen, mask,
+ 2);
+ if (rc < 0)
+ return rc;
+ priv->wol_arp = true;
+ }
+
+ if (wol->wolopts & WAKE_MCAST) {
+ /* Need to match multicast destination address. */
+ val = MII_LAN874X_PHY_WOL_FILTER_MCASTTEN;
+ rc = lan874x_set_wol_pattern(phydev, val, data, 0, NULL, 0);
+ if (rc < 0)
+ return rc;
+ priv->wol_arp = false;
+ }
+
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_UCAST)) {
+ const u8 *mac = (const u8 *)ndev->dev_addr;
+ int i, reg;
+
+ reg = MII_LAN874X_PHY_MMD_WOL_RX_ADDRC;
+ for (i = 0; i < 6; i += 2, reg--) {
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg,
+ ((mac[i + 1] << 8) | mac[i]));
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val_wucsr);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+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_get_edpd(struct phy_device *phydev, u16 *edpd)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+
+ if (!priv)
+ return -EOPNOTSUPP;
+
+ if (!priv->edpd_enable)
+ *edpd = ETHTOOL_PHY_EDPD_DISABLE;
+ else if (!priv->edpd_max_wait_ms)
+ *edpd = ETHTOOL_PHY_EDPD_NO_TX;
+ else
+ *edpd = PHY_STATE_MACH_MS + priv->edpd_max_wait_ms;
+
+ return 0;
+}
+
+static int smsc_phy_set_edpd(struct phy_device *phydev, u16 edpd)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+
+ if (!priv)
+ return -EOPNOTSUPP;
+
+ switch (edpd) {
+ case ETHTOOL_PHY_EDPD_DISABLE:
+ priv->edpd_enable = false;
+ break;
+ case ETHTOOL_PHY_EDPD_NO_TX:
+ priv->edpd_enable = true;
+ priv->edpd_max_wait_ms = 0;
+ break;
+ case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
+ edpd = PHY_STATE_MACH_MS + EDPD_MAX_WAIT_DFLT_MS;
+ fallthrough;
+ default:
+ if (phydev->irq != PHY_POLL)
+ return -EOPNOTSUPP;
+ if (edpd < PHY_STATE_MACH_MS || edpd > PHY_STATE_MACH_MS + 1000)
+ return -EINVAL;
+ priv->edpd_enable = true;
+ priv->edpd_max_wait_ms = edpd - PHY_STATE_MACH_MS;
+ }
+
+ priv->edpd_mode_set_by_user = true;
+
+ return smsc_phy_config_edpd(phydev);
+}
+
+int smsc_phy_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_EDPD:
+ return smsc_phy_get_edpd(phydev, data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL_GPL(smsc_phy_get_tunable);
+
+int smsc_phy_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tuna, const void *data)
+{
+ switch (tuna->id) {
+ case ETHTOOL_PHY_EDPD:
+ return smsc_phy_set_edpd(phydev, *(u16 *)data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL_GPL(smsc_phy_set_tunable);
+
+int smsc_phy_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct smsc_phy_priv *priv;
+ struct clk *refclk;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->edpd_enable = true;
+ priv->edpd_max_wait_ms = EDPD_MAX_WAIT_DFLT_MS;
+
+ if (device_property_present(dev, "smsc,disable-energy-detect"))
+ priv->edpd_enable = false;
+
+ phydev->priv = priv;
+
+ /* Make clk optional to keep DTB backward compatibility. */
+ refclk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(refclk))
+ return dev_err_probe(dev, PTR_ERR(refclk),
+ "Failed to request clock\n");
+
+ return clk_set_rate(refclk, 50 * 1000 * 1000);
+}
+EXPORT_SYMBOL_GPL(smsc_phy_probe);
+
+static struct phy_driver smsc_phy_driver[] = {
+{
+ .phy_id = 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
+ .phy_id_mask = 0xfffffff0,
+ .name = "SMSC LAN83C185",
+
+ /* PHY_BASIC_FEATURES */
+
+ .probe = smsc_phy_probe,
+
+ /* basic functions */
+ .config_init = smsc_phy_config_init,
+ .soft_reset = smsc_phy_reset,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
+ .phy_id_mask = 0xfffffff0,
+ .name = "SMSC LAN8187",
+
+ /* PHY_BASIC_FEATURES */
+
+ .probe = smsc_phy_probe,
+
+ /* basic functions */
+ .config_init = smsc_phy_config_init,
+ .soft_reset = smsc_phy_reset,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ /* Statistics */
+ .get_sset_count = smsc_get_sset_count,
+ .get_strings = smsc_get_strings,
+ .get_stats = smsc_get_stats,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ /* This covers internal PHY (phy_id: 0x0007C0C3) for
+ * LAN9500 (PID: 0x9500), LAN9514 (PID: 0xec00), LAN9505 (PID: 0x9505)
+ */
+ .phy_id = 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
+ .phy_id_mask = 0xfffffff0,
+ .name = "SMSC LAN8700",
+
+ /* PHY_BASIC_FEATURES */
+
+ .probe = smsc_phy_probe,
+
+ /* basic functions */
+ .read_status = lan87xx_read_status,
+ .config_init = smsc_phy_config_init,
+ .soft_reset = smsc_phy_reset,
+ .config_aneg = lan87xx_config_aneg,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ /* Statistics */
+ .get_sset_count = smsc_get_sset_count,
+ .get_strings = smsc_get_strings,
+ .get_stats = smsc_get_stats,
+
+ .get_tunable = smsc_phy_get_tunable,
+ .set_tunable = smsc_phy_set_tunable,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
+ .phy_id_mask = 0xfffffff0,
+ .name = "SMSC LAN911x Internal PHY",
+
+ /* PHY_BASIC_FEATURES */
+
+ .probe = smsc_phy_probe,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ /* This covers internal PHY (phy_id: 0x0007C0F0) for
+ * LAN9500A (PID: 0x9E00), LAN9505A (PID: 0x9E01)
+ */
+ .phy_id = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
+ .phy_id_mask = 0xfffffff0,
+ .name = "SMSC LAN8710/LAN8720",
+
+ /* PHY_BASIC_FEATURES */
+
+ .probe = smsc_phy_probe,
+
+ /* basic functions */
+ .read_status = lan87xx_read_status,
+ .config_init = smsc_phy_config_init,
+ .soft_reset = smsc_phy_reset,
+ .config_aneg = lan95xx_config_aneg_ext,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ /* Statistics */
+ .get_sset_count = smsc_get_sset_count,
+ .get_strings = smsc_get_strings,
+ .get_stats = smsc_get_stats,
+
+ .get_tunable = smsc_phy_get_tunable,
+ .set_tunable = smsc_phy_set_tunable,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = 0x0007c110,
+ .phy_id_mask = 0xfffffff0,
+ .name = "SMSC LAN8740",
+
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_RST_AFTER_CLK_EN,
+
+ .probe = smsc_phy_probe,
+
+ /* basic functions */
+ .read_status = lan87xx_read_status,
+ .config_init = lan874x_phy_config_init,
+ .soft_reset = smsc_phy_reset,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ /* Statistics */
+ .get_sset_count = smsc_get_sset_count,
+ .get_strings = smsc_get_strings,
+ .get_stats = smsc_get_stats,
+
+ .get_tunable = smsc_phy_get_tunable,
+ .set_tunable = smsc_phy_set_tunable,
+
+ /* WoL */
+ .set_wol = lan874x_set_wol,
+ .get_wol = lan874x_get_wol,
+
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = 0x0007c130, /* 0x0007c130 and 0x0007c131 */
+ /* This mask (0xfffffff2) is to differentiate from
+ * LAN88xx (phy_id 0x0007c132)
+ * and allows future phy_id revisions.
+ */
+ .phy_id_mask = 0xfffffff2,
+ .name = "Microchip LAN8742",
+
+ /* PHY_BASIC_FEATURES */
+ .flags = PHY_RST_AFTER_CLK_EN,
+
+ .probe = smsc_phy_probe,
+
+ /* basic functions */
+ .read_status = lan87xx_read_status,
+ .config_init = lan874x_phy_config_init,
+ .soft_reset = smsc_phy_reset,
+
+ /* IRQ related */
+ .config_intr = smsc_phy_config_intr,
+ .handle_interrupt = smsc_phy_handle_interrupt,
+
+ /* Statistics */
+ .get_sset_count = smsc_get_sset_count,
+ .get_strings = smsc_get_strings,
+ .get_stats = smsc_get_stats,
+
+ .get_tunable = smsc_phy_get_tunable,
+ .set_tunable = smsc_phy_set_tunable,
+
+ /* WoL */
+ .set_wol = lan874x_set_wol,
+ .get_wol = lan874x_get_wol,
+
+ .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 },
+ { 0x0007c130, 0xfffffff2 },
+ { }
+};
+
+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..7196e927c
--- /dev/null
+++ b/drivers/net/phy/spi_ks8995.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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
+ */
+
+#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/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_switch {
+ struct spi_device *spi;
+ struct mutex lock;
+ struct gpio_desc *reset_gpio;
+ 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 = kobj_to_dev(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 = kobj_to_dev(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;
+}
+
+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];
+
+ ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
+ GPIOD_OUT_HIGH);
+ err = PTR_ERR_OR_ZERO(ks->reset_gpio);
+ if (err) {
+ dev_err(&spi->dev,
+ "failed to get reset gpio: %d\n", err);
+ return err;
+ }
+
+ err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
+ if (err)
+ return err;
+
+ /* de-assert switch reset */
+ /* FIXME: this likely requires a delay */
+ gpiod_set_value_cansleep(ks->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 void 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 */
+ gpiod_set_value_cansleep(ks->reset_gpio, 1);
+}
+
+/* ------------------------------------------------------------------------ */
+static struct spi_driver ks8995_driver = {
+ .driver = {
+ .name = "spi-ks8995",
+ .of_match_table = 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..309e4c349
--- /dev/null
+++ b/drivers/net/phy/ste10Xp.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/net/phy/ste10Xp.c
+ *
+ * Driver for STMicroelectronics STe10Xp PHYs
+ *
+ * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ *
+ * Copyright (c) 2008 STMicroelectronics Limited
+ */
+
+#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_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, MII_XCIIS);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int ste10Xp_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* clear any pending interrupts */
+ err = ste10Xp_ack_interrupt(phydev);
+ if (err)
+ return err;
+
+ /* Enable all STe101P interrupts (PR12) */
+ err = phy_write(phydev, MII_XIE, MII_XIE_DEFAULT_MASK);
+ } else {
+ err = phy_write(phydev, MII_XIE, 0);
+ if (err)
+ return err;
+
+ err = ste10Xp_ack_interrupt(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t ste10Xp_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, MII_XCIIS);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & MII_XIE_DEFAULT_MASK))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static struct phy_driver ste10xp_pdriver[] = {
+{
+ .phy_id = STE101P_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "STe101p",
+ /* PHY_BASIC_FEATURES */
+ .config_init = ste10Xp_config_init,
+ .config_intr = ste10Xp_config_intr,
+ .handle_interrupt = ste10Xp_handle_interrupt,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ .phy_id = STE100P_PHY_ID,
+ .phy_id_mask = 0xffffffff,
+ .name = "STe100p",
+ /* PHY_BASIC_FEATURES */
+ .config_init = ste10Xp_config_init,
+ .config_intr = ste10Xp_config_intr,
+ .handle_interrupt = ste10Xp_handle_interrupt,
+ .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/stubs.c b/drivers/net/phy/stubs.c
new file mode 100644
index 000000000..cfb9f275e
--- /dev/null
+++ b/drivers/net/phy/stubs.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Stubs for PHY library functionality called by the core network stack.
+ * These are necessary because CONFIG_PHYLIB can be a module, and built-in
+ * code cannot directly call symbols exported by modules.
+ */
+#include <linux/phylib_stubs.h>
+
+const struct phylib_stubs *phylib_stubs;
+EXPORT_SYMBOL_GPL(phylib_stubs);
diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c
new file mode 100644
index 000000000..59f1ba4d4
--- /dev/null
+++ b/drivers/net/phy/swphy.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Software PHY emulation
+ *
+ * Code taken from fixed_phy.c by Russell King.
+ *
+ * Author: Vitaly Bordug <vbordug@ru.mvista.com>
+ * Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * Copyright (c) 2006-2007 MontaVista Software, Inc.
+ */
+#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 bmsr;
+ u16 lpa;
+ u16 lpagb;
+ u16 estat;
+};
+
+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] = {
+ .lpa = LPA_10FULL | LPA_10HALF,
+ },
+ [SWMII_SPEED_100] = {
+ .bmsr = BMSR_100FULL | BMSR_100HALF,
+ .lpa = LPA_100FULL | LPA_100HALF,
+ },
+ [SWMII_SPEED_1000] = {
+ .bmsr = BMSR_ESTATEN,
+ .lpagb = LPA_1000FULL | LPA_1000HALF,
+ .estat = ESTATUS_1000_TFULL | ESTATUS_1000_THALF,
+ },
+};
+
+static const struct swmii_regs duplex[] = {
+ [SWMII_DUPLEX_HALF] = {
+ .bmsr = BMSR_ESTATEN | BMSR_100HALF,
+ .lpa = LPA_10HALF | LPA_100HALF,
+ .lpagb = LPA_1000HALF,
+ .estat = ESTATUS_1000_THALF,
+ },
+ [SWMII_DUPLEX_FULL] = {
+ .bmsr = BMSR_ESTATEN | BMSR_100FULL,
+ .lpa = LPA_10FULL | LPA_100FULL,
+ .lpagb = LPA_1000FULL,
+ .estat = ESTATUS_1000_TFULL,
+ },
+};
+
+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 estat = 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;
+ estat |= speed[speed_index].estat & duplex[duplex_index].estat;
+
+ if (state->link) {
+ bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
+
+ 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_ANENABLE;
+ case MII_BMSR:
+ return bmsr;
+ case MII_PHYSID1:
+ case MII_PHYSID2:
+ return 0;
+ case MII_LPA:
+ return lpa;
+ case MII_STAT1000:
+ return lpagb;
+ case MII_ESTATUS:
+ return estat;
+
+ /*
+ * 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..8057ea8db
--- /dev/null
+++ b/drivers/net/phy/teranetics.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Teranetics PHY
+ *
+ * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
+ *
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ */
+
+#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",
+ .features = PHY_10GBIT_FEATURES,
+ .aneg_done = teranetics_aneg_done,
+ .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..38834347a
--- /dev/null
+++ b/drivers/net/phy/uPD60620.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Renesas PHY uPD60620.
+ *
+ * Copyright (C) 2015 Softing Industrial Automation GmbH
+ */
+
+#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;
+ linkmode_zero(phydev->lp_advertising);
+ 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;
+
+ mii_lpa_to_linkmode_lpa_t(phydev->lp_advertising,
+ phy_state);
+
+ phy_resolve_aneg_pause(phydev);
+ }
+ }
+ 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",
+ /* 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..897b979ec
--- /dev/null
+++ b/drivers/net/phy/vitesse.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Vitesse PHYs
+ *
+ * Author: Kriston Carson
+ */
+
+#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
+#define MII_VSC8244_ISTAT_MASK (MII_VSC8244_ISTAT_SPEED | \
+ MII_VSC8244_ISTAT_LINK | \
+ MII_VSC8244_ISTAT_DUPLEX)
+
+#define MII_VSC8221_ISTAT_MASK MII_VSC8244_ISTAT_LINK
+
+/* 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_VSC8572 0x000704d0
+#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 0;
+}
+
+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 0;
+}
+
+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 0;
+}
+
+static int vsc82xx_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ /* Don't bother to ACK the interrupts since the 824x cannot
+ * clear the interrupts if they are disabled.
+ */
+ 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_VSC8572 ||
+ 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 irqreturn_t vsc82xx_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status, irq_mask;
+
+ if (phydev->drv->phy_id == PHY_ID_VSC8244 ||
+ phydev->drv->phy_id == PHY_ID_VSC8572 ||
+ phydev->drv->phy_id == PHY_ID_VSC8601)
+ irq_mask = MII_VSC8244_ISTAT_MASK;
+ else
+ irq_mask = MII_VSC8221_ISTAT_MASK;
+
+ irq_status = phy_read(phydev, MII_VSC8244_ISTAT);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & irq_mask))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+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,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc824x_config_init,
+ .config_aneg = &vsc82x4_config_aneg,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+}, {
+ .phy_id = PHY_ID_VSC8244,
+ .name = "Vitesse VSC8244",
+ .phy_id_mask = 0x000fffc0,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc824x_config_init,
+ .config_aneg = &vsc82x4_config_aneg,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+}, {
+ .phy_id = PHY_ID_VSC8572,
+ .name = "Vitesse VSC8572",
+ .phy_id_mask = 0x000ffff0,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc824x_config_init,
+ .config_aneg = &vsc82x4_config_aneg,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+}, {
+ .phy_id = PHY_ID_VSC8601,
+ .name = "Vitesse VSC8601",
+ .phy_id_mask = 0x000ffff0,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc8601_config_init,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+}, {
+ .phy_id = PHY_ID_VSC7385,
+ .name = "Vitesse VSC7385",
+ .phy_id_mask = 0x000ffff0,
+ /* 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,
+ /* 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,
+ /* 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,
+ /* 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,
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc824x_config_init,
+ .config_aneg = &vsc82x4_config_aneg,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+}, {
+ /* Vitesse 8221 */
+ .phy_id = PHY_ID_VSC8221,
+ .phy_id_mask = 0x000ffff0,
+ .name = "Vitesse VSC8221",
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc8221_config_init,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+}, {
+ /* Vitesse 8211 */
+ .phy_id = PHY_ID_VSC8211,
+ .phy_id_mask = 0x000ffff0,
+ .name = "Vitesse VSC8211",
+ /* PHY_GBIT_FEATURES */
+ .config_init = &vsc8221_config_init,
+ .config_intr = &vsc82xx_config_intr,
+ .handle_interrupt = &vsc82xx_handle_interrupt,
+} };
+
+module_phy_driver(vsc82xx_driver);
+
+static struct mdio_device_id __maybe_unused vitesse_tbl[] = {
+ { PHY_ID_VSC8234, 0x000ffff0 },
+ { PHY_ID_VSC8244, 0x000fffc0 },
+ { PHY_ID_VSC8572, 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..7fd9fe6a6
--- /dev/null
+++ b/drivers/net/phy/xilinx_gmii2rgmii.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* 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
+ */
+#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 void xgmiitorgmii_configure(struct gmii2rgmii *priv, int speed)
+{
+ struct mii_bus *bus = priv->mdio->bus;
+ int addr = priv->mdio->addr;
+ u16 val;
+
+ val = mdiobus_read(bus, addr, XILINX_GMII2RGMII_REG);
+ val &= ~XILINX_GMII2RGMII_SPEED_MASK;
+
+ if (speed == SPEED_1000)
+ val |= BMCR_SPEED1000;
+ else if (speed == SPEED_100)
+ val |= BMCR_SPEED100;
+ else
+ val |= BMCR_SPEED10;
+
+ mdiobus_write(bus, addr, XILINX_GMII2RGMII_REG, val);
+}
+
+static int xgmiitorgmii_read_status(struct phy_device *phydev)
+{
+ struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio);
+ 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;
+
+ xgmiitorgmii_configure(priv, phydev->speed);
+
+ return 0;
+}
+
+static int xgmiitorgmii_set_loopback(struct phy_device *phydev, bool enable)
+{
+ struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio);
+ int err;
+
+ if (priv->phy_drv->set_loopback)
+ err = priv->phy_drv->set_loopback(phydev, enable);
+ else
+ err = genphy_loopback(phydev, enable);
+ if (err < 0)
+ return err;
+
+ xgmiitorgmii_configure(priv, phydev->speed);
+
+ 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");
+ put_device(&priv->phy_dev->mdio.dev);
+ 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->conv_phy_drv.set_loopback = xgmiitorgmii_set_loopback;
+ mdiodev_set_drvdata(&priv->phy_dev->mdio, 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");