From a9ed3aa9b87ee41e8ab3ff471b1331c154767e45 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Sat, 26 Sep 2026 07:57:59 +1000 Subject: Merge tag 'drm-misc-fixes-2026-09-24' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-fixes A number of fixes: - bridge: - samsung-dsim: fix GPIO lifetime - client: Null pointer dereference fix - imagination: error handling fix, page handling fix - nouveau: fix reference leaks, double-frees, out-of-bounds accesses, use-after-frees, don't reject config without SCDC, a number of workarounds - virtio: fix memory leak, reference leaks, null pointer dereference, add pixel blend mode, cache coherency fix Signed-off-by: Dave Airlie From: Maxime Ripard Link: https://patch.msgid.link/arU22zzqUGDEco1y@houat --- drivers/net/phy/Kconfig | 494 ++ drivers/net/phy/Makefile | 104 + drivers/net/phy/adin.c | 1069 ++++ drivers/net/phy/adin1100.c | 354 ++ drivers/net/phy/adin1140-phy.c | 72 + drivers/net/phy/air_an8801.c | 1156 +++++ drivers/net/phy/air_en8811h.c | 1574 ++++++ drivers/net/phy/air_phy_lib.c | 211 + drivers/net/phy/air_phy_lib.h | 41 + drivers/net/phy/amd.c | 120 + drivers/net/phy/aquantia/Kconfig | 6 + drivers/net/phy/aquantia/Makefile | 6 + drivers/net/phy/aquantia/aquantia.h | 259 + drivers/net/phy/aquantia/aquantia_firmware.c | 385 ++ drivers/net/phy/aquantia/aquantia_hwmon.c | 216 + drivers/net/phy/aquantia/aquantia_leds.c | 160 + drivers/net/phy/aquantia/aquantia_main.c | 1593 ++++++ drivers/net/phy/as21xxx.c | 1088 ++++ drivers/net/phy/ax88796b.c | 130 + drivers/net/phy/ax88796b_rust.rs | 133 + drivers/net/phy/bcm-cygnus.c | 292 ++ drivers/net/phy/bcm-phy-lib.c | 1194 +++++ drivers/net/phy/bcm-phy-lib.h | 129 + drivers/net/phy/bcm-phy-ptp.c | 957 ++++ drivers/net/phy/bcm54140.c | 895 ++++ drivers/net/phy/bcm63xx.c | 102 + drivers/net/phy/bcm7xxx.c | 976 ++++ drivers/net/phy/bcm84881.c | 474 ++ drivers/net/phy/bcm87xx.c | 222 + drivers/net/phy/broadcom.c | 1781 +++++++ drivers/net/phy/cicada.c | 154 + drivers/net/phy/cortina.c | 99 + drivers/net/phy/dap8211r.c | 220 + drivers/net/phy/davicom.c | 220 + drivers/net/phy/dp83640.c | 1482 ++++++ drivers/net/phy/dp83640_reg.h | 268 + drivers/net/phy/dp83822.c | 1261 +++++ drivers/net/phy/dp83848.c | 168 + drivers/net/phy/dp83867.c | 1233 +++++ drivers/net/phy/dp83869.c | 950 ++++ drivers/net/phy/dp83tc811.c | 415 ++ drivers/net/phy/dp83td510.c | 980 ++++ drivers/net/phy/dp83tg720.c | 678 +++ drivers/net/phy/et1011c.c | 102 + drivers/net/phy/fixed_phy.c | 230 + drivers/net/phy/icplus.c | 635 +++ drivers/net/phy/intel-xway.c | 789 +++ drivers/net/phy/linkmode.c | 95 + drivers/net/phy/lxt.c | 358 ++ drivers/net/phy/marvell-88q2xxx.c | 1154 +++++ drivers/net/phy/marvell-88x2222.c | 604 +++ drivers/net/phy/marvell.c | 4205 +++++++++++++++ drivers/net/phy/marvell10g.c | 1486 ++++++ drivers/net/phy/mdio-open-alliance.h | 95 + drivers/net/phy/mdio_bus.c | 627 +++ drivers/net/phy/mdio_bus_provider.c | 761 +++ drivers/net/phy/mdio_device.c | 315 ++ drivers/net/phy/mdio_devres.c | 135 + drivers/net/phy/mediatek/Kconfig | 53 + drivers/net/phy/mediatek/Makefile | 6 + drivers/net/phy/mediatek/mtk-2p5ge.c | 413 ++ drivers/net/phy/mediatek/mtk-fe-soc.c | 52 + drivers/net/phy/mediatek/mtk-ge-soc.c | 1585 ++++++ drivers/net/phy/mediatek/mtk-ge.c | 171 + drivers/net/phy/mediatek/mtk-phy-lib.c | 354 ++ drivers/net/phy/mediatek/mtk.h | 104 + drivers/net/phy/meson-gxl.c | 238 + drivers/net/phy/micrel.c | 7139 ++++++++++++++++++++++++++ drivers/net/phy/microchip.c | 601 +++ drivers/net/phy/microchip_rds_ptp.c | 1330 +++++ drivers/net/phy/microchip_rds_ptp.h | 247 + drivers/net/phy/microchip_t1.c | 2176 ++++++++ drivers/net/phy/microchip_t1s.c | 582 +++ drivers/net/phy/mii_timestamper.c | 135 + drivers/net/phy/motorcomm.c | 3180 ++++++++++++ drivers/net/phy/mscc/Makefile | 15 + drivers/net/phy/mscc/mscc.h | 523 ++ drivers/net/phy/mscc/mscc_fc_buffer.h | 64 + drivers/net/phy/mscc/mscc_mac.h | 159 + drivers/net/phy/mscc/mscc_macsec.c | 1011 ++++ drivers/net/phy/mscc/mscc_macsec.h | 323 ++ drivers/net/phy/mscc/mscc_main.c | 3003 +++++++++++ drivers/net/phy/mscc/mscc_ptp.c | 1645 ++++++ drivers/net/phy/mscc/mscc_ptp.h | 473 ++ drivers/net/phy/mscc/mscc_serdes.c | 650 +++ drivers/net/phy/mscc/mscc_serdes.h | 31 + drivers/net/phy/mxl-86110.c | 981 ++++ drivers/net/phy/mxl-gpy.c | 1503 ++++++ drivers/net/phy/national.c | 181 + drivers/net/phy/ncn26000.c | 171 + drivers/net/phy/nxp-c45-tja11xx-macsec.c | 1725 +++++++ drivers/net/phy/nxp-c45-tja11xx.c | 2190 ++++++++ drivers/net/phy/nxp-c45-tja11xx.h | 63 + drivers/net/phy/nxp-cbtx.c | 227 + drivers/net/phy/nxp-tja11xx.c | 942 ++++ drivers/net/phy/open_alliance_helpers.c | 77 + drivers/net/phy/open_alliance_helpers.h | 47 + drivers/net/phy/phy-c45.c | 1967 +++++++ drivers/net/phy/phy-caps.h | 70 + drivers/net/phy/phy-core.c | 903 ++++ drivers/net/phy/phy.c | 2106 ++++++++ drivers/net/phy/phy_caps.c | 447 ++ drivers/net/phy/phy_device.c | 4038 +++++++++++++++ drivers/net/phy/phy_led_triggers.c | 155 + drivers/net/phy/phy_link_topology.c | 115 + drivers/net/phy/phy_package.c | 442 ++ drivers/net/phy/phy_port.c | 217 + drivers/net/phy/phylib-internal.h | 32 + drivers/net/phy/phylib.h | 41 + drivers/net/phy/phylink.c | 4434 ++++++++++++++++ drivers/net/phy/qcom/Kconfig | 31 + drivers/net/phy/qcom/Makefile | 6 + drivers/net/phy/qcom/at803x.c | 1249 +++++ drivers/net/phy/qcom/qca807x.c | 856 +++ drivers/net/phy/qcom/qca808x.c | 686 +++ drivers/net/phy/qcom/qca83xx.c | 269 + drivers/net/phy/qcom/qcom-phy-lib.c | 776 +++ drivers/net/phy/qcom/qcom.h | 271 + drivers/net/phy/qsemi.c | 163 + drivers/net/phy/qt2025.rs | 110 + drivers/net/phy/realtek/Kconfig | 16 + drivers/net/phy/realtek/Makefile | 4 + drivers/net/phy/realtek/realtek.h | 10 + drivers/net/phy/realtek/realtek_hwmon.c | 74 + drivers/net/phy/realtek/realtek_main.c | 3426 ++++++++++++ drivers/net/phy/rockchip.c | 200 + drivers/net/phy/sfp-bus.c | 865 ++++ drivers/net/phy/sfp.c | 3503 +++++++++++++ drivers/net/phy/sfp.h | 45 + drivers/net/phy/smsc.c | 900 ++++ drivers/net/phy/ste10Xp.c | 137 + drivers/net/phy/stubs.c | 10 + drivers/net/phy/swphy.c | 175 + drivers/net/phy/swphy.h | 10 + drivers/net/phy/teranetics.c | 96 + drivers/net/phy/uPD60620.c | 98 + drivers/net/phy/vitesse.c | 732 +++ drivers/net/phy/xilinx_gmii2rgmii.c | 149 + 138 files changed, 100116 insertions(+) create mode 100644 drivers/net/phy/Kconfig create mode 100644 drivers/net/phy/Makefile create mode 100644 drivers/net/phy/adin.c create mode 100644 drivers/net/phy/adin1100.c create mode 100644 drivers/net/phy/adin1140-phy.c create mode 100644 drivers/net/phy/air_an8801.c create mode 100644 drivers/net/phy/air_en8811h.c create mode 100644 drivers/net/phy/air_phy_lib.c create mode 100644 drivers/net/phy/air_phy_lib.h create mode 100644 drivers/net/phy/amd.c create mode 100644 drivers/net/phy/aquantia/Kconfig create mode 100644 drivers/net/phy/aquantia/Makefile create mode 100644 drivers/net/phy/aquantia/aquantia.h create mode 100644 drivers/net/phy/aquantia/aquantia_firmware.c create mode 100644 drivers/net/phy/aquantia/aquantia_hwmon.c create mode 100644 drivers/net/phy/aquantia/aquantia_leds.c create mode 100644 drivers/net/phy/aquantia/aquantia_main.c create mode 100644 drivers/net/phy/as21xxx.c create mode 100644 drivers/net/phy/ax88796b.c create mode 100644 drivers/net/phy/ax88796b_rust.rs create mode 100644 drivers/net/phy/bcm-cygnus.c create mode 100644 drivers/net/phy/bcm-phy-lib.c create mode 100644 drivers/net/phy/bcm-phy-lib.h create mode 100644 drivers/net/phy/bcm-phy-ptp.c create mode 100644 drivers/net/phy/bcm54140.c create mode 100644 drivers/net/phy/bcm63xx.c create mode 100644 drivers/net/phy/bcm7xxx.c create mode 100644 drivers/net/phy/bcm84881.c create mode 100644 drivers/net/phy/bcm87xx.c create mode 100644 drivers/net/phy/broadcom.c create mode 100644 drivers/net/phy/cicada.c create mode 100644 drivers/net/phy/cortina.c create mode 100644 drivers/net/phy/dap8211r.c create mode 100644 drivers/net/phy/davicom.c create mode 100644 drivers/net/phy/dp83640.c create mode 100644 drivers/net/phy/dp83640_reg.h create mode 100644 drivers/net/phy/dp83822.c create mode 100644 drivers/net/phy/dp83848.c create mode 100644 drivers/net/phy/dp83867.c create mode 100644 drivers/net/phy/dp83869.c create mode 100644 drivers/net/phy/dp83tc811.c create mode 100644 drivers/net/phy/dp83td510.c create mode 100644 drivers/net/phy/dp83tg720.c create mode 100644 drivers/net/phy/et1011c.c create mode 100644 drivers/net/phy/fixed_phy.c create mode 100644 drivers/net/phy/icplus.c create mode 100644 drivers/net/phy/intel-xway.c create mode 100644 drivers/net/phy/linkmode.c create mode 100644 drivers/net/phy/lxt.c create mode 100644 drivers/net/phy/marvell-88q2xxx.c create mode 100644 drivers/net/phy/marvell-88x2222.c create mode 100644 drivers/net/phy/marvell.c create mode 100644 drivers/net/phy/marvell10g.c create mode 100644 drivers/net/phy/mdio-open-alliance.h create mode 100644 drivers/net/phy/mdio_bus.c create mode 100644 drivers/net/phy/mdio_bus_provider.c create mode 100644 drivers/net/phy/mdio_device.c create mode 100644 drivers/net/phy/mdio_devres.c create mode 100644 drivers/net/phy/mediatek/Kconfig create mode 100644 drivers/net/phy/mediatek/Makefile create mode 100644 drivers/net/phy/mediatek/mtk-2p5ge.c create mode 100644 drivers/net/phy/mediatek/mtk-fe-soc.c create mode 100644 drivers/net/phy/mediatek/mtk-ge-soc.c create mode 100644 drivers/net/phy/mediatek/mtk-ge.c create mode 100644 drivers/net/phy/mediatek/mtk-phy-lib.c create mode 100644 drivers/net/phy/mediatek/mtk.h create mode 100644 drivers/net/phy/meson-gxl.c create mode 100644 drivers/net/phy/micrel.c create mode 100644 drivers/net/phy/microchip.c create mode 100644 drivers/net/phy/microchip_rds_ptp.c create mode 100644 drivers/net/phy/microchip_rds_ptp.h create mode 100644 drivers/net/phy/microchip_t1.c create mode 100644 drivers/net/phy/microchip_t1s.c create mode 100644 drivers/net/phy/mii_timestamper.c create mode 100644 drivers/net/phy/motorcomm.c create mode 100644 drivers/net/phy/mscc/Makefile create mode 100644 drivers/net/phy/mscc/mscc.h create mode 100644 drivers/net/phy/mscc/mscc_fc_buffer.h create mode 100644 drivers/net/phy/mscc/mscc_mac.h create mode 100644 drivers/net/phy/mscc/mscc_macsec.c create mode 100644 drivers/net/phy/mscc/mscc_macsec.h create mode 100644 drivers/net/phy/mscc/mscc_main.c create mode 100644 drivers/net/phy/mscc/mscc_ptp.c create mode 100644 drivers/net/phy/mscc/mscc_ptp.h create mode 100644 drivers/net/phy/mscc/mscc_serdes.c create mode 100644 drivers/net/phy/mscc/mscc_serdes.h create mode 100644 drivers/net/phy/mxl-86110.c create mode 100644 drivers/net/phy/mxl-gpy.c create mode 100644 drivers/net/phy/national.c create mode 100644 drivers/net/phy/ncn26000.c create mode 100644 drivers/net/phy/nxp-c45-tja11xx-macsec.c create mode 100644 drivers/net/phy/nxp-c45-tja11xx.c create mode 100644 drivers/net/phy/nxp-c45-tja11xx.h create mode 100644 drivers/net/phy/nxp-cbtx.c create mode 100644 drivers/net/phy/nxp-tja11xx.c create mode 100644 drivers/net/phy/open_alliance_helpers.c create mode 100644 drivers/net/phy/open_alliance_helpers.h create mode 100644 drivers/net/phy/phy-c45.c create mode 100644 drivers/net/phy/phy-caps.h create mode 100644 drivers/net/phy/phy-core.c create mode 100644 drivers/net/phy/phy.c create mode 100644 drivers/net/phy/phy_caps.c create mode 100644 drivers/net/phy/phy_device.c create mode 100644 drivers/net/phy/phy_led_triggers.c create mode 100644 drivers/net/phy/phy_link_topology.c create mode 100644 drivers/net/phy/phy_package.c create mode 100644 drivers/net/phy/phy_port.c create mode 100644 drivers/net/phy/phylib-internal.h create mode 100644 drivers/net/phy/phylib.h create mode 100644 drivers/net/phy/phylink.c create mode 100644 drivers/net/phy/qcom/Kconfig create mode 100644 drivers/net/phy/qcom/Makefile create mode 100644 drivers/net/phy/qcom/at803x.c create mode 100644 drivers/net/phy/qcom/qca807x.c create mode 100644 drivers/net/phy/qcom/qca808x.c create mode 100644 drivers/net/phy/qcom/qca83xx.c create mode 100644 drivers/net/phy/qcom/qcom-phy-lib.c create mode 100644 drivers/net/phy/qcom/qcom.h create mode 100644 drivers/net/phy/qsemi.c create mode 100644 drivers/net/phy/qt2025.rs create mode 100644 drivers/net/phy/realtek/Kconfig create mode 100644 drivers/net/phy/realtek/Makefile create mode 100644 drivers/net/phy/realtek/realtek.h create mode 100644 drivers/net/phy/realtek/realtek_hwmon.c create mode 100644 drivers/net/phy/realtek/realtek_main.c create mode 100644 drivers/net/phy/rockchip.c create mode 100644 drivers/net/phy/sfp-bus.c create mode 100644 drivers/net/phy/sfp.c create mode 100644 drivers/net/phy/sfp.h create mode 100644 drivers/net/phy/smsc.c create mode 100644 drivers/net/phy/ste10Xp.c create mode 100644 drivers/net/phy/stubs.c create mode 100644 drivers/net/phy/swphy.c create mode 100644 drivers/net/phy/swphy.h create mode 100644 drivers/net/phy/teranetics.c create mode 100644 drivers/net/phy/uPD60620.c create mode 100644 drivers/net/phy/vitesse.c create mode 100644 drivers/net/phy/xilinx_gmii2rgmii.c (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig new file mode 100644 index 000000000..b4ef927fd --- /dev/null +++ b/drivers/net/phy/Kconfig @@ -0,0 +1,494 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PHY Layer Configuration +# + +config PHYLINK + tristate + 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" + help + Ethernet controllers are usually attached to PHY + devices. This option provides infrastructure for + managing PHY devices. + +if PHYLIB + +config SWPHY + bool + +config PHY_PACKAGE + tristate + +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: + :: + + Where speed is in the form: + Mbps OR Gbps OR link + for any speed known to the PHY. + +config OPEN_ALLIANCE_HELPERS + bool + +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 RUST_PHYLIB_ABSTRACTIONS + bool "Rust PHYLIB abstractions support" + depends on RUST + depends on PHYLIB=y + help + Adds support needed for PHY drivers written in Rust. It provides + a wrapper around the C phylib core. + +config SFP + tristate "SFP cage support" + depends on I2C && PHYLINK + depends on HWMON || HWMON=n + select MDIO_I2C + +comment "MII PHY device drivers" + +config AS21XXX_PHY + tristate "Aeonsemi AS21xxx PHYs" + help + Currently supports the Aeonsemi AS21xxx PHY. + + These are C45 PHYs 10G that require all a generic firmware. + + Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1, + AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1, + AS21210PB1 that all register with the PHY ID 0x7500 0x7500 + before the firmware is loaded. + +config AIR_AN8801_PHY + tristate "Airoha AN8801 Gigabit PHY" + select AIR_NET_PHYLIB + help + Currently supports the Airoha AN8801R PHY. + +config AIR_EN8811H_PHY + tristate "Airoha EN8811H 2.5 Gigabit PHY" + select AIR_NET_PHYLIB + select PHY_COMMON_PROPS + help + Currently supports the Airoha EN8811H PHY. + +config AIR_NET_PHYLIB + tristate + help + Airoha Ethernet PHY common library + +config AMD_PHY + tristate "AMD and Altima PHYs" + help + Currently supports the AMD am79c874 and Altima AC101L. + +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 ADIN1140_PHY + tristate "Analog Devices ADIN1140 10BASE-T1S PHY" + help + Adds support for the Analog Devices, Inc. ADIN1140's internal + 10BASE-T1S PHY. + +config AMCC_QT2025_PHY + tristate "AMCC QT2025 PHY" + depends on RUST_PHYLIB_ABSTRACTIONS + depends on RUST_FW_LOADER_ABSTRACTIONS + help + Adds support for the Applied Micro Circuits Corporation QT2025 PHY. + +source "drivers/net/phy/aquantia/Kconfig" + +config AX88796B_PHY + tristate "Asix PHYs" + help + Currently supports the Asix Electronics PHY found in the X-Surf 100 + AX88796B package. + +config AX88796B_RUST_PHY + bool "Rust reference driver for Asix PHYs" + depends on RUST_PHYLIB_ABSTRACTIONS && AX88796B_PHY + help + Uses the Rust reference driver for Asix PHYs (ax88796b_rust.ko). + The features are equivalent. It 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 + select PHY_PACKAGE + 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 DAP8211R_PHY + tristate "DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY" + depends on OF + help + Support for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. + This PHY is designed for enterprise and industrial networking + applications, supporting 10/100/1000 Mbps operation. Supports + RGMII interface with configurable TX/RX clock delays. + +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 22F 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" + depends on HWMON || HWMON=n + 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 MAXLINEAR_86110_PHY + tristate "MaxLinear MXL86110 PHY support" + help + Support for the MaxLinear MXL86110 Gigabit Ethernet + Physical Layer transceiver. + The MXL86110 is commonly used in networking equipment such as + routers, switches, and embedded systems, providing the + physical interface for 10/100/1000 Mbps Ethernet connections + over copper media. + If you are using a board with the MXL86110 PHY connected to your + Ethernet MAC, you should enable this option. + +source "drivers/net/phy/mediatek/Kconfig" + +config MICREL_PHY + tristate "Micrel PHYs" + depends on PTP_1588_CLOCK_OPTIONAL + select PHY_PACKAGE + help + Supports the KSZ8xxx, KSZ9xxx, and LAN88xx families of Micrel/Microchip PHYs. + +config MICROCHIP_T1S_PHY + tristate "Microchip 10BASE-T1S Ethernet PHYs" + help + Currently supports the LAN8670/1/2 Rev.B1/C1/C2/D0 and + LAN8650/1 Rev.B0/B1 Internal PHYs. + +config MICROCHIP_PHY + tristate "Microchip PHYs" + help + Supports the LAN88XX PHYs. + +config MICROCHIP_T1_PHY + tristate "Microchip T1 PHYs" + select MICROCHIP_PHY_RDS_PTP if NETWORK_PHY_TIMESTAMPING + depends on PTP_1588_CLOCK_OPTIONAL + help + Supports the LAN8XXX PHYs. + +config MICROCHIP_PHY_RDS_PTP + tristate + help + Currently supports LAN887X T1 PHY + +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 + select PHY_PACKAGE + 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 + depends on MACSEC || !MACSEC + help + Enable support for NXP C45 TJA11XX PHYs. + Currently supports the TJA1103, TJA1104, TJA1120 and TJA1121 PHYs. + +config NXP_TJA11XX_PHY + tristate "NXP TJA11xx PHYs support" + depends on HWMON + help + Currently supports the NXP TJA1100, TJA1101 and TJA1102 PHYs. + +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. + +source "drivers/net/phy/qcom/Kconfig" + +config QSEMI_PHY + tristate "Quality Semiconductor PHYs" + help + Currently supports the qs6612 + +source "drivers/net/phy/realtek/Kconfig" + +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 DP83TG720_PHY + tristate "Texas Instruments DP83TG720 Ethernet 1000Base-T1 PHY" + select OPEN_ALLIANCE_HELPERS + help + The DP83TG720S-Q1 is an automotive Ethernet physical layer + transceiver compliant with IEEE 802.3bp and Open Alliance + standards. It supports key functions necessary for + transmitting and receiving data over both unshielded and + shielded single twisted-pair cables. This device offers + flexible xMII interface options, including support for both + RGMII and SGMII MAC interfaces. It's suitable for applications + requiring high-speed data transmission in automotive + networking environments. + +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 diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile new file mode 100644 index 000000000..25c4a3c24 --- /dev/null +++ b/drivers/net/phy/Makefile @@ -0,0 +1,104 @@ +# 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 phy_link_topology.o \ + phy_caps.o mdio_bus_provider.o phy_port.o \ + mdio_device.o + +ifdef CONFIG_PHYLIB +# built-in whenever PHYLIB is built-in or module +obj-y += stubs.o +endif + +libphy-$(CONFIG_SWPHY) += swphy.o +libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o +libphy-$(CONFIG_OPEN_ALLIANCE_HELPERS) += open_alliance_helpers.o + +obj-$(CONFIG_PHYLIB) += mdio_bus.o +obj-$(CONFIG_PHYLINK) += phylink.o +obj-$(CONFIG_PHYLIB) += libphy.o +obj-$(CONFIG_PHYLIB) += mdio_devres.o +obj-$(CONFIG_PHY_PACKAGE) += phy_package.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_ADIN1140_PHY) += adin1140-phy.o +obj-$(CONFIG_AIR_AN8801_PHY) += air_an8801.o +obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o +obj-$(CONFIG_AIR_NET_PHYLIB) += air_phy_lib.o +obj-$(CONFIG_AMD_PHY) += amd.o +obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o +obj-$(CONFIG_AQUANTIA_PHY) += aquantia/ +obj-$(CONFIG_AS21XXX_PHY) += as21xxx.o +ifdef CONFIG_AX88796B_RUST_PHY + obj-$(CONFIG_AX88796B_PHY) += ax88796b_rust.o +else + obj-$(CONFIG_AX88796B_PHY) += ax88796b.o +endif +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_DAP8211R_PHY) += dap8211r.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_DP83TG720_PHY) += dp83tg720.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_MAXLINEAR_86110_PHY) += mxl-86110.o +obj-y += mediatek/ +obj-$(CONFIG_MESON_GXL_PHY) += meson-gxl.o +obj-$(CONFIG_MICREL_PHY) += micrel.o +obj-$(CONFIG_MICROCHIP_PHY) += microchip.o +obj-$(CONFIG_MICROCHIP_PHY_RDS_PTP) += microchip_rds_ptp.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 +nxp-c45-tja-objs += nxp-c45-tja11xx.o +ifdef CONFIG_MACSEC +nxp-c45-tja-objs += nxp-c45-tja11xx-macsec.o +endif +obj-$(CONFIG_NXP_C45_TJA11XX_PHY) += nxp-c45-tja.o +obj-$(CONFIG_NXP_CBTX_PHY) += nxp-cbtx.o +obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.o +obj-y += qcom/ +obj-$(CONFIG_QSEMI_PHY) += qsemi.o +obj-$(CONFIG_REALTEK_PHY) += realtek/ +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..3a934051b --- /dev/null +++ b/drivers/net/phy/adin.c @@ -0,0 +1,1069 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices Industrial Ethernet PHYs + * + * Copyright 2019 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_FLD_EN_REG 0x8E27 +#define ADIN1300_FLD_PCS_ERR_100_EN BIT(7) +#define ADIN1300_FLD_PCS_ERR_1000_EN BIT(6) +#define ADIN1300_FLD_SLCR_OUT_STUCK_100_EN BIT(5) +#define ADIN1300_FLD_SLCR_OUT_STUCK_1000_EN BIT(4) +#define ADIN1300_FLD_SLCR_IN_ZDET_100_EN BIT(3) +#define ADIN1300_FLD_SLCR_IN_ZDET_1000_EN BIT(2) +#define ADIN1300_FLD_SLCR_IN_INVLD_100_EN BIT(1) +#define ADIN1300_FLD_SLCR_IN_INVLD_1000_EN BIT(0) +/* These bits are the ones which are enabled by default. */ +#define ADIN1300_FLD_EN_ON \ + (ADIN1300_FLD_SLCR_OUT_STUCK_100_EN | \ + ADIN1300_FLD_SLCR_OUT_STUCK_1000_EN | \ + ADIN1300_FLD_SLCR_IN_ZDET_100_EN | \ + ADIN1300_FLD_SLCR_IN_ZDET_1000_EN | \ + ADIN1300_FLD_SLCR_IN_INVLD_1000_EN) + +#define ADIN1300_CLOCK_STOP_REG 0x9400 +#define ADIN1300_LPI_WAKE_ERR_CNT_REG 0xa000 + +#define ADIN1300_B_100_ZPTM_DIMRX 0xB685 +#define ADIN1300_B_100_ZPTM_EN_DIMRX BIT(0) + +#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_fast_down(struct phy_device *phydev, u8 *msecs) +{ + int reg; + + reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_FLD_EN_REG); + if (reg < 0) + return reg; + + if (reg & ADIN1300_FLD_EN_ON) + *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_ON; + else + *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF; + + return 0; +} + +static int adin_set_fast_down(struct phy_device *phydev, const u8 *msecs) +{ + if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_ON) + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + ADIN1300_FLD_EN_REG, + ADIN1300_FLD_EN_ON); + + if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF) + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + ADIN1300_FLD_EN_REG, + ADIN1300_FLD_EN_ON); + + return -EINVAL; +} + +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); + case ETHTOOL_PHY_FAST_LINK_DOWN: + return adin_get_fast_down(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); + case ETHTOOL_PHY_FAST_LINK_DOWN: + return adin_set_fast_down(phydev, 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_zptm100(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + + if (!(device_property_read_bool(dev, "adi,low-cmode-impedance"))) + return 0; + + /* clear bit 0 to configure for lowest common-mode impedance */ + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + ADIN1300_B_100_ZPTM_DIMRX, + ADIN1300_B_100_ZPTM_EN_DIMRX); +} + +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; + + rc = adin_config_zptm100(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++) + ethtool_puts(&data, adin_hw_stats[i].string); +} + +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 const 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..8f9753d43 --- /dev/null +++ b/drivers/net/phy/adin1100.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * Driver for Analog Devices Industrial Ethernet T1L PHYs + * + * Copyright 2020 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_ID_ADIN1100 0x0283bc81 +#define PHY_ID_ADIN1110 0x0283bc91 +#define PHY_ID_ADIN2111 0x0283bca1 + +#define ADIN_PHY_SUBSYS_IRQ_MASK 0x0021 +#define ADIN_LINK_STAT_CHNG_IRQ_EN BIT(1) + +#define ADIN_PHY_SUBSYS_IRQ_STATUS 0x0011 +#define ADIN_LINK_STAT_CHNG BIT(1) + +#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_phy_ack_intr(struct phy_device *phydev) +{ + /* Clear pending interrupts */ + int rc = phy_read_mmd(phydev, MDIO_MMD_VEND2, + ADIN_PHY_SUBSYS_IRQ_STATUS); + + return rc < 0 ? rc : 0; +} + +static int adin_config_intr(struct phy_device *phydev) +{ + u16 irq_mask; + int ret; + + ret = adin_phy_ack_intr(phydev); + if (ret) + return ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + irq_mask = ADIN_LINK_STAT_CHNG_IRQ_EN; + else + irq_mask = 0; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + ADIN_PHY_SUBSYS_IRQ_MASK, + ADIN_LINK_STAT_CHNG_IRQ_EN, irq_mask); +} + +static irqreturn_t adin_phy_handle_interrupt(struct phy_device *phydev) +{ + int irq_status; + + irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, + ADIN_PHY_SUBSYS_IRQ_STATUS); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (!(irq_status & ADIN_LINK_STAT_CHNG)) + return IRQ_NONE; + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int adin_set_powerdown_mode(struct phy_device *phydev, bool en) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, + ADIN_CRSM_SFT_PD_CNTRL, + en ? ADIN_CRSM_SFT_PD_CNTRL_EN : 0); + 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) == en, + 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, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + 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, + .config_intr = adin_config_intr, + .handle_interrupt = adin_phy_handle_interrupt, + .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 const 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/adin1140-phy.c b/drivers/net/phy/adin1140-phy.c new file mode 100644 index 000000000..d35da4ad6 --- /dev/null +++ b/drivers/net/phy/adin1140-phy.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include + +#define ADIN1140_PHY_ID 0x0283be00 + +#define ADIN1140_PCS_CTRL 0x08f3 +#define ADIN1140_PCS_CTRL_LOOPBACK BIT(14) + +static int adin1140_config_aneg(struct phy_device *phydev) +{ + /* phylib tries to clear BIT(12) in MDIO_CTRL1, since AN is disabled. + * However, on the ADIN1140, that field is non-standard, being used + * to control the reset status of the PHY (thus it needs to remain set). + */ + return 0; +} + +static int adin1140_loopback(struct phy_device *phydev, bool enable, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + return phy_modify_mmd(phydev, MDIO_MMD_PCS, ADIN1140_PCS_CTRL, + ADIN1140_PCS_CTRL_LOOPBACK, + enable ? ADIN1140_PCS_CTRL_LOOPBACK : 0); +} + +static int adin1140_read_status(struct phy_device *phydev) +{ + phydev->link = 1; + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_10; + phydev->autoneg = AUTONEG_DISABLE; + + return 0; +} + +static struct phy_driver adin1140_driver[] = { + { + PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID), + .name = "ADIN1140_PHY", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .read_status = adin1140_read_status, + .config_aneg = adin1140_config_aneg, + .set_loopback = adin1140_loopback, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, + .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(adin1140_driver); + +static const struct mdio_device_id __maybe_unused adin1140_tbl[] = { + { PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, adin1140_tbl); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/air_an8801.c b/drivers/net/phy/air_an8801.c new file mode 100644 index 000000000..f97c78e43 --- /dev/null +++ b/drivers/net/phy/air_an8801.c @@ -0,0 +1,1156 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Airoha AN8801 Gigabit PHY. + * + * Copyright (C) 2025 Airoha Technology Corp. + * Copyright (C) 2025 Collabora Ltd. + * AngeloGioacchino Del Regno + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "air_phy_lib.h" + +#define AN8801R_PHY_ID 0xc0ff0421 + +/* MII Registers */ + +/* MII Registers - Airoha Page 1 */ +#define AN8801_EXT_REG_PHY 0x14 +#define AN8801_EXT_PHY_STATUS0 GENMASK(1, 0) +#define AN8801_EXT_PHY_DOWNSHIFT_CTL GENMASK(3, 2) /* 2 to 5 1G auto-neg attempts (0..3) */ +#define AN8801_EXT_PHY_DOWNSHIFT_EN BIT(4) +#define AN8801_EXT_PHY_CTRL0 BIT(5) +#define AN8801_EXT_PHY_STATUS1 GENMASK(8, 6) +#define AN8801_EXT_PHY_CTRL1 GENMASK(14, 9) + +/* MII Registers - Airoha Page 4 */ +#define AN8801_PBUS_ACCESS BIT(28) +#define AN8801_PBUS_EPHY_ACCESS BIT(24) +#define AN8801_PBUS_CL22_ACCESS BIT(23) + +/* BPBUS Registers */ +#define AN8801_BPBUS_REG_LED_GPIO 0x54 +#define AN8801_BPBUS_REG_LED_ID_SEL 0x58 +#define LED_ID_GPIO_SEL(led, gpio) ((led) << ((gpio) * 3)) +#define LED_ID_GPIO_SEL_MASK(gpio) (0x7 << ((gpio) * 3)) +#define AN8801_BPBUS_REG_GPIO_MODE 0x70 +#define AN8801_BPBUS_REG_PHY_IRQ_GPIO 0x7c +#define AN8801_PHY_IRQ_GPIO_NUM_MASK GENMASK(19, 16) +#define AN8801_PHY_IRQ_GPIO_NUM 1 + +#define AN8801_BPBUS_REG_CKO 0x1a4 +#define AN8801_CKO_OUTPUT_MODE_AUTO 3 + +#define AN8801_BPBUS_REG_LINK_MODE 0x5054 +#define AN8801_BPBUS_LINK_MODE_1000 BIT(0) + +#define AN8801_BPBUS_REG_BYPASS_PTP 0x21c004 +#define AN8801_BYP_PTP_SGMII_TO_GPHY BIT(8) +#define AN8801_BYP_PTP_RGMII_TO_GPHY BIT(0) + +#define AN8801_BPBUS_REG_TXDLY_STEP 0x21c024 +#define RGMII_DELAY_STEP_MASK GENMASK(2, 0) +#define RGMII_DELAY_NO_STEP 0 +#define RGMII_DELAY_STEP_1 1 +#define RGMII_DELAY_STEP_2 2 +#define RGMII_DELAY_STEP_3 3 +#define RGMII_DELAY_STEP_4 4 +#define RGMII_DELAY_STEP_5 5 +#define RGMII_DELAY_STEP_6 6 +#define RGMII_DELAY_STEP_7 7 +#define RGMII_TXDELAY_FORCE_MODE BIT(24) + +/* Default RGMII TX delay setting, corresponding to a 1.883ns delay */ +#define AN8801_RGMII_TXDELAY_DEFAULT RGMII_DELAY_STEP_4 + +#define AN8801_BPBUS_REG_RXDLY_STEP 0x21c02c +#define RGMII_RXDELAY_ALIGN BIT(4) +#define RGMII_RXDELAY_FORCE_MODE BIT(24) + +/* Default RGMII RX delay setting, corresponding to a 1.992ns delay, + * when align bit is set or -0.008ns otherwise. + */ +#define AN8801_RGMII_RXDELAY_DEFAULT RGMII_DELAY_NO_STEP + +#define AN8801_BPBUS_REG_EFIFO_CTL(x) (0x270004 + (0x100 * (x))) /* 0..2 */ +#define AN8801_EFIFO_ALL_EN GENMASK(7, 0) +#define AN8801_EFIFO_RX_EN BIT(0) +#define AN8801_EFIFO_TX_EN BIT(1) +#define AN8801_EFIFO_RX_CLK_EN BIT(2) +#define AN8801_EFIFO_TX_CLK_EN BIT(3) +#define AN8801_EFIFO_RX_EEE_EN BIT(4) +#define AN8801_EFIFO_TX_EEE_EN BIT(5) +#define AN8801_EFIFO_RX_ODD_NIBBLE_EN BIT(6) +#define AN8801_EFIFO_TX_ODD_NIBBLE_EN BIT(7) + +#define AN8801_BPBUS_REG_WOL_MAC_16_47 0x285114 +#define AN8801_BPBUS_REG_WOL_MAC_0_15 0x285118 + +#define AN8801_BPBUS_REG_WAKEUP_CTL1 0x285400 +#define AN8801_WOL_WAKE_MAGIC_EN GENMASK(3, 1) +#define AN8801_WOL_WAKE_LNKCHG_EN BIT(4) + +#define AN8801_BPBUS_REG_WAKEUP_CTL2 0x285404 +#define AN8801_WAKE_OUT_TYPE_PULSE BIT(0) /* Set/Unset: Pulse/Static */ +#define AN8801_WAKE_OUT_POLARITY_NEG BIT(1) /* Set/Unset: Negative/Positive */ +#define AN8801_WAKE_OUT_WIDTH GENMASK(3, 2) +#define AN8801_WAKE_OUT_84MS 0 +#define AN8801_WAKE_OUT_168MS 1 +#define AN8801_WAKE_OUT_336MS 2 +#define AN8801_WAKE_OUT_672MS 3 +#define AN8801_WAKE_OUT_EN BIT(4) +#define AN8801_PME_WAKEUP_CLR BIT(8) + +#define AN8801_BPBUS_REG_WAKE_IRQ_EN 0x285700 +#define AN8801_BPBUS_REG_WAKE_IRQ_STS 0x285704 +#define AN8801_IRQ_WAKE_LNKCHG BIT(0) /* Wake on link change */ +#define AN8801_IRQ_WAKE_UNIPKT BIT(1) /* Wake on unicast packet */ +#define AN8801_IRQ_WAKE_MULPKT BIT(2) /* Wake on multicast packet */ +#define AN8801_IRQ_WAKE_BCPKT BIT(3) /* Wake on broadcast packet */ +#define AN8801_IRQ_WAKE_MAGICPKT BIT(4) /* Wake on magic packet */ +#define AN8801_IRQ_WAKE_ALL GENMASK(4, 0) + +/* MDIO_MMD_VEND1 Registers */ +#define AN8801_PHY_TX_PAIR_DLY_SEL_GBE 0x13 +#define AN8801_PHY_PAIR_DLY_SEL_A_GBE GENMASK(14, 12) +#define AN8801_PHY_PAIR_DLY_SEL_B_GBE GENMASK(10, 8) +#define AN8801_PHY_PAIR_DLY_SEL_C_GBE GENMASK(6, 4) +#define AN8801_PHY_PAIR_DLY_SEL_D_GBE GENMASK(2, 0) +#define AN8801_PHY_RXADC_CTRL 0xd8 +#define AN8801_PHY_RXADC_SAMP_PHSEL_A BIT(12) +#define AN8801_PHY_RXADC_SAMP_PHSEL_B BIT(8) +#define AN8801_PHY_RXADC_SAMP_PHSEL_C BIT(4) +#define AN8801_PHY_RXADC_SAMP_PHSEL_D BIT(0) +#define AN8801_PHY_RXADC_REV_0 0xd9 +#define AN8801_PHY_RXADC_REV_MASK_A GENMASK(15, 8) +#define AN8801_PHY_RXADC_REV_MASK_B GENMASK(7, 0) +#define AN8801_PHY_RXADC_REV_1 0xda +#define AN8801_PHY_RXADC_REV_MASK_C GENMASK(15, 8) +#define AN8801_PHY_RXADC_REV_MASK_D GENMASK(7, 0) + +/* MDIO_MMD_VEND2 Registers */ +#define LED_BCR 0x21 +#define LED_BCR_MODE_MASK GENMASK(1, 0) +#define LED_BCR_TIME_TEST BIT(2) +#define LED_BCR_CLK_EN BIT(3) +#define LED_BCR_EVT_ALL BIT(4) +#define LED_BCR_EXT_CTRL BIT(15) +#define LED_BCR_MODE_DISABLE 0 +#define LED_BCR_MODE_2LED 1 +#define LED_BCR_MODE_3LED_1 2 +#define LED_BCR_MODE_3LED_2 3 + +#define LED_ON_DUR 0x22 +#define LED_ON_DUR_MASK GENMASK(15, 0) + +#define LED_BLINK_DUR 0x23 +#define LED_BLINK_DUR_MASK GENMASK(15, 0) + +#define LED_ON_CTRL(i) (0x24 + ((i) * 2)) +#define LED_ON_EVT_MASK GENMASK(6, 0) +#define LED_ON_EVT_LINK_1000M BIT(0) +#define LED_ON_EVT_LINK_100M BIT(1) +#define LED_ON_EVT_LINK_10M BIT(2) +#define LED_ON_EVT_LINK_DN BIT(3) +#define LED_ON_EVT_FDX BIT(4) +#define LED_ON_EVT_HDX BIT(5) +#define LED_ON_EVT_FORCE BIT(6) +#define LED_ON_POL BIT(14) +#define LED_ON_EN BIT(15) + +#define LED_BLINK_CTRL(i) (0x25 + ((i) * 2)) +#define LED_BLINK_EVT_MASK GENMASK(9, 0) +#define LED_BLINK_EVT_1000M_TX BIT(0) +#define LED_BLINK_EVT_1000M_RX BIT(1) +#define LED_BLINK_EVT_100M_TX BIT(2) +#define LED_BLINK_EVT_100M_RX BIT(3) +#define LED_BLINK_EVT_10M_TX BIT(4) +#define LED_BLINK_EVT_10M_RX BIT(5) +#define LED_BLINK_EVT_COLLISION BIT(6) +#define LED_BLINK_EVT_RX_CRC_ERR BIT(7) +#define LED_BLINK_EVT_RX_IDLE_ERR BIT(8) +#define LED_BLINK_EVT_FORCE BIT(9) + +#define AN8801R_NUM_LEDS 3 +#define AN8801_PERIOD_SHIFT 15 +#define AN8801_PERIOD_UNIT 32768 /* (1 << AN8801_PERIOD_SHIFT) */ +#define AN8801_MAX_PERIOD_MS 2147 + +#define AN8801_REG_PHY_INTERNAL0 0x600 +#define AN8801_REG_PHY_INTERNAL1 0x601 +#define AN8801_PHY_INTFUNC_MASK GENMASK(15, 0) /* PHY internal functions */ + +enum an8801r_led_fn { + AN8801R_LED_FN_NONE, + AN8801R_LED_FN_LINK, + AN8801R_LED_FN_ACTIVITY, + AN8801R_LED_FN_MAX, +}; + +struct an8801r_priv { + bool wake_magic_enabled; + bool wake_lnkchg_enabled; +}; + +static const unsigned long an8801r_led_trig = 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_RX_ERR) | + BIT(TRIGGER_NETDEV_TX); + +static int an8801_buckpbus_reg_rmw(struct phy_device *phydev, + u32 addr, u32 mask, u32 set) +{ + return air_phy_buckpbus_reg_modify(phydev, + addr | AN8801_PBUS_ACCESS, + mask, set); +} + +static int an8801_buckpbus_reg_set_bits(struct phy_device *phydev, + u32 addr, u32 mask) +{ + return air_phy_buckpbus_reg_modify(phydev, + addr | AN8801_PBUS_ACCESS, + mask, mask); +} + +static int an8801_buckpbus_reg_clear_bits(struct phy_device *phydev, + u32 addr, u32 mask) +{ + return air_phy_buckpbus_reg_modify(phydev, + addr | AN8801_PBUS_ACCESS, + mask, 0); +} + +static int an8801_buckpbus_reg_write(struct phy_device *phydev, u32 addr, + u32 data) +{ + return air_phy_buckpbus_reg_write(phydev, + addr | AN8801_PBUS_ACCESS, + data); +} + +static int an8801_buckpbus_reg_read(struct phy_device *phydev, u32 addr, + u32 *data) +{ + return air_phy_buckpbus_reg_read(phydev, + addr | AN8801_PBUS_ACCESS, + data); +} + +static u32 an8801r_led_blink_ms_to_hw(unsigned long req_ms) +{ + u32 req_ns, regval; + + if (req_ms > AN8801_MAX_PERIOD_MS) + req_ms = AN8801_MAX_PERIOD_MS; + + req_ns = req_ms * NSEC_PER_MSEC; + + /* Round to the nearest period unit... */ + regval = req_ns + (AN8801_PERIOD_UNIT / 2); + + /* ...and now divide by the full period */ + regval >>= AN8801_PERIOD_SHIFT; + + return regval; +} + +static int an8801r_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + u32 hw_delay_on, hw_delay_off; + u16 blink_dur; + bool blink; + int ret; + + if (index >= AN8801R_NUM_LEDS) + return -EINVAL; + + if (delay_on && delay_off) { + blink = true; + + if (*delay_on == 0 || *delay_off == 0) { + *delay_on = 64; + *delay_off = 64; + } + + hw_delay_on = an8801r_led_blink_ms_to_hw(*delay_on); + hw_delay_off = an8801r_led_blink_ms_to_hw(*delay_off); + } else { + blink = false; + } + + if (blink) { + blink_dur = (u16)min(hw_delay_on + hw_delay_off, + LED_BLINK_DUR_MASK); + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_DUR, + blink_dur); + if (ret) + goto error; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_DUR, + hw_delay_on); + if (ret) + goto error; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index), + LED_BLINK_EVT_MASK, + blink ? LED_BLINK_EVT_FORCE : 0); + if (ret) + return ret; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EVT_MASK, + 0); + if (ret) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EN, blink ? LED_ON_EN : 0); + +error: + phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EN, 0); + return ret; +} + +static int an8801r_led_brightness_set(struct phy_device *phydev, u8 index, + enum led_brightness value) +{ + int ret; + + if (index >= AN8801R_NUM_LEDS) + return -EINVAL; + + /* Disable blink first if previously enabled */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index), + LED_BLINK_EVT_MASK, 0); + if (ret) + return ret; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EVT_MASK, + (value == LED_OFF) ? 0 : LED_ON_EVT_FORCE); + if (ret) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EN, (value == LED_OFF) ? 0 : LED_ON_EN); +} + +static int an8801r_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int on, blink; + + if (index >= AN8801R_NUM_LEDS) + return -EINVAL; + + on = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index)); + if (on < 0) + return on; + + blink = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index)); + if (blink < 0) + return blink; + + if (FIELD_GET(LED_ON_EVT_LINK_10M, on)) + __set_bit(TRIGGER_NETDEV_LINK_10, rules); + + if (FIELD_GET(LED_ON_EVT_LINK_100M, on)) + __set_bit(TRIGGER_NETDEV_LINK_100, rules); + + if (FIELD_GET(LED_ON_EVT_LINK_1000M, on)) + __set_bit(TRIGGER_NETDEV_LINK_1000, rules); + + if (FIELD_GET(LED_ON_EVT_LINK_10M, on) && + FIELD_GET(LED_ON_EVT_LINK_100M, on) && + FIELD_GET(LED_ON_EVT_LINK_1000M, on)) + __set_bit(TRIGGER_NETDEV_LINK, rules); + + if (FIELD_GET(LED_BLINK_EVT_10M_RX, blink) || + FIELD_GET(LED_BLINK_EVT_100M_RX, blink) || + FIELD_GET(LED_BLINK_EVT_1000M_RX, blink)) + __set_bit(TRIGGER_NETDEV_RX, rules); + + if (FIELD_GET(LED_BLINK_EVT_10M_TX, blink) || + FIELD_GET(LED_BLINK_EVT_100M_TX, blink) || + FIELD_GET(LED_BLINK_EVT_1000M_TX, blink)) + __set_bit(TRIGGER_NETDEV_TX, rules); + + if (FIELD_GET(LED_BLINK_EVT_RX_CRC_ERR, blink)) + __set_bit(TRIGGER_NETDEV_RX_ERR, rules); + + return 0; +} + +static int an8801r_led_trig_to_hw(unsigned long rules, u16 *on, u16 *blink) +{ + /* All combinations of the supported triggers are allowed */ + if (rules & ~an8801r_led_trig) + return -EOPNOTSUPP; + + if (test_bit(TRIGGER_NETDEV_LINK_10, &rules)) + *on |= LED_ON_EVT_LINK_10M; + + if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) + *on |= LED_ON_EVT_LINK_100M; + + if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) + *on |= LED_ON_EVT_LINK_1000M; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules)) { + *on |= LED_ON_EVT_LINK_10M; + *on |= LED_ON_EVT_LINK_100M; + *on |= LED_ON_EVT_LINK_1000M; + } + + if (test_bit(TRIGGER_NETDEV_RX, &rules)) { + *blink |= LED_BLINK_EVT_10M_RX; + *blink |= LED_BLINK_EVT_100M_RX; + *blink |= LED_BLINK_EVT_1000M_RX; + } + + if (test_bit(TRIGGER_NETDEV_TX, &rules)) { + *blink |= LED_BLINK_EVT_10M_TX; + *blink |= LED_BLINK_EVT_100M_TX; + *blink |= LED_BLINK_EVT_1000M_TX; + } + + if (test_bit(TRIGGER_NETDEV_RX_ERR, &rules)) + *blink |= LED_BLINK_EVT_RX_CRC_ERR; + + return 0; +} + +static int an8801r_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 on = 0, blink = 0; + + if (index >= AN8801R_NUM_LEDS) + return -EINVAL; + + return an8801r_led_trig_to_hw(rules, &on, &blink); +} + +static int an8801r_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 on = 0, blink = 0; + int ret; + + if (index >= AN8801R_NUM_LEDS) + return -EINVAL; + + ret = an8801r_led_trig_to_hw(rules, &on, &blink); + if (ret) + return ret; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EVT_MASK, on); + if (ret) + return ret; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index), + LED_BLINK_EVT_MASK, blink); + if (ret) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_EN, (on | blink) ? LED_ON_EN : 0); +} + +static int an8801r_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + bool active_high = true; + unsigned long mode; + + if (index >= AN8801R_NUM_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_HIGH: + break; + case PHY_LED_ACTIVE_LOW: + active_high = false; + break; + default: + return -EINVAL; + } + } + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), + LED_ON_POL, active_high ? LED_ON_POL : 0); +} + +static int an8801r_led_init(struct phy_device *phydev, u8 *led_cfg) +{ + int led_id, ret; + + /* Set LED BCR Enable */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, LED_BCR, + LED_BCR_EXT_CTRL | LED_BCR_CLK_EN); + if (ret) + return ret; + + for (led_id = 0; led_id < AN8801R_NUM_LEDS; led_id++) { + unsigned long led_trigger = 0; + u32 led_gpio = led_id + 1; + + switch (led_cfg[led_id]) { + case AN8801R_LED_FN_LINK: + led_trigger = BIT(TRIGGER_NETDEV_LINK); + break; + case AN8801R_LED_FN_ACTIVITY: + led_trigger = BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + break; + default: + led_trigger = 0; + break; + } + + ret = an8801_buckpbus_reg_set_bits(phydev, + AN8801_BPBUS_REG_LED_GPIO, + BIT(led_gpio)); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_rmw(phydev, + AN8801_BPBUS_REG_LED_ID_SEL, + LED_ID_GPIO_SEL_MASK(led_gpio), + LED_ID_GPIO_SEL(led_id, + led_gpio)); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_clear_bits(phydev, + AN8801_BPBUS_REG_GPIO_MODE, + BIT(led_gpio)); + if (ret) + return ret; + + if (!led_trigger) + continue; + + ret = an8801r_led_hw_control_set(phydev, led_id, led_trigger); + if (ret) + return ret; + } + + return ret; +} + +static int an8801r_reset_wake(struct phy_device *phydev) +{ + struct an8801r_priv *priv = phydev->priv; + u32 reg_val = 0; + int ret; + + /* Enable wakeup clear and disable wake up output */ + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL2, + AN8801_PME_WAKEUP_CLR | + AN8801_WAKE_OUT_POLARITY_NEG); + if (ret) + return ret; + + /* Clear WAKEUP_CTL1 register before enabling the wakeup events + * again + */ + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL1, + 0); + if (ret) + return ret; + + if (priv->wake_magic_enabled) + reg_val |= AN8801_WOL_WAKE_MAGIC_EN; + + if (priv->wake_lnkchg_enabled) + reg_val |= AN8801_WOL_WAKE_LNKCHG_EN; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL1, + reg_val); + if (ret) + return ret; + + /* Disable wake up clear and re-enable wake up output */ + return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL2, + AN8801_WAKE_OUT_POLARITY_NEG | + AN8801_WAKE_OUT_EN); +} + +static int an8801r_ack_interrupt(struct phy_device *phydev) +{ + int ret; + + /* Reset wake status */ + ret = an8801r_reset_wake(phydev); + if (ret) + return ret; + + /* Clear the interrupts by writing the reg */ + return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKE_IRQ_STS, + AN8801_IRQ_WAKE_ALL); +} + +static int an8801r_config_intr(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + u32 val = FIELD_PREP(AN8801_PHY_IRQ_GPIO_NUM_MASK, + AN8801_PHY_IRQ_GPIO_NUM); + + ret = an8801_buckpbus_reg_write(phydev, + AN8801_BPBUS_REG_PHY_IRQ_GPIO, + val); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_set_bits(phydev, + AN8801_BPBUS_REG_WAKE_IRQ_EN, + AN8801_IRQ_WAKE_LNKCHG); + if (ret) + return ret; + + } else { + ret = an8801_buckpbus_reg_write(phydev, + AN8801_BPBUS_REG_PHY_IRQ_GPIO, + 0); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_clear_bits(phydev, + AN8801_BPBUS_REG_WAKE_IRQ_EN, + AN8801_IRQ_WAKE_LNKCHG); + if (ret) + return ret; + } + + return an8801r_ack_interrupt(phydev); +} + +static irqreturn_t an8801r_handle_interrupt(struct phy_device *phydev) +{ + u32 irq_status = 0; + bool irq_handled = false; + int ret; + + ret = an8801_buckpbus_reg_read(phydev, AN8801_BPBUS_REG_WAKE_IRQ_STS, + &irq_status); + if (ret) + return IRQ_NONE; + + ret = an8801r_ack_interrupt(phydev); + if (ret) + return IRQ_NONE; + + if (irq_status & AN8801_IRQ_WAKE_MAGICPKT) { + pm_wakeup_event(&phydev->mdio.dev, 0); + irq_handled = true; + } + + if (irq_status & AN8801_IRQ_WAKE_LNKCHG) { + phy_trigger_machine(phydev); + irq_handled = true; + } + + return irq_handled ? IRQ_HANDLED : IRQ_NONE; +} + +static void an8801r_get_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + u32 reg_val; + int ret; + + /* If the PHY is not capable of waking the system, then WoL can not + * be supported. + */ + if (!device_can_wakeup(&phydev->mdio.dev)) { + wol->supported = 0; + return; + } + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + + ret = an8801_buckpbus_reg_read(phydev, AN8801_BPBUS_REG_WAKEUP_CTL1, + ®_val); + if (ret) + return; + + if (reg_val & AN8801_WOL_WAKE_MAGIC_EN) + wol->wolopts |= WAKE_MAGIC; + else + wol->wolopts &= ~WAKE_MAGIC; +} + +static int an8801r_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + struct an8801r_priv *priv = phydev->priv; + struct net_device *attach_dev; + const u8 *macaddr; + u32 reg_val; + int ret; + + if (!device_can_wakeup(&phydev->mdio.dev)) + return -EOPNOTSUPP; + + if (wol->wolopts & ~WAKE_MAGIC) + return -EINVAL; + + if (wol->wolopts & WAKE_MAGIC) { + attach_dev = phydev->attached_dev; + if (!attach_dev) + return -ENODEV; + + macaddr = (const u8 *)attach_dev->dev_addr; + if (!is_valid_ether_addr(macaddr)) + return -EINVAL; + + /* MAC bits 16..47 */ + reg_val = (macaddr[2] << 24) | (macaddr[3] << 16); + reg_val |= (macaddr[4] << 8) | (macaddr[5]); + + ret = an8801_buckpbus_reg_write(phydev, + AN8801_BPBUS_REG_WOL_MAC_16_47, + reg_val); + if (ret) + return ret; + + /* MAC bits 0..15 */ + reg_val = (macaddr[0] << 8) | (macaddr[1]); + + ret = an8801_buckpbus_reg_write(phydev, + AN8801_BPBUS_REG_WOL_MAC_0_15, + reg_val); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_set_bits(phydev, + AN8801_BPBUS_REG_WAKEUP_CTL1, + AN8801_WOL_WAKE_MAGIC_EN); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_set_bits(phydev, + AN8801_BPBUS_REG_WAKE_IRQ_EN, + AN8801_IRQ_WAKE_MAGICPKT); + if (ret) + return ret; + + } else { + ret = an8801_buckpbus_reg_clear_bits(phydev, + AN8801_BPBUS_REG_WAKEUP_CTL1, + AN8801_WOL_WAKE_MAGIC_EN); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_clear_bits(phydev, + AN8801_BPBUS_REG_WAKE_IRQ_EN, + AN8801_IRQ_WAKE_MAGICPKT); + if (ret) + return ret; + } + + priv->wake_magic_enabled = !!(wol->wolopts & WAKE_MAGIC); + + return device_set_wakeup_enable(&phydev->mdio.dev, + priv->wake_magic_enabled); +} + +static int an8801r_of_init_leds(struct phy_device *phydev, u8 *led_cfg) +{ + struct device *dev = &phydev->mdio.dev; + struct device_node *np = dev->of_node; + struct device_node *leds; + u32 function_enum_idx; + int ret = 0; + + if (!np) + return 0; + + /* If devicetree is present, leds configuration is required */ + leds = of_get_child_by_name(np, "leds"); + if (!leds) + return 0; + + for_each_available_child_of_node_scoped(leds, led) { + u32 led_idx; + + ret = of_property_read_u32(led, "reg", &led_idx); + if (ret) + goto out; + + if (led_idx >= AN8801R_NUM_LEDS) { + ret = -EINVAL; + goto out; + } + + ret = of_property_read_u32(led, "function-enumerator", + &function_enum_idx); + if (ret) { + function_enum_idx = AN8801R_LED_FN_NONE; + ret = 0; + } + + if (function_enum_idx >= AN8801R_LED_FN_MAX) { + ret = -EINVAL; + goto out; + } + + led_cfg[led_idx] = function_enum_idx; + } +out: + of_node_put(leds); + return ret; +} + +static int an8801r_rgmii_rxdelay(struct phy_device *phydev, bool enable, + u16 delay_steps) +{ + u32 reg_val; + + if (delay_steps > RGMII_DELAY_STEP_MASK) + return -EINVAL; + + if (enable) { + /* Set force mode bit to enable RX delay insertion */ + reg_val = delay_steps | RGMII_RXDELAY_FORCE_MODE; + + /* Set align bit to add extra offset for RX delay */ + reg_val |= RGMII_RXDELAY_ALIGN; + } else { + reg_val = 0; + } + + return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_RXDLY_STEP, + reg_val); +} + +static int an8801r_rgmii_txdelay(struct phy_device *phydev, bool enable, + u16 delay_steps) +{ + u32 reg_val; + + if (delay_steps > RGMII_DELAY_STEP_MASK) + return -EINVAL; + + if (enable) { + /* Set force mode bit to enable TX delay insertion */ + reg_val = delay_steps | RGMII_TXDELAY_FORCE_MODE; + } else { + reg_val = 0; + } + + return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_TXDLY_STEP, + reg_val); +} + +static int an8801r_rgmii_delay_config(struct phy_device *phydev) +{ + bool enable_delay; + u16 delay_step; + int ret; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { + enable_delay = true; + delay_step = AN8801_RGMII_TXDELAY_DEFAULT; + } else { + enable_delay = false; + delay_step = RGMII_DELAY_NO_STEP; + } + + ret = an8801r_rgmii_txdelay(phydev, enable_delay, delay_step); + if (ret) + return ret; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { + enable_delay = true; + delay_step = AN8801_RGMII_RXDELAY_DEFAULT; + } else { + enable_delay = false; + delay_step = RGMII_DELAY_NO_STEP; + } + + return an8801r_rgmii_rxdelay(phydev, enable_delay, delay_step); +} + +static int an8801r_config_init(struct phy_device *phydev) +{ + u8 led_default_function[AN8801R_NUM_LEDS] = { 0 }; + int ret; + + ret = an8801r_of_init_leds(phydev, led_default_function); + if (ret) + return ret; + + /* Disable Low Power Mode (LPM) */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL0, + FIELD_PREP(AN8801_PHY_INTFUNC_MASK, 0x1e)); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL1, + FIELD_PREP(AN8801_PHY_INTFUNC_MASK, 0x2)); + if (ret) + return ret; + + /* Set the PHY to perform auto-downshift after 3 auto-negotiation + * attempts + */ + ret = phy_write_paged(phydev, AIR_PHY_PAGE_EXTENDED_1, + AN8801_EXT_REG_PHY, + FIELD_PREP(AN8801_EXT_PHY_CTRL1, 0x1d) | + FIELD_PREP(AN8801_EXT_PHY_DOWNSHIFT_CTL, 1) | + AN8801_EXT_PHY_DOWNSHIFT_EN); + if (ret < 0) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_BYPASS_PTP, + AN8801_BYP_PTP_RGMII_TO_GPHY); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(0), + AN8801_EFIFO_RX_EN | + AN8801_EFIFO_TX_EN | + AN8801_EFIFO_RX_CLK_EN | + AN8801_EFIFO_TX_CLK_EN | + AN8801_EFIFO_RX_EEE_EN | + AN8801_EFIFO_TX_EEE_EN); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(1), + AN8801_EFIFO_ALL_EN); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(2), + AN8801_EFIFO_ALL_EN); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, + AN8801_PHY_TX_PAIR_DLY_SEL_GBE, + FIELD_PREP(AN8801_PHY_PAIR_DLY_SEL_A_GBE, 4) | + FIELD_PREP(AN8801_PHY_PAIR_DLY_SEL_C_GBE, 4)); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8801_PHY_RXADC_CTRL, + AN8801_PHY_RXADC_SAMP_PHSEL_A | + AN8801_PHY_RXADC_SAMP_PHSEL_C); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8801_PHY_RXADC_REV_0, + FIELD_PREP(AN8801_PHY_RXADC_REV_MASK_A, 1)); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8801_PHY_RXADC_REV_1, + FIELD_PREP(AN8801_PHY_RXADC_REV_MASK_C, 1)); + if (ret) + return ret; + + ret = an8801r_rgmii_delay_config(phydev); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_CKO, + AN8801_CKO_OUTPUT_MODE_AUTO); + if (ret) + return ret; + + ret = an8801r_led_init(phydev, led_default_function); + if (ret) { + phydev_err(phydev, "Cannot initialize LEDs: %d\n", ret); + return ret; + } + + return 0; +} + +static int an8801r_read_status(struct phy_device *phydev) +{ + int prev_speed, ret; + u32 val; + + prev_speed = phydev->speed; + + ret = genphy_read_status(phydev); + if (ret) + return ret; + + if (!phydev->link) { + phydev->speed = SPEED_UNKNOWN; + return 0; + } + + if (prev_speed != phydev->speed) { + /* Ensure that PHY switches to 1G speed when available, + * by configuring the function mode for either 1G or 100M/10M + * operation. + * Therefore, set the link mode register, after read_status + * determines the link speed. + */ + val = phydev->speed == SPEED_1000 ? + AN8801_BPBUS_LINK_MODE_1000 : 0; + + return an8801_buckpbus_reg_rmw(phydev, + AN8801_BPBUS_REG_LINK_MODE, + AN8801_BPBUS_LINK_MODE_1000, + val); + } + + return 0; +} + +static int an8801r_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct an8801r_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wake_lnkchg_enabled = true; + + phydev->priv = priv; + + /* Mark this PHY as wakeup capable and register the interrupt as a + * wakeup IRQ if the PHY is marked as a wakeup source in devicetree, + * and the interrupt is valid. + */ + if (of_property_read_bool(dev->of_node, "wakeup-source") && + phy_interrupt_is_valid(phydev)) { + device_set_wakeup_capable(dev, true); + devm_pm_set_wake_irq(dev, phydev->irq); + } + + return 0; +} + +static int an8801r_suspend(struct phy_device *phydev) +{ + struct an8801r_priv *priv = phydev->priv; + int ret; + + /* If the PHY may wake up by a Wake-on-LAN (WOL) event, disable the link + * interrupt to only keep the WOL magic interrupt enabled + */ + if (device_may_wakeup(&phydev->mdio.dev)) { + priv->wake_lnkchg_enabled = false; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = an8801_buckpbus_reg_clear_bits(phydev, + AN8801_BPBUS_REG_WAKE_IRQ_EN, + AN8801_IRQ_WAKE_LNKCHG); + if (ret) + return ret; + } + + /* Reset WOL status */ + ret = an8801r_reset_wake(phydev); + if (ret) + return ret; + } + + if (!phydev->wol_enabled) + return genphy_suspend(phydev); + + return 0; +} + +static int an8801r_resume(struct phy_device *phydev) +{ + struct an8801r_priv *priv = phydev->priv; + int ret; + + ret = genphy_resume(phydev); + if (ret) + return ret; + + /* Restore the interrupt enable so phylib can receive link + * state interrupts. + */ + if (device_may_wakeup(&phydev->mdio.dev)) { + priv->wake_lnkchg_enabled = true; + + ret = an8801_buckpbus_reg_set_bits(phydev, + AN8801_BPBUS_REG_WAKEUP_CTL1, + AN8801_WOL_WAKE_LNKCHG_EN); + if (ret) + return ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = an8801_buckpbus_reg_set_bits(phydev, + AN8801_BPBUS_REG_WAKE_IRQ_EN, + AN8801_IRQ_WAKE_LNKCHG); + } + } + + return ret; +} + +static struct phy_driver airoha_driver[] = { +{ + PHY_ID_MATCH_MODEL(AN8801R_PHY_ID), + .name = "Airoha AN8801R", + .probe = an8801r_probe, + .config_init = an8801r_config_init, + .suspend = an8801r_suspend, + .resume = an8801r_resume, + .config_aneg = genphy_config_aneg, + .read_status = an8801r_read_status, + .config_intr = an8801r_config_intr, + .handle_interrupt = an8801r_handle_interrupt, + .set_wol = an8801r_set_wol, + .get_wol = an8801r_get_wol, + .read_page = air_phy_read_page, + .write_page = air_phy_write_page, + .flags = PHY_ALWAYS_CALL_SUSPEND, + .led_brightness_set = an8801r_led_brightness_set, + .led_blink_set = an8801r_led_blink_set, + .led_hw_is_supported = an8801r_led_hw_is_supported, + .led_hw_control_set = an8801r_led_hw_control_set, + .led_hw_control_get = an8801r_led_hw_control_get, + .led_polarity_set = an8801r_led_polarity_set, +} }; +module_phy_driver(airoha_driver); + +static struct mdio_device_id __maybe_unused an8801_tbl[] = { + { PHY_ID_MATCH_MODEL(AN8801R_PHY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(mdio, an8801_tbl); + +MODULE_DESCRIPTION("Airoha AN8801 PHY driver"); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c new file mode 100644 index 000000000..0eeb7b9a4 --- /dev/null +++ b/drivers/net/phy/air_en8811h.c @@ -0,0 +1,1574 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Airoha EN8811H and AN8811HB 2.5 Gigabit PHYs. + * + * Limitations: + * - Only full duplex supported + * - Forced speed (AN off) is not supported by hardware (100Mbps) + * + * Source originated from airoha's en8811h.c and en8811h.h v1.2.1 + * with AN8811HB bits from air_an8811hb.c v0.0.4 + * + * Copyright (C) 2023, 2026 Airoha Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "air_phy_lib.h" + +#define EN8811H_PHY_ID 0x03a2a411 +#define AN8811HB_PHY_ID 0xc0ff04a0 + +#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" +#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" +#define AN8811HB_MD32_DM "airoha/an8811hb/EthMD32_CRC.DM.bin" +#define AN8811HB_MD32_DSP "airoha/an8811hb/EthMD32_CRC.DSP.bin" + +#define AIR_FW_ADDR_DM 0x00000000 +#define AIR_FW_ADDR_DSP 0x00100000 + +/* MII Registers */ +#define AIR_AUX_CTRL_STATUS 0x1d +#define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2) +#define AIR_AUX_CTRL_STATUS_SPEED_10 0x0 +#define AIR_AUX_CTRL_STATUS_SPEED_100 0x4 +#define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8 +#define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc + +/* Registers on MDIO_MMD_VEND1 */ +#define EN8811H_PHY_FW_STATUS 0x8009 +#define EN8811H_PHY_READY 0x02 + +#define AIR_PHY_MCU_CMD_0 0x800b +#define AIR_PHY_MCU_CMD_1 0x800c +#define AIR_PHY_MCU_CMD_1_MODE1 0x0 +#define AIR_PHY_MCU_CMD_2 0x800d +#define AIR_PHY_MCU_CMD_2_MODE1 0x0 +#define AIR_PHY_MCU_CMD_3 0x800e +#define AIR_PHY_MCU_CMD_3_MODE1 0x1101 +#define AIR_PHY_MCU_CMD_3_DOCMD 0x1100 +#define AIR_PHY_MCU_CMD_4 0x800f +#define AIR_PHY_MCU_CMD_4_MODE1 0x0002 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_A 0x00d7 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_B 0x00d8 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_C 0x00d9 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_D 0x00da +#define AIR_PHY_MCU_CMD_4_INTCLR 0x00e4 + +/* Registers on MDIO_MMD_VEND2 */ +#define AIR_PHY_LED_BCR 0x021 +#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0) +#define AIR_PHY_LED_BCR_TIME_TEST BIT(2) +#define AIR_PHY_LED_BCR_CLK_EN BIT(3) +#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15) + +#define AIR_PHY_LED_DUR_ON 0x022 + +#define AIR_PHY_LED_DUR_BLINK 0x023 + +#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2)) +#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8)) +#define AIR_PHY_LED_ON_LINK1000 BIT(0) +#define AIR_PHY_LED_ON_LINK100 BIT(1) +#define AIR_PHY_LED_ON_LINK10 BIT(2) +#define AIR_PHY_LED_ON_LINKDOWN BIT(3) +#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */ +#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */ +#define AIR_PHY_LED_ON_FORCE_ON BIT(6) +#define AIR_PHY_LED_ON_LINK2500 BIT(8) +#define AIR_PHY_LED_ON_POLARITY BIT(14) +#define AIR_PHY_LED_ON_ENABLE BIT(15) + +#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2)) +#define AIR_PHY_LED_BLINK_1000TX BIT(0) +#define AIR_PHY_LED_BLINK_1000RX BIT(1) +#define AIR_PHY_LED_BLINK_100TX BIT(2) +#define AIR_PHY_LED_BLINK_100RX BIT(3) +#define AIR_PHY_LED_BLINK_10TX BIT(4) +#define AIR_PHY_LED_BLINK_10RX BIT(5) +#define AIR_PHY_LED_BLINK_COLLISION BIT(6) +#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7) +#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8) +#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9) +#define AIR_PHY_LED_BLINK_2500TX BIT(10) +#define AIR_PHY_LED_BLINK_2500RX BIT(11) + +/* Registers on BUCKPBUS */ +#define AIR_PHY_CONTROL 0x3a9c +#define AIR_PHY_CONTROL_INTERNAL BIT(11) + +#define EN8811H_2P5G_LPA 0x3b30 +#define EN8811H_2P5G_LPA_2P5G BIT(0) + +#define EN8811H_FW_VERSION 0x3b3c + +#define EN8811H_POLARITY 0xca0f8 +#define EN8811H_POLARITY_TX_NORMAL BIT(0) +#define EN8811H_POLARITY_RX_REVERSE BIT(1) + +#define EN8811H_GPIO_OUTPUT 0xcf8b8 +#define EN8811H_GPIO_OUTPUT_345 (BIT(3) | BIT(4) | BIT(5)) + +#define EN8811H_HWTRAP1 0xcf914 +#define EN8811H_HWTRAP1_CKO BIT(12) +#define EN8811H_CLK_CGM 0xcf958 +#define EN8811H_CLK_CGM_CKO BIT(26) + +#define EN8811H_FW_CTRL_1 0x0f0018 +#define EN8811H_FW_CTRL_1_START 0x0 +#define EN8811H_FW_CTRL_1_FINISH 0x1 +#define EN8811H_FW_CTRL_2 0x800000 +#define EN8811H_FW_CTRL_2_LOADING BIT(11) + +#define AN8811HB_CRC_PM_SET1 0xf020c +#define AN8811HB_CRC_PM_MON2 0xf0218 +#define AN8811HB_CRC_PM_MON3 0xf021c +#define AN8811HB_CRC_DM_SET1 0xf0224 +#define AN8811HB_CRC_DM_MON2 0xf0230 +#define AN8811HB_CRC_DM_MON3 0xf0234 +#define AN8811HB_CRC_RD_EN BIT(0) +#define AN8811HB_CRC_ST (BIT(0) | BIT(1)) +#define AN8811HB_CRC_CHECK_PASS BIT(0) + +#define AN8811HB_TX_POLARITY 0x5ce004 +#define AN8811HB_TX_POLARITY_NORMAL BIT(7) +#define AN8811HB_RX_POLARITY 0x5ce61c +#define AN8811HB_RX_POLARITY_NORMAL BIT(7) + +#define AN8811HB_GPIO_OUTPUT 0x5cf8b8 +#define AN8811HB_GPIO_OUTPUT_345 (BIT(3) | BIT(4) | BIT(5)) + +#define AN8811HB_HWTRAP1 0x5cf910 +#define AN8811HB_HWTRAP2 0x5cf914 +#define AN8811HB_HWTRAP2_CKO BIT(28) + +#define AN8811HB_CLK_DRV 0x5cf9e4 +#define AN8811HB_CLK_DRV_CKO_MASK GENMASK(14, 12) +#define AN8811HB_CLK_DRV_CKOPWD BIT(12) +#define AN8811HB_CLK_DRV_CKO_LDPWD BIT(13) +#define AN8811HB_CLK_DRV_CKO_LPPWD BIT(14) + +#define AN8811HB_MCU_SW_RST 0x5cf9f8 +#define AN8811HB_MCU_SW_RST_HOLD BIT(16) +#define AN8811HB_MCU_SW_RST_RUN (BIT(16) | BIT(0)) +#define AN8811HB_MCU_SW_START 0x5cf9fc +#define AN8811HB_MCU_SW_START_EN BIT(16) + +/* MII register constants for PBUS access (PHY addr + 8) */ +#define AIR_PBUS_ADDR_HIGH 0x1c +#define AIR_PBUS_DATA_HIGH 0x10 +#define AIR_PBUS_REG_ADDR_HIGH_MASK GENMASK(15, 6) +#define AIR_PBUS_REG_ADDR_LOW_MASK GENMASK(5, 2) + +/* Led definitions */ +#define EN8811H_LED_COUNT 3 + +#define EN8811H_PBUS_ADDR_OFFS 8 + +/* Default LED setup: + * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx + * GPIO4 <-> LED1 On: Link detected at 2500 or 1000 Mbps + * GPIO3 <-> LED2 On: Link detected at 2500 or 100 Mbps + */ +#define AIR_DEFAULT_TRIGGER_LED0 (BIT(TRIGGER_NETDEV_LINK) | \ + BIT(TRIGGER_NETDEV_RX) | \ + BIT(TRIGGER_NETDEV_TX)) +#define AIR_DEFAULT_TRIGGER_LED1 (BIT(TRIGGER_NETDEV_LINK_2500) | \ + BIT(TRIGGER_NETDEV_LINK_1000)) +#define AIR_DEFAULT_TRIGGER_LED2 (BIT(TRIGGER_NETDEV_LINK_2500) | \ + BIT(TRIGGER_NETDEV_LINK_100)) + +struct led { + unsigned long rules; + unsigned long state; +}; + +#define clk_hw_to_en8811h_priv(_hw) \ + container_of(_hw, struct en8811h_priv, hw) + +struct en8811h_priv { + u32 firmware_version; + bool mcu_needs_restart; + struct led led[EN8811H_LED_COUNT]; + struct clk_hw hw; + struct phy_device *phydev; + unsigned int cko_is_enabled; + struct mdio_device *pbusdev; +}; + +enum { + AIR_PHY_LED_STATE_FORCE_ON, + AIR_PHY_LED_STATE_FORCE_BLINK, +}; + +enum { + AIR_PHY_LED_DUR_BLINK_32MS, + AIR_PHY_LED_DUR_BLINK_64MS, + AIR_PHY_LED_DUR_BLINK_128MS, + AIR_PHY_LED_DUR_BLINK_256MS, + AIR_PHY_LED_DUR_BLINK_512MS, + AIR_PHY_LED_DUR_BLINK_1024MS, +}; + +enum { + AIR_LED_DISABLE, + AIR_LED_ENABLE, +}; + +enum { + AIR_ACTIVE_LOW, + AIR_ACTIVE_HIGH, +}; + +enum { + AIR_LED_MODE_DISABLE, + AIR_LED_MODE_USER_DEFINE, +}; + +#define AIR_PHY_LED_DUR_UNIT 1024 +#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS) + +static const unsigned long en8811h_led_trig = BIT(TRIGGER_NETDEV_FULL_DUPLEX) | + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + +static int __air_pbus_reg_write(struct mdio_device *mdiodev, + u32 pbus_reg, u32 pbus_data) +{ + int ret; + + ret = __mdiobus_write(mdiodev->bus, mdiodev->addr, AIR_EXT_PAGE_ACCESS, + upper_16_bits(pbus_reg)); + if (ret < 0) + return ret; + + ret = __mdiobus_write(mdiodev->bus, mdiodev->addr, AIR_PBUS_ADDR_HIGH, + FIELD_GET(AIR_PBUS_REG_ADDR_HIGH_MASK, pbus_reg)); + if (ret < 0) + return ret; + + ret = __mdiobus_write(mdiodev->bus, mdiodev->addr, + FIELD_GET(AIR_PBUS_REG_ADDR_LOW_MASK, pbus_reg), + lower_16_bits(pbus_data)); + if (ret < 0) + return ret; + + return __mdiobus_write(mdiodev->bus, mdiodev->addr, AIR_PBUS_DATA_HIGH, + upper_16_bits(pbus_data)); +} + +static int __air_write_buf(struct phy_device *phydev, u32 address, + const struct firmware *fw) +{ + unsigned int offset; + int ret; + u16 val; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(address)); + if (ret < 0) + return ret; + + for (offset = 0; offset < fw->size; offset += 4) { + val = get_unaligned_le16(&fw->data[offset + 2]); + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, val); + if (ret < 0) + return ret; + + val = get_unaligned_le16(&fw->data[offset]); + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int air_write_buf(struct phy_device *phydev, u32 address, + const struct firmware *fw) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_write_buf(phydev, address, fw); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +static int en8811h_wait_mcu_ready(struct phy_device *phydev) +{ + int ret, reg_value; + + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); + if (ret) + return ret; + + /* Because of mdio-lock, may have to wait for multiple loads */ + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + EN8811H_PHY_FW_STATUS, reg_value, + reg_value == EN8811H_PHY_READY, + 20000, 7500000, true); + if (ret) { + phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value); + return -ENODEV; + } + + return 0; +} + +static int an8811hb_check_crc(struct phy_device *phydev, u32 set1, + u32 mon2, u32 mon3) +{ + u32 pbus_value; + int retry = 25; + int ret; + + /* Configure CRC */ + ret = air_phy_buckpbus_reg_modify(phydev, set1, + AN8811HB_CRC_RD_EN, + AN8811HB_CRC_RD_EN); + if (ret < 0) + return ret; + air_phy_buckpbus_reg_read(phydev, set1, &pbus_value); + + do { + msleep(300); + air_phy_buckpbus_reg_read(phydev, mon2, &pbus_value); + + /* We do not know what errors this check is supposed + * catch or what to do about a failure. So print the + * result and continue like the vendor driver does. + */ + if (pbus_value & AN8811HB_CRC_ST) { + air_phy_buckpbus_reg_read(phydev, mon3, &pbus_value); + phydev_dbg(phydev, "CRC Check %s!\n", + pbus_value & AN8811HB_CRC_CHECK_PASS ? + "PASS" : "FAIL"); + return air_phy_buckpbus_reg_modify(phydev, set1, + AN8811HB_CRC_RD_EN, + 0); + } + } while (--retry); + + phydev_err(phydev, "CRC Check is not ready (%u)\n", pbus_value); + return -ENODEV; +} + +static void en8811h_print_fw_version(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + + air_phy_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, + &priv->firmware_version); + phydev_info(phydev, "MD32 firmware version: %08x\n", + priv->firmware_version); +} + +static int an8811hb_load_file(struct phy_device *phydev, const char *name, + u32 address) +{ + struct device *dev = &phydev->mdio.dev; + const struct firmware *fw; + int ret; + + ret = request_firmware_direct(&fw, name, dev); + if (ret < 0) + return ret; + + ret = air_write_buf(phydev, address, fw); + release_firmware(fw); + return ret; +} + +static int an8811hb_mcu_assert(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + phy_lock_mdio_bus(phydev); + + ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_RST, + AN8811HB_MCU_SW_RST_HOLD); + if (ret < 0) + goto unlock; + + ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_START, 0); + if (ret < 0) + goto unlock; + + msleep(50); + phydev_dbg(phydev, "MCU asserted\n"); + +unlock: + phy_unlock_mdio_bus(phydev); + return ret; +} + +static int an8811hb_mcu_deassert(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + phy_lock_mdio_bus(phydev); + + ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_START, + AN8811HB_MCU_SW_START_EN); + if (ret < 0) + goto unlock; + + ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_RST, + AN8811HB_MCU_SW_RST_RUN); + if (ret < 0) + goto unlock; + + msleep(50); + phydev_dbg(phydev, "MCU deasserted\n"); + +unlock: + phy_unlock_mdio_bus(phydev); + return ret; +} + +static int an8811hb_load_firmware(struct phy_device *phydev) +{ + int ret; + + ret = an8811hb_mcu_assert(phydev); + if (ret < 0) + return ret; + + ret = an8811hb_mcu_deassert(phydev); + if (ret < 0) + return ret; + + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + return ret; + + ret = an8811hb_load_file(phydev, AN8811HB_MD32_DM, AIR_FW_ADDR_DM); + if (ret < 0) + return ret; + + ret = an8811hb_check_crc(phydev, AN8811HB_CRC_DM_SET1, + AN8811HB_CRC_DM_MON2, + AN8811HB_CRC_DM_MON3); + if (ret < 0) + return ret; + + ret = an8811hb_load_file(phydev, AN8811HB_MD32_DSP, AIR_FW_ADDR_DSP); + if (ret < 0) + return ret; + + ret = an8811hb_check_crc(phydev, AN8811HB_CRC_PM_SET1, + AN8811HB_CRC_PM_MON2, + AN8811HB_CRC_PM_MON3); + if (ret < 0) + return ret; + + return en8811h_wait_mcu_ready(phydev); +} + +static int en8811h_load_firmware(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + const struct firmware *fw1, *fw2; + int ret; + + ret = request_firmware_direct(&fw1, EN8811H_MD32_DM, dev); + if (ret < 0) + return ret; + + ret = request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev); + if (ret < 0) + goto en8811h_load_firmware_rel1; + + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, + EN8811H_FW_CTRL_2_LOADING); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_write_buf(phydev, AIR_FW_ADDR_DM, fw1); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, fw2); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, 0); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = en8811h_wait_mcu_ready(phydev); + if (ret < 0) + goto en8811h_load_firmware_out; + + en8811h_print_fw_version(phydev); + +en8811h_load_firmware_out: + release_firmware(fw2); + +en8811h_load_firmware_rel1: + release_firmware(fw1); + + if (ret < 0) + phydev_err(phydev, "Load firmware failed: %d\n", ret); + + return ret; +} + +static int en8811h_restart_mcu(struct phy_device *phydev) +{ + int ret; + + if (phy_id_compare_model(phydev->phy_id, AN8811HB_PHY_ID)) { + ret = an8811hb_mcu_assert(phydev); + if (ret < 0) + return ret; + + ret = an8811hb_mcu_deassert(phydev); + if (ret < 0) + return ret; + } + + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + return ret; + + return en8811h_wait_mcu_ready(phydev); +} + +static int air_hw_led_on_set(struct phy_device *phydev, u8 index, bool on) +{ + struct en8811h_priv *priv = phydev->priv; + bool changed; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (on) + changed = !test_and_set_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + else + changed = !!test_and_clear_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + + changed |= (priv->led[index].rules != 0); + + /* clear netdev trigger rules in case LED_OFF has been set */ + if (!on) + priv->led[index].rules = 0; + + if (changed) + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_MASK, + on ? AIR_PHY_LED_ON_FORCE_ON : 0); + + return 0; +} + +static int air_hw_led_blink_set(struct phy_device *phydev, u8 index, + bool blinking) +{ + struct en8811h_priv *priv = phydev->priv; + bool changed; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (blinking) + changed = !test_and_set_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + else + changed = !!test_and_clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + + changed |= (priv->led[index].rules != 0); + + if (changed) + return phy_write_mmd(phydev, MDIO_MMD_VEND2, + AIR_PHY_LED_BLINK(index), + blinking ? + AIR_PHY_LED_BLINK_FORCE_BLINK : 0); + else + return 0; +} + +static int air_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct en8811h_priv *priv = phydev->priv; + bool blinking = false; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) { + blinking = true; + *delay_on = 50; + *delay_off = 50; + } + + err = air_hw_led_blink_set(phydev, index, blinking); + if (err) + return err; + + /* led-blink set, so switch led-on off */ + err = air_hw_led_on_set(phydev, index, false); + if (err) + return err; + + /* hw-control is off*/ + if (!!test_bit(AIR_PHY_LED_STATE_FORCE_BLINK, &priv->led[index].state)) + priv->led[index].rules = 0; + + return 0; +} + +static int air_led_brightness_set(struct phy_device *phydev, u8 index, + enum led_brightness value) +{ + struct en8811h_priv *priv = phydev->priv; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + /* led-on set, so switch led-blink off */ + err = air_hw_led_blink_set(phydev, index, false); + if (err) + return err; + + err = air_hw_led_on_set(phydev, index, (value != LED_OFF)); + if (err) + return err; + + /* hw-control is off */ + if (!!test_bit(AIR_PHY_LED_STATE_FORCE_ON, &priv->led[index].state)) + priv->led[index].rules = 0; + + return 0; +} + +static int air_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + struct en8811h_priv *priv = phydev->priv; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + *rules = priv->led[index].rules; + + return 0; +}; + +static int air_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + struct en8811h_priv *priv = phydev->priv; + u16 on = 0, blink = 0; + int ret; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + priv->led[index].rules = rules; + + if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX)) + on |= AIR_PHY_LED_ON_FDX; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK10; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK100; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK1000; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK2500; + + if (rules & BIT(TRIGGER_NETDEV_RX)) { + blink |= AIR_PHY_LED_BLINK_10RX | + AIR_PHY_LED_BLINK_100RX | + AIR_PHY_LED_BLINK_1000RX | + AIR_PHY_LED_BLINK_2500RX; + } + + if (rules & BIT(TRIGGER_NETDEV_TX)) { + blink |= AIR_PHY_LED_BLINK_10TX | + AIR_PHY_LED_BLINK_100TX | + AIR_PHY_LED_BLINK_1000TX | + AIR_PHY_LED_BLINK_2500TX; + } + + if (blink || on) { + /* switch hw-control on, so led-on and led-blink are off */ + clear_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + } else { + priv->led[index].rules = 0; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_MASK, on); + + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index), + blink); +}; + +static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol) +{ + int val = 0; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (state == AIR_LED_ENABLE) + val |= AIR_PHY_LED_ON_ENABLE; + else + val &= ~AIR_PHY_LED_ON_ENABLE; + + if (pol == AIR_ACTIVE_HIGH) + val |= AIR_PHY_LED_ON_POLARITY; + else + val &= ~AIR_PHY_LED_ON_POLARITY; + + err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_ENABLE | + AIR_PHY_LED_ON_POLARITY, val); + + if (err < 0) + return err; + + return 0; +} + +static int air_leds_init(struct phy_device *phydev, int num, int dur, int mode) +{ + struct en8811h_priv *priv = phydev->priv; + int ret, i; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK, + dur); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON, + dur >> 1); + if (ret < 0) + return ret; + + switch (mode) { + case AIR_LED_MODE_DISABLE: + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_MODE_MASK, 0); + if (ret < 0) + return ret; + break; + case AIR_LED_MODE_USER_DEFINE: + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_CLK_EN, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_CLK_EN); + if (ret < 0) + return ret; + break; + default: + phydev_err(phydev, "LED mode %d is not supported\n", mode); + return -EINVAL; + } + + for (i = 0; i < num; ++i) { + ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH); + if (ret < 0) { + phydev_err(phydev, "LED%d init failed: %d\n", i, ret); + return ret; + } + air_led_hw_control_set(phydev, i, priv->led[i].rules); + } + + return 0; +} + +static int en8811h_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~en8811h_led_trig) + return -EOPNOTSUPP; + + return 0; +}; + +static unsigned long an8811hb_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + u32 pbus_value; + int ret; + + ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value); + if (ret < 0) + return ret; + + return (pbus_value & AN8811HB_HWTRAP2_CKO) ? 50000000 : 25000000; +} + +static int an8811hb_clk_enable(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + + return air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV, + AN8811HB_CLK_DRV_CKO_MASK, + AN8811HB_CLK_DRV_CKO_MASK); +} + +static void an8811hb_clk_disable(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + + air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV, + AN8811HB_CLK_DRV_CKO_MASK, 0); +} + +static int an8811hb_clk_is_enabled(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + u32 pbus_value; + int ret; + + ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_CLK_DRV, &pbus_value); + if (ret < 0) + return ret; + + return (pbus_value & AN8811HB_CLK_DRV_CKO_MASK); +} + +static int an8811hb_clk_save_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + priv->cko_is_enabled = an8811hb_clk_is_enabled(hw); + + return 0; +} + +static void an8811hb_clk_restore_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + if (!priv->cko_is_enabled) + an8811hb_clk_disable(hw); +} + +static const struct clk_ops an8811hb_clk_ops = { + .recalc_rate = an8811hb_clk_recalc_rate, + .enable = an8811hb_clk_enable, + .disable = an8811hb_clk_disable, + .is_enabled = an8811hb_clk_is_enabled, + .save_context = an8811hb_clk_save_context, + .restore_context = an8811hb_clk_restore_context, +}; + +static int an8811hb_clk_provider_setup(struct device *dev, struct clk_hw *hw) +{ + struct clk_init_data init; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-cko", + fwnode_get_name(dev_fwnode(dev))); + if (!init.name) + return -ENOMEM; + + init.ops = &an8811hb_clk_ops; + init.flags = 0; + init.num_parents = 0; + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); +} + +static unsigned long en8811h_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + u32 pbus_value; + int ret; + + ret = air_phy_buckpbus_reg_read(phydev, EN8811H_HWTRAP1, &pbus_value); + if (ret < 0) + return ret; + + return (pbus_value & EN8811H_HWTRAP1_CKO) ? 50000000 : 25000000; +} + +static int en8811h_clk_enable(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + + return air_phy_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM, + EN8811H_CLK_CGM_CKO, + EN8811H_CLK_CGM_CKO); +} + +static void en8811h_clk_disable(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + + air_phy_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM, + EN8811H_CLK_CGM_CKO, 0); +} + +static int en8811h_clk_is_enabled(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + u32 pbus_value; + int ret; + + ret = air_phy_buckpbus_reg_read(phydev, EN8811H_CLK_CGM, &pbus_value); + if (ret < 0) + return ret; + + return (pbus_value & EN8811H_CLK_CGM_CKO); +} + +static int en8811h_clk_save_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + priv->cko_is_enabled = en8811h_clk_is_enabled(hw); + + return 0; +} + +static void en8811h_clk_restore_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + if (!priv->cko_is_enabled) + en8811h_clk_disable(hw); +} + +static const struct clk_ops en8811h_clk_ops = { + .recalc_rate = en8811h_clk_recalc_rate, + .enable = en8811h_clk_enable, + .disable = en8811h_clk_disable, + .is_enabled = en8811h_clk_is_enabled, + .save_context = en8811h_clk_save_context, + .restore_context = en8811h_clk_restore_context, +}; + +static int en8811h_clk_provider_setup(struct device *dev, struct clk_hw *hw) +{ + struct clk_init_data init; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-cko", + fwnode_get_name(dev_fwnode(dev))); + if (!init.name) + return -ENOMEM; + + init.ops = &en8811h_clk_ops; + init.flags = 0; + init.num_parents = 0; + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); +} + +static int en8811h_leds_setup(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0; + priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1; + priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2; + + ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, + AIR_LED_MODE_DISABLE); + if (ret < 0) + phydev_err(phydev, "Failed to disable leds: %d\n", ret); + + return ret; +} + +static int an8811hb_probe(struct phy_device *phydev) +{ + struct mdio_device *mdiodev; + struct en8811h_priv *priv; + int ret; + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct en8811h_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + /* + * The AN8811HB PHY address is restricted to 8-15 (decimal), + * depending on the board hardware strapping. + * This means the PBUS address is only in the range 16-21 (decimal), + * so we do not need to handle the case + * where the PBUS address exceeds 31 (decimal). + */ + mdiodev = mdio_device_create(phydev->mdio.bus, + phydev->mdio.addr + EN8811H_PBUS_ADDR_OFFS); + if (IS_ERR(mdiodev)) + return PTR_ERR(mdiodev); + + ret = mdio_device_register(mdiodev); + if (ret) + goto err_dev_free; + + priv->pbusdev = mdiodev; + + ret = an8811hb_load_firmware(phydev); + if (ret < 0) { + phydev_err(phydev, "Load firmware failed: %d\n", ret); + goto err_dev_create; + } + + en8811h_print_fw_version(phydev); + + /* mcu has just restarted after firmware load */ + priv->mcu_needs_restart = false; + + /* MDIO_DEVS1/2 empty, so set mmds_present bits here */ + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + + ret = en8811h_leds_setup(phydev); + if (ret < 0) + goto err_dev_create; + + priv->phydev = phydev; + /* Co-Clock Output */ + ret = an8811hb_clk_provider_setup(&phydev->mdio.dev, &priv->hw); + if (ret) + goto err_dev_create; + + /* Configure led gpio pins as output */ + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT, + AN8811HB_GPIO_OUTPUT_345, + AN8811HB_GPIO_OUTPUT_345); + if (ret < 0) + goto err_dev_create; + + return 0; + +err_dev_create: + mdio_device_remove(mdiodev); + +err_dev_free: + mdio_device_free(mdiodev); + return ret; +} + +static int en8811h_probe(struct phy_device *phydev) +{ + struct en8811h_priv *priv; + int ret; + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct en8811h_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + ret = en8811h_load_firmware(phydev); + if (ret < 0) + return ret; + + /* mcu has just restarted after firmware load */ + priv->mcu_needs_restart = false; + + /* MDIO_DEVS1/2 empty, so set mmds_present bits here */ + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + + ret = en8811h_leds_setup(phydev); + if (ret < 0) + return ret; + + priv->phydev = phydev; + /* Co-Clock Output */ + ret = en8811h_clk_provider_setup(&phydev->mdio.dev, &priv->hw); + if (ret) + return ret; + + return 0; +} + +static int an8811hb_config_serdes_polarity(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + u32 pbus_value = 0; + unsigned int pol; + int ret; + + ret = phy_get_manual_rx_polarity(dev_fwnode(dev), + phy_modes(phydev->interface), &pol); + if (ret) + return ret; + if (pol == PHY_POL_NORMAL) + pbus_value |= AN8811HB_RX_POLARITY_NORMAL; + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY, + AN8811HB_RX_POLARITY_NORMAL, + pbus_value); + if (ret < 0) + return ret; + + ret = phy_get_manual_tx_polarity(dev_fwnode(dev), + phy_modes(phydev->interface), &pol); + if (ret) + return ret; + pbus_value = 0; + if (pol == PHY_POL_NORMAL) + pbus_value |= AN8811HB_TX_POLARITY_NORMAL; + return air_phy_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY, + AN8811HB_TX_POLARITY_NORMAL, + pbus_value); +} + +static int en8811h_config_serdes_polarity(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + unsigned int pol, default_pol; + u32 pbus_value = 0; + int ret; + + default_pol = PHY_POL_NORMAL; + if (device_property_read_bool(dev, "airoha,pnswap-rx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_rx_polarity(dev_fwnode(dev), phy_modes(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_INVERT) + pbus_value |= EN8811H_POLARITY_RX_REVERSE; + + default_pol = PHY_POL_NORMAL; + if (device_property_read_bool(dev, "airoha,pnswap-tx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_tx_polarity(dev_fwnode(dev), phy_modes(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_NORMAL) + pbus_value |= EN8811H_POLARITY_TX_NORMAL; + + return air_phy_buckpbus_reg_modify(phydev, EN8811H_POLARITY, + EN8811H_POLARITY_RX_REVERSE | + EN8811H_POLARITY_TX_NORMAL, + pbus_value); +} + +static int an8811hb_config_init(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + /* If restart happened in .probe(), no need to restart now */ + if (priv->mcu_needs_restart) { + ret = en8811h_restart_mcu(phydev); + if (ret < 0) + return ret; + } else { + /* Next calls to .config_init() mcu needs to restart */ + priv->mcu_needs_restart = true; + } + + ret = an8811hb_config_serdes_polarity(phydev); + if (ret < 0) + return ret; + + ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, + AIR_LED_MODE_USER_DEFINE); + if (ret < 0) + phydev_err(phydev, "Failed to initialize leds: %d\n", ret); + + return ret; +} + +static int en8811h_config_init(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + /* If restart happened in .probe(), no need to restart now */ + if (priv->mcu_needs_restart) { + ret = en8811h_restart_mcu(phydev); + if (ret < 0) + return ret; + } else { + /* Next calls to .config_init() mcu needs to restart */ + priv->mcu_needs_restart = true; + } + + /* Select mode 1, the only mode supported. + * Configures the SerDes for 2500Base-X with rate adaptation + */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_1, + AIR_PHY_MCU_CMD_1_MODE1); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_2, + AIR_PHY_MCU_CMD_2_MODE1); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3, + AIR_PHY_MCU_CMD_3_MODE1); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4, + AIR_PHY_MCU_CMD_4_MODE1); + if (ret < 0) + return ret; + + ret = en8811h_config_serdes_polarity(phydev); + if (ret < 0) + return ret; + + ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, + AIR_LED_MODE_USER_DEFINE); + if (ret < 0) { + phydev_err(phydev, "Failed to initialize leds: %d\n", ret); + return ret; + } + + /* Configure led gpio pins as output. Must be redone on every + * .config_init(), not just once in .probe(): en8811h_restart_mcu() + * resets buckpbus-mapped MCU state (incl. this register) on every + * call after the first, e.g. on link renegotiation or ifup/ifdown. + */ + ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT, + EN8811H_GPIO_OUTPUT_345, + EN8811H_GPIO_OUTPUT_345); + if (ret < 0) + return ret; + + return 0; +} + +static int en8811h_get_features(struct phy_device *phydev) +{ + 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 en8811h_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + return RATE_MATCH_PAUSE; +} + +static int en8811h_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + int ret; + u32 adv; + + if (phydev->autoneg == AUTONEG_DISABLE) { + phydev_warn(phydev, "Disabling autoneg is not supported\n"); + return -EINVAL; + } + + 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_ADV2_5G, adv); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return __genphy_config_aneg(phydev, changed); +} + +static int en8811h_read_status(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + u32 pbus_value; + int ret, val; + + ret = genphy_update_link(phydev); + if (ret) + return ret; + + 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; + phydev->rate_matching = RATE_MATCH_PAUSE; + + ret = genphy_read_master_slave(phydev); + if (ret < 0) + return ret; + + ret = genphy_read_lpa(phydev); + if (ret < 0) + return ret; + + if (phy_id_compare_model(phydev->phy_id, AN8811HB_PHY_ID)) { + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + val & MDIO_AN_10GBT_STAT_LP2_5G); + } else { + /* Get link partner 2.5GBASE-T ability from vendor register */ + ret = air_phy_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA, + &pbus_value); + if (ret < 0) + return ret; + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + pbus_value & EN8811H_2P5G_LPA_2P5G); + } + + if (phydev->autoneg_complete) + phy_resolve_aneg_pause(phydev); + + if (!phydev->link) + return 0; + + /* Get real speed from vendor register */ + val = phy_read(phydev, AIR_AUX_CTRL_STATUS); + if (val < 0) + return val; + switch (val & AIR_AUX_CTRL_STATUS_SPEED_MASK) { + case AIR_AUX_CTRL_STATUS_SPEED_2500: + phydev->speed = SPEED_2500; + break; + case AIR_AUX_CTRL_STATUS_SPEED_1000: + phydev->speed = SPEED_1000; + break; + case AIR_AUX_CTRL_STATUS_SPEED_100: + phydev->speed = SPEED_100; + break; + case AIR_AUX_CTRL_STATUS_SPEED_10: + phydev->speed = SPEED_10; + break; + } + + /* Firmware before version 24011202 has no vendor register 2P5G_LPA. + * Assume link partner advertised it if connected at 2500Mbps. + */ + if (priv->firmware_version < 0x24011202) { + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + phydev->speed == SPEED_2500); + } + + /* Only supports full duplex */ + phydev->duplex = DUPLEX_FULL; + + return 0; +} + +static int en8811h_clear_intr(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3, + AIR_PHY_MCU_CMD_3_DOCMD); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4, + AIR_PHY_MCU_CMD_4_INTCLR); + if (ret < 0) + return ret; + + return 0; +} + +static irqreturn_t en8811h_handle_interrupt(struct phy_device *phydev) +{ + int ret; + + ret = en8811h_clear_intr(phydev); + if (ret < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int en8811h_resume(struct phy_device *phydev) +{ + clk_restore_context(); + + return genphy_resume(phydev); +} + +static int en8811h_suspend(struct phy_device *phydev) +{ + clk_save_context(); + + return genphy_suspend(phydev); +} + +static void an8811hb_remove(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + + if (priv->pbusdev) { + mdio_device_remove(priv->pbusdev); + mdio_device_free(priv->pbusdev); + } +} + +static struct phy_driver en8811h_driver[] = { +{ + PHY_ID_MATCH_MODEL(EN8811H_PHY_ID), + .name = "Airoha EN8811H", + .probe = en8811h_probe, + .get_features = en8811h_get_features, + .config_init = en8811h_config_init, + .get_rate_matching = en8811h_get_rate_matching, + .config_aneg = en8811h_config_aneg, + .read_status = en8811h_read_status, + .resume = en8811h_resume, + .suspend = en8811h_suspend, + .config_intr = en8811h_clear_intr, + .handle_interrupt = en8811h_handle_interrupt, + .led_hw_is_supported = en8811h_led_hw_is_supported, + .read_page = air_phy_read_page, + .write_page = air_phy_write_page, + .led_blink_set = air_led_blink_set, + .led_brightness_set = air_led_brightness_set, + .led_hw_control_set = air_led_hw_control_set, + .led_hw_control_get = air_led_hw_control_get, +}, +{ + PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID), + .name = "Airoha AN8811HB", + .probe = an8811hb_probe, + .remove = an8811hb_remove, + .get_features = en8811h_get_features, + .config_init = an8811hb_config_init, + .get_rate_matching = en8811h_get_rate_matching, + .config_aneg = en8811h_config_aneg, + .read_status = en8811h_read_status, + .resume = en8811h_resume, + .suspend = en8811h_suspend, + .config_intr = en8811h_clear_intr, + .handle_interrupt = en8811h_handle_interrupt, + .led_hw_is_supported = en8811h_led_hw_is_supported, + .read_page = air_phy_read_page, + .write_page = air_phy_write_page, + .led_blink_set = air_led_blink_set, + .led_brightness_set = air_led_brightness_set, + .led_hw_control_set = air_led_hw_control_set, + .led_hw_control_get = air_led_hw_control_get, +} }; + +module_phy_driver(en8811h_driver); + +static const struct mdio_device_id __maybe_unused en8811h_tbl[] = { + { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) }, + { PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, en8811h_tbl); +MODULE_FIRMWARE(EN8811H_MD32_DM); +MODULE_FIRMWARE(EN8811H_MD32_DSP); +MODULE_FIRMWARE(AN8811HB_MD32_DM); +MODULE_FIRMWARE(AN8811HB_MD32_DSP); + +MODULE_DESCRIPTION("Airoha EN8811H and AN8811HB PHY drivers"); +MODULE_AUTHOR("Airoha"); +MODULE_AUTHOR("Eric Woudstra "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c new file mode 100644 index 000000000..5141db19f --- /dev/null +++ b/drivers/net/phy/air_phy_lib.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Airoha Ethernet PHY common library + * + * Copyright (C) 2026 Airoha Technology Corp. + * Copyright (C) 2026 Collabora Ltd. + * Louis-Alexis Eyraud + */ + +#include +#include +#include +#include + +#include "air_phy_lib.h" + +static int __air_buckpbus_reg_read(struct phy_device *phydev, + u32 pbus_address, u32 *pbus_data) +{ + int pbus_data_low, pbus_data_high; + int ret; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) + return pbus_data_high; + + pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) + return pbus_data_low; + + *pbus_data = pbus_data_low | (pbus_data_high << 16); + return 0; +} + +static int __air_buckpbus_reg_write(struct phy_device *phydev, + u32 pbus_address, u32 pbus_data) +{ + int ret; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data)); + if (ret < 0) + return ret; + + return 0; +} + +static int __air_buckpbus_reg_modify(struct phy_device *phydev, + u32 pbus_address, u32 mask, u32 set) +{ + int pbus_data_low, pbus_data_high; + u32 pbus_data_old, pbus_data_new; + int ret; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) + return pbus_data_high; + + pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) + return pbus_data_low; + + pbus_data_old = pbus_data_low | (pbus_data_high << 16); + pbus_data_new = (pbus_data_old & ~mask) | set; + if (pbus_data_new == pbus_data_old) + return 0; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data_new)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data_new)); + if (ret < 0) + return ret; + + return 0; +} + +int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, + u32 *pbus_data) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} +EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_read); + +int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, + u32 pbus_data) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_write(phydev, pbus_address, + pbus_data); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} +EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_write); + +int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, + u32 mask, u32 set) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask, + set); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} +EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify); + +int air_phy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, AIR_EXT_PAGE_ACCESS); +} +EXPORT_SYMBOL_GPL(air_phy_read_page); + +int air_phy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page); +} +EXPORT_SYMBOL_GPL(air_phy_write_page); + +MODULE_DESCRIPTION("Airoha PHY Library"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Louis-Alexis Eyraud"); diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h new file mode 100644 index 000000000..01bb32e7c --- /dev/null +++ b/drivers/net/phy/air_phy_lib.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2026 Airoha Technology Corp. + * Copyright (C) 2026 Collabora Ltd. + * Louis-Alexis Eyraud + */ + +#ifndef __AIR_PHY_LIB_H +#define __AIR_PHY_LIB_H + +#include + +#define AIR_EXT_PAGE_ACCESS 0x1f + +#define AIR_PHY_PAGE_STANDARD 0x0000 +#define AIR_PHY_PAGE_EXTENDED_1 0x0001 +#define AIR_PHY_PAGE_EXTENDED_4 0x0004 + +/* MII Registers Page 4*/ +#define AIR_BPBUS_MODE 0x10 +#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000 +#define AIR_BPBUS_MODE_ADDR_INCR BIT(15) +#define AIR_BPBUS_WR_ADDR_HIGH 0x11 +#define AIR_BPBUS_WR_ADDR_LOW 0x12 +#define AIR_BPBUS_WR_DATA_HIGH 0x13 +#define AIR_BPBUS_WR_DATA_LOW 0x14 +#define AIR_BPBUS_RD_ADDR_HIGH 0x15 +#define AIR_BPBUS_RD_ADDR_LOW 0x16 +#define AIR_BPBUS_RD_DATA_HIGH 0x17 +#define AIR_BPBUS_RD_DATA_LOW 0x18 + +int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, + u32 mask, u32 set); +int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, + u32 *pbus_data); +int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, + u32 pbus_data); +int air_phy_read_page(struct phy_device *phydev); +int air_phy_write_page(struct phy_device *phydev, int page); + +#endif /* __AIR_PHY_LIB_H */ diff --git a/drivers/net/phy/amd.c b/drivers/net/phy/amd.c new file mode 100644 index 000000000..75b5fe655 --- /dev/null +++ b/drivers/net/phy/amd.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for AMD am79c PHYs + * + * Author: Heiko Schocher + * + * Copyright (c) 2011 DENX Software Engineering GmbH + */ +#include +#include +#include +#include +#include +#include + +#define PHY_ID_AC101L 0x00225520 +#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 "); +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_drivers[] = { + { + .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, + }, + { + .phy_id = PHY_ID_AC101L, + .name = "AC101L", + .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_drivers); + +static const struct mdio_device_id __maybe_unused amd_tbl[] = { + { PHY_ID_AC101L, 0xfffffff0 }, + { PHY_ID_AM79C874, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, amd_tbl); diff --git a/drivers/net/phy/aquantia/Kconfig b/drivers/net/phy/aquantia/Kconfig new file mode 100644 index 000000000..1a6567858 --- /dev/null +++ b/drivers/net/phy/aquantia/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +config AQUANTIA_PHY + tristate "Aquantia PHYs" + select CRC_ITU_T + help + Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405 diff --git a/drivers/net/phy/aquantia/Makefile b/drivers/net/phy/aquantia/Makefile new file mode 100644 index 000000000..c6c4d494e --- /dev/null +++ b/drivers/net/phy/aquantia/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +aquantia-objs += aquantia_main.o aquantia_firmware.o aquantia_leds.o +ifdef CONFIG_HWMON +aquantia-objs += aquantia_hwmon.o +endif +obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o diff --git a/drivers/net/phy/aquantia/aquantia.h b/drivers/net/phy/aquantia/aquantia.h new file mode 100644 index 000000000..31427ee34 --- /dev/null +++ b/drivers/net/phy/aquantia/aquantia.h @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* HWMON driver for Aquantia PHY + * + * Author: Nikita Yushchenko + * Author: Andrew Lunn + * Author: Heiner Kallweit + */ + +#ifndef AQUANTIA_H +#define AQUANTIA_H + +#include +#include + +/* Vendor specific 1, MDIO_MMD_VEND1 */ +#define VEND1_GLOBAL_SC 0x0 +#define VEND1_GLOBAL_SC_SOFT_RESET BIT(15) +#define VEND1_GLOBAL_SC_LOW_POWER BIT(11) + +#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_MAILBOX_INTERFACE1 0x0200 +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE BIT(15) +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE BIT(14) +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET BIT(12) +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_BUSY BIT(8) + +#define VEND1_GLOBAL_MAILBOX_INTERFACE2 0x0201 +#define VEND1_GLOBAL_MAILBOX_INTERFACE3 0x0202 +#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK GENMASK(15, 0) +#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK, (u16)((x) >> 16)) +#define VEND1_GLOBAL_MAILBOX_INTERFACE4 0x0203 +#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK GENMASK(15, 2) +#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK, (u16)(x)) + +#define VEND1_GLOBAL_MAILBOX_INTERFACE5 0x0204 +#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK GENMASK(15, 0) +#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK, (u16)((x) >> 16)) +#define VEND1_GLOBAL_MAILBOX_INTERFACE6 0x0205 +#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK GENMASK(15, 0) +#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK, (u16)(x)) + +/* 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_SERDES_MODE GENMASK(2, 0) +#define VEND1_GLOBAL_CFG_SERDES_MODE_XFI 0 +#define VEND1_GLOBAL_CFG_SERDES_MODE_SGMII 3 +#define VEND1_GLOBAL_CFG_SERDES_MODE_OCSGMII 4 +#define VEND1_GLOBAL_CFG_SERDES_MODE_XFI5G 6 +#define VEND1_GLOBAL_CFG_AUTONEG_ENA BIT(3) +#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 + +/* Vendor specific 1, MDIO_MMD_VEND2 */ +#define VEND1_GLOBAL_CONTROL2 0xc001 +#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST BIT(15) +#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD BIT(6) +#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL BIT(0) + +#define VEND1_GLOBAL_LED_PROV 0xc430 +#define AQR_LED_PROV(x) (VEND1_GLOBAL_LED_PROV + (x)) +#define VEND1_GLOBAL_LED_PROV_LINK2500 BIT(14) +#define VEND1_GLOBAL_LED_PROV_LINK5000 BIT(15) +#define VEND1_GLOBAL_LED_PROV_FORCE_ON BIT(8) +#define VEND1_GLOBAL_LED_PROV_LINK10000 BIT(7) +#define VEND1_GLOBAL_LED_PROV_LINK1000 BIT(6) +#define VEND1_GLOBAL_LED_PROV_LINK100 BIT(5) +#define VEND1_GLOBAL_LED_PROV_RX_ACT BIT(3) +#define VEND1_GLOBAL_LED_PROV_TX_ACT BIT(2) +#define VEND1_GLOBAL_LED_PROV_ACT_STRETCH GENMASK(0, 1) + +#define VEND1_GLOBAL_LED_PROV_LINK_MASK (VEND1_GLOBAL_LED_PROV_LINK100 | \ + VEND1_GLOBAL_LED_PROV_LINK1000 | \ + VEND1_GLOBAL_LED_PROV_LINK10000 | \ + VEND1_GLOBAL_LED_PROV_LINK5000 | \ + VEND1_GLOBAL_LED_PROV_LINK2500) + +#define VEND1_GLOBAL_LED_DRIVE 0xc438 +#define VEND1_GLOBAL_LED_DRIVE_VDD BIT(1) +#define AQR_LED_DRIVE(x) (VEND1_GLOBAL_LED_DRIVE + (x)) + +#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) + +#define VEND1_GLOBAL_GEN_STAT2 0xc831 +#define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15) + +#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 + +/* 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 + +#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) + +#define AQR_MAX_LEDS 3 + +/* Custom driver definitions for constructing a single variable out of + * aggregate firmware build information. These do not represent hardware + * fields. + */ +#define AQR_FW_FINGERPRINT_MAJOR GENMASK_ULL(63, 56) +#define AQR_FW_FINGERPRINT_MINOR GENMASK_ULL(55, 48) +#define AQR_FW_FINGERPRINT_BUILD_ID GENMASK_ULL(47, 40) +#define AQR_FW_FINGERPRINT_PROV_ID GENMASK_ULL(39, 32) +#define AQR_FW_FINGERPRINT_MISC_ID GENMASK_ULL(31, 16) +#define AQR_FW_FINGERPRINT_MISC_VER GENMASK_ULL(15, 0) +#define AQR_FW_FINGERPRINT(major, minor, build_id, prov_id, misc_id, misc_ver) \ + (FIELD_PREP(AQR_FW_FINGERPRINT_MAJOR, major) | \ + FIELD_PREP(AQR_FW_FINGERPRINT_MINOR, minor) | \ + FIELD_PREP(AQR_FW_FINGERPRINT_BUILD_ID, build_id) | \ + FIELD_PREP(AQR_FW_FINGERPRINT_PROV_ID, prov_id) | \ + FIELD_PREP(AQR_FW_FINGERPRINT_MISC_ID, misc_id) | \ + FIELD_PREP(AQR_FW_FINGERPRINT_MISC_VER, misc_ver)) + +/* 10G-QXGMII firmware for NXP SPF-30841 riser board (AQR412C) */ +#define AQR_G3_V4_3_C_AQR_NXP_SPF_30841_MUSX_ID40019_VER1198 \ + AQR_FW_FINGERPRINT(4, 3, 0xc, 1, 40019, 1198) + +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) + +static const struct { + int speed; + u16 reg; +} aqr_global_cfg_regs[] = { + { SPEED_10, VEND1_GLOBAL_CFG_10M, }, + { SPEED_100, VEND1_GLOBAL_CFG_100M, }, + { SPEED_1000, VEND1_GLOBAL_CFG_1G, }, + { SPEED_2500, VEND1_GLOBAL_CFG_2_5G, }, + { SPEED_5000, VEND1_GLOBAL_CFG_5G, }, + { SPEED_10000, VEND1_GLOBAL_CFG_10G, }, +}; + +#define AQR_NUM_GLOBAL_CFG ARRAY_SIZE(aqr_global_cfg_regs) + +enum aqr_rate_adaptation { + AQR_RATE_ADAPT_NONE, + AQR_RATE_ADAPT_USX, + AQR_RATE_ADAPT_PAUSE, +}; + +struct aqr_global_syscfg { + int speed; + phy_interface_t interface; + enum aqr_rate_adaptation rate_adapt; +}; + +struct aqr107_priv { + u64 sgmii_stats[AQR107_SGMII_STAT_SZ]; + u64 fingerprint; + unsigned long leds_active_low; + unsigned long leds_active_high; + bool wait_on_global_cfg; + struct aqr_global_syscfg global_cfg[AQR_NUM_GLOBAL_CFG]; +}; + +#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 + +int aqr_firmware_load(struct phy_device *phydev); + +int aqr_phy_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off); +int aqr_phy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value); +int aqr_phy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules); +int aqr_phy_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules); +int aqr_phy_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules); +int aqr_phy_led_active_low_set(struct phy_device *phydev, int index, bool enable); +int aqr_phy_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes); +int aqr_wait_reset_complete(struct phy_device *phydev); + +#endif /* AQUANTIA_H */ diff --git a/drivers/net/phy/aquantia/aquantia_firmware.c b/drivers/net/phy/aquantia/aquantia_firmware.c new file mode 100644 index 000000000..569256152 --- /dev/null +++ b/drivers/net/phy/aquantia/aquantia_firmware.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +#include + +#include "aquantia.h" + +#define UP_RESET_SLEEP 100 + +/* addresses of memory segments in the phy */ +#define DRAM_BASE_ADDR 0x3FFE0000 +#define IRAM_BASE_ADDR 0x40000000 + +/* firmware image format constants */ +#define VERSION_STRING_SIZE 0x40 +#define VERSION_STRING_OFFSET 0x0200 +/* primary offset is written at an offset from the start of the fw blob */ +#define PRIMARY_OFFSET_OFFSET 0x8 +/* primary offset needs to be then added to a base offset */ +#define PRIMARY_OFFSET_SHIFT 12 +#define PRIMARY_OFFSET(x) ((x) << PRIMARY_OFFSET_SHIFT) +#define HEADER_OFFSET 0x300 + +struct aqr_fw_header { + u32 padding; + u8 iram_offset[3]; + u8 iram_size[3]; + u8 dram_offset[3]; + u8 dram_size[3]; +} __packed; + +enum aqr_fw_src { + AQR_FW_SRC_NVMEM = 0, + AQR_FW_SRC_FS, +}; + +static const char * const aqr_fw_src_string[] = { + [AQR_FW_SRC_NVMEM] = "NVMEM", + [AQR_FW_SRC_FS] = "FS", +}; + +/* AQR firmware doesn't have fixed offsets for iram and dram section + * but instead provide an header with the offset to use on reading + * and parsing the firmware. + * + * AQR firmware can't be trusted and each offset is validated to be + * not negative and be in the size of the firmware itself. + */ +static bool aqr_fw_validate_get(size_t size, size_t offset, size_t get_size) +{ + return offset + get_size <= size; +} + +static int aqr_fw_get_be16(const u8 *data, size_t offset, size_t size, u16 *value) +{ + if (!aqr_fw_validate_get(size, offset, sizeof(u16))) + return -EINVAL; + + *value = get_unaligned_be16(data + offset); + + return 0; +} + +static int aqr_fw_get_le16(const u8 *data, size_t offset, size_t size, u16 *value) +{ + if (!aqr_fw_validate_get(size, offset, sizeof(u16))) + return -EINVAL; + + *value = get_unaligned_le16(data + offset); + + return 0; +} + +static int aqr_fw_get_le24(const u8 *data, size_t offset, size_t size, u32 *value) +{ + if (!aqr_fw_validate_get(size, offset, sizeof(u8) * 3)) + return -EINVAL; + + *value = get_unaligned_le24(data + offset); + + return 0; +} + +/* load data into the phy's memory */ +static int aqr_fw_load_memory(struct phy_device *phydev, u32 addr, + const u8 *data, size_t len) +{ + u16 crc = 0, up_crc; + size_t pos; + + phy_write_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLOBAL_MAILBOX_INTERFACE1, + VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET); + phy_write_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLOBAL_MAILBOX_INTERFACE3, + VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(addr)); + phy_write_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLOBAL_MAILBOX_INTERFACE4, + VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(addr)); + + /* We assume and enforce the size to be word aligned. + * If a firmware that is not word aligned is found, please report upstream. + */ + for (pos = 0; pos < len; pos += sizeof(u32)) { + u8 crc_data[4]; + u32 word; + + /* FW data is always stored in little-endian */ + word = get_unaligned_le32((const u32 *)(data + pos)); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE5, + VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(word)); + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE6, + VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(word)); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE1, + VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE | + VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE); + + /* Word is swapped internally and MAILBOX CRC is calculated + * using big-endian order. Mimic what the PHY does to have a + * matching CRC... + */ + crc_data[0] = word >> 24; + crc_data[1] = word >> 16; + crc_data[2] = word >> 8; + crc_data[3] = word; + + /* ...calculate CRC as we load data... */ + crc = crc_itu_t(crc, crc_data, sizeof(crc_data)); + } + /* ...gets CRC from MAILBOX after we have loaded the entire section... */ + up_crc = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE2); + /* ...and make sure it does match our calculated CRC */ + if (crc != up_crc) { + phydev_err(phydev, "CRC mismatch: calculated 0x%04x PHY 0x%04x\n", + crc, up_crc); + return -EINVAL; + } + + return 0; +} + +static int aqr_fw_boot(struct phy_device *phydev, const u8 *data, size_t size, + enum aqr_fw_src fw_src) +{ + u16 calculated_crc, read_crc, read_primary_offset; + u32 iram_offset = 0, iram_size = 0; + u32 dram_offset = 0, dram_size = 0; + char version[VERSION_STRING_SIZE]; + u32 primary_offset = 0; + int ret; + + /* extract saved CRC at the end of the fw + * CRC is saved in big-endian as PHY is BE + */ + ret = aqr_fw_get_be16(data, size - sizeof(u16), size, &read_crc); + if (ret) { + phydev_err(phydev, "bad firmware CRC in firmware\n"); + return ret; + } + calculated_crc = crc_itu_t(0, data, size - sizeof(u16)); + if (read_crc != calculated_crc) { + phydev_err(phydev, "bad firmware CRC: file 0x%04x calculated 0x%04x\n", + read_crc, calculated_crc); + return -EINVAL; + } + + /* Get the primary offset to extract DRAM and IRAM sections. */ + ret = aqr_fw_get_le16(data, PRIMARY_OFFSET_OFFSET, size, &read_primary_offset); + if (ret) { + phydev_err(phydev, "bad primary offset in firmware\n"); + return ret; + } + primary_offset = PRIMARY_OFFSET(read_primary_offset); + + /* Find the DRAM and IRAM sections within the firmware file. + * Make sure the fw_header is correctly in the firmware. + */ + if (!aqr_fw_validate_get(size, primary_offset + HEADER_OFFSET, + sizeof(struct aqr_fw_header))) { + phydev_err(phydev, "bad fw_header in firmware\n"); + return -EINVAL; + } + + /* offset are in LE and values needs to be converted to cpu endian */ + ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET + + offsetof(struct aqr_fw_header, iram_offset), + size, &iram_offset); + if (ret) { + phydev_err(phydev, "bad iram offset in firmware\n"); + return ret; + } + ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET + + offsetof(struct aqr_fw_header, iram_size), + size, &iram_size); + if (ret) { + phydev_err(phydev, "invalid iram size in firmware\n"); + return ret; + } + ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET + + offsetof(struct aqr_fw_header, dram_offset), + size, &dram_offset); + if (ret) { + phydev_err(phydev, "bad dram offset in firmware\n"); + return ret; + } + ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET + + offsetof(struct aqr_fw_header, dram_size), + size, &dram_size); + if (ret) { + phydev_err(phydev, "invalid dram size in firmware\n"); + return ret; + } + + /* Increment the offset with the primary offset. + * Validate iram/dram offset and size. + */ + iram_offset += primary_offset; + if (iram_size % sizeof(u32)) { + phydev_err(phydev, "iram size if not aligned to word size. Please report this upstream!\n"); + return -EINVAL; + } + if (!aqr_fw_validate_get(size, iram_offset, iram_size)) { + phydev_err(phydev, "invalid iram offset for iram size\n"); + return -EINVAL; + } + + dram_offset += primary_offset; + if (dram_size % sizeof(u32)) { + phydev_err(phydev, "dram size if not aligned to word size. Please report this upstream!\n"); + return -EINVAL; + } + if (!aqr_fw_validate_get(size, dram_offset, dram_size)) { + phydev_err(phydev, "invalid iram offset for iram size\n"); + return -EINVAL; + } + + phydev_dbg(phydev, "primary %d IRAM offset=%d size=%d DRAM offset=%d size=%d\n", + primary_offset, iram_offset, iram_size, dram_offset, dram_size); + + if (!aqr_fw_validate_get(size, dram_offset + VERSION_STRING_OFFSET, + VERSION_STRING_SIZE)) { + phydev_err(phydev, "invalid version in firmware\n"); + return -EINVAL; + } + strscpy(version, (char *)data + dram_offset + VERSION_STRING_OFFSET, + VERSION_STRING_SIZE); + if (version[0] == '\0') { + phydev_err(phydev, "invalid version in firmware\n"); + return -EINVAL; + } + phydev_info(phydev, "loading firmware version '%s' from '%s'\n", version, + aqr_fw_src_string[fw_src]); + + /* stall the microcprocessor */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2, + VEND1_GLOBAL_CONTROL2_UP_RUN_STALL | VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD); + + phydev_dbg(phydev, "loading DRAM 0x%08x from offset=%d size=%d\n", + DRAM_BASE_ADDR, dram_offset, dram_size); + ret = aqr_fw_load_memory(phydev, DRAM_BASE_ADDR, data + dram_offset, + dram_size); + if (ret) + return ret; + + phydev_dbg(phydev, "loading IRAM 0x%08x from offset=%d size=%d\n", + IRAM_BASE_ADDR, iram_offset, iram_size); + ret = aqr_fw_load_memory(phydev, IRAM_BASE_ADDR, data + iram_offset, + iram_size); + if (ret) + return ret; + + /* make sure soft reset and low power mode are clear */ + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_SC, + VEND1_GLOBAL_SC_SOFT_RESET | VEND1_GLOBAL_SC_LOW_POWER); + + /* Release the microprocessor. UP_RESET must be held for 100 usec. */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2, + VEND1_GLOBAL_CONTROL2_UP_RUN_STALL | + VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD | + VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST); + usleep_range(UP_RESET_SLEEP, UP_RESET_SLEEP * 2); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2, + VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD); + + return 0; +} + +static int aqr_firmware_load_nvmem(struct phy_device *phydev) +{ + struct nvmem_cell *cell; + size_t size; + u8 *buf; + int ret; + + cell = nvmem_cell_get(&phydev->mdio.dev, "firmware"); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &size); + if (IS_ERR(buf)) { + ret = PTR_ERR(buf); + goto exit; + } + + ret = aqr_fw_boot(phydev, buf, size, AQR_FW_SRC_NVMEM); + if (ret) + phydev_err(phydev, "firmware loading failed: %d\n", ret); + + kfree(buf); +exit: + nvmem_cell_put(cell); + + return ret; +} + +static int aqr_firmware_load_fs(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + const struct firmware *fw; + const char *fw_name; + int ret; + + ret = device_property_read_string(dev, "firmware-name", &fw_name); + if (ret) { + phydev_err(phydev, "failed to read firmware-name: %d\n", ret); + return ret; + } + + ret = request_firmware(&fw, fw_name, dev); + if (ret) { + phydev_err(phydev, "failed to find FW file %s (%d)\n", + fw_name, ret); + return ret; + } + + ret = aqr_fw_boot(phydev, fw->data, fw->size, AQR_FW_SRC_FS); + if (ret) + phydev_err(phydev, "firmware loading failed: %d\n", ret); + + release_firmware(fw); + + return ret; +} + +int aqr_firmware_load(struct phy_device *phydev) +{ + int ret; + + /* Check if the firmware is not already loaded by polling + * the current version returned by the PHY. + */ + ret = aqr_wait_reset_complete(phydev); + switch (ret) { + case 0: + /* Some firmware is loaded => do nothing */ + return 0; + case -ETIMEDOUT: + /* VEND1_GLOBAL_FW_ID still reads 0 after 2 seconds of polling. + * We don't have full confidence that no firmware is loaded (in + * theory it might just not have loaded yet), but we will + * assume that, and load a new image. + */ + ret = aqr_firmware_load_nvmem(phydev); + if (ret == -EPROBE_DEFER || !ret) + return ret; + + ret = aqr_firmware_load_fs(phydev); + if (ret) + return ret; + break; + default: + /* PHY read error, propagate it to the caller */ + return ret; + } + + return 0; +} diff --git a/drivers/net/phy/aquantia/aquantia_hwmon.c b/drivers/net/phy/aquantia/aquantia_hwmon.c new file mode 100644 index 000000000..1a714b56b --- /dev/null +++ b/drivers/net/phy/aquantia/aquantia_hwmon.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0 +/* HWMON driver for Aquantia PHY + * + * Author: Nikita Yushchenko + * Author: Andrew Lunn + * Author: Heiner Kallweit + */ + +#include +#include +#include +#include + +#include "aquantia.h" + +#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 const struct hwmon_channel_info * const aqr_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + 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), + 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/aquantia_leds.c b/drivers/net/phy/aquantia/aquantia_leds.c new file mode 100644 index 000000000..951f46104 --- /dev/null +++ b/drivers/net/phy/aquantia/aquantia_leds.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* LED driver for Aquantia PHY + * + * Author: Daniel Golle + */ + +#include + +#include "aquantia.h" + +int aqr_phy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + if (index >= AQR_MAX_LEDS) + return -EINVAL; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_PROV(index), + VEND1_GLOBAL_LED_PROV_LINK_MASK | + VEND1_GLOBAL_LED_PROV_FORCE_ON | + VEND1_GLOBAL_LED_PROV_RX_ACT | + VEND1_GLOBAL_LED_PROV_TX_ACT, + value ? VEND1_GLOBAL_LED_PROV_FORCE_ON : 0); +} + +static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX)); + +int aqr_phy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= AQR_MAX_LEDS) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~supported_triggers) + return -EOPNOTSUPP; + + return 0; +} + +int aqr_phy_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index >= AQR_MAX_LEDS) + return -EINVAL; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_PROV(index)); + if (val < 0) + return val; + + *rules = 0; + if (val & VEND1_GLOBAL_LED_PROV_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + + if (val & VEND1_GLOBAL_LED_PROV_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + + if (val & VEND1_GLOBAL_LED_PROV_LINK2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + + if (val & VEND1_GLOBAL_LED_PROV_LINK5000) + *rules |= BIT(TRIGGER_NETDEV_LINK_5000); + + if (val & VEND1_GLOBAL_LED_PROV_LINK10000) + *rules |= BIT(TRIGGER_NETDEV_LINK_10000); + + if (val & VEND1_GLOBAL_LED_PROV_RX_ACT) + *rules |= BIT(TRIGGER_NETDEV_RX); + + if (val & VEND1_GLOBAL_LED_PROV_TX_ACT) + *rules |= BIT(TRIGGER_NETDEV_TX); + + return 0; +} + +int aqr_phy_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + + if (index >= AQR_MAX_LEDS) + return -EINVAL; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) + val |= VEND1_GLOBAL_LED_PROV_LINK100; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) + val |= VEND1_GLOBAL_LED_PROV_LINK1000; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) + val |= VEND1_GLOBAL_LED_PROV_LINK2500; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_5000) | BIT(TRIGGER_NETDEV_LINK))) + val |= VEND1_GLOBAL_LED_PROV_LINK5000; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_10000) | BIT(TRIGGER_NETDEV_LINK))) + val |= VEND1_GLOBAL_LED_PROV_LINK10000; + + if (rules & BIT(TRIGGER_NETDEV_RX)) + val |= VEND1_GLOBAL_LED_PROV_RX_ACT; + + if (rules & BIT(TRIGGER_NETDEV_TX)) + val |= VEND1_GLOBAL_LED_PROV_TX_ACT; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_PROV(index), + VEND1_GLOBAL_LED_PROV_LINK_MASK | + VEND1_GLOBAL_LED_PROV_FORCE_ON | + VEND1_GLOBAL_LED_PROV_RX_ACT | + VEND1_GLOBAL_LED_PROV_TX_ACT, val); +} + +int aqr_phy_led_active_low_set(struct phy_device *phydev, int index, bool enable) +{ + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_DRIVE(index), + VEND1_GLOBAL_LED_DRIVE_VDD, + enable ? 0 : VEND1_GLOBAL_LED_DRIVE_VDD); +} + +int aqr_phy_led_polarity_set(struct phy_device *phydev, int index, unsigned long modes) +{ + bool force_active_low = false, force_active_high = false; + struct aqr107_priv *priv = phydev->priv; + u32 mode; + + if (index >= AQR_MAX_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + force_active_low = true; + break; + case PHY_LED_ACTIVE_HIGH: + force_active_high = true; + break; + default: + return -EINVAL; + } + } + + /* Save LED driver vdd state to restore on SW reset */ + if (force_active_low) + priv->leds_active_low |= BIT(index); + + if (force_active_high) + priv->leds_active_high |= BIT(index); + + if (force_active_high || force_active_low) + return aqr_phy_led_active_low_set(phydev, index, force_active_low); + + return -EINVAL; +} diff --git a/drivers/net/phy/aquantia/aquantia_main.c b/drivers/net/phy/aquantia/aquantia_main.c new file mode 100644 index 000000000..e95d391a6 --- /dev/null +++ b/drivers/net/phy/aquantia/aquantia_main.c @@ -0,0 +1,1593 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Aquantia PHY + * + * Author: Shaohui Xie + * + * Copyright 2015 Freescale Semiconductor, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#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_AQR111 0x03a1b610 +#define PHY_ID_AQR111B0 0x03a1b612 +#define PHY_ID_AQR112 0x03a1b662 +#define PHY_ID_AQR412 0x03a1b6f2 +#define PHY_ID_AQR412C 0x03a1b712 +#define PHY_ID_AQR113 0x31c31c40 +#define PHY_ID_AQR113C 0x31c31c12 +#define PHY_ID_AQR114C 0x31c31c22 +#define PHY_ID_AQR115 0x31c31c63 +#define PHY_ID_AQR115C 0x31c31c33 +#define PHY_ID_AQR813 0x31c31cb2 + +#define MDIO_PHYXS_VEND_PROV2 0xc441 +#define MDIO_PHYXS_VEND_PROV2_USX_AN BIT(3) + +#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_OFF 9 +#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_EXC_PHYID_INFO BIT(6) +#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_RESVD_VEND_PROV 0xc410 +#define MDIO_AN_RESVD_VEND_PROV_MDIX_AUTO 0 +#define MDIO_AN_RESVD_VEND_PROV_MDIX_MDI 1 +#define MDIO_AN_RESVD_VEND_PROV_MDIX_MDIX 2 +#define MDIO_AN_RESVD_VEND_PROV_MDIX_MASK GENMASK(1, 0) + +#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_RESVD_VEND_STATUS1 0xc810 +#define MDIO_AN_RESVD_VEND_STATUS1_MDIX BIT(8) + +#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 PMAPMD_FW_MISC_ID 0xc41d +#define PMAPMD_FW_MISC_VER 0xc41e + +#define PMAPMD_RSVD_VEND_PROV 0xe400 +#define PMAPMD_RSVD_VEND_PROV_MDI_CONF GENMASK(1, 0) +#define PMAPMD_RSVD_VEND_PROV_MDI_REVERSE BIT(0) +#define PMAPMD_RSVD_VEND_PROV_MDI_FORCE BIT(1) + +#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) + +/* 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 + +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_set_mdix(struct phy_device *phydev, int mdix) +{ + u16 val = 0; + + switch (mdix) { + case ETH_TP_MDI: + val = MDIO_AN_RESVD_VEND_PROV_MDIX_MDI; + break; + case ETH_TP_MDI_X: + val = MDIO_AN_RESVD_VEND_PROV_MDIX_MDIX; + break; + case ETH_TP_MDI_AUTO: + case ETH_TP_MDI_INVALID: + default: + val = MDIO_AN_RESVD_VEND_PROV_MDIX_AUTO; + break; + } + + return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_RESVD_VEND_PROV, + MDIO_AN_RESVD_VEND_PROV_MDIX_MASK, val); +} + +static int aqr_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + u16 reg; + int ret; + + ret = aqr_set_mdix(phydev, phydev->mdix_ctrl); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + 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); + } + + val = genphy_c45_aneg_done(phydev); + if (val < 0) + return val; + if (val) { + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_RESVD_VEND_STATUS1); + if (val < 0) + return val; + if (val & MDIO_AN_RESVD_VEND_STATUS1_MDIX) + phydev->mdix = ETH_TP_MDI_X; + else + phydev->mdix = ETH_TP_MDI; + } else { + phydev->mdix = ETH_TP_MDI_INVALID; + } + + return genphy_c45_read_status(phydev); +} + +static int aqr105_get_features(struct phy_device *phydev) +{ + int ret; + + /* Normal feature discovery */ + ret = genphy_c45_pma_read_abilities(phydev); + if (ret) + return ret; + + /* The AQR105 PHY misses to indicate the 2.5G and 5G modes, so add them + * here + */ + linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->supported); + + /* The AQR105 PHY suppports both RJ45 and SFP+ interfaces */ + linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported); + + return 0; +} + +static int aqr105_setup_forced(struct phy_device *phydev) +{ + int vend = MDIO_AN_VEND_PROV_EXC_PHYID_INFO; + int ctrl10 = 0; + int adv = ADVERTISE_CSMA; + int ret; + + switch (phydev->speed) { + case SPEED_100: + adv |= ADVERTISE_100FULL; + break; + case SPEED_1000: + adv |= ADVERTISE_NPAGE; + if (phydev->duplex == DUPLEX_FULL) + vend |= MDIO_AN_VEND_PROV_1000BASET_FULL; + else + vend |= MDIO_AN_VEND_PROV_1000BASET_HALF; + break; + case SPEED_2500: + adv |= (ADVERTISE_NPAGE | ADVERTISE_XNP); + vend |= MDIO_AN_VEND_PROV_2500BASET_FULL; + break; + case SPEED_5000: + adv |= (ADVERTISE_NPAGE | ADVERTISE_XNP); + vend |= MDIO_AN_VEND_PROV_5000BASET_FULL; + break; + case SPEED_10000: + adv |= (ADVERTISE_NPAGE | ADVERTISE_XNP); + ctrl10 |= MDIO_AN_10GBT_CTRL_ADV10G; + break; + default: + return -EINVAL; + } + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE, adv); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV, vend); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, ctrl10); + if (ret < 0) + return ret; + + /* set by vendor driver, but should be on by default */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, + MDIO_AN_CTRL1_XNP); + if (ret < 0) + return ret; + + return genphy_c45_an_disable_aneg(phydev); +} + +static int aqr105_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + u16 reg; + int ret; + + ret = aqr_set_mdix(phydev, phydev->mdix_ctrl); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + if (phydev->autoneg == AUTONEG_DISABLE) + return aqr105_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_gen1_read_rate(struct phy_device *phydev) +{ + 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; + break; + case MDIO_AN_TX_VEND_STATUS1_100BASETX: + phydev->speed = SPEED_100; + break; + case MDIO_AN_TX_VEND_STATUS1_1000BASET: + phydev->speed = SPEED_1000; + break; + case MDIO_AN_TX_VEND_STATUS1_2500BASET: + phydev->speed = SPEED_2500; + break; + case MDIO_AN_TX_VEND_STATUS1_5000BASET: + phydev->speed = SPEED_5000; + break; + case MDIO_AN_TX_VEND_STATUS1_10GBASET: + phydev->speed = SPEED_10000; + break; + default: + phydev->speed = SPEED_UNKNOWN; + } + + return 0; +} + +/* Quad port PHYs like AQR412(C) have 4 system interfaces, but they can also be + * used with a single system interface over which all 4 ports are multiplexed + * (10G-QXGMII). To the MDIO registers, this mode is indistinguishable from + * USXGMII (which implies a single 10G port). + * + * To not rely solely on the device tree, we allow the regular system interface + * detection to work as usual, but we replace USXGMII with 10G-QXGMII based on + * the specific fingerprint of firmware images that are known to be for MUSX. + */ +static phy_interface_t aqr_translate_interface(struct phy_device *phydev, + phy_interface_t interface) +{ + struct aqr107_priv *priv = phydev->priv; + + if (phy_id_compare(phydev->drv->phy_id, PHY_ID_AQR412C, phydev->drv->phy_id_mask) && + priv->fingerprint == AQR_G3_V4_3_C_AQR_NXP_SPF_30841_MUSX_ID40019_VER1198 && + interface == PHY_INTERFACE_MODE_USXGMII) + return PHY_INTERFACE_MODE_10G_QXGMII; + + return interface; +} + +static int aqr_gen1_read_status(struct phy_device *phydev) +{ + phy_interface_t interface; + int ret; + int val; + + ret = aqr_read_status(phydev); + if (ret) + return ret; + + if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE) + return 0; + + /* The status register is not immediately correct on line side link up. + * Poll periodically until it reflects the correct ON state. + * Only return fail for read error, timeout defaults to OFF state. + */ + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PHYXS, + MDIO_PHYXS_VEND_IF_STATUS, val, + (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val) != + MDIO_PHYXS_VEND_IF_STATUS_TYPE_OFF), + AQR107_OP_IN_PROG_SLEEP, + AQR107_OP_IN_PROG_TIMEOUT, false); + if (ret && ret != -ETIMEDOUT) + return ret; + + switch (FIELD_GET(MDIO_PHYXS_VEND_IF_STATUS_TYPE_MASK, val)) { + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KR: + interface = PHY_INTERFACE_MODE_10GKR; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_KX: + interface = PHY_INTERFACE_MODE_1000BASEKX; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XFI: + interface = PHY_INTERFACE_MODE_10GBASER; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_USXGMII: + interface = PHY_INTERFACE_MODE_USXGMII; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_XAUI: + interface = PHY_INTERFACE_MODE_XAUI; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_SGMII: + interface = PHY_INTERFACE_MODE_SGMII; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_RXAUI: + interface = PHY_INTERFACE_MODE_RXAUI; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OCSGMII: + interface = PHY_INTERFACE_MODE_2500BASEX; + break; + case MDIO_PHYXS_VEND_IF_STATUS_TYPE_OFF: + default: + phydev->link = false; + interface = PHY_INTERFACE_MODE_NA; + break; + } + + phydev->interface = aqr_translate_interface(phydev, interface); + + /* Read rate from vendor register */ + return aqr_gen1_read_rate(phydev); +} + +static int aqr_gen2_read_status(struct phy_device *phydev) +{ + struct aqr107_priv *priv = phydev->priv; + int i, ret; + + ret = aqr_gen1_read_status(phydev); + if (ret) + return ret; + + for (i = 0; i < AQR_NUM_GLOBAL_CFG; i++) { + struct aqr_global_syscfg *syscfg = &priv->global_cfg[i]; + + if (syscfg->speed != phydev->speed) + continue; + + if (syscfg->rate_adapt == AQR_RATE_ADAPT_PAUSE) + phydev->rate_matching = RATE_MATCH_PAUSE; + else + phydev->rate_matching = RATE_MATCH_NONE; + break; + } + + return 0; +} + +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; + } +} + +#define AQR_FW_WAIT_SLEEP_US 20000 +#define AQR_FW_WAIT_TIMEOUT_US 2000000 + +/* 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. + */ +int aqr_wait_reset_complete(struct phy_device *phydev) +{ + int ret, val; + + ret = read_poll_timeout(phy_read_mmd, val, val != 0, + AQR_FW_WAIT_SLEEP_US, AQR_FW_WAIT_TIMEOUT_US, + false, phydev, MDIO_MMD_VEND1, + VEND1_GLOBAL_FW_ID); + if (val < 0) { + phydev_err(phydev, "Failed to read VEND1_GLOBAL_FW_ID: %pe\n", + ERR_PTR(val)); + return val; + } + + return ret; +} + +static int aqr_build_fingerprint(struct phy_device *phydev) +{ + u8 fw_major, fw_minor, build_id, prov_id; + struct aqr107_priv *priv = phydev->priv; + u16 misc_id, misc_ver; + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_FW_ID); + if (val < 0) + return val; + + 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 val; + + build_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_FW_BUILD_ID, val); + prov_id = FIELD_GET(VEND1_GLOBAL_RSVD_STAT1_PROV_ID, val); + + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_FW_MISC_ID); + if (val < 0) + return val; + + misc_id = val; + + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_FW_MISC_VER); + if (val < 0) + return val; + + misc_ver = val; + + priv->fingerprint = AQR_FW_FINGERPRINT(fw_major, fw_minor, build_id, + prov_id, misc_id, misc_ver); + + phydev_dbg(phydev, "FW %u.%u, Build %u, Provisioning %u, Misc ID %u, Version %u\n", + fw_major, fw_minor, build_id, prov_id, misc_id, misc_ver); + + return 0; +} + +static int aqr107_config_mdi(struct phy_device *phydev) +{ + struct device_node *np = phydev->mdio.dev.of_node; + u32 mdi_conf; + int ret; + + ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf); + + /* Do nothing in case property "marvell,mdi-cfg-order" is not present */ + if (ret == -EINVAL || ret == -ENOSYS) + return 0; + + if (ret) + return ret; + + if (mdi_conf & ~PMAPMD_RSVD_VEND_PROV_MDI_REVERSE) + return -EINVAL; + + return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV, + PMAPMD_RSVD_VEND_PROV_MDI_CONF, + mdi_conf | PMAPMD_RSVD_VEND_PROV_MDI_FORCE); +} + +static int aqr_gen1_config_init(struct phy_device *phydev) +{ + struct aqr107_priv *priv = phydev->priv; + u32 led_idx; + 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_10G_QXGMII && + 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 = aqr_wait_reset_complete(phydev); + if (!ret) { + /* The PHY might work without a firmware image, so only build a + * fingerprint if the firmware was initialized. + */ + ret = aqr_build_fingerprint(phydev); + if (ret) + return ret; + } + + ret = aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT); + if (ret) + return ret; + + ret = aqr107_config_mdi(phydev); + if (ret) + return ret; + + /* Restore LED polarity state after reset */ + for_each_set_bit(led_idx, &priv->leds_active_low, AQR_MAX_LEDS) { + ret = aqr_phy_led_active_low_set(phydev, led_idx, true); + if (ret) + return ret; + } + + for_each_set_bit(led_idx, &priv->leds_active_high, AQR_MAX_LEDS) { + ret = aqr_phy_led_active_low_set(phydev, led_idx, false); + if (ret) + return ret; + } + + return 0; +} + +/* Walk the media-speed configuration registers to determine which + * host-side serdes modes may be used by the PHY depending on the + * negotiated media speed. + */ +static int aqr_gen2_read_global_syscfg(struct phy_device *phydev) +{ + struct aqr107_priv *priv = phydev->priv; + unsigned int serdes_mode, rate_adapt; + phy_interface_t interface; + int i, val; + + for (i = 0; i < AQR_NUM_GLOBAL_CFG; i++) { + struct aqr_global_syscfg *syscfg = &priv->global_cfg[i]; + + syscfg->speed = aqr_global_cfg_regs[i].speed; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, + aqr_global_cfg_regs[i].reg); + if (val < 0) + return val; + + serdes_mode = FIELD_GET(VEND1_GLOBAL_CFG_SERDES_MODE, val); + rate_adapt = FIELD_GET(VEND1_GLOBAL_CFG_RATE_ADAPT, val); + + switch (serdes_mode) { + case VEND1_GLOBAL_CFG_SERDES_MODE_XFI: + if (rate_adapt == VEND1_GLOBAL_CFG_RATE_ADAPT_USX) + interface = PHY_INTERFACE_MODE_USXGMII; + else + interface = PHY_INTERFACE_MODE_10GBASER; + break; + + case VEND1_GLOBAL_CFG_SERDES_MODE_XFI5G: + interface = PHY_INTERFACE_MODE_5GBASER; + break; + + case VEND1_GLOBAL_CFG_SERDES_MODE_OCSGMII: + interface = PHY_INTERFACE_MODE_2500BASEX; + break; + + case VEND1_GLOBAL_CFG_SERDES_MODE_SGMII: + interface = PHY_INTERFACE_MODE_SGMII; + break; + + default: + phydev_warn(phydev, "unrecognised serdes mode %u\n", + serdes_mode); + interface = PHY_INTERFACE_MODE_NA; + break; + } + + syscfg->interface = aqr_translate_interface(phydev, interface); + + switch (rate_adapt) { + case VEND1_GLOBAL_CFG_RATE_ADAPT_NONE: + syscfg->rate_adapt = AQR_RATE_ADAPT_NONE; + break; + case VEND1_GLOBAL_CFG_RATE_ADAPT_USX: + syscfg->rate_adapt = AQR_RATE_ADAPT_USX; + break; + case VEND1_GLOBAL_CFG_RATE_ADAPT_PAUSE: + syscfg->rate_adapt = AQR_RATE_ADAPT_PAUSE; + break; + default: + phydev_warn(phydev, "unrecognized rate adapt mode %u\n", + rate_adapt); + break; + } + + phydev_dbg(phydev, + "Media speed %d uses host interface %s with %s\n", + syscfg->speed, phy_modes(syscfg->interface), + syscfg->rate_adapt == AQR_RATE_ADAPT_NONE ? "no rate adaptation" : + syscfg->rate_adapt == AQR_RATE_ADAPT_PAUSE ? "rate adaptation through flow control" : + syscfg->rate_adapt == AQR_RATE_ADAPT_USX ? "rate adaptation through symbol replication" : + "unrecognized rate adaptation type"); + } + + return 0; +} + +static int aqr_gen2_fill_interface_modes(struct phy_device *phydev) +{ + unsigned long *possible = phydev->possible_interfaces; + struct aqr107_priv *priv = phydev->priv; + phy_interface_t interface; + int i, val, ret; + + /* It's been observed on some models that - when coming out of suspend + * - the FW signals that the PHY is ready but the GLOBAL_CFG registers + * continue on returning zeroes for some time. Let's poll the 100M + * register until it returns a real value as both 113c and 115c support + * this mode. + */ + if (priv->wait_on_global_cfg) { + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + VEND1_GLOBAL_CFG_100M, val, + val != 0, 1000, 100000, false); + if (ret) + return ret; + } + + ret = aqr_gen2_read_global_syscfg(phydev); + if (ret) + return ret; + + for (i = 0; i < AQR_NUM_GLOBAL_CFG; i++) { + interface = priv->global_cfg[i].interface; + if (interface != PHY_INTERFACE_MODE_NA) + __set_bit(interface, possible); + } + + return 0; +} + +static int aqr_gen2_config_init(struct phy_device *phydev) +{ + int ret; + + ret = aqr_gen1_config_init(phydev); + if (ret) + return ret; + + return aqr_gen2_fill_interface_modes(phydev); +} + +static int aqr_gen3_config_init(struct phy_device *phydev) +{ + return aqr_gen2_config_init(phydev); +} + +static int aqcs109_config_init(struct phy_device *phydev) +{ + /* Check that the PHY interface type is compatible */ + if (phydev->interface != PHY_INTERFACE_MODE_SGMII && + phydev->interface != PHY_INTERFACE_MODE_2500BASEX) + return -ENODEV; + + return aqr_gen2_config_init(phydev); +} + +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 aqr_gen1_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 aqr_gen2_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + if (iface == PHY_INTERFACE_MODE_10GBASER || + iface == PHY_INTERFACE_MODE_2500BASEX) + return RATE_MATCH_PAUSE; + return RATE_MATCH_NONE; +} + +static int aqr_gen1_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 aqr_gen1_wait_processor_intensive_op(phydev); +} + +static int aqr_gen1_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 aqr_gen1_wait_processor_intensive_op(phydev); +} + +static int aqr115c_get_features(struct phy_device *phydev) +{ + unsigned long *supported = phydev->supported; + + /* PHY supports speeds up to 2.5G with autoneg. PMA capabilities + * are not useful. + */ + linkmode_or(supported, supported, phy_gbit_features); + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, supported); + + return 0; +} + +static int aqr111_get_features(struct phy_device *phydev) +{ + /* PHY supports speeds up to 5G with autoneg. PMA capabilities + * are not useful. + */ + aqr115c_get_features(phydev); + linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + phydev->supported); + + return 0; +} + +static int aqr_gen4_config_init(struct phy_device *phydev) +{ + struct aqr107_priv *priv = phydev->priv; + int ret; + + priv->wait_on_global_cfg = true; + + ret = aqr_gen3_config_init(phydev); + if (ret < 0) + return ret; + + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_TXDIS, + MDIO_PMD_TXDIS_GLOBAL); + if (ret) + return ret; + + return aqr_gen1_wait_processor_intensive_op(phydev); +} + +static unsigned int aqr_gen2_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_SGMII || + interface == PHY_INTERFACE_MODE_USXGMII || + interface == PHY_INTERFACE_MODE_10G_QXGMII) + return LINK_INBAND_ENABLE | LINK_INBAND_DISABLE; + + return 0; +} + +static int aqr_gen2_config_inband(struct phy_device *phydev, unsigned int modes) +{ + struct aqr107_priv *priv = phydev->priv; + + if (phydev->interface == PHY_INTERFACE_MODE_USXGMII || + phydev->interface == PHY_INTERFACE_MODE_10G_QXGMII) { + u16 set = 0; + + if (modes == LINK_INBAND_ENABLE) + set = MDIO_PHYXS_VEND_PROV2_USX_AN; + + return phy_modify_mmd(phydev, MDIO_MMD_PHYXS, + MDIO_PHYXS_VEND_PROV2, + MDIO_PHYXS_VEND_PROV2_USX_AN, set); + } + + for (int i = 0; i < AQR_NUM_GLOBAL_CFG; i++) { + struct aqr_global_syscfg *syscfg = &priv->global_cfg[i]; + u16 set = 0; + int err; + + if (syscfg->interface != phydev->interface) + continue; + + if (modes == LINK_INBAND_ENABLE) + set = VEND1_GLOBAL_CFG_AUTONEG_ENA; + + err = phy_modify_mmd(phydev, MDIO_MMD_VEND1, + aqr_global_cfg_regs[i].reg, + VEND1_GLOBAL_CFG_AUTONEG_ENA, set); + if (err) + return err; + } + + return 0; +} + +static int aqr107_probe(struct phy_device *phydev) +{ + int ret; + + phydev->priv = devm_kzalloc(&phydev->mdio.dev, + sizeof(struct aqr107_priv), GFP_KERNEL); + if (!phydev->priv) + return -ENOMEM; + + ret = aqr_firmware_load(phydev); + if (ret) + return ret; + + 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", + .get_features = aqr105_get_features, + .probe = aqr107_probe, + .config_init = aqr_gen1_config_init, + .config_aneg = aqr105_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen1_read_status, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_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 = aqr_gen2_get_rate_matching, + .config_init = aqr_gen2_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_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, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQCS109), + .name = "Aquantia AQCS109", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_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 = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .get_features = aqr115c_get_features, + .link_change_notify = aqr107_link_change_notify, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR111), + .name = "Aquantia AQR111", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen3_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .get_features = aqr111_get_features, + .link_change_notify = aqr107_link_change_notify, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR111B0), + .name = "Aquantia AQR111B0", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen3_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .get_features = aqr111_get_features, + .link_change_notify = aqr107_link_change_notify, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + 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, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR112), + .name = "Aquantia AQR112", + .probe = aqr107_probe, + .config_init = aqr_gen3_config_init, + .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 = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .read_status = aqr_gen2_read_status, + .get_rate_matching = aqr_gen2_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, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR412), + .name = "Aquantia AQR412", + .probe = aqr107_probe, + .config_init = aqr_gen3_config_init, + .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 = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .read_status = aqr_gen2_read_status, + .get_rate_matching = aqr_gen2_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, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR412C), + .name = "Aquantia AQR412C", + .probe = aqr107_probe, + .config_init = aqr_gen3_config_init, + .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 = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .read_status = aqr_gen2_read_status, + .get_rate_matching = aqr_gen2_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, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR113), + .name = "Aquantia AQR113", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen4_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_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, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR113C), + .name = "Aquantia AQR113C", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen4_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_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, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR114C), + .name = "Aquantia AQR114C", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen4_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .get_features = aqr111_get_features, + .link_change_notify = aqr107_link_change_notify, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR115), + .name = "Aquantia AQR115", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen4_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .get_features = aqr115c_get_features, + .link_change_notify = aqr107_link_change_notify, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR115C), + .name = "Aquantia AQR115C", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen4_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .get_features = aqr115c_get_features, + .link_change_notify = aqr107_link_change_notify, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_AQR813), + .name = "Aquantia AQR813", + .probe = aqr107_probe, + .get_rate_matching = aqr_gen2_get_rate_matching, + .config_init = aqr_gen4_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr_gen2_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr_gen1_suspend, + .resume = aqr_gen1_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, + .led_brightness_set = aqr_phy_led_brightness_set, + .led_hw_is_supported = aqr_phy_led_hw_is_supported, + .led_hw_control_set = aqr_phy_led_hw_control_set, + .led_hw_control_get = aqr_phy_led_hw_control_get, + .led_polarity_set = aqr_phy_led_polarity_set, + .inband_caps = aqr_gen2_inband_caps, + .config_inband = aqr_gen2_config_inband, +}, +}; + +module_phy_driver(aqr_driver); + +static const 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_AQR111) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR111B0) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR112) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR412) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR412C) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR113) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR114C) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR115) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR115C) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR813) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, aqr_tbl); + +MODULE_DESCRIPTION("Aquantia PHY driver"); +MODULE_AUTHOR("Shaohui Xie "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c new file mode 100644 index 000000000..d5738117e --- /dev/null +++ b/drivers/net/phy/as21xxx.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Aeonsemi AS21XXxX PHY Driver + * + * Author: Christian Marangi + */ + +#include +#include +#include +#include +#include + +#define VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR 0x3 +#define VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR 0x4 + +#define VEND1_GLB_REG_CPU_CTRL 0xe +#define VEND1_GLB_CPU_CTRL_MASK GENMASK(4, 0) +#define VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK GENMASK(12, 8) +#define VEND1_GLB_CPU_CTRL_LED_POLARITY(_n) FIELD_PREP(VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK, \ + BIT(_n)) + +#define VEND1_FW_START_ADDR 0x100 + +#define VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD 0x101 +#define VEND1_GLB_REG_MDIO_INDIRECT_LOAD 0x102 + +#define VEND1_GLB_REG_MDIO_INDIRECT_STATUS 0x103 + +#define VEND1_PTP_CLK 0x142 +#define VEND1_PTP_CLK_EN BIT(6) + +/* 5 LED at step of 0x20 + * FE: Fast-Ethernet (10/100) + * GE: Gigabit-Ethernet (1000) + * NG: New-Generation (2500/5000/10000) + */ +#define VEND1_LED_REG(_n) (0x1800 + ((_n) * 0x10)) +#define VEND1_LED_REG_A_EVENT GENMASK(15, 11) +#define VEND1_LED_CONF 0x1881 +#define VEND1_LED_CONFG_BLINK GENMASK(7, 0) + +#define VEND1_SPEED_STATUS 0x4002 +#define VEND1_SPEED_MASK GENMASK(7, 0) +#define VEND1_SPEED_10000 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x3) +#define VEND1_SPEED_5000 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x5) +#define VEND1_SPEED_2500 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x9) +#define VEND1_SPEED_1000 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x10) +#define VEND1_SPEED_100 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x20) +#define VEND1_SPEED_10 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x0) + +#define VEND1_IPC_CMD 0x5801 +#define AEON_IPC_CMD_PARITY BIT(15) +#define AEON_IPC_CMD_SIZE GENMASK(10, 6) +#define AEON_IPC_CMD_OPCODE GENMASK(5, 0) + +#define IPC_CMD_NOOP 0x0 /* Do nothing */ +#define IPC_CMD_INFO 0x1 /* Get Firmware Version */ +#define IPC_CMD_SYS_CPU 0x2 /* SYS_CPU */ +#define IPC_CMD_BULK_DATA 0xa /* Pass bulk data in ipc registers. */ +#define IPC_CMD_BULK_WRITE 0xc /* Write bulk data to memory */ +#define IPC_CMD_CFG_PARAM 0x1a /* Write config parameters to memory */ +#define IPC_CMD_NG_TESTMODE 0x1b /* Set NG test mode and tone */ +#define IPC_CMD_TEMP_MON 0x15 /* Temperature monitoring function */ +#define IPC_CMD_SET_LED 0x23 /* Set led */ + +#define VEND1_IPC_STS 0x5802 +#define AEON_IPC_STS_PARITY BIT(15) +#define AEON_IPC_STS_SIZE GENMASK(14, 10) +#define AEON_IPC_STS_OPCODE GENMASK(9, 4) +#define AEON_IPC_STS_STATUS GENMASK(3, 0) +#define AEON_IPC_STS_STATUS_RCVD FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x1) +#define AEON_IPC_STS_STATUS_PROCESS FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x2) +#define AEON_IPC_STS_STATUS_SUCCESS FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x4) +#define AEON_IPC_STS_STATUS_ERROR FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x8) +#define AEON_IPC_STS_STATUS_BUSY FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xe) +#define AEON_IPC_STS_STATUS_READY FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xf) + +#define VEND1_IPC_DATA0 0x5808 +#define VEND1_IPC_DATA1 0x5809 +#define VEND1_IPC_DATA2 0x580a +#define VEND1_IPC_DATA3 0x580b +#define VEND1_IPC_DATA4 0x580c +#define VEND1_IPC_DATA5 0x580d +#define VEND1_IPC_DATA6 0x580e +#define VEND1_IPC_DATA7 0x580f +#define VEND1_IPC_DATA(_n) (VEND1_IPC_DATA0 + (_n)) + +/* Sub command of CMD_INFO */ +#define IPC_INFO_VERSION 0x1 + +/* Sub command of CMD_SYS_CPU */ +#define IPC_SYS_CPU_REBOOT 0x3 +#define IPC_SYS_CPU_IMAGE_OFST 0x4 +#define IPC_SYS_CPU_IMAGE_CHECK 0x5 +#define IPC_SYS_CPU_PHY_ENABLE 0x6 + +/* Sub command of CMD_CFG_PARAM */ +#define IPC_CFG_PARAM_DIRECT 0x4 + +/* CFG DIRECT sub command */ +#define IPC_CFG_PARAM_DIRECT_NG_PHYCTRL 0x1 +#define IPC_CFG_PARAM_DIRECT_CU_AN 0x2 +#define IPC_CFG_PARAM_DIRECT_SDS_PCS 0x3 +#define IPC_CFG_PARAM_DIRECT_AUTO_EEE 0x4 +#define IPC_CFG_PARAM_DIRECT_SDS_PMA 0x5 +#define IPC_CFG_PARAM_DIRECT_DPC_RA 0x6 +#define IPC_CFG_PARAM_DIRECT_DPC_PKT_CHK 0x7 +#define IPC_CFG_PARAM_DIRECT_DPC_SDS_WAIT_ETH 0x8 +#define IPC_CFG_PARAM_DIRECT_WDT 0x9 +#define IPC_CFG_PARAM_DIRECT_SDS_RESTART_AN 0x10 +#define IPC_CFG_PARAM_DIRECT_TEMP_MON 0x11 +#define IPC_CFG_PARAM_DIRECT_WOL 0x12 + +/* Sub command of CMD_TEMP_MON */ +#define IPC_CMD_TEMP_MON_GET 0x4 + +#define AS21XXX_MDIO_AN_C22 0xffe0 + +#define PHY_ID_AS21XXX 0x75009410 +/* AS21xxx ID Legend + * AS21x1xxB1 + * ^ ^^ + * | |J: Supports SyncE/PTP + * | |P: No SyncE/PTP support + * | 1: Supports 2nd Serdes + * | 2: Not 2nd Serdes support + * 0: 10G, 5G, 2.5G + * 5: 5G, 2.5G + * 2: 2.5G + */ +#define PHY_ID_AS21011JB1 0x75009402 +#define PHY_ID_AS21011PB1 0x75009412 +#define PHY_ID_AS21010JB1 0x75009422 +#define PHY_ID_AS21010PB1 0x75009432 +#define PHY_ID_AS21511JB1 0x75009442 +#define PHY_ID_AS21511PB1 0x75009452 +#define PHY_ID_AS21510JB1 0x75009462 +#define PHY_ID_AS21510PB1 0x75009472 +#define PHY_ID_AS21210JB1 0x75009482 +#define PHY_ID_AS21210PB1 0x75009492 +#define PHY_VENDOR_AEONSEMI 0x75009400 + +#define AEON_MAX_LEDS 5 +#define AEON_IPC_DELAY 10000 +#define AEON_IPC_TIMEOUT (AEON_IPC_DELAY * 100) +#define AEON_IPC_DATA_NUM_REGISTERS 8 +#define AEON_IPC_DATA_MAX (AEON_IPC_DATA_NUM_REGISTERS * sizeof(u16)) + +#define AEON_BOOT_ADDR 0x1000 +#define AEON_CPU_BOOT_ADDR 0x2000 +#define AEON_CPU_CTRL_FW_LOAD (BIT(4) | BIT(2) | BIT(1) | BIT(0)) +#define AEON_CPU_CTRL_FW_START BIT(0) + +enum as21xxx_led_event { + VEND1_LED_REG_A_EVENT_ON_10 = 0x0, + VEND1_LED_REG_A_EVENT_ON_100, + VEND1_LED_REG_A_EVENT_ON_1000, + VEND1_LED_REG_A_EVENT_ON_2500, + VEND1_LED_REG_A_EVENT_ON_5000, + VEND1_LED_REG_A_EVENT_ON_10000, + VEND1_LED_REG_A_EVENT_ON_FE_GE, + VEND1_LED_REG_A_EVENT_ON_NG, + VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX, + VEND1_LED_REG_A_EVENT_ON_COLLISION, + VEND1_LED_REG_A_EVENT_BLINK_TX, + VEND1_LED_REG_A_EVENT_BLINK_RX, + VEND1_LED_REG_A_EVENT_BLINK_ACT, + VEND1_LED_REG_A_EVENT_ON_LINK, + VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT, + VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX, + VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT, + VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT, + VEND1_LED_REG_A_EVENT_ON_NG_BLINK_FE_GE, + VEND1_LED_REG_A_EVENT_ON_FD_BLINK_COLLISION, + VEND1_LED_REG_A_EVENT_ON, + VEND1_LED_REG_A_EVENT_OFF, +}; + +struct as21xxx_led_pattern_info { + unsigned int pattern; + u16 val; +}; + +struct as21xxx_priv { + bool parity_status; + /* Protect concurrent IPC access */ + struct mutex ipc_lock; +}; + +static struct as21xxx_led_pattern_info as21xxx_led_supported_pattern[] = { + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10), + .val = VEND1_LED_REG_A_EVENT_ON_10 + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_100), + .val = VEND1_LED_REG_A_EVENT_ON_100 + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_1000), + .val = VEND1_LED_REG_A_EVENT_ON_1000 + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_2500), + .val = VEND1_LED_REG_A_EVENT_ON_2500 + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_5000), + .val = VEND1_LED_REG_A_EVENT_ON_5000 + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10000), + .val = VEND1_LED_REG_A_EVENT_ON_10000 + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK), + .val = VEND1_LED_REG_A_EVENT_ON_LINK + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000), + .val = VEND1_LED_REG_A_EVENT_ON_FE_GE + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000), + .val = VEND1_LED_REG_A_EVENT_ON_NG + }, + { + .pattern = BIT(TRIGGER_NETDEV_FULL_DUPLEX), + .val = VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX + }, + { + .pattern = BIT(TRIGGER_NETDEV_TX), + .val = VEND1_LED_REG_A_EVENT_BLINK_TX + }, + { + .pattern = BIT(TRIGGER_NETDEV_RX), + .val = VEND1_LED_REG_A_EVENT_BLINK_RX + }, + { + .pattern = BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX), + .val = VEND1_LED_REG_A_EVENT_BLINK_ACT + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000), + .val = VEND1_LED_REG_A_EVENT_ON_LINK + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000) | + BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX), + .val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000) | + BIT(TRIGGER_NETDEV_RX), + .val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX), + .val = VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT + }, + { + .pattern = BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000) | + BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX), + .val = VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT + } +}; + +static int aeon_firmware_boot(struct phy_device *phydev, const u8 *data, + size_t size) +{ + int i, ret; + u16 val; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL, + VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_LOAD); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_FW_START_ADDR, + AEON_BOOT_ADDR); + if (ret) + return ret; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD, + 0x3ffc, 0xc000); + if (ret) + return ret; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLB_REG_MDIO_INDIRECT_STATUS); + if (val > 1) { + phydev_err(phydev, "wrong origin mdio_indirect_status: %x\n", val); + return -EINVAL; + } + + /* Firmware is always aligned to u16 */ + for (i = 0; i < size; i += 2) { + val = data[i + 1] << 8 | data[i]; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLB_REG_MDIO_INDIRECT_LOAD, val); + if (ret) + return ret; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR, + lower_16_bits(AEON_CPU_BOOT_ADDR)); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR, + upper_16_bits(AEON_CPU_BOOT_ADDR)); + if (ret) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL, + VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_START); +} + +static int aeon_firmware_load(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + const struct firmware *fw; + const char *fw_name; + int ret; + + ret = of_property_read_string(dev->of_node, "firmware-name", + &fw_name); + if (ret) + return ret; + + ret = request_firmware(&fw, fw_name, dev); + if (ret) { + phydev_err(phydev, "failed to find FW file %s (%d)\n", + fw_name, ret); + return ret; + } + + ret = aeon_firmware_boot(phydev, fw->data, fw->size); + + release_firmware(fw); + + return ret; +} + +static bool aeon_ipc_ready(u16 val, bool parity_status) +{ + u16 status; + + if (FIELD_GET(AEON_IPC_STS_PARITY, val) != parity_status) + return false; + + status = val & AEON_IPC_STS_STATUS; + + return status != AEON_IPC_STS_STATUS_RCVD && + status != AEON_IPC_STS_STATUS_PROCESS && + status != AEON_IPC_STS_STATUS_BUSY; +} + +static int aeon_ipc_wait_cmd(struct phy_device *phydev, bool parity_status) +{ + u16 val; + + /* Exit condition logic: + * - Wait for parity bit equal + * - Wait for status success, error OR ready + */ + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS, val, + aeon_ipc_ready(val, parity_status), + AEON_IPC_DELAY, AEON_IPC_TIMEOUT, false); +} + +static int aeon_ipc_send_cmd(struct phy_device *phydev, + struct as21xxx_priv *priv, + u16 cmd, u16 *ret_sts) +{ + bool curr_parity; + int ret; + + /* The IPC sync by using a single parity bit. + * Each CMD have alternately this bit set or clear + * to understand correct flow and packet order. + */ + curr_parity = priv->parity_status; + if (priv->parity_status) + cmd |= AEON_IPC_CMD_PARITY; + + /* Always update parity for next packet */ + priv->parity_status = !priv->parity_status; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_CMD, cmd); + if (ret) + return ret; + + /* Wait for packet to be processed */ + usleep_range(AEON_IPC_DELAY, AEON_IPC_DELAY + 5000); + + /* With no ret_sts, ignore waiting for packet completion + * (ipc parity bit sync) + */ + if (!ret_sts) + return 0; + + ret = aeon_ipc_wait_cmd(phydev, curr_parity); + if (ret) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS); + if (ret < 0) + return ret; + + *ret_sts = ret; + if ((*ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_SUCCESS) + return -EINVAL; + + return 0; +} + +/* If data is NULL, return 0 or negative error. + * If data not NULL, return number of Bytes received from IPC or + * a negative error. + */ +static int aeon_ipc_send_msg(struct phy_device *phydev, + u16 opcode, u16 *data, unsigned int data_len, + u16 *ret_data) +{ + struct as21xxx_priv *priv = phydev->priv; + unsigned int ret_size; + u16 cmd, ret_sts; + int ret; + int i; + + /* IPC have a max of 8 register to transfer data, + * make sure we never exceed this. + */ + if (data_len > AEON_IPC_DATA_MAX) + return -EINVAL; + + for (i = 0; i < data_len / sizeof(u16); i++) + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i), + data[i]); + + cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) | + FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode); + + mutex_lock(&priv->ipc_lock); + + ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); + if (ret) { + phydev_err(phydev, "failed to send ipc msg for %x: %d\n", + opcode, ret); + goto out; + } + + if (!data) + goto out; + + if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR) { + ret = -EINVAL; + goto out; + } + + /* Prevent IPC from stack smashing the kernel. + * We can't trust IPC to return a good value and we always + * preallocate space for 16 Bytes. + */ + ret_size = FIELD_GET(AEON_IPC_STS_SIZE, ret_sts); + if (ret_size > AEON_IPC_DATA_MAX) { + ret = -EINVAL; + goto out; + } + + /* Read data from IPC data register for ret_size value from IPC */ + for (i = 0; i < DIV_ROUND_UP(ret_size, sizeof(u16)); i++) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i)); + if (ret < 0) + goto out; + + ret_data[i] = ret; + } + + ret = ret_size; + +out: + mutex_unlock(&priv->ipc_lock); + + return ret; +} + +static int aeon_ipc_noop(struct phy_device *phydev, + struct as21xxx_priv *priv, u16 *ret_sts) +{ + u16 cmd; + + cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) | + FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_NOOP); + + return aeon_ipc_send_cmd(phydev, priv, cmd, ret_sts); +} + +/* Logic to sync parity bit with IPC. + * We send 2 NOP cmd with same partity and we wait for IPC + * to handle the packet only for the second one. This way + * we make sure we are sync for every next cmd. + */ +static int aeon_ipc_sync_parity(struct phy_device *phydev, + struct as21xxx_priv *priv) +{ + u16 ret_sts; + int ret; + + mutex_lock(&priv->ipc_lock); + + /* Send NOP with no parity */ + aeon_ipc_noop(phydev, priv, NULL); + + /* Reset packet parity */ + priv->parity_status = false; + + /* Send second NOP with no parity */ + ret = aeon_ipc_noop(phydev, priv, &ret_sts); + + mutex_unlock(&priv->ipc_lock); + + /* We expect to return -EINVAL */ + if (ret != -EINVAL) + return ret; + + if ((ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_READY) { + phydev_err(phydev, "Invalid IPC status on sync parity: %x\n", + ret_sts); + return -EINVAL; + } + + return 0; +} + +static int aeon_ipc_get_fw_version(struct phy_device *phydev) +{ + u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS], data[1]; + char fw_version[AEON_IPC_DATA_MAX + 1]; + int ret; + + data[0] = IPC_INFO_VERSION; + + ret = aeon_ipc_send_msg(phydev, IPC_CMD_INFO, data, + sizeof(data), ret_data); + if (ret < 0) + return ret; + + /* Make sure FW version is NULL terminated */ + memcpy(fw_version, ret_data, ret); + fw_version[ret] = '\0'; + + phydev_info(phydev, "Firmware Version: %s\n", fw_version); + + return 0; +} + +static int aeon_dpc_ra_enable(struct phy_device *phydev) +{ + u16 data[2]; + + data[0] = IPC_CFG_PARAM_DIRECT; + data[1] = IPC_CFG_PARAM_DIRECT_DPC_RA; + + return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data, + sizeof(data), NULL); +} + +static int as21xxx_probe(struct phy_device *phydev) +{ + struct as21xxx_priv *priv; + int ret; + + priv = devm_kzalloc(&phydev->mdio.dev, + sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + ret = devm_mutex_init(&phydev->mdio.dev, + &priv->ipc_lock); + if (ret) + return ret; + + ret = aeon_ipc_sync_parity(phydev, priv); + if (ret) + return ret; + + ret = aeon_ipc_get_fw_version(phydev); + if (ret) + return ret; + + /* Enable PTP clk if not already Enabled */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PTP_CLK, + VEND1_PTP_CLK_EN); + if (ret) + return ret; + + return aeon_dpc_ra_enable(phydev); +} + +static int as21xxx_read_link(struct phy_device *phydev, int *bmcr) +{ + int status; + + /* Normal C22 BMCR report inconsistent data, use + * the mapped C22 in C45 to have more consistent link info. + */ + *bmcr = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR); + if (*bmcr < 0) + return *bmcr; + + /* Autoneg is being started, therefore disregard current + * link status and report link as down. + */ + if (*bmcr & BMCR_ANRESTART) { + phydev->link = 0; + return 0; + } + + status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); + if (status < 0) + return status; + + phydev->link = !!(status & MDIO_STAT1_LSTATUS); + + return 0; +} + +static int as21xxx_read_c22_lpa(struct phy_device *phydev) +{ + int lpagb; + + /* MII_STAT1000 are only filled in the mapped C22 + * in C45, use that to fill lpagb values and check. + */ + lpagb = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_STAT1000); + if (lpagb < 0) + return lpagb; + + if (lpagb & LPA_1000MSFAIL) { + int adv = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + 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); + + return 0; +} + +static int as21xxx_read_status(struct phy_device *phydev) +{ + int bmcr, old_link = phydev->link; + int ret; + + ret = as21xxx_read_link(phydev, &bmcr); + if (ret) + return ret; + + /* 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; + + if (phydev->autoneg == AUTONEG_ENABLE) { + ret = genphy_c45_read_lpa(phydev); + if (ret) + return ret; + + ret = as21xxx_read_c22_lpa(phydev); + if (ret) + return ret; + + phy_resolve_aneg_linkmode(phydev); + } else { + int speed; + + linkmode_zero(phydev->lp_advertising); + + speed = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_SPEED_STATUS); + if (speed < 0) + return speed; + + switch (speed & VEND1_SPEED_STATUS) { + case VEND1_SPEED_10000: + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_5000: + phydev->speed = SPEED_5000; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_2500: + phydev->speed = SPEED_2500; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_1000: + phydev->speed = SPEED_1000; + if (bmcr & BMCR_FULLDPLX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + break; + case VEND1_SPEED_100: + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_10: + phydev->speed = SPEED_10; + phydev->duplex = DUPLEX_FULL; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int as21xxx_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + u16 val = VEND1_LED_REG_A_EVENT_OFF; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + if (value) + val = VEND1_LED_REG_A_EVENT_ON; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, + VEND1_LED_REG(index), + VEND1_LED_REG_A_EVENT, + FIELD_PREP(VEND1_LED_REG_A_EVENT, val)); +} + +static int as21xxx_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int i; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) + if (rules == as21xxx_led_supported_pattern[i].pattern) + return 0; + + return -EOPNOTSUPP; +} + +static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int i, val; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index)); + if (val < 0) + return val; + + val = FIELD_GET(VEND1_LED_REG_A_EVENT, val); + for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) + if (val == as21xxx_led_supported_pattern[i].val) { + *rules = as21xxx_led_supported_pattern[i].pattern; + return 0; + } + + /* Should be impossible */ + return -EINVAL; +} + +static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + int i; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) + if (rules == as21xxx_led_supported_pattern[i].pattern) { + val = as21xxx_led_supported_pattern[i].val; + break; + } + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, + VEND1_LED_REG(index), + VEND1_LED_REG_A_EVENT, + FIELD_PREP(VEND1_LED_REG_A_EVENT, val)); +} + +static int as21xxx_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + bool led_active_low = false; + u16 mask, val = 0; + u32 mode; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + led_active_low = true; + break; + case PHY_LED_ACTIVE_HIGH: /* default mode */ + led_active_low = false; + break; + default: + return -EINVAL; + } + } + + mask = VEND1_GLB_CPU_CTRL_LED_POLARITY(index); + if (led_active_low) + val = VEND1_GLB_CPU_CTRL_LED_POLARITY(index); + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, + VEND1_GLB_REG_CPU_CTRL, + mask, val); +} + +static int as21xxx_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + struct as21xxx_priv *priv; + u16 ret_sts; + u32 phy_id; + int ret; + + /* Skip PHY that are not AS21xxx */ + if (!phy_id_compare_vendor(phydev->c45_ids.device_ids[MDIO_MMD_PCS], + PHY_VENDOR_AEONSEMI)) + return genphy_match_phy_device(phydev, phydrv); + + /* Read PHY ID to handle firmware loaded or HW reset */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID1); + if (ret < 0) + return ret; + phy_id = ret << 16; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID2); + if (ret < 0) + return ret; + phy_id |= ret; + + /* With PHY ID not the generic AS21xxx one assume + * the firmware just loaded + */ + if (phy_id != PHY_ID_AS21XXX) + return phy_id == phydrv->phy_id; + + /* Allocate temp priv and load the firmware */ + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->ipc_lock); + + ret = aeon_firmware_load(phydev); + if (ret) + goto out; + + /* Sync parity... */ + ret = aeon_ipc_sync_parity(phydev, priv); + if (ret) + goto out; + + /* ...and send a third NOOP cmd to wait for firmware finish loading */ + ret = aeon_ipc_noop(phydev, priv, &ret_sts); + if (ret) + goto out; + +out: + mutex_destroy(&priv->ipc_lock); + kfree(priv); + + /* Return can either be 0 or a negative error code. + * Returning 0 here means THIS is NOT a suitable PHY. + * + * For the specific case of the generic Aeonsemi PHY ID that + * needs the firmware the be loaded first to have a correct PHY ID, + * this is OK as a matching PHY ID will be found right after. + * This relies on the driver probe order where the first PHY driver + * probed is the generic one. + */ + return ret; +} + +static struct phy_driver as21xxx_drivers[] = { + { + /* PHY expose in C45 as 0x7500 0x9410 + * before firmware is loaded. + * This driver entry must be attempted first to load + * the firmware and thus update the ID registers. + */ + PHY_ID_MATCH_EXACT(PHY_ID_AS21XXX), + .name = "Aeonsemi AS21xxx", + .match_phy_device = as21xxx_match_phy_device, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21011JB1), + .name = "Aeonsemi AS21011JB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21011PB1), + .name = "Aeonsemi AS21011PB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21010PB1), + .name = "Aeonsemi AS21010PB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21010JB1), + .name = "Aeonsemi AS21010JB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21210PB1), + .name = "Aeonsemi AS21210PB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21510JB1), + .name = "Aeonsemi AS21510JB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21510PB1), + .name = "Aeonsemi AS21510PB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21511JB1), + .name = "Aeonsemi AS21511JB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21210JB1), + .name = "Aeonsemi AS21210JB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS21511PB1), + .name = "Aeonsemi AS21511PB1", + .probe = as21xxx_probe, + .match_phy_device = as21xxx_match_phy_device, + .read_status = as21xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, +}; +module_phy_driver(as21xxx_drivers); + +static struct mdio_device_id __maybe_unused as21xxx_tbl[] = { + { PHY_ID_MATCH_VENDOR(PHY_VENDOR_AEONSEMI) }, + { } +}; +MODULE_DEVICE_TABLE(mdio, as21xxx_tbl); + +MODULE_DESCRIPTION("Aeonsemi AS21xxx PHY driver"); +MODULE_AUTHOR("Christian Marangi "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c new file mode 100644 index 000000000..f20ddf649 --- /dev/null +++ b/drivers/net/phy/ax88796b.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Driver for Asix PHYs + * + * Author: Michael Schmitz + */ +#include +#include +#include +#include +#include +#include + +#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 "); +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_MATCH_MODEL(PHY_ID_ASIX_AX88796B), + .name = "Asix Electronics AX88796B", + /* PHY_BASIC_FEATURES */ + .soft_reset = asix_soft_reset, +} }; + +module_phy_driver(asix_driver); + +static const 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_MATCH_MODEL(PHY_ID_ASIX_AX88796B) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, asix_tbl); diff --git a/drivers/net/phy/ax88796b_rust.rs b/drivers/net/phy/ax88796b_rust.rs new file mode 100644 index 000000000..2d24628a4 --- /dev/null +++ b/drivers/net/phy/ax88796b_rust.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2023 FUJITA Tomonori + +//! Rust Asix PHYs driver +//! +//! C version of this driver: [`drivers/net/phy/ax88796b.c`](./ax88796b.c) +use kernel::{ + net::phy::{self, reg::C22, DeviceId, Driver}, + prelude::*, + uapi, +}; + +kernel::module_phy_driver! { + drivers: [PhyAX88772A, PhyAX88772C, PhyAX88796B], + device_table: [ + DeviceId::new_with_driver::(), + DeviceId::new_with_driver::(), + DeviceId::new_with_driver::() + ], + name: "rust_asix_phy", + authors: ["FUJITA Tomonori "], + description: "Rust Asix PHYs driver", + license: "GPL", +} + +const BMCR_SPEED100: u16 = uapi::BMCR_SPEED100 as u16; +const BMCR_FULLDPLX: u16 = uapi::BMCR_FULLDPLX as u16; + +// Performs 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. +fn asix_soft_reset(dev: &mut phy::Device) -> Result { + dev.write(C22::BMCR, 0)?; + dev.genphy_soft_reset() +} + +struct PhyAX88772A; + +#[vtable] +impl Driver for PhyAX88772A { + const FLAGS: u32 = phy::flags::IS_INTERNAL; + const NAME: &'static CStr = c"Asix Electronics AX88772A"; + const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_exact_mask(0x003b1861); + + // 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. + fn read_status(dev: &mut phy::Device) -> Result { + dev.genphy_update_link()?; + if !dev.is_link_up() { + return Ok(0); + } + // If MII_LPA is 0, phy_resolve_aneg_linkmode() will fail to resolve + // linkmode so use MII_BMCR as default values. + let ret = dev.read(C22::BMCR)?; + + if ret & BMCR_SPEED100 != 0 { + dev.set_speed(uapi::SPEED_100); + } else { + dev.set_speed(uapi::SPEED_10); + } + + let duplex = if ret & BMCR_FULLDPLX != 0 { + phy::DuplexMode::Full + } else { + phy::DuplexMode::Half + }; + dev.set_duplex(duplex); + + dev.genphy_read_lpa()?; + + if dev.is_autoneg_enabled() && dev.is_autoneg_completed() { + dev.resolve_aneg_linkmode(); + } + + Ok(0) + } + + fn suspend(dev: &mut phy::Device) -> Result { + dev.genphy_suspend() + } + + fn resume(dev: &mut phy::Device) -> Result { + dev.genphy_resume() + } + + fn soft_reset(dev: &mut phy::Device) -> Result { + asix_soft_reset(dev) + } + + fn link_change_notify(dev: &mut phy::Device) { + // Reset PHY, otherwise MII_LPA will provide outdated information. + // This issue is reproducible only with some link partner PHYs. + if dev.state() == phy::DeviceState::NoLink { + let _ = dev.init_hw(); + let _ = dev.start_aneg(); + } + } +} + +struct PhyAX88772C; + +#[vtable] +impl Driver for PhyAX88772C { + const FLAGS: u32 = phy::flags::IS_INTERNAL; + const NAME: &'static CStr = c"Asix Electronics AX88772C"; + const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_exact_mask(0x003b1881); + + fn suspend(dev: &mut phy::Device) -> Result { + dev.genphy_suspend() + } + + fn resume(dev: &mut phy::Device) -> Result { + dev.genphy_resume() + } + + fn soft_reset(dev: &mut phy::Device) -> Result { + asix_soft_reset(dev) + } +} + +struct PhyAX88796B; + +#[vtable] +impl Driver for PhyAX88796B { + const NAME: &'static CStr = c"Asix Electronics AX88796B"; + const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_model_mask(0x003b1841); + + fn soft_reset(dev: &mut phy::Device) -> Result { + asix_soft_reset(dev) + } +} diff --git a/drivers/net/phy/bcm-cygnus.c b/drivers/net/phy/bcm-cygnus.c new file mode 100644 index 000000000..15cbef820 --- /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 +#include +#include +#include + +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 const 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..b64beade8 --- /dev/null +++ b/drivers/net/phy/bcm-phy-lib.c @@ -0,0 +1,1194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2017 Broadcom + */ + +#include "bcm-phy-lib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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++) + ethtool_puts(&data, bcm_phy_hw_stats[i].string); +} +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_update_stats_shadow(struct phy_device *phydev, u64 *shadow) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) + bcm_phy_get_stat(phydev, shadow, i); +} +EXPORT_SYMBOL_GPL(bcm_phy_update_stats_shadow); + +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; +} + +static int bcm_setup_lre_forced(struct phy_device *phydev) +{ + u16 ctl = 0; + + phydev->pause = 0; + phydev->asym_pause = 0; + + if (phydev->speed == SPEED_100) + ctl |= LRECR_SPEED100; + + if (phydev->duplex != DUPLEX_FULL) + return -EOPNOTSUPP; + + return phy_modify(phydev, MII_BCM54XX_LRECR, LRECR_SPEED100, ctl); +} + +/** + * bcm_linkmode_adv_to_lre_adv_t - translate linkmode advertisement to LDS + * @advertising: the linkmode advertisement settings + * Return: LDS Auto-Negotiation Advertised Ability register value + * + * A small helper function that translates linkmode advertisement + * settings to phy LDS autonegotiation advertisements for the + * MII_BCM54XX_LREANAA register of Broadcom PHYs capable of LDS + */ +static u32 bcm_linkmode_adv_to_lre_adv_t(unsigned long *advertising) +{ + u32 result = 0; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + advertising)) + result |= LREANAA_10_1PAIR; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + advertising)) + result |= LREANAA_100_1PAIR; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertising)) + result |= LRELPA_PAUSE; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising)) + result |= LRELPA_PAUSE_ASYM; + + return result; +} + +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); + +int bcm_setup_lre_master_slave(struct phy_device *phydev) +{ + u16 ctl = 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + case MASTER_SLAVE_CFG_MASTER_FORCE: + ctl = LRECR_MASTER; + break; + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + 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; + } + + return phy_modify_changed(phydev, MII_BCM54XX_LRECR, LRECR_MASTER, ctl); +} +EXPORT_SYMBOL_GPL(bcm_setup_lre_master_slave); + +int bcm_config_lre_aneg(struct phy_device *phydev, bool changed) +{ + int err; + + if (genphy_c45_an_config_eee_aneg(phydev) > 0) + changed = true; + + err = bcm_setup_lre_master_slave(phydev); + if (err < 0) + return err; + else if (err) + changed = true; + + if (phydev->autoneg != AUTONEG_ENABLE) + return bcm_setup_lre_forced(phydev); + + err = bcm_config_lre_advert(phydev); + if (err < 0) + return err; + else if (err) + changed = true; + + return genphy_check_and_restart_aneg(phydev, changed); +} +EXPORT_SYMBOL_GPL(bcm_config_lre_aneg); + +/** + * bcm_config_lre_advert - sanitize and advertise Long-Distance Signaling + * auto-negotiation parameters + * @phydev: target phy_device struct + * Return: 0 if the PHY's advertisement hasn't changed, < 0 on error, + * > 0 if it has changed + * + * Writes MII_BCM54XX_LREANAA with the appropriate values. The values are to be + * sanitized before, to make sure we only advertise what is supported. + * The sanitization is done already in phy_ethtool_ksettings_set() + */ +int bcm_config_lre_advert(struct phy_device *phydev) +{ + u32 adv = bcm_linkmode_adv_to_lre_adv_t(phydev->advertising); + + /* Setup BroadR-Reach mode advertisement */ + return phy_modify_changed(phydev, MII_BCM54XX_LREANAA, + LRE_ADVERTISE_ALL | LREANAA_PAUSE | + LREANAA_PAUSE_ASYM, adv); +} +EXPORT_SYMBOL_GPL(bcm_config_lre_advert); + +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..bba94ce96 --- /dev/null +++ b/drivers/net/phy/bcm-phy-lib.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015 Broadcom Corporation + */ + +#ifndef _LINUX_BCM_PHY_LIB_H +#define _LINUX_BCM_PHY_LIB_H + +#include +#include +#include + +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_update_stats_shadow(struct phy_device *phydev, u64 *shadow); +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); + +int bcm_setup_lre_master_slave(struct phy_device *phydev); +int bcm_config_lre_aneg(struct phy_device *phydev, bool changed); +int bcm_config_lre_advert(struct phy_device *phydev); + +#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..65d609ed6 --- /dev/null +++ b/drivers/net/phy/bcm-phy-ptp.c @@ -0,0 +1,957 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Meta Platforms Inc. + * Copyright (C) 2022 Jonathan Lemon + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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_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, + .supported_perout_flags = PTP_PEROUT_DUTY_CYCLE, + .supported_extts_flags = PTP_STRICT_FLAGS | PTP_RISING_EDGE, +}; + +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_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct bcm_ptp_private *priv = mii2priv(mii_ts); + + cfg->rx_filter = priv->hwts_rx ? HWTSTAMP_FILTER_PTP_V2_EVENT + : HWTSTAMP_FILTER_NONE; + cfg->tx_type = priv->tx_type; + + return 0; +} + +static int bcm_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) +{ + struct bcm_ptp_private *priv = mii2priv(mii_ts); + u16 mode, ctrl; + + 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 0; +} + +static int bcm_ptp_ts_info(struct mii_timestamper *mii_ts, + struct kernel_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_set = bcm_ptp_hwtstamp_set; + priv->mii_ts.hwtstamp_get = bcm_ptp_hwtstamp_get; + 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; + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; + + priv->phydev = phydev; + bcm_ptp_init(priv); + + return priv; +} +EXPORT_SYMBOL_GPL(bcm_ptp_probe); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Broadcom PHY PTP driver"); diff --git a/drivers/net/phy/bcm54140.c b/drivers/net/phy/bcm54140.c new file mode 100644 index 000000000..a8edf45fd --- /dev/null +++ b/drivers/net/phy/bcm54140.c @@ -0,0 +1,895 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Broadcom BCM54140 Quad SGMII/QSGMII Copper/Fiber Gigabit PHY + * + * Copyright (c) 2020 Michael Walle + */ + +#include +#include +#include +#include +#include + +#include "phylib.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 + +enum bcm54140_global_phy { + BCM54140_BASE_ADDR = 0, +}; + +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, BCM54140_BASE_ADDR, + MII_BCM54XX_RDB_ADDR, rdb); + if (ret < 0) + goto out; + + ret = __phy_package_read(phydev, BCM54140_BASE_ADDR, + 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, BCM54140_BASE_ADDR, + MII_BCM54XX_RDB_ADDR, rdb); + if (ret < 0) + goto out; + + ret = __phy_package_write(phydev, BCM54140_BASE_ADDR, + 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 const 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..b46a736a3 --- /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 +#include + +#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 "); +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 const 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..71a163f62 --- /dev/null +++ b/drivers/net/phy/bcm7xxx.c @@ -0,0 +1,976 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Broadcom BCM7xxx internal transceivers support. + * + * Copyright (C) 2014-2017 Broadcom + */ + +#include +#include +#include +#include "bcm-phy-lib.h" +#include +#include +#include +#include + +/* 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_suspend(struct phy_device *phydev) +{ + struct bcm7xxx_phy_priv *priv = phydev->priv; + + mutex_lock(&phydev->lock); + bcm_phy_update_stats_shadow(phydev, priv->stats); + mutex_unlock(&phydev->lock); + + return genphy_suspend(phydev); +} + +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, \ + .suspend = bcm7xxx_28nm_suspend, \ + .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, \ + .suspend = bcm7xxx_28nm_suspend, \ + .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, \ + .suspend = bcm7xxx_28nm_suspend, \ +} + +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 const 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..2ae70dcf8 --- /dev/null +++ b/drivers/net/phy/bcm84881.c @@ -0,0 +1,474 @@ +// 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 +#include +#include + +enum { + MDIO_AN_C22 = 0xffe0, +}; + +/* BCM8489x LED controller (BCM84891L datasheet 2.4.1.58). Each pin has + * CTL bits in 0xA83B (stride 3: 2-bit CTL + 1-bit OE_N) plus MASK_LOW/ + * MASK_EXT source selects. LED4 is firmware-controlled; always RMW. + */ +#define BCM8489X_LED_CTL 0xa83b +#define BCM8489X_LED_CTL_ON(i) (0x2 << ((i) * 3)) +#define BCM8489X_LED_CTL_MASK(i) (0x3 << ((i) * 3)) + +#define BCM8489X_LED_SRC_RX BIT(1) +#define BCM8489X_LED_SRC_TX BIT(2) +#define BCM8489X_LED_SRC_1000 BIT(3) /* high only at 1000 */ +#define BCM8489X_LED_SRC_100_1000 BIT(4) /* high at 100 and 1000 */ +#define BCM8489X_LED_SRC_FORCE BIT(5) /* always-1 source */ +#define BCM8489X_LED_SRC_10G BIT(7) +#define BCM8489X_LED_SRCX_2500 BIT(2) +#define BCM8489X_LED_SRCX_5000 BIT(3) + +#define BCM8489X_MAX_LEDS 2 + +static const struct { + u16 mask_low; + u16 mask_ext; +} bcm8489x_led_regs[BCM8489X_MAX_LEDS] = { + { 0xa82c, 0xa8ef }, /* LED1 */ + { 0xa82f, 0xa8f0 }, /* LED2 */ +}; + +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 void bcm84881_fill_possible_interfaces(struct phy_device *phydev) +{ + unsigned long *possible = phydev->possible_interfaces; + + __set_bit(PHY_INTERFACE_MODE_SGMII, possible); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, possible); + __set_bit(PHY_INTERFACE_MODE_10GBASER, possible); +} + +static int bcm84881_config_init(struct phy_device *phydev) +{ + bcm84881_fill_possible_interfaces(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 bcm8489x_config_init(struct phy_device *phydev) +{ + __set_bit(PHY_INTERFACE_MODE_USXGMII, phydev->possible_interfaces); + + if (phydev->interface != PHY_INTERFACE_MODE_USXGMII) + return -ENODEV; + + /* MDIO_CTRL1_LPOWER is set at boot on the tested platform. Does not + * recur on ifdown/ifup, cable events, or link-partner advertisement + * changes; clear it once. + */ + return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_LPOWER); +} + +static int bcm8489x_led_write(struct phy_device *phydev, u8 index, + u16 low, u16 ext) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_low, low); + if (ret) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_ext, ext); + if (ret) + return ret; + return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, BCM8489X_LED_CTL, + BCM8489X_LED_CTL_MASK(index), + (low | ext) ? BCM8489X_LED_CTL_ON(index) : 0); +} + +static int bcm8489x_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + return bcm8489x_led_write(phydev, index, + value ? BCM8489X_LED_SRC_FORCE : 0, 0); +} + +static const unsigned long bcm8489x_supported_triggers = + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + +static int bcm8489x_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + if (rules & ~bcm8489x_supported_triggers) + return -EOPNOTSUPP; + + /* Source bit 4 lights at both 100 and 1000; "100 only" isn't + * representable in hardware. Accept LINK_100 only alongside + * LINK_1000 or LINK so the offload is precise. + */ + if ((rules & BIT(TRIGGER_NETDEV_LINK_100)) && + !(rules & (BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK)))) + return -EOPNOTSUPP; + + return 0; +} + +static int bcm8489x_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 low = 0, ext = 0; + + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) + low |= BCM8489X_LED_SRC_100_1000; + if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) + low |= BCM8489X_LED_SRC_1000; + if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) + ext |= BCM8489X_LED_SRCX_2500; + if (rules & (BIT(TRIGGER_NETDEV_LINK_5000) | BIT(TRIGGER_NETDEV_LINK))) + ext |= BCM8489X_LED_SRCX_5000; + if (rules & (BIT(TRIGGER_NETDEV_LINK_10000) | BIT(TRIGGER_NETDEV_LINK))) + low |= BCM8489X_LED_SRC_10G; + if (rules & BIT(TRIGGER_NETDEV_RX)) + low |= BCM8489X_LED_SRC_RX; + if (rules & BIT(TRIGGER_NETDEV_TX)) + low |= BCM8489X_LED_SRC_TX; + + return bcm8489x_led_write(phydev, index, low, ext); +} + +static int bcm8489x_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int low, ext; + + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + low = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_low); + if (low < 0) + return low; + ext = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_ext); + if (ext < 0) + return ext; + + *rules = 0; + if (low & BCM8489X_LED_SRC_100_1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + if (low & BCM8489X_LED_SRC_1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + if (ext & BCM8489X_LED_SRCX_2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + if (ext & BCM8489X_LED_SRCX_5000) + *rules |= BIT(TRIGGER_NETDEV_LINK_5000); + if (low & BCM8489X_LED_SRC_10G) + *rules |= BIT(TRIGGER_NETDEV_LINK_10000); + if (low & BCM8489X_LED_SRC_RX) + *rules |= BIT(TRIGGER_NETDEV_RX); + if (low & BCM8489X_LED_SRC_TX) + *rules |= BIT(TRIGGER_NETDEV_TX); + + 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 bmsr; + + 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 bmsr; + + 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; + } + + /* BCM84891/92 on USXGMII: the host interface mode doesn't change + * with copper speed (USXGMII symbol replication; the MAC receives + * the negotiated copper speed, not 10G, so no rate adaptation). + * Skip 0x4011; phy_resolve_aneg_linkmode() above already set the + * speed. Only bcm8489x_config_init() allows USXGMII. + */ + if (phydev->interface == PHY_INTERFACE_MODE_USXGMII) + return genphy_c45_read_mdix(phydev); + + /* 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); +} + +/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII + * or 802.3z control word, so inband will not work. + */ +static unsigned int bcm84881_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + return LINK_INBAND_DISABLE; +} + +static struct phy_driver bcm84881_drivers[] = { + { + .phy_id = 0xae025150, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM84881", + .inband_caps = bcm84881_inband_caps, + .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, + }, { + PHY_ID_MATCH_MODEL(0x35905080), + .name = "Broadcom BCM84891", + .inband_caps = bcm84881_inband_caps, + .config_init = bcm8489x_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, + .led_brightness_set = bcm8489x_led_brightness_set, + .led_hw_is_supported = bcm8489x_led_hw_is_supported, + .led_hw_control_set = bcm8489x_led_hw_control_set, + .led_hw_control_get = bcm8489x_led_hw_control_get, + }, { + PHY_ID_MATCH_MODEL(0x359050a0), + .name = "Broadcom BCM84892", + .inband_caps = bcm84881_inband_caps, + .config_init = bcm8489x_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, + .led_brightness_set = bcm8489x_led_brightness_set, + .led_hw_is_supported = bcm8489x_led_hw_is_supported, + .led_hw_control_set = bcm8489x_led_hw_control_set, + .led_hw_control_get = bcm8489x_led_hw_control_get, + }, +}; + +module_phy_driver(bcm84881_drivers); + +/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */ +static const struct mdio_device_id __maybe_unused bcm84881_tbl[] = { + { 0xae025150, 0xfffffff0 }, + { PHY_ID_MATCH_MODEL(0x35905080) }, + { PHY_ID_MATCH_MODEL(0x359050a0) }, + { }, +}; +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("Broadcom BCM84881/BCM84891/BCM84892 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..299f9a8f3 --- /dev/null +++ b/drivers/net/phy/bcm87xx.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011 - 2012 Cavium, Inc. + */ + +#include +#include +#include + +#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 = ,...; + * + * There may be one or more sets of : + * + * 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 bcm87xx_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return phydev->c45_ids.device_ids[4] == phydrv->phy_id; +} + +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 = bcm87xx_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 = bcm87xx_match_phy_device, +} }; + +module_phy_driver(bcm87xx_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Broadcom BCM87xx PHY driver"); diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c new file mode 100644 index 000000000..d1a4edb34 --- /dev/null +++ b/drivers/net/phy/broadcom.c @@ -0,0 +1,1781 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/net/phy/broadcom.c + * + * Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet + * transceivers. + * + * Broadcom BCM54810, BCM54811 BroadR-Reach transceivers. + * + * Copyright (c) 2006 Maciej W. Rozycki + * + * Inspired by code written by Amy Fong. + */ + +#include "bcm-phy-lib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + bool brr_mode; +}; + +/* Link modes for BCM58411 PHY */ +static const int bcm54811_linkmodes[] = { + ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + ETHTOOL_LINK_MODE_100baseT_Half_BIT, + ETHTOOL_LINK_MODE_10baseT_Full_BIT, + ETHTOOL_LINK_MODE_10baseT_Half_BIT +}; + +/* Long-Distance Signaling (BroadR-Reach mode aneg) relevant linkmode bits */ +static const int lds_br_bits[] = { + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + ETHTOOL_LINK_MODE_100baseT1_Full_BIT +}; + +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 (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) || + phy_id_compare_model(phydev->drv->phy_id, 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 (phy_id_compare_model(phydev->drv->phy_id, 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 (!(phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM57780) || + phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) || + phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610M) || + phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54210E) || + phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54810) || + phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54811))) + return; + + val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3); + if (val < 0) + return; + + orig = val; + + if ((phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) || + phy_id_compare_model(phydev->drv->phy_id, 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 (!phy_id_compare_model(phydev->drv->phy_id, + 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 (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54210E) || + phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54810) || + phy_id_compare_model(phydev->drv->phy_id, 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 bcm5481x_set_brrmode(struct phy_device *phydev, bool on) +{ + int reg; + int err; + u16 val; + + reg = bcm_phy_read_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL); + + if (reg < 0) + return reg; + + if (on) + reg |= BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN; + else + reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN; + + err = bcm_phy_write_exp(phydev, + BCM54810_EXP_BROADREACH_LRE_MISC_CTL, reg); + if (err) + return err; + + /* Ensure LRE or IEEE register set is accessed according to the brr + * on/off, thus set the override + */ + val = BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL_EN; + if (!on) + val |= BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL_OVERRIDE_VAL; + + return bcm_phy_write_exp(phydev, + BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL, val); +} + +static int bcm54811_config_init(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int err, reg, exp_sync_ethernet, aux_rgmii_en; + + /* 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); + if (reg < 0) + return reg; + err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0, + BCM54612E_LED4_CLK125OUT_EN | reg); + if (err < 0) + return err; + } + + /* With BCM54811, BroadR-Reach implies no autoneg */ + if (priv->brr_mode) + phydev->autoneg = 0; + + /* Enable MII Lite (No TXER, RXER, CRS, COL) if configured */ + if (phydev->interface == PHY_INTERFACE_MODE_MIILITE) + exp_sync_ethernet = BCM_EXP_SYNC_ETHERNET_MII_LITE; + else + exp_sync_ethernet = 0; + + err = bcm_phy_modify_exp(phydev, BCM_EXP_SYNC_ETHERNET, + BCM_EXP_SYNC_ETHERNET_MII_LITE, + exp_sync_ethernet); + if (err < 0) + return err; + + /* Enable RGMII if configured */ + if (phy_interface_is_rgmii(phydev)) + aux_rgmii_en = MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_EN | + MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN; + else + aux_rgmii_en = 0; + + /* Also writing Reserved bits 6:5 because the documentation requires + * them to be written to 0b11 + */ + err = bcm54xx_auxctl_write(phydev, + MII_BCM54XX_AUXCTL_SHDWSEL_MISC, + MII_BCM54XX_AUXCTL_MISC_WREN | + aux_rgmii_en | + MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RSVD); + if (err < 0) + return err; + + return bcm5481x_set_brrmode(phydev, priv->brr_mode); +} + +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 ((phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) || + phy_id_compare_model(phydev->drv->phy_id, 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 (phydev->drv->phy_id & PHY_ID_MATCH_MODEL_MASK) { + 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; + case PHY_ID_BCM54811: + err = bcm54811_config_init(phydev); + 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) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int ret = 0; + + mutex_lock(&phydev->lock); + bcm_phy_update_stats_shadow(phydev, priv->stats); + mutex_unlock(&phydev->lock); + + 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; +} + + +/** + * bcm5481x_read_abilities - read PHY abilities from LRESR or Clause 22 + * (BMSR) registers, based on whether the PHY is in BroadR-Reach or IEEE mode + * @phydev: target phy_device struct + * + * Description: Reads the PHY's abilities and populates phydev->supported + * accordingly. The register to read the abilities from is determined by + * the brr mode setting of the PHY as read from the device tree. + * Note that the LRE and IEEE sets of abilities are disjunct, in other words, + * not only the link modes differ, but also the auto-negotiation and + * master-slave setup is controlled differently. + * + * Returns: 0 on success, < 0 on failure + */ +static int bcm5481x_read_abilities(struct phy_device *phydev) +{ + struct device_node *np = phydev->mdio.dev.of_node; + struct bcm54xx_phy_priv *priv = phydev->priv; + int i, val, err, aneg; + + for (i = 0; i < ARRAY_SIZE(bcm54811_linkmodes); i++) + linkmode_clear_bit(bcm54811_linkmodes[i], phydev->supported); + + priv->brr_mode = of_property_read_bool(np, "brr-mode"); + + /* Set BroadR-Reach mode as configured in the DT. */ + err = bcm5481x_set_brrmode(phydev, priv->brr_mode); + if (err) + return err; + + if (priv->brr_mode) { + linkmode_set_bit_array(phy_basic_ports_array, + ARRAY_SIZE(phy_basic_ports_array), + phydev->supported); + + val = phy_read(phydev, MII_BCM54XX_LRESR); + if (val < 0) + return val; + + /* BCM54811 is not capable of LDS but the corresponding bit + * in LRESR is set to 1 and marked "Ignore" in the datasheet. + * So we must read the bcm54811 as unable to auto-negotiate + * in BroadR-Reach mode. + */ + if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54811)) + aneg = 0; + else + aneg = val & LRESR_LDSABILITY; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + phydev->supported, + aneg); + linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + phydev->supported, + val & LRESR_100_1PAIR); + linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + phydev->supported, + val & LRESR_10_1PAIR); + return 0; + } + + return genphy_read_abilities(phydev); +} + +static int bcm5481x_config_delay_swap(struct phy_device *phydev) +{ + struct device_node *np = phydev->mdio.dev.of_node; + + /* Set up the delay. */ + bcm54xx_config_clock_delay(phydev); + + if (of_property_read_bool(np, "enet-phy-lane-swap")) { + /* Lane Swap - Undocumented register...magic! */ + int ret = bcm_phy_write_exp(phydev, + MII_BCM54XX_EXP_SEL_ER + 0x9, + 0x11B); + if (ret < 0) + return ret; + } + + return 0; +} + +static int bcm5481_config_aneg(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int ret; + + /* Aneg firstly. */ + if (priv->brr_mode) + ret = bcm_config_lre_aneg(phydev, false); + else + ret = genphy_config_aneg(phydev); + + if (ret) + return ret; + + /* Then we can set up the delay and swap. */ + return bcm5481x_config_delay_swap(phydev); +} + +static int bcm54811_config_aneg(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int ret; + + /* Aneg firstly. */ + if (priv->brr_mode) { + /* BCM54811 is only capable of autonegotiation in IEEE mode. + * In BroadR-Reach mode, disable the Long Distance Signaling, + * the BRR mode autoneg as supported in other Broadcom PHYs. + * This bit is marked as "Reserved" and "Default 1, must be + * written to 0 after every device reset" in the datasheet. + */ + ret = phy_modify(phydev, MII_BCM54XX_LRECR, LRECR_LDSEN, 0); + if (ret < 0) + return ret; + ret = bcm_config_lre_aneg(phydev, false); + } else { + ret = genphy_config_aneg(phydev); + } + + if (ret) + return ret; + + /* Then we can set up the delay and swap. */ + return bcm5481x_config_delay_swap(phydev); +} + +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; + bool changed; + int err; + + if (priv->mode_1000bx_en) + err = genphy_c37_read_status(phydev, &changed); + 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; + + /* Read to clear status bits */ + reg = phy_read(phydev, MII_BRCM_FET_INTREG); + if (reg < 0) + return reg; + + /* Unmask events we are interested in and mask interrupts globally. */ + if (phydev->drv->phy_id == PHY_ID_BCM5221) + reg = MII_BRCM_FET_IR_ENABLE | + MII_BRCM_FET_IR_MASK; + else + 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; + + phy_lock_mdio_bus(phydev); + + err = __phy_write(phydev, MII_BRCM_FET_BRCMTEST, reg); + if (err < 0) { + phy_unlock_mdio_bus(phydev); + return err; + } + + if (phydev->drv->phy_id != PHY_ID_BCM5221) { + /* Set the LED mode */ + reg = __phy_read(phydev, MII_BRCM_FET_SHDW_AUXMODE4); + if (reg < 0) { + err = reg; + goto done; + } + + err = __phy_modify(phydev, MII_BRCM_FET_SHDW_AUXMODE4, + MII_BRCM_FET_SHDW_AM4_LED_MASK, + MII_BRCM_FET_SHDW_AM4_LED_MODE1); + 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; + + phy_unlock_mdio_bus(phydev); + + 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; + + phy_lock_mdio_bus(phydev); + + err = __phy_write(phydev, MII_BRCM_FET_BRCMTEST, reg); + if (err < 0) { + phy_unlock_mdio_bus(phydev); + return err; + } + + if (phydev->drv->phy_id == PHY_ID_BCM5221) + /* Force Low Power Mode with clock enabled */ + reg = BCM5221_SHDW_AM4_EN_CLK_LPM | BCM5221_SHDW_AM4_FORCE_LPM; + else + /* Set standby mode */ + reg = MII_BRCM_FET_SHDW_AM4_STANDBY; + + err = __phy_set_bits(phydev, MII_BRCM_FET_SHDW_AUXMODE4, reg); + + /* Disable shadow register access */ + err2 = __phy_write(phydev, MII_BRCM_FET_BRCMTEST, brcmtest); + if (!err) + err = err2; + + phy_unlock_mdio_bus(phydev); + + return err; +} + +static int bcm5221_config_aneg(struct phy_device *phydev) +{ + int ret, val; + + ret = genphy_config_aneg(phydev); + if (ret) + return ret; + + switch (phydev->mdix_ctrl) { + case ETH_TP_MDI: + val = BCM5221_AEGSR_MDIX_DIS; + break; + case ETH_TP_MDI_X: + val = BCM5221_AEGSR_MDIX_DIS | BCM5221_AEGSR_MDIX_MAN_SWAP; + break; + case ETH_TP_MDI_AUTO: + val = 0; + break; + default: + return 0; + } + + return phy_modify(phydev, BCM5221_AEGSR, BCM5221_AEGSR_MDIX_MAN_SWAP | + BCM5221_AEGSR_MDIX_DIS, + val); +} + +static int bcm5221_read_status(struct phy_device *phydev) +{ + int ret; + + /* Read MDIX status */ + ret = phy_read(phydev, BCM5221_AEGSR); + if (ret < 0) + return ret; + + if (ret & BCM5221_AEGSR_MDIX_DIS) { + if (ret & BCM5221_AEGSR_MDIX_MAN_SWAP) + phydev->mdix_ctrl = ETH_TP_MDI_X; + else + phydev->mdix_ctrl = ETH_TP_MDI; + } else { + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + } + + if (ret & BCM5221_AEGSR_MDIX_STATUS) + phydev->mdix = ETH_TP_MDI_X; + else + phydev->mdix = ETH_TP_MDI; + + return genphy_read_status(phydev); +} + +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 int lre_read_master_slave(struct phy_device *phydev) +{ + int cfg = MASTER_SLAVE_CFG_UNKNOWN, state; + int val; + + /* In BroadR-Reach mode we are always capable of master-slave + * and there is no preferred master or slave configuration + */ + phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + + val = phy_read(phydev, MII_BCM54XX_LRECR); + if (val < 0) + return val; + + if ((val & LRECR_LDSEN) == 0) { + if (val & LRECR_MASTER) + cfg = MASTER_SLAVE_CFG_MASTER_FORCE; + else + cfg = MASTER_SLAVE_CFG_SLAVE_FORCE; + } + + val = phy_read(phydev, MII_BCM54XX_LRELDSE); + if (val < 0) + return val; + + if (val & LDSE_MASTER) + state = MASTER_SLAVE_STATE_MASTER; + else + state = MASTER_SLAVE_STATE_SLAVE; + + phydev->master_slave_get = cfg; + phydev->master_slave_state = state; + + return 0; +} + +/* Read LDS Link Partner Ability in BroadR-Reach mode */ +static int lre_read_lpa(struct phy_device *phydev) +{ + int i, lrelpa; + + if (phydev->autoneg != AUTONEG_ENABLE) { + if (!phydev->autoneg_complete) { + /* aneg not yet done, reset all relevant bits */ + for (i = 0; i < ARRAY_SIZE(lds_br_bits); i++) + linkmode_clear_bit(lds_br_bits[i], + phydev->lp_advertising); + + return 0; + } + + /* Long-Distance Signaling Link Partner Ability */ + lrelpa = phy_read(phydev, MII_BCM54XX_LRELPA); + if (lrelpa < 0) + return lrelpa; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_PAUSE_ASYM); + linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_PAUSE); + linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_100_1PAIR); + linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_10_1PAIR); + } else { + linkmode_zero(phydev->lp_advertising); + } + + return 0; +} + +static int lre_read_status_fixed(struct phy_device *phydev) +{ + int lrecr = phy_read(phydev, MII_BCM54XX_LRECR); + + if (lrecr < 0) + return lrecr; + + phydev->duplex = DUPLEX_FULL; + + if (lrecr & LRECR_SPEED100) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + + return 0; +} + +/** + * lre_update_link - update link status in @phydev + * @phydev: target phy_device struct + * Return: 0 on success, < 0 on error + * + * 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. + * This is a genphy_update_link modified to work on LRE registers + * of BroadR-Reach PHY + */ +static int lre_update_link(struct phy_device *phydev) +{ + int status = 0, lrecr; + + lrecr = phy_read(phydev, MII_BCM54XX_LRECR); + if (lrecr < 0) + return lrecr; + + /* Autoneg is being started, therefore disregard BMSR value and + * report link as down. + */ + if (lrecr & 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_BCM54XX_LRESR); + if (status < 0) + return status; + else if (status & LRESR_LSTATUS) + goto done; + } + + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BCM54XX_LRESR); + if (status < 0) + return status; +done: + phydev->link = status & LRESR_LSTATUS ? 1 : 0; + phydev->autoneg_complete = status & LRESR_LDSCOMPLETE ? 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; +} + +/* Get the status in BroadRReach mode just like genphy_read_status does +* in normal mode +*/ +static int bcm54811_lre_read_status(struct phy_device *phydev) +{ + int err, old_link = phydev->link; + + /* Update the link, but return if there was an error */ + err = lre_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 = lre_read_master_slave(phydev); + if (err < 0) + return err; + + /* Read LDS Link Partner Ability */ + err = lre_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 = lre_read_status_fixed(phydev); + + return err; +} + +static int bcm54811_read_status(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + + if (priv->brr_mode) + return bcm54811_lre_read_status(phydev); + + return genphy_read_status(phydev); +} + +static int bcm54xx_disable_autonomous_eee(struct phy_device *phydev) +{ + return bcm_phy_modify_exp(phydev, BCM54XX_TOP_MISC_MII_BUF_CNTL0, + BCM54XX_MII_BUF_CNTL0_AUTOGREEEN_EN, 0); +} + +static struct phy_driver broadcom_drivers[] = { +{ + PHY_ID_MATCH_MODEL(PHY_ID_BCM5411), + .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_MATCH_MODEL(PHY_ID_BCM5421), + .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_MATCH_MODEL(PHY_ID_BCM54210E), + .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, + .disable_autonomous_eee = bcm54xx_disable_autonomous_eee, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_BCM5461), + .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_MATCH_MODEL(PHY_ID_BCM54612E), + .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, + .suspend = bcm54xx_suspend, + .resume = bcm54xx_resume, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_BCM54616S), + .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_MATCH_MODEL(PHY_ID_BCM5464), + .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_MATCH_MODEL(PHY_ID_BCM5481), + .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_MATCH_MODEL(PHY_ID_BCM54810), + .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_MATCH_MODEL(PHY_ID_BCM54811), + .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 = bcm54xx_config_init, + .config_aneg = bcm54811_config_aneg, + .config_intr = bcm_phy_config_intr, + .handle_interrupt = bcm_phy_handle_interrupt, + .read_status = bcm54811_read_status, + .get_features = bcm5481x_read_abilities, + .suspend = bcm54xx_suspend, + .resume = bcm54xx_resume, + .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_BCM5482), + .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_MATCH_MODEL(PHY_ID_BCM50610), + .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_MATCH_MODEL(PHY_ID_BCM50610M), + .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_MATCH_MODEL(PHY_ID_BCM57780), + .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_MATCH_MODEL(PHY_ID_BCMAC131), + .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_MATCH_MODEL(PHY_ID_BCM5241), + .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_MATCH_MODEL(PHY_ID_BCM5221), + .name = "Broadcom BCM5221", + /* 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, + .config_aneg = bcm5221_config_aneg, + .read_status = bcm5221_read_status, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_BCM5395), + .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_MATCH_MODEL(PHY_ID_BCM53125), + .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_MATCH_MODEL(PHY_ID_BCM53128), + .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_MATCH_MODEL(PHY_ID_BCM89610), + .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 const struct mdio_device_id __maybe_unused broadcom_tbl[] = { + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5411) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5421) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM54210E) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5461) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM54612E) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM54616S) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5464) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5481) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM54810) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM54811) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5482) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM50610) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM50610M) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM57780) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCMAC131) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5221) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5241) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM5395) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM53125) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM53128) }, + { PHY_ID_MATCH_MODEL(PHY_ID_BCM89610) }, + { } +}; + +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..d87cf8b94 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* 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 const 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..3b65f37f1 --- /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 +#include + +#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 const 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/dap8211r.c b/drivers/net/phy/dap8211r.c new file mode 100644 index 000000000..0bf770f8e --- /dev/null +++ b/drivers/net/phy/dap8211r.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. + * + * Specifications: + * - IEEE 802.3 10BASE-Te, 100BASE-TX, 1000BASE-T + * - IEEE 802.3az-2010 Energy Efficient Ethernet + * - IEEE 1588 SyncE support + * - RGMII + * + * Author: Artem Shimko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAP8211R_PHY_ID 0x0008011B +#define DAP8211R_PHY_ID_MASK 0xFFFFFFFF + +#define DAP8211R_EXT_ADD 0x1E +#define DAP8211R_EXT_DATA 0x1F + +#define DAP8211R_PHY_CON 0xA001 +#define DAP8211R_PHY_SW_RST BIT(15) + +#define DAP8211R_RGMII_CON 0xA003 +#define DAP8211R_RGMII_TX_DEL_MASK GENMASK(3, 0) +#define DAP8211R_RGMII_RX_DEL_MASK GENMASK(13, 10) + +#define DAP8211R_RGMII_CONFIG_MASK (DAP8211R_RGMII_RX_DEL_MASK | DAP8211R_RGMII_TX_DEL_MASK) + +/* + * Hardware reset RX RGMII default delay from the datasheet: 0 * 150ps == 0.00ns + * Used when rx-internal-delay-ps is not specified in DT for RGMII mode and + * in RGMII_TXID to set rx delay. + */ +#define DAP8211R_INITIAL_RX_DEL_VAL 0 + +/* + * Hardware reset TX RGMII default delay from the datasheet: 1 * 150ps == 0.15ns + * Used when tx-internal-delay-ps is not specified in DT for RGMII mode and + * in RGMII_RXID to set tx delay. + */ +#define DAP8211R_INITIAL_TX_DEL_VAL 1 + +/* + * Default RGMII delay: 13 * 150 == 1.95ns + * Used when rx-internal-delay-ps or tx-internal-delay-ps are not specified in DT + * for RGMII ID modes to set tx or rx delay. + */ +#define DAP8211R_DEFAULT_DEL_SEL 0xD + +static const int dap8211r_internal_delay[] = {0, 150, 300, 450, 600, 750, 900, + 1050, 1200, 1350, 1500, 1650, 1800, + 1950, 2100, 2250}; + +#define DAP8211R_DELAY_SIZE ARRAY_SIZE(dap8211r_internal_delay) + +/** + * dap8211r_read_ext() - Read extended register + * @phydev: PHY device structure + * @reg: Extended register address + * + * Reads a PHY extended register using the indirect access method. + * The caller must hold the MDIO bus lock. + * + * Return: Register value on success, or negative error code + */ +static int dap8211r_read_ext(struct phy_device *phydev, u16 reg) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); + if (ret < 0) + goto out; + + ret = __phy_read(phydev, DAP8211R_EXT_DATA); +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * dap8211r_modify_ext() - Modify extended register bits + * @phydev: PHY device structure + * @reg: Extended register address + * @mask: Bit mask of bits to clear + * @set: Bit mask of bits to set + * + * Modifies a PHY extended register using the indirect access method. + * New value = (old value & ~mask) | set. + * The caller must hold the MDIO bus lock. + * + * Return: 0 on success, or negative error code + */ +static int dap8211r_modify_ext(struct phy_device *phydev, u16 reg, u16 mask, u16 set) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); + if (ret < 0) + goto out; + + ret = __phy_modify(phydev, DAP8211R_EXT_DATA, mask, set); +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * dap8211r_config_init() - Initialize PHY + * @phydev: PHY device structure + * + * Configures the PHY during initialization: + * - RGMII delays based on interface mode + * - Software reset to apply settings (low active, self clear) + * + * Return: 0 on success, or negative error code + */ +static int dap8211r_config_init(struct phy_device *phydev) +{ + u16 set = 0; + int ret, val; + s32 rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; + s32 tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; + + if (!phy_interface_is_rgmii(phydev)) + return 0; + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID) + rx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, + DAP8211R_DELAY_SIZE, true); + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID) + tx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, + DAP8211R_DELAY_SIZE, false); + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; + + if (tx_internal_delay < 0) + tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + if (rx_internal_delay < 0) + rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + fallthrough; + case PHY_INTERFACE_MODE_RGMII_TXID: + if (tx_internal_delay < 0) + tx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; + break; + default: + phydev_err(phydev, "Unsupported interface: %d\n", + phydev->interface); + return -EINVAL; + } + + set |= FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK, rx_internal_delay); + set |= FIELD_PREP(DAP8211R_RGMII_TX_DEL_MASK, tx_internal_delay); + + ret = dap8211r_modify_ext(phydev, DAP8211R_PHY_CON, DAP8211R_PHY_SW_RST, 0); + if (ret) + return ret; + + /* Wait for reset self-clear (from low active to high) */ + ret = read_poll_timeout(dap8211r_read_ext, val, + (val & DAP8211R_PHY_SW_RST), + 20, 200, false, phydev, DAP8211R_PHY_CON); + if (ret) + return ret; + if (val < 0) + return val; + + ret = dap8211r_modify_ext(phydev, DAP8211R_RGMII_CON, DAP8211R_RGMII_CONFIG_MASK, set); + if (ret) + return ret; + + return 0; +} + +static struct phy_driver dap8211r_driver[] = { + { + PHY_ID_MATCH_EXACT(DAP8211R_PHY_ID), + .name = "DAP8211R Gigabit Ethernet", + .soft_reset = genphy_soft_reset, + .config_init = dap8211r_config_init, + .read_status = genphy_read_status, + .set_loopback = genphy_loopback, + .config_aneg = genphy_config_aneg, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; +module_phy_driver(dap8211r_driver); + +MODULE_DESCRIPTION("DAP8211R Gigabit Ethernet PHY driver"); +MODULE_AUTHOR("Artem Shimko "); +MODULE_LICENSE("GPL"); + +static const struct mdio_device_id __maybe_unused dap8211r_tb[] = { + { DAP8211R_PHY_ID, DAP8211R_PHY_ID_MASK }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(mdio, dap8211r_tb); + diff --git a/drivers/net/phy/davicom.c b/drivers/net/phy/davicom.c new file mode 100644 index 000000000..fa3692508 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 const 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..6867e7c6f --- /dev/null +++ b/drivers/net/phy/dp83640.c @@ -0,0 +1,1482 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dp83640_reg.h" +#include "phylib.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 { + /* 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; + /* protected by the PTP core pin configuration lock */ + struct ptp_pin_desc pin_config[DP83640_N_PINS]; +}; + +/* 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; + } +} + +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 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: + 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_clock_init(struct dp83640_clock *clock) +{ + mutex_init(&clock->extreg_lock); + mutex_init(&clock->clock_lock); + INIT_LIST_HEAD(&clock->phylist); + clock->caps.pin_config = clock->pin_config; + 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.supported_extts_flags = PTP_RISING_EDGE | + PTP_FALLING_EDGE | + PTP_STRICT_FLAGS; + 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; + /* Initialize the runtime pin configuration from gpio_tab. */ + dp83640_gpio_defaults(clock->caps.pin_config); +} + +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 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_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct dp83640_private *dp83640 = + container_of(mii_ts, struct dp83640_private, mii_ts); + + cfg->rx_filter = dp83640->hwts_rx_en; + cfg->tx_type = dp83640->hwts_tx_en; + + return 0; +} + +static int dp83640_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) +{ + struct dp83640_private *dp83640 = + container_of(mii_ts, struct dp83640_private, mii_ts); + u16 txcfg0, rxcfg0; + + 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 = HWTSTAMP_FILTER_PTP_V1_L4_EVENT; + 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 = HWTSTAMP_FILTER_PTP_V2_L4_EVENT; + 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 = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + 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 = HWTSTAMP_FILTER_PTP_V2_EVENT; + 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 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 kernel_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_private *dp83640; + struct dp83640_clock *clock; + int err, i; + + if (phydev->mdio.addr == BROADCAST_ADDR) + return 0; + + err = phy_package_join(phydev, BROADCAST_ADDR, sizeof(*clock)); + if (err) + return err; + + clock = phy_package_get_priv(phydev); + /* Ensure other PHY probes wait for shared clock initialization. */ + phy_package_lock(phydev); + if (phy_package_probe_once(phydev)) + dp83640_clock_init(clock); + phy_package_unlock(phydev); + + mutex_lock(&clock->clock_lock); + + dp83640 = kzalloc_obj(struct dp83640_private); + if (!dp83640) { + err = -ENOMEM; + goto no_memory; + } + + dp83640->phydev = phydev; + dp83640->mii_ts.rxtstamp = dp83640_rxtstamp; + dp83640->mii_ts.txtstamp = dp83640_txtstamp; + dp83640->mii_ts.hwtstamp_set = dp83640_hwtstamp_set; + dp83640->mii_ts.hwtstamp_get = dp83640_hwtstamp_get; + 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); + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; + 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); + + mutex_unlock(&clock->clock_lock); + + return 0; + +no_register: + clock->chosen = NULL; + clock->ptp_clock = NULL; + phydev->default_timestamp = false; + phydev->mii_ts = NULL; + phydev->priv = NULL; + kfree(dp83640); +no_memory: + mutex_unlock(&clock->clock_lock); + phy_package_leave(phydev); + 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; + mutex_lock(&clock->clock_lock); + + 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; + } + } + } + + mutex_unlock(&clock->clock_lock); + kfree(dp83640); + + phy_package_leave(phydev); +} + +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, +}, +}; + +module_phy_driver(dp83640_driver); + +MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver"); +MODULE_AUTHOR("Richard Cochran "); +MODULE_LICENSE("GPL"); + +static const 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..6fc86be9d --- /dev/null +++ b/drivers/net/phy/dp83822.c @@ -0,0 +1,1261 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Driver for the Texas Instruments DP83822, DP83825 and DP83826 PHYs. + * + * Copyright (C) 2017 Texas Instruments Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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_MLEDCR 0x25 +#define MII_DP83822_LDCTRL 0x403 +#define MII_DP83822_LEDCFG1 0x460 +#define MII_DP83822_IOCTRL 0x461 +#define MII_DP83822_IOCTRL1 0x462 +#define MII_DP83822_IOCTRL2 0x463 +#define MII_DP83822_GENCFG 0x465 +#define MII_DP83822_SOR1 0x467 + +/* DP83826 specific registers */ +#define MII_DP83826_VOD_CFG1 0x30b +#define MII_DP83826_VOD_CFG2 0x30c + +/* GENCFG */ +#define DP83822_SIG_DET_LOW BIT(0) + +/* Control Register 2 bits */ +#define DP83822_FX_ENABLE BIT(14) + +#define DP83822_SW_RESET BIT(15) +#define DP83822_DIG_RESTART 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_RMII_MODE_EN BIT(5) +#define DP83822_RMII_MODE_SEL BIT(7) +#define DP83822_RGMII_MODE_EN BIT(9) +#define DP83822_RX_CLK_SHIFT BIT(12) +#define DP83822_TX_CLK_SHIFT BIT(11) + +/* MLEDCR bits */ +#define DP83822_MLEDCR_CFG GENMASK(6, 3) +#define DP83822_MLEDCR_ROUTE GENMASK(1, 0) +#define DP83822_MLEDCR_ROUTE_LED_0 DP83822_MLEDCR_ROUTE + +/* LEDCFG1 bits */ +#define DP83822_LEDCFG1_LED1_CTRL GENMASK(11, 8) +#define DP83822_LEDCFG1_LED3_CTRL GENMASK(7, 4) + +/* IOCTRL bits */ +#define DP83822_IOCTRL_MAC_IMPEDANCE_CTRL GENMASK(4, 1) + +/* IOCTRL1 bits */ +#define DP83822_IOCTRL1_GPIO3_CTRL GENMASK(10, 8) +#define DP83822_IOCTRL1_GPIO3_CTRL_LED3 BIT(0) +#define DP83822_IOCTRL1_GPIO1_CTRL GENMASK(2, 0) +#define DP83822_IOCTRL1_GPIO1_CTRL_LED_1 BIT(0) + +/* LDCTRL bits */ +#define DP83822_100BASE_TX_LINE_DRIVER_SWING GENMASK(7, 4) + +/* IOCTRL2 bits */ +#define DP83822_IOCTRL2_GPIO2_CLK_SRC GENMASK(6, 4) +#define DP83822_IOCTRL2_GPIO2_CTRL GENMASK(2, 0) +#define DP83822_IOCTRL2_GPIO2_CTRL_CLK_REF GENMASK(1, 0) +#define DP83822_IOCTRL2_GPIO2_CTRL_MLED BIT(0) + +#define DP83822_CLK_SRC_MAC_IF 0x0 +#define DP83822_CLK_SRC_XI 0x1 +#define DP83822_CLK_SRC_INT_REF 0x2 +#define DP83822_CLK_SRC_RMII_MASTER_MODE_REF 0x4 +#define DP83822_CLK_SRC_FREE_RUNNING 0x6 +#define DP83822_CLK_SRC_RECOVERED 0x7 + +#define DP83822_LED_FN_LINK 0x0 /* Link established */ +#define DP83822_LED_FN_RX_TX 0x1 /* Receive or Transmit activity */ +#define DP83822_LED_FN_TX 0x2 /* Transmit activity */ +#define DP83822_LED_FN_RX 0x3 /* Receive activity */ +#define DP83822_LED_FN_COLLISION 0x4 /* Collision detected */ +#define DP83822_LED_FN_LINK_100_BTX 0x5 /* 100 BTX link established */ +#define DP83822_LED_FN_LINK_10_BT 0x6 /* 10BT link established */ +#define DP83822_LED_FN_FULL_DUPLEX 0x7 /* Full duplex */ +#define DP83822_LED_FN_LINK_RX_TX 0x8 /* Link established, blink for rx or tx activity */ +#define DP83822_LED_FN_ACTIVE_STRETCH 0x9 /* Active Stretch Signal */ +#define DP83822_LED_FN_MII_LINK 0xa /* MII LINK (100BT+FD) */ +#define DP83822_LED_FN_LPI_MODE 0xb /* LPI Mode (EEE) */ +#define DP83822_LED_FN_RX_TX_ERR 0xc /* TX/RX MII Error */ +#define DP83822_LED_FN_LINK_LOST 0xd /* Link Lost */ +#define DP83822_LED_FN_PRBS_ERR 0xe /* Blink for PRBS error */ + +/* 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 + +/* DP83826: VOD_CFG1 & VOD_CFG2 */ +#define DP83826_VOD_CFG1_MINUS_MDIX_MASK GENMASK(13, 12) +#define DP83826_VOD_CFG1_MINUS_MDI_MASK GENMASK(11, 6) +#define DP83826_VOD_CFG2_MINUS_MDIX_MASK GENMASK(15, 12) +#define DP83826_VOD_CFG2_PLUS_MDIX_MASK GENMASK(11, 6) +#define DP83826_VOD_CFG2_PLUS_MDI_MASK GENMASK(5, 0) +#define DP83826_CFG_DAC_MINUS_MDIX_5_TO_4 GENMASK(5, 4) +#define DP83826_CFG_DAC_MINUS_MDIX_3_TO_0 GENMASK(3, 0) +#define DP83826_CFG_DAC_PERCENT_PER_STEP 625 +#define DP83826_CFG_DAC_PERCENT_DEFAULT 10000 +#define DP83826_CFG_DAC_MINUS_DEFAULT 0x30 +#define DP83826_CFG_DAC_PLUS_DEFAULT 0x10 + +#define MII_DP83822_FIBER_ADVERTISE (ADVERTISED_TP | ADVERTISED_MII | \ + ADVERTISED_FIBRE | \ + ADVERTISED_Pause | ADVERTISED_Asym_Pause) + +#define DP83822_MAX_LED_PINS 4 + +#define DP83822_LED_INDEX_LED_0 0 +#define DP83822_LED_INDEX_LED_1_GPIO1 1 +#define DP83822_LED_INDEX_COL_GPIO2 2 +#define DP83822_LED_INDEX_RX_D3_GPIO3 3 + +struct dp83822_private { + bool fx_signal_det_low; + int fx_enabled; + u16 fx_sd_enable; + u8 cfg_dac_minus; + u8 cfg_dac_plus; + struct ethtool_wolinfo wol; + bool set_gpio2_clk_out; + u32 gpio2_clk_out; + bool led_pin_enable[DP83822_MAX_LED_PINS]; + int tx_amplitude_100base_tx_index; + int mac_termination_index; +}; + +static int dp83822_config_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, MDIO_MMD_VEND2, MII_DP83822_WOL_DA1, + (mac[1] << 8) | mac[0]); + phy_write_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_WOL_DA2, + (mac[3] << 8) | mac[2]); + phy_write_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_WOL_DA3, + (mac[5] << 8) | mac[4]); + + value = phy_read_mmd(phydev, MDIO_MMD_VEND2, + 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, MDIO_MMD_VEND2, + MII_DP83822_RXSOP1, + (wol->sopass[1] << 8) | wol->sopass[0]); + phy_write_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_RXSOP2, + (wol->sopass[3] << 8) | wol->sopass[2]); + phy_write_mmd(phydev, MDIO_MMD_VEND2, + 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, MDIO_MMD_VEND2, + MII_DP83822_WOL_CFG, value); + } else { + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_WOL_CFG, + DP83822_WOL_EN | + DP83822_WOL_MAGIC_EN | + DP83822_WOL_SECURE_ON); + } +} + +static int dp83822_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + struct dp83822_private *dp83822 = phydev->priv; + int ret; + + ret = dp83822_config_wol(phydev, wol); + if (!ret) + memcpy(&dp83822->wol, wol, sizeof(*wol)); + return ret; +} + +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, MDIO_MMD_VEND2, 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, MDIO_MMD_VEND2, + MII_DP83822_RXSOP1); + wol->sopass[0] = (sopass_val & 0xff); + wol->sopass[1] = (sopass_val >> 8); + + sopass_val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_RXSOP2); + wol->sopass[2] = (sopass_val & 0xff); + wol->sopass[3] = (sopass_val >> 8); + + sopass_val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + 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); + + if (!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); + + if (!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 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_leds(struct phy_device *phydev) +{ + struct dp83822_private *dp83822 = phydev->priv; + int ret; + + if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_0]) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR, + DP83822_MLEDCR_ROUTE, + FIELD_PREP(DP83822_MLEDCR_ROUTE, + DP83822_MLEDCR_ROUTE_LED_0)); + if (ret) + return ret; + } else if (dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2]) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL2, + DP83822_IOCTRL2_GPIO2_CTRL, + FIELD_PREP(DP83822_IOCTRL2_GPIO2_CTRL, + DP83822_IOCTRL2_GPIO2_CTRL_MLED)); + if (ret) + return ret; + } + + if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_1_GPIO1]) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL1, + DP83822_IOCTRL1_GPIO1_CTRL, + FIELD_PREP(DP83822_IOCTRL1_GPIO1_CTRL, + DP83822_IOCTRL1_GPIO1_CTRL_LED_1)); + if (ret) + return ret; + } + + if (dp83822->led_pin_enable[DP83822_LED_INDEX_RX_D3_GPIO3]) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL1, + DP83822_IOCTRL1_GPIO3_CTRL, + FIELD_PREP(DP83822_IOCTRL1_GPIO3_CTRL, + DP83822_IOCTRL1_GPIO3_CTRL_LED3)); + if (ret) + return ret; + } + + return 0; +} + +static int dp83822_config_init(struct phy_device *phydev) +{ + struct dp83822_private *dp83822 = phydev->priv; + int rgmii_delay = 0; + s32 rx_int_delay; + s32 tx_int_delay; + int err = 0; + int bmcr; + + if (dp83822->set_gpio2_clk_out) + phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL2, + DP83822_IOCTRL2_GPIO2_CTRL | + DP83822_IOCTRL2_GPIO2_CLK_SRC, + FIELD_PREP(DP83822_IOCTRL2_GPIO2_CTRL, + DP83822_IOCTRL2_GPIO2_CTRL_CLK_REF) | + FIELD_PREP(DP83822_IOCTRL2_GPIO2_CLK_SRC, + dp83822->gpio2_clk_out)); + + if (dp83822->tx_amplitude_100base_tx_index >= 0) + phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_LDCTRL, + DP83822_100BASE_TX_LINE_DRIVER_SWING, + FIELD_PREP(DP83822_100BASE_TX_LINE_DRIVER_SWING, + dp83822->tx_amplitude_100base_tx_index)); + + if (dp83822->mac_termination_index >= 0) + phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL, + DP83822_IOCTRL_MAC_IMPEDANCE_CTRL, + FIELD_PREP(DP83822_IOCTRL_MAC_IMPEDANCE_CTRL, + dp83822->mac_termination_index)); + + err = dp83822_config_init_leds(phydev); + if (err) + return err; + + if (phy_interface_is_rgmii(phydev)) { + rx_int_delay = phy_get_internal_delay(phydev, NULL, 0, true); + + /* Set DP83822_RX_CLK_SHIFT to enable rx clk internal delay */ + if (rx_int_delay > 0) + rgmii_delay |= DP83822_RX_CLK_SHIFT; + + tx_int_delay = phy_get_internal_delay(phydev, NULL, 0, false); + + /* Set DP83822_TX_CLK_SHIFT to disable tx clk internal delay */ + if (tx_int_delay <= 0) + rgmii_delay |= DP83822_TX_CLK_SHIFT; + + err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_RCSR, + DP83822_RX_CLK_SHIFT | DP83822_TX_CLK_SHIFT, rgmii_delay); + if (err) + return err; + + err = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_RCSR, DP83822_RGMII_MODE_EN); + + if (err) + return err; + } else { + err = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_RCSR, DP83822_RGMII_MODE_EN); + + if (err) + return err; + } + + 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, MDIO_MMD_VEND2, + MII_DP83822_GENCFG, + DP83822_SIG_DET_LOW); + if (err) + return err; + } + } + return dp83822_config_wol(phydev, &dp83822->wol); +} + +static int dp8382x_config_rmii_mode(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + const char *of_val; + int ret; + + if (!device_property_read_string(dev, "ti,rmii-mode", &of_val)) { + if (strcmp(of_val, "master") == 0) { + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_RCSR, + DP83822_RMII_MODE_SEL); + } else if (strcmp(of_val, "slave") == 0) { + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_RCSR, + DP83822_RMII_MODE_SEL); + } else { + phydev_err(phydev, "Invalid value for ti,rmii-mode property (%s)\n", + of_val); + ret = -EINVAL; + } + + if (ret) + return ret; + } + + return 0; +} + +static int dp83826_config_init(struct phy_device *phydev) +{ + struct dp83822_private *dp83822 = phydev->priv; + u16 val, mask; + int ret; + + if (phydev->interface == PHY_INTERFACE_MODE_RMII) { + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_RCSR, + DP83822_RMII_MODE_EN); + if (ret) + return ret; + + ret = dp8382x_config_rmii_mode(phydev); + if (ret) + return ret; + } else { + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_RCSR, + DP83822_RMII_MODE_EN); + if (ret) + return ret; + } + + if (dp83822->cfg_dac_minus != DP83826_CFG_DAC_MINUS_DEFAULT) { + val = FIELD_PREP(DP83826_VOD_CFG1_MINUS_MDI_MASK, dp83822->cfg_dac_minus) | + FIELD_PREP(DP83826_VOD_CFG1_MINUS_MDIX_MASK, + FIELD_GET(DP83826_CFG_DAC_MINUS_MDIX_5_TO_4, + dp83822->cfg_dac_minus)); + mask = DP83826_VOD_CFG1_MINUS_MDIX_MASK | DP83826_VOD_CFG1_MINUS_MDI_MASK; + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83826_VOD_CFG1, mask, val); + if (ret) + return ret; + + val = FIELD_PREP(DP83826_VOD_CFG2_MINUS_MDIX_MASK, + FIELD_GET(DP83826_CFG_DAC_MINUS_MDIX_3_TO_0, + dp83822->cfg_dac_minus)); + mask = DP83826_VOD_CFG2_MINUS_MDIX_MASK; + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83826_VOD_CFG2, mask, val); + if (ret) + return ret; + } + + if (dp83822->cfg_dac_plus != DP83826_CFG_DAC_PLUS_DEFAULT) { + val = FIELD_PREP(DP83826_VOD_CFG2_PLUS_MDIX_MASK, dp83822->cfg_dac_plus) | + FIELD_PREP(DP83826_VOD_CFG2_PLUS_MDI_MASK, dp83822->cfg_dac_plus); + mask = DP83826_VOD_CFG2_PLUS_MDIX_MASK | DP83826_VOD_CFG2_PLUS_MDI_MASK; + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83826_VOD_CFG2, mask, val); + if (ret) + return ret; + } + + return dp83822_config_wol(phydev, &dp83822->wol); +} + +static int dp83825_config_init(struct phy_device *phydev) +{ + struct dp83822_private *dp83822 = phydev->priv; + int ret; + + ret = dp8382x_config_rmii_mode(phydev); + if (ret) + return ret; + + return dp83822_config_wol(phydev, &dp83822->wol); +} + +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); +} + +#if IS_ENABLED(CONFIG_OF_MDIO) +static const u32 tx_amplitude_100base_tx_gain[] = { + 80, 82, 83, 85, 87, 88, 90, 92, + 93, 95, 97, 98, 100, 102, 103, 105, +}; + +static const u32 mac_termination[] = { + 99, 91, 84, 78, 73, 69, 65, 61, 58, 55, 53, 50, 48, 46, 44, 43, +}; + +static int dp83822_of_init_leds(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct dp83822_private *dp83822 = phydev->priv; + struct device_node *leds; + u32 index; + int err; + + if (!node) + return 0; + + leds = of_get_child_by_name(node, "leds"); + if (!leds) + return 0; + + for_each_available_child_of_node_scoped(leds, led) { + err = of_property_read_u32(led, "reg", &index); + if (err) { + of_node_put(leds); + return err; + } + + if (index <= DP83822_LED_INDEX_RX_D3_GPIO3) { + dp83822->led_pin_enable[index] = true; + } else { + of_node_put(leds); + return -EINVAL; + } + } + + of_node_put(leds); + /* LED_0 and COL(GPIO2) use the MLED function. MLED can be routed to + * only one of these two pins at a time. + */ + if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_0] && + dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2]) { + phydev_err(phydev, "LED_0 and COL(GPIO2) cannot be used as LED output at the same time\n"); + return -EINVAL; + } + + if (dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2] && + dp83822->set_gpio2_clk_out) { + phydev_err(phydev, "COL(GPIO2) cannot be used as LED output, already used as clock output\n"); + return -EINVAL; + } + + if (dp83822->led_pin_enable[DP83822_LED_INDEX_RX_D3_GPIO3] && + phydev->interface != PHY_INTERFACE_MODE_RMII) { + phydev_err(phydev, "RX_D3 can only be used as LED output when in RMII mode\n"); + return -EINVAL; + } + + return 0; +} + +static int dp83822_of_init(struct phy_device *phydev) +{ + struct dp83822_private *dp83822 = phydev->priv; + struct device *dev = &phydev->mdio.dev; + const char *of_val; + int i, ret; + u32 val; + + if (!device_property_read_string(dev, "ti,gpio2-clk-out", &of_val)) { + if (strcmp(of_val, "mac-if") == 0) { + dp83822->gpio2_clk_out = DP83822_CLK_SRC_MAC_IF; + } else if (strcmp(of_val, "xi") == 0) { + dp83822->gpio2_clk_out = DP83822_CLK_SRC_XI; + } else if (strcmp(of_val, "int-ref") == 0) { + dp83822->gpio2_clk_out = DP83822_CLK_SRC_INT_REF; + } else if (strcmp(of_val, "rmii-master-mode-ref") == 0) { + dp83822->gpio2_clk_out = DP83822_CLK_SRC_RMII_MASTER_MODE_REF; + } else if (strcmp(of_val, "free-running") == 0) { + dp83822->gpio2_clk_out = DP83822_CLK_SRC_FREE_RUNNING; + } else if (strcmp(of_val, "recovered") == 0) { + dp83822->gpio2_clk_out = DP83822_CLK_SRC_RECOVERED; + } else { + phydev_err(phydev, + "Invalid value for ti,gpio2-clk-out property (%s)\n", + of_val); + return -EINVAL; + } + + dp83822->set_gpio2_clk_out = true; + } + + ret = phy_get_tx_amplitude_gain(phydev, dev, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + &val); + if (!ret) { + for (i = 0; i < ARRAY_SIZE(tx_amplitude_100base_tx_gain); i++) { + if (tx_amplitude_100base_tx_gain[i] == val) { + dp83822->tx_amplitude_100base_tx_index = i; + break; + } + } + + if (dp83822->tx_amplitude_100base_tx_index < 0) { + phydev_err(phydev, + "Invalid value for tx-amplitude-100base-tx-percent property (%u)\n", + val); + return -EINVAL; + } + } + + ret = phy_get_mac_termination(phydev, dev, &val); + if (!ret) { + for (i = 0; i < ARRAY_SIZE(mac_termination); i++) { + if (mac_termination[i] == val) { + dp83822->mac_termination_index = i; + break; + } + } + + if (dp83822->mac_termination_index < 0) { + phydev_err(phydev, + "Invalid value for mac-termination-ohms property (%u)\n", + val); + return -EINVAL; + } + } + + return dp83822_of_init_leds(phydev); +} + +static int dp83826_to_dac_minus_one_regval(int percent) +{ + int tmp = DP83826_CFG_DAC_PERCENT_DEFAULT - percent; + + return tmp / DP83826_CFG_DAC_PERCENT_PER_STEP; +} + +static int dp83826_to_dac_plus_one_regval(int percent) +{ + int tmp = percent - DP83826_CFG_DAC_PERCENT_DEFAULT; + + return tmp / DP83826_CFG_DAC_PERCENT_PER_STEP; +} + +static void dp83826_of_init(struct phy_device *phydev) +{ + struct dp83822_private *dp83822 = phydev->priv; + struct device *dev = &phydev->mdio.dev; + u32 val; + + dp83822->cfg_dac_minus = DP83826_CFG_DAC_MINUS_DEFAULT; + if (!device_property_read_u32(dev, "ti,cfg-dac-minus-one-bp", &val)) + dp83822->cfg_dac_minus += dp83826_to_dac_minus_one_regval(val); + + dp83822->cfg_dac_plus = DP83826_CFG_DAC_PLUS_DEFAULT; + if (!device_property_read_u32(dev, "ti,cfg-dac-plus-one-bp", &val)) + dp83822->cfg_dac_plus += dp83826_to_dac_plus_one_regval(val); +} +#else +static int dp83822_of_init(struct phy_device *phydev) +{ + return 0; +} + +static void dp83826_of_init(struct phy_device *phydev) +{ +} +#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, MDIO_MMD_VEND2, 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_attach_mdi_port(struct phy_device *phydev, + struct phy_port *port) +{ + struct dp83822_private *dp83822 = phydev->priv; + int ret; + + if (port->mediums) { + if (phy_port_is_fiber(port)) + dp83822->fx_enabled = true; + } else { + ret = dp83822_read_straps(phydev); + if (ret) + return ret; + +#if IS_ENABLED(CONFIG_OF_MDIO) + if (dp83822->fx_enabled && dp83822->fx_sd_enable) + dp83822->fx_signal_det_low = + device_property_present(&phydev->mdio.dev, + "ti,link-loss-low"); + + /* ti,fiber-mode is still used for backwards compatibility, but + * has been replaced with the mdi node definition, see + * ethernet-port.yaml + */ + if (!dp83822->fx_enabled) + dp83822->fx_enabled = + device_property_present(&phydev->mdio.dev, + "ti,fiber-mode"); +#endif /* CONFIG_OF_MDIO */ + + if (dp83822->fx_enabled) { + port->mediums = BIT(ETHTOOL_LINK_MEDIUM_BASEF); + } else { + /* This PHY can only to 100BaseTX max, so on 2 pairs */ + port->pairs = 2; + port->mediums = BIT(ETHTOOL_LINK_MEDIUM_BASET); + } + } + + return 0; +} + +static int dp8382x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct dp83822_private *dp83822; + struct clk *clk; + + dp83822 = devm_kzalloc(dev, sizeof(*dp83822), GFP_KERNEL); + if (!dp83822) + return -ENOMEM; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) { + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to request ref clock\n"); + } + + dp83822->tx_amplitude_100base_tx_index = -1; + dp83822->mac_termination_index = -1; + phydev->priv = dp83822; + + return 0; +} + +static int dp83822_probe(struct phy_device *phydev) +{ + int ret; + + ret = dp8382x_probe(phydev); + if (ret) + return ret; + + return dp83822_of_init(phydev); +} + +static int dp83826_probe(struct phy_device *phydev) +{ + int ret; + + ret = dp8382x_probe(phydev); + if (ret) + return ret; + + dp83826_of_init(phydev); + + return 0; +} + +static int dp83822_suspend(struct phy_device *phydev) +{ + int value; + + value = phy_read_mmd(phydev, MDIO_MMD_VEND2, 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, MDIO_MMD_VEND2, MII_DP83822_WOL_CFG); + + phy_write_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_WOL_CFG, value | + DP83822_WOL_CLR_INDICATION); + + return 0; +} + +static int dp83822_led_mode(u8 index, unsigned long rules) +{ + switch (rules) { + case BIT(TRIGGER_NETDEV_LINK): + return DP83822_LED_FN_LINK; + case BIT(TRIGGER_NETDEV_LINK_10): + return DP83822_LED_FN_LINK_10_BT; + case BIT(TRIGGER_NETDEV_LINK_100): + return DP83822_LED_FN_LINK_100_BTX; + case BIT(TRIGGER_NETDEV_FULL_DUPLEX): + return DP83822_LED_FN_FULL_DUPLEX; + case BIT(TRIGGER_NETDEV_TX): + return DP83822_LED_FN_TX; + case BIT(TRIGGER_NETDEV_RX): + return DP83822_LED_FN_RX; + case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return DP83822_LED_FN_RX_TX; + case BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR): + return DP83822_LED_FN_RX_TX_ERR; + case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return DP83822_LED_FN_LINK_RX_TX; + default: + return -EOPNOTSUPP; + } +} + +static int dp83822_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int mode; + + mode = dp83822_led_mode(index, rules); + if (mode < 0) + return mode; + + return 0; +} + +static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int mode; + + mode = dp83822_led_mode(index, rules); + if (mode < 0) + return mode; + + if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2) + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG, + FIELD_PREP(DP83822_MLEDCR_CFG, mode)); + else if (index == DP83822_LED_INDEX_LED_1_GPIO1) + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_LEDCFG1, + DP83822_LEDCFG1_LED1_CTRL, + FIELD_PREP(DP83822_LEDCFG1_LED1_CTRL, + mode)); + else + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + MII_DP83822_LEDCFG1, + DP83822_LEDCFG1_LED3_CTRL, + FIELD_PREP(DP83822_LEDCFG1_LED3_CTRL, + mode)); +} + +static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2) { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR); + if (val < 0) + return val; + + val = FIELD_GET(DP83822_MLEDCR_CFG, val); + } else { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_LEDCFG1); + if (val < 0) + return val; + + if (index == DP83822_LED_INDEX_LED_1_GPIO1) + val = FIELD_GET(DP83822_LEDCFG1_LED1_CTRL, val); + else + val = FIELD_GET(DP83822_LEDCFG1_LED3_CTRL, val); + } + + switch (val) { + case DP83822_LED_FN_LINK: + *rules = BIT(TRIGGER_NETDEV_LINK); + break; + case DP83822_LED_FN_LINK_10_BT: + *rules = BIT(TRIGGER_NETDEV_LINK_10); + break; + case DP83822_LED_FN_LINK_100_BTX: + *rules = BIT(TRIGGER_NETDEV_LINK_100); + break; + case DP83822_LED_FN_FULL_DUPLEX: + *rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX); + break; + case DP83822_LED_FN_TX: + *rules = BIT(TRIGGER_NETDEV_TX); + break; + case DP83822_LED_FN_RX: + *rules = BIT(TRIGGER_NETDEV_RX); + break; + case DP83822_LED_FN_RX_TX: + *rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); + break; + case DP83822_LED_FN_RX_TX_ERR: + *rules = BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR); + break; + case DP83822_LED_FN_LINK_RX_TX: + *rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX); + break; + default: + *rules = 0; + break; + } + + 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, \ + .led_hw_is_supported = dp83822_led_hw_is_supported, \ + .led_hw_control_set = dp83822_led_hw_control_set, \ + .led_hw_control_get = dp83822_led_hw_control_get, \ + .attach_mdi_port = dp83822_attach_mdi_port \ + } + +#define DP83825_PHY_DRIVER(_id, _name) \ + { \ + PHY_ID_MATCH_MODEL(_id), \ + .name = (_name), \ + /* PHY_BASIC_FEATURES */ \ + .probe = dp8382x_probe, \ + .soft_reset = dp83822_phy_reset, \ + .config_init = dp83825_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, \ + } + +#define DP83826_PHY_DRIVER(_id, _name) \ + { \ + PHY_ID_MATCH_MODEL(_id), \ + .name = (_name), \ + /* PHY_BASIC_FEATURES */ \ + .probe = dp83826_probe, \ + .soft_reset = dp83822_phy_reset, \ + .config_init = dp83826_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"), + DP83825_PHY_DRIVER(DP83825I_PHY_ID, "TI DP83825I"), + DP83825_PHY_DRIVER(DP83825S_PHY_ID, "TI DP83825S"), + DP83825_PHY_DRIVER(DP83825CM_PHY_ID, "TI DP83825M"), + DP83825_PHY_DRIVER(DP83825CS_PHY_ID, "TI DP83825CS"), + DP83826_PHY_DRIVER(DP83826C_PHY_ID, "TI DP83826C"), + DP83826_PHY_DRIVER(DP83826NC_PHY_ID, "TI DP83826NC"), +}; +module_phy_driver(dp83822_driver); + +static const 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 +#include + +#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 const 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, \ + \ + .flags = PHY_RST_AFTER_CLK_EN, \ + } + +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 "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c new file mode 100644 index 000000000..61a941aa0 --- /dev/null +++ b/drivers/net/phy/dp83867.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Driver for the Texas Instruments DP83867 PHY + * + * Copyright (C) 2015 Texas Instruments Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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_EN BIT(7) +#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_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_SGMII_EN BIT(11) +#define DP83867_PHYCR_FORCE_LINK_GOOD BIT(10) +#define DP83867_PHYCR_MDIX_MASK GENMASK(6, 5) +#define DP83867_PHYCR_MDIX_MDI (0x0 << 5) +#define DP83867_PHYCR_MDIX_MDIX (0x1 << 5) +#define DP83867_PHYCR_MDIX_AUTO (0x3 << 5) + +/* RGMIIDCTL bits */ +#define DP83867_RGMII_TX_CLK_DELAY_MAX 0xf +#define DP83867_RGMII_TX_CLK_DELAY_SHIFT 4 +#define DP83867_RGMII_RX_CLK_DELAY_MAX 0xf +#define DP83867_RGMII_RX_CLK_DELAY_SHIFT 0 + +/* 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) +#define DP83867_PHYSTS_MDIX_CD BIT(9) +#define DP83867_PHYSTS_MDIX_AB BIT(8) +#define DP83867_PHYSTS_MDIX_MASK (DP83867_PHYSTS_MDIX_AB | \ + DP83867_PHYSTS_MDIX_CD) + +/* 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) +#define DP83867_LED_POLARITY(x) BIT((x) * 4 + 2) + +#define DP83867_LED_FN(idx, val) (((val) & 0xf) << ((idx) * 4)) +#define DP83867_LED_FN_MASK(idx) (0xf << ((idx) * 4)) +#define DP83867_LED_FN_RX_ERR 0xe /* Receive Error */ +#define DP83867_LED_FN_RX_TX_ERR 0xd /* Receive Error or Transmit Error */ +#define DP83867_LED_FN_LINK_RX_TX 0xb /* Link established, blink for rx or tx activity */ +#define DP83867_LED_FN_FULL_DUPLEX 0xa /* Full duplex */ +#define DP83867_LED_FN_LINK_100_1000_BT 0x9 /* 100/1000BT link established */ +#define DP83867_LED_FN_LINK_10_100_BT 0x8 /* 10/100BT link established */ +#define DP83867_LED_FN_LINK_10_BT 0x7 /* 10BT link established */ +#define DP83867_LED_FN_LINK_100_BTX 0x6 /* 100 BTX link established */ +#define DP83867_LED_FN_LINK_1000_BT 0x5 /* 1000 BT link established */ +#define DP83867_LED_FN_COLLISION 0x4 /* Collision detected */ +#define DP83867_LED_FN_RX 0x3 /* Receive activity */ +#define DP83867_LED_FN_TX 0x2 /* Transmit activity */ +#define DP83867_LED_FN_RX_TX 0x1 /* Receive or Transmit activity */ +#define DP83867_LED_FN_LINK 0x0 /* Link established */ + +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; + + if (!(status & DP83867_PHYSTS_LINK)) { + phydev->mdix = ETH_TP_MDI_INVALID; + } else { + switch (status & DP83867_PHYSTS_MDIX_MASK) { + case 0: + phydev->mdix = ETH_TP_MDI; + break; + case DP83867_PHYSTS_MDIX_MASK: + phydev->mdix = ETH_TP_MDI_X; + break; + default: + phydev->mdix = ETH_TP_MDI_INVALID; + break; + } + } + + 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; +} + +#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_RGMIIDCTL_2_00_NS; + 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_RGMIIDCTL_2_00_NS; + 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; + + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + + /* 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; + + /* 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; + } + + /* Although the DP83867 reports EEE capability through the + * MDIO_PCS_EEE_ABLE and MDIO_AN_EEE_ADV registers, the feature + * is not actually implemented in hardware. + */ + phy_disable_eee(phydev); + + 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); + + val &= ~DP83867_PHYCR_SGMII_EN; + 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) | + DP83867_PHYCR_SGMII_EN; + } + + ret = phy_write(phydev, MII_DP83867_PHYCTRL, val); + if (ret) + return ret; + + if (phy_interface_is_rgmii(phydev)) { + /* Set up RGMII delays */ + val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL); + + val |= DP83867_RGMII_EN; + 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); + + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIIDCTL, + dp83867->rx_id_delay | + (dp83867->tx_id_delay << DP83867_RGMII_TX_CLK_DELAY_SHIFT)); + } else { + val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL); + val &= ~DP83867_RGMII_EN; + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL, val); + } + + /* 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_config_mdix(struct phy_device *phydev, u8 ctrl) +{ + int val; + + switch (ctrl) { + case ETH_TP_MDI: + val = DP83867_PHYCR_MDIX_MDI; + break; + case ETH_TP_MDI_X: + val = DP83867_PHYCR_MDIX_MDIX; + break; + case ETH_TP_MDI_AUTO: + val = DP83867_PHYCR_MDIX_AUTO; + break; + default: + return -EINVAL; + } + + return phy_modify(phydev, MII_DP83867_PHYCTRL, + DP83867_PHYCR_MDIX_MASK, val); +} + +static int dp83867_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = dp83867_config_mdix(phydev, phydev->mdix_ctrl); + if (ret) + return ret; + + return genphy_config_aneg(phydev); +} + +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; + + val = phy_modify_changed(phydev, DP83867_CFG2, + DP83867_SGMII_AUTONEG_EN, 0); + + /* Keep the in-band setting made by dp83867_config_inband() */ + if (val != 0) + phy_set_bits(phydev, DP83867_CFG2, + DP83867_SGMII_AUTONEG_EN); + } +} + +static int dp83867_loopback(struct phy_device *phydev, bool enable, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + 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 int dp83867_led_mode(u8 index, unsigned long rules) +{ + if (index >= DP83867_LED_COUNT) + return -EINVAL; + + switch (rules) { + case BIT(TRIGGER_NETDEV_LINK): + return DP83867_LED_FN_LINK; + case BIT(TRIGGER_NETDEV_LINK_10): + return DP83867_LED_FN_LINK_10_BT; + case BIT(TRIGGER_NETDEV_LINK_100): + return DP83867_LED_FN_LINK_100_BTX; + case BIT(TRIGGER_NETDEV_FULL_DUPLEX): + return DP83867_LED_FN_FULL_DUPLEX; + case BIT(TRIGGER_NETDEV_TX): + return DP83867_LED_FN_TX; + case BIT(TRIGGER_NETDEV_RX): + return DP83867_LED_FN_RX; + case BIT(TRIGGER_NETDEV_LINK_1000): + return DP83867_LED_FN_LINK_1000_BT; + case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return DP83867_LED_FN_RX_TX; + case BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000): + return DP83867_LED_FN_LINK_100_1000_BT; + case BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100): + return DP83867_LED_FN_LINK_10_100_BT; + case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return DP83867_LED_FN_LINK_RX_TX; + default: + return -EOPNOTSUPP; + } +} + +static int dp83867_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int ret; + + ret = dp83867_led_mode(index, rules); + if (ret < 0) + return ret; + + return 0; +} + +static int dp83867_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int mode, ret; + + mode = dp83867_led_mode(index, rules); + if (mode < 0) + return mode; + + ret = phy_modify(phydev, DP83867_LEDCR1, DP83867_LED_FN_MASK(index), + DP83867_LED_FN(index, mode)); + if (ret) + return ret; + + return phy_modify(phydev, DP83867_LEDCR2, DP83867_LED_DRV_EN(index), 0); +} + +static int dp83867_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + val = phy_read(phydev, DP83867_LEDCR1); + if (val < 0) + return val; + + val &= DP83867_LED_FN_MASK(index); + val >>= index * 4; + + switch (val) { + case DP83867_LED_FN_LINK: + *rules = BIT(TRIGGER_NETDEV_LINK); + break; + case DP83867_LED_FN_LINK_10_BT: + *rules = BIT(TRIGGER_NETDEV_LINK_10); + break; + case DP83867_LED_FN_LINK_100_BTX: + *rules = BIT(TRIGGER_NETDEV_LINK_100); + break; + case DP83867_LED_FN_FULL_DUPLEX: + *rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX); + break; + case DP83867_LED_FN_TX: + *rules = BIT(TRIGGER_NETDEV_TX); + break; + case DP83867_LED_FN_RX: + *rules = BIT(TRIGGER_NETDEV_RX); + break; + case DP83867_LED_FN_LINK_1000_BT: + *rules = BIT(TRIGGER_NETDEV_LINK_1000); + break; + case DP83867_LED_FN_RX_TX: + *rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); + break; + case DP83867_LED_FN_LINK_100_1000_BT: + *rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000); + break; + case DP83867_LED_FN_LINK_10_100_BT: + *rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100); + break; + case DP83867_LED_FN_LINK_RX_TX: + *rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX); + break; + default: + *rules = 0; + break; + } + + return 0; +} + +static int dp83867_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + /* Default active high */ + u16 polarity = DP83867_LED_POLARITY(index); + u32 mode; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + polarity = 0; + break; + case PHY_LED_ACTIVE_HIGH: + polarity = DP83867_LED_POLARITY(index); + break; + default: + return -EINVAL; + } + } + return phy_modify(phydev, DP83867_LEDCR2, + DP83867_LED_POLARITY(index), polarity); +} + +static unsigned int dp83867_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_SGMII) + return LINK_INBAND_ENABLE | LINK_INBAND_DISABLE; + + return 0; +} + +static int dp83867_config_inband(struct phy_device *phydev, unsigned int modes) +{ + int val = 0; + + if (modes == LINK_INBAND_ENABLE) + val = DP83867_SGMII_AUTONEG_EN; + + return phy_modify(phydev, DP83867_CFG2, DP83867_SGMII_AUTONEG_EN, 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, + .config_aneg = dp83867_config_aneg, + .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, + .led_hw_is_supported = dp83867_led_hw_is_supported, + .led_hw_control_set = dp83867_led_hw_control_set, + .led_hw_control_get = dp83867_led_hw_control_get, + .led_polarity_set = dp83867_led_polarity_set, + + .inband_caps = dp83867_inband_caps, + .config_inband = dp83867_config_inband, + }, +}; +module_phy_driver(dp83867_driver); + +static const 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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_ANA_PLL_PROG_PI 0x00c6 +#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(11, 9) +#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_config_aneg(struct phy_device *phydev) +{ + struct dp83869_private *dp83869 = phydev->priv; + + if (dp83869->mode != DP83869_RGMII_1000_BASE) + return genphy_config_aneg(phydev); + + return genphy_c37_config_aneg(phydev); +} + +static int dp83869_read_status(struct phy_device *phydev) +{ + struct dp83869_private *dp83869 = phydev->priv; + bool changed; + int ret; + + if (dp83869->mode == DP83869_RGMII_1000_BASE) + return genphy_c37_read_status(phydev, &changed); + + ret = genphy_read_status(phydev); + if (ret) + return ret; + + if (dp83869->mode == DP83869_RGMII_100_BASE) { + if (phydev->link) { + 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 = FIELD_GET(DP83869_STRAP_OP_MODE_MASK, val); + + 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 device_node *of_node = phydev->mdio.dev.of_node; + struct dp83869_private *dp83869 = phydev->priv; + 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, + &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, + &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); + + 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 || + dp83869->mode == DP83869_RGMII_COPPER_ETHERNET) { + 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) { + /* + * Table 7-121 in datasheet says we have to set register 0xc6 + * to value 0x10 before CLK_O_SEL can be modified. + */ + ret = phy_write_mmd(phydev, DP83869_DEVADDR, + DP83869_ANA_PLL_PROG_PI, 0x10); + if (ret) + return ret; + + 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, \ + .config_aneg = dp83869_config_aneg, \ + .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 const 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 +#include +#include +#include +#include +#include +#include +#include + +#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_features = genphy_c45_pma_read_ext_abilities, + .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 const 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 + */ + +#include +#include +#include +#include +#include + +#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_CTRL 0x1f +#define DP83TD510E_CTRL_HW_RESET BIT(15) +#define DP83TD510E_CTRL_SW_RESET BIT(14) + +/* + * DP83TD510E_PKT_STAT_x registers correspond to similarly named registers + * in the datasheet (PKT_STAT_1 through PKT_STAT_6). These registers store + * 32-bit or 16-bit counters for TX and RX statistics and must be read in + * sequence to ensure the counters are cleared correctly. + * + * - DP83TD510E_PKT_STAT_1: Contains TX packet count bits [15:0]. + * - DP83TD510E_PKT_STAT_2: Contains TX packet count bits [31:16]. + * - DP83TD510E_PKT_STAT_3: Contains TX error packet count. + * - DP83TD510E_PKT_STAT_4: Contains RX packet count bits [15:0]. + * - DP83TD510E_PKT_STAT_5: Contains RX packet count bits [31:16]. + * - DP83TD510E_PKT_STAT_6: Contains RX error packet count. + * + * Keeping the register names as defined in the datasheet helps maintain + * clarity and alignment with the documentation. + */ +#define DP83TD510E_PKT_STAT_1 0x12b +#define DP83TD510E_PKT_STAT_2 0x12c +#define DP83TD510E_PKT_STAT_3 0x12d +#define DP83TD510E_PKT_STAT_4 0x12e +#define DP83TD510E_PKT_STAT_5 0x12f +#define DP83TD510E_PKT_STAT_6 0x130 + +#define DP83TD510E_AN_STAT_1 0x60c +#define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15) + +#define DP83TD510E_MSE_DETECT 0xa85 +#define DP83TD510E_MSE_MAX U16_MAX + +#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 */ +}; + +struct dp83td510_stats { + u64 tx_pkt_cnt; + u64 tx_err_pkt_cnt; + u64 rx_pkt_cnt; + u64 rx_err_pkt_cnt; +}; + +struct dp83td510_priv { + bool alcd_test_active; + struct dp83td510_stats stats; +}; + +/* Time Domain Reflectometry (TDR) Functionality of DP83TD510 PHY + * + * I assume that this PHY is using a variation of Spread Spectrum Time Domain + * Reflectometry (SSTDR) rather than the commonly used TDR found in many PHYs. + * Here are the following observations which likely confirm this: + * - The DP83TD510 PHY transmits a modulated signal of configurable length + * (default 16000 µs) instead of a single pulse pattern, which is typical + * for traditional TDR. + * - The pulse observed on the wire, triggered by the HW RESET register, is not + * part of the cable testing process. + * + * I assume that SSTDR seems to be a logical choice for the 10BaseT1L + * environment due to improved noise resistance, making it suitable for + * environments with significant electrical noise, such as long 10BaseT1L cable + * runs. + * + * Configuration Variables: + * The SSTDR variation used in this PHY involves more configuration variables + * that can dramatically affect the functionality and precision of cable + * testing. Since most of these configuration options are either not well + * documented or documented with minimal details, the following sections + * describe my understanding and observations of these variables and their + * impact on TDR functionality. + * + * Timeline: + * ,<--cfg_pre_silence_time + * | ,<-SSTDR Modulated Transmission + * | | ,<--cfg_post_silence_time + * | | | ,<--Force Link Mode + * |<--'-->|<-------'------->|<--'-->|<--------'------->| + * + * - cfg_pre_silence_time: Optional silence time before TDR transmission starts. + * - SSTDR Modulated Transmission: Transmission duration configured by + * cfg_tdr_tx_duration and amplitude configured by cfg_tdr_tx_type. + * - cfg_post_silence_time: Silence time after TDR transmission. + * - Force Link Mode: If nothing is configured after cfg_post_silence_time, + * the PHY continues in force link mode without autonegotiation. + */ + +#define DP83TD510E_TDR_CFG 0x1e +#define DP83TD510E_TDR_START BIT(15) +#define DP83TD510E_TDR_DONE BIT(1) +#define DP83TD510E_TDR_FAIL BIT(0) + +#define DP83TD510E_TDR_CFG1 0x300 +/* cfg_tdr_tx_type: Transmit voltage level for TDR. + * 0 = 1V, 1 = 2.4V + * Note: Using different voltage levels may not work + * in all configuration variations. For example, setting + * 2.4V may give different cable length measurements. + * Other settings may be needed to make it work properly. + */ +#define DP83TD510E_TDR_TX_TYPE BIT(12) +#define DP83TD510E_TDR_TX_TYPE_1V 0 +#define DP83TD510E_TDR_TX_TYPE_2_4V 1 +/* cfg_post_silence_time: Time after the TDR sequence. Since we force master mode + * for the TDR will proceed with forced link state after this time. For Linux + * it is better to set max value to avoid false link state detection. + */ +#define DP83TD510E_TDR_CFG1_POST_SILENCE_TIME GENMASK(3, 2) +#define DP83TD510E_TDR_CFG1_POST_SILENCE_TIME_0MS 0 +#define DP83TD510E_TDR_CFG1_POST_SILENCE_TIME_10MS 1 +#define DP83TD510E_TDR_CFG1_POST_SILENCE_TIME_100MS 2 +#define DP83TD510E_TDR_CFG1_POST_SILENCE_TIME_1000MS 3 +/* cfg_pre_silence_time: Time before the TDR sequence. It should be enough to + * settle down all pulses and reflections. Since for 10BASE-T1L we have + * maximum 2000m cable length, we can set it to 1ms. + */ +#define DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME GENMASK(1, 0) +#define DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME_0MS 0 +#define DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME_10MS 1 +#define DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME_100MS 2 +#define DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME_1000MS 3 + +#define DP83TD510E_TDR_CFG2 0x301 +#define DP83TD510E_TDR_END_TAP_INDEX_1 GENMASK(14, 8) +#define DP83TD510E_TDR_END_TAP_INDEX_1_DEF 36 +#define DP83TD510E_TDR_START_TAP_INDEX_1 GENMASK(6, 0) +#define DP83TD510E_TDR_START_TAP_INDEX_1_DEF 4 + +#define DP83TD510E_TDR_CFG3 0x302 +/* cfg_tdr_tx_duration: Duration of the TDR transmission in microseconds. + * This value sets the duration of the modulated signal used for TDR + * measurements. + * - Default: 16000 µs + * - Observation: A minimum duration of 6000 µs is recommended to ensure + * accurate detection of cable faults. Durations shorter than 6000 µs may + * result in incomplete data, especially for shorter cables (e.g., 20 meters), + * leading to false "OK" results. Longer durations (e.g., 6000 µs or more) + * provide better accuracy, particularly for detecting open circuits. + */ +#define DP83TD510E_TDR_TX_DURATION_US GENMASK(15, 0) +#define DP83TD510E_TDR_TX_DURATION_US_DEF 16000 + +#define DP83TD510E_TDR_FAULT_CFG1 0x303 +#define DP83TD510E_TDR_FLT_LOC_OFFSET_1 GENMASK(14, 8) +#define DP83TD510E_TDR_FLT_LOC_OFFSET_1_DEF 4 +#define DP83TD510E_TDR_FLT_INIT_1 GENMASK(7, 0) +#define DP83TD510E_TDR_FLT_INIT_1_DEF 62 + +#define DP83TD510E_TDR_FAULT_STAT 0x30c +#define DP83TD510E_TDR_PEAK_DETECT BIT(11) +#define DP83TD510E_TDR_PEAK_SIGN BIT(10) +#define DP83TD510E_TDR_PEAK_LOCATION GENMASK(9, 0) + +/* Not documented registers and values but recommended according to + * "DP83TD510E Cable Diagnostics Toolkit revC" + */ +#define DP83TD510E_UNKN_030E 0x30e +#define DP83TD510E_030E_VAL 0x2520 + +#define DP83TD510E_LEDS_CFG_1 0x460 +#define DP83TD510E_LED_FN(idx, val) (((val) & 0xf) << ((idx) * 4)) +#define DP83TD510E_LED_FN_MASK(idx) (0xf << ((idx) * 4)) +/* link OK */ +#define DP83TD510E_LED_MODE_LINK_OK 0x0 +/* TX/RX activity */ +#define DP83TD510E_LED_MODE_TX_RX_ACTIVITY 0x1 +/* TX activity */ +#define DP83TD510E_LED_MODE_TX_ACTIVITY 0x2 +/* RX activity */ +#define DP83TD510E_LED_MODE_RX_ACTIVITY 0x3 +/* LR */ +#define DP83TD510E_LED_MODE_LR 0x4 +/* SR */ +#define DP83TD510E_LED_MODE_SR 0x5 +/* LED SPEED: High for 10Base-T */ +#define DP83TD510E_LED_MODE_LED_SPEED 0x6 +/* Duplex mode */ +#define DP83TD510E_LED_MODE_DUPLEX 0x7 +/* link + blink on activity with stretch option */ +#define DP83TD510E_LED_MODE_LINK_BLINK 0x8 +/* blink on activity with stretch option */ +#define DP83TD510E_LED_MODE_BLINK_ACTIVITY 0x9 +/* blink on tx activity with stretch option */ +#define DP83TD510E_LED_MODE_BLINK_TX 0xa +/* blink on rx activity with stretch option */ +#define DP83TD510E_LED_MODE_BLINK_RX 0xb +/* link_lost */ +#define DP83TD510E_LED_MODE_LINK_LOST 0xc +/* PRBS error: toggles on error */ +#define DP83TD510E_LED_MODE_PRBS_ERROR 0xd +/* XMII TX/RX Error with stretch option */ +#define DP83TD510E_LED_MODE_XMII_ERR 0xe + +#define DP83TD510E_LED_COUNT 4 + +#define DP83TD510E_LEDS_CFG_2 0x469 +#define DP83TD510E_LED_POLARITY(idx) BIT((idx) * 4 + 2) +#define DP83TD510E_LED_DRV_VAL(idx) BIT((idx) * 4 + 1) +#define DP83TD510E_LED_DRV_EN(idx) BIT((idx) * 4) + +#define DP83TD510E_ALCD_STAT 0xa9f +#define DP83TD510E_ALCD_COMPLETE BIT(15) +#define DP83TD510E_ALCD_CABLE_LENGTH GENMASK(10, 0) + +static int dp83td510_get_mse_capability(struct phy_device *phydev, + struct phy_mse_capability *cap) +{ + /* DP83TD510E documents only a single (average) MSE register + * (used to derive SQI); no peak or worst-peak counters are + * described. Advertise only PHY_MSE_CAP_AVG. + */ + cap->supported_caps = PHY_MSE_CAP_AVG; + /* 10BASE-T1L is a single-pair medium, so there are no B/C/D channels. + * We still advertise PHY_MSE_CAP_CHANNEL_A to indicate that the PHY + * can attribute the measurement to a specific pair (the only one), + * rather than exposing it only as a link-aggregate. + * + * Rationale: + * - Keeps the ethtool MSE_GET selection logic consistent: per-channel + * (A/B/C/D) is preferred over WORST/LINK, so userspace receives a + * CHANNEL_A nest instead of LINK. + * - Signals to tools that "per-pair" data is available (even if there's + * just one pair), avoiding the impression that only aggregate values + * are supported. + * - Remains compatible with multi-pair PHYs and uniform UI handling. + * + * Note: WORST and other channels are not advertised on 10BASE-T1L. + */ + cap->supported_caps |= PHY_MSE_CHANNEL_A | PHY_MSE_CAP_LINK; + cap->max_average_mse = DP83TD510E_MSE_MAX; + + /* The datasheet does not specify the refresh rate or symbol count, + * but based on similar PHYs and standards, we can assume a common + * value. For 10BASE-T1L, the symbol rate is 7.5 MBd. A common + * diagnostic interval is around 1ms. + * 7.5e6 symbols/sec * 0.001 sec = 7500 symbols. + */ + cap->refresh_rate_ps = 1000000000; /* 1 ms */ + cap->num_symbols = 7500; + + return 0; +} + +static int dp83td510_get_mse_snapshot(struct phy_device *phydev, + enum phy_mse_channel channel, + struct phy_mse_snapshot *snapshot) +{ + int ret; + + if (channel != PHY_MSE_CHANNEL_LINK && + channel != PHY_MSE_CHANNEL_A) + return -EOPNOTSUPP; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_MSE_DETECT); + if (ret < 0) + return ret; + + snapshot->average_mse = ret; + + return 0; +} + +static int dp83td510_led_brightness_set(struct phy_device *phydev, u8 index, + enum led_brightness brightness) +{ + u32 val; + + if (index >= DP83TD510E_LED_COUNT) + return -EINVAL; + + val = DP83TD510E_LED_DRV_EN(index); + + if (brightness) + val |= DP83TD510E_LED_DRV_VAL(index); + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_LEDS_CFG_2, + DP83TD510E_LED_DRV_VAL(index) | + DP83TD510E_LED_DRV_EN(index), val); +} + +static int dp83td510_led_mode(u8 index, unsigned long rules) +{ + if (index >= DP83TD510E_LED_COUNT) + return -EINVAL; + + switch (rules) { + case BIT(TRIGGER_NETDEV_LINK): + return DP83TD510E_LED_MODE_LINK_OK; + case BIT(TRIGGER_NETDEV_LINK_10): + return DP83TD510E_LED_MODE_LED_SPEED; + case BIT(TRIGGER_NETDEV_FULL_DUPLEX): + return DP83TD510E_LED_MODE_DUPLEX; + case BIT(TRIGGER_NETDEV_TX): + return DP83TD510E_LED_MODE_TX_ACTIVITY; + case BIT(TRIGGER_NETDEV_RX): + return DP83TD510E_LED_MODE_RX_ACTIVITY; + case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return DP83TD510E_LED_MODE_TX_RX_ACTIVITY; + case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX): + return DP83TD510E_LED_MODE_LINK_BLINK; + default: + return -EOPNOTSUPP; + } +} + +static int dp83td510_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int ret; + + ret = dp83td510_led_mode(index, rules); + if (ret < 0) + return ret; + + return 0; +} + +static int dp83td510_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int mode, ret; + + mode = dp83td510_led_mode(index, rules); + if (mode < 0) + return mode; + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_LEDS_CFG_1, + DP83TD510E_LED_FN_MASK(index), + DP83TD510E_LED_FN(index, mode)); + if (ret) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_LEDS_CFG_2, + DP83TD510E_LED_DRV_EN(index), 0); +} + +static int dp83td510_led_hw_control_get(struct phy_device *phydev, + u8 index, unsigned long *rules) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_LEDS_CFG_1); + if (val < 0) + return val; + + val &= DP83TD510E_LED_FN_MASK(index); + val >>= index * 4; + + switch (val) { + case DP83TD510E_LED_MODE_LINK_OK: + *rules = BIT(TRIGGER_NETDEV_LINK); + break; + /* LED mode: LED SPEED (10BaseT1L indicator) */ + case DP83TD510E_LED_MODE_LED_SPEED: + *rules = BIT(TRIGGER_NETDEV_LINK_10); + break; + case DP83TD510E_LED_MODE_DUPLEX: + *rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX); + break; + case DP83TD510E_LED_MODE_TX_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_TX); + break; + case DP83TD510E_LED_MODE_RX_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_RX); + break; + case DP83TD510E_LED_MODE_TX_RX_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); + break; + case DP83TD510E_LED_MODE_LINK_BLINK: + *rules = BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX); + break; + default: + *rules = 0; + break; + } + + return 0; +} + +static int dp83td510_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + u16 polarity = DP83TD510E_LED_POLARITY(index); + u32 mode; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + polarity = 0; + break; + case PHY_LED_ACTIVE_HIGH: + polarity = DP83TD510E_LED_POLARITY(index); + break; + default: + return -EINVAL; + } + } + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_LEDS_CFG_2, + DP83TD510E_LED_POLARITY(index), polarity); +} + +/** + * dp83td510_update_stats - Update the PHY statistics for the DP83TD510 PHY. + * @phydev: Pointer to the phy_device structure. + * + * The function reads the PHY statistics registers and updates the statistics + * structure. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83td510_update_stats(struct phy_device *phydev) +{ + struct dp83td510_priv *priv = phydev->priv; + u32 count; + int ret; + + /* The DP83TD510E_PKT_STAT registers are divided into two groups: + * - Group 1 (TX stats): DP83TD510E_PKT_STAT_1 to DP83TD510E_PKT_STAT_3 + * - Group 2 (RX stats): DP83TD510E_PKT_STAT_4 to DP83TD510E_PKT_STAT_6 + * + * Registers in each group are cleared only after reading them in a + * plain sequence (e.g., 1, 2, 3 for Group 1 or 4, 5, 6 for Group 2). + * Any deviation from the sequence, such as reading 1, 2, 1, 2, 3, will + * prevent the group from being cleared. Additionally, the counters + * for a group are frozen as soon as the first register in that group + * is accessed. + */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PKT_STAT_1); + if (ret < 0) + return ret; + /* tx_pkt_cnt_15_0 */ + count = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PKT_STAT_2); + if (ret < 0) + return ret; + /* tx_pkt_cnt_31_16 */ + count |= ret << 16; + priv->stats.tx_pkt_cnt += count; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PKT_STAT_3); + if (ret < 0) + return ret; + /* tx_err_pkt_cnt */ + priv->stats.tx_err_pkt_cnt += ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PKT_STAT_4); + if (ret < 0) + return ret; + /* rx_pkt_cnt_15_0 */ + count = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PKT_STAT_5); + if (ret < 0) + return ret; + /* rx_pkt_cnt_31_16 */ + count |= ret << 16; + priv->stats.rx_pkt_cnt += count; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PKT_STAT_6); + if (ret < 0) + return ret; + /* rx_err_pkt_cnt */ + priv->stats.rx_err_pkt_cnt += ret; + + return 0; +} + +static void dp83td510_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct dp83td510_priv *priv = phydev->priv; + + stats->tx_packets = priv->stats.tx_pkt_cnt; + stats->tx_errors = priv->stats.tx_err_pkt_cnt; + stats->rx_packets = priv->stats.rx_pkt_cnt; + stats->rx_errors = priv->stats.rx_err_pkt_cnt; +} + +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; +} + +/** + * dp83td510_cable_test_start - Start the cable test for the DP83TD510 PHY. + * @phydev: Pointer to the phy_device structure. + * + * This sequence is implemented according to the "Application Note DP83TD510E + * Cable Diagnostics Toolkit revC". + * + * Returns: 0 on success, a negative error code on failure. + */ +static int dp83td510_cable_test_start(struct phy_device *phydev) +{ + struct dp83td510_priv *priv = phydev->priv; + int ret; + + /* If link partner is active, we won't be able to use TDR, since + * we can't force link partner to be silent. The autonegotiation + * pulses will be too frequent and the TDR sequence will be + * too long. So, TDR will always fail. Since the link is established + * we already know that the cable is working, so we can get some + * extra information line the cable length using ALCD. + */ + if (phydev->link) { + priv->alcd_test_active = true; + return 0; + } + + priv->alcd_test_active = false; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_CTRL, + DP83TD510E_CTRL_HW_RESET); + if (ret) + return ret; + + ret = genphy_c45_an_disable_aneg(phydev); + if (ret) + return ret; + + /* Force master mode */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, + MDIO_PMA_PMD_BT1_CTRL_CFG_MST); + if (ret) + return ret; + + /* There is no official recommendation for this register, but it is + * better to use 1V for TDR since other values seems to be optimized + * for this amplitude. Except of amplitude, it is better to configure + * pre TDR silence time to 10ms to avoid false reflections (value 0 + * seems to be too short, otherwise we need to implement own silence + * time). Also, post TDR silence time should be set to 1000ms to avoid + * false link state detection, it fits to the polling time of the + * PHY framework. The idea is to wait until + * dp83td510_cable_test_get_status() will be called and reconfigure + * the PHY to the default state within the post silence time window. + */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG1, + DP83TD510E_TDR_TX_TYPE | + DP83TD510E_TDR_CFG1_POST_SILENCE_TIME | + DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME, + DP83TD510E_TDR_TX_TYPE_1V | + DP83TD510E_TDR_CFG1_PRE_SILENCE_TIME_10MS | + DP83TD510E_TDR_CFG1_POST_SILENCE_TIME_1000MS); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG2, + FIELD_PREP(DP83TD510E_TDR_END_TAP_INDEX_1, + DP83TD510E_TDR_END_TAP_INDEX_1_DEF) | + FIELD_PREP(DP83TD510E_TDR_START_TAP_INDEX_1, + DP83TD510E_TDR_START_TAP_INDEX_1_DEF)); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_FAULT_CFG1, + FIELD_PREP(DP83TD510E_TDR_FLT_LOC_OFFSET_1, + DP83TD510E_TDR_FLT_LOC_OFFSET_1_DEF) | + FIELD_PREP(DP83TD510E_TDR_FLT_INIT_1, + DP83TD510E_TDR_FLT_INIT_1_DEF)); + if (ret) + return ret; + + /* Undocumented register, from the "Application Note DP83TD510E Cable + * Diagnostics Toolkit revC". + */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_UNKN_030E, + DP83TD510E_030E_VAL); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG3, + DP83TD510E_TDR_TX_DURATION_US_DEF); + if (ret) + return ret; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_CTRL, + DP83TD510E_CTRL_SW_RESET); + if (ret) + return ret; + + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG, + DP83TD510E_TDR_START); +} + +/** + * dp83td510_cable_test_get_tdr_status - Get the status of the TDR test for the + * DP83TD510 PHY. + * @phydev: Pointer to the phy_device structure. + * @finished: Pointer to a boolean that indicates whether the test is finished. + * + * The function sets the @finished flag to true if the test is complete. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83td510_cable_test_get_tdr_status(struct phy_device *phydev, + bool *finished) +{ + int ret, stat; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG); + if (ret < 0) + return ret; + + if (!(ret & DP83TD510E_TDR_DONE)) + return 0; + + if (!(ret & DP83TD510E_TDR_FAIL)) { + int location; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + DP83TD510E_TDR_FAULT_STAT); + if (ret < 0) + return ret; + + if (ret & DP83TD510E_TDR_PEAK_DETECT) { + if (ret & DP83TD510E_TDR_PEAK_SIGN) + stat = ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + else + stat = ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + + location = FIELD_GET(DP83TD510E_TDR_PEAK_LOCATION, + ret) * 100; + ethnl_cable_test_fault_length(phydev, + ETHTOOL_A_CABLE_PAIR_A, + location); + } else { + stat = ETHTOOL_A_CABLE_RESULT_CODE_OK; + } + } else { + /* Most probably we have active link partner */ + stat = ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } + + *finished = true; + + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, stat); + + return phy_init_hw(phydev); +} + +/** + * dp83td510_cable_test_get_alcd_status - Get the status of the ALCD test for the + * DP83TD510 PHY. + * @phydev: Pointer to the phy_device structure. + * @finished: Pointer to a boolean that indicates whether the test is finished. + * + * The function sets the @finished flag to true if the test is complete. + * The function reads the cable length and reports it to the user. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83td510_cable_test_get_alcd_status(struct phy_device *phydev, + bool *finished) +{ + unsigned int location; + int ret, phy_sts; + + phy_sts = phy_read(phydev, DP83TD510E_PHY_STS); + + if (!(phy_sts & DP83TD510E_LINK_STATUS)) { + /* If the link is down, we can't do any thing usable now */ + ethnl_cable_test_result_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A, + ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC, + ETHTOOL_A_CABLE_INF_SRC_ALCD); + *finished = true; + return 0; + } + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_ALCD_STAT); + if (ret < 0) + return ret; + + if (!(ret & DP83TD510E_ALCD_COMPLETE)) + return 0; + + location = FIELD_GET(DP83TD510E_ALCD_CABLE_LENGTH, ret) * 100; + + ethnl_cable_test_fault_length_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A, + location, + ETHTOOL_A_CABLE_INF_SRC_ALCD); + + ethnl_cable_test_result_with_src(phydev, ETHTOOL_A_CABLE_PAIR_A, + ETHTOOL_A_CABLE_RESULT_CODE_OK, + ETHTOOL_A_CABLE_INF_SRC_ALCD); + *finished = true; + + return 0; +} + +/** + * dp83td510_cable_test_get_status - Get the status of the cable test for the + * DP83TD510 PHY. + * @phydev: Pointer to the phy_device structure. + * @finished: Pointer to a boolean that indicates whether the test is finished. + * + * The function sets the @finished flag to true if the test is complete. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83td510_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + struct dp83td510_priv *priv = phydev->priv; + *finished = false; + + if (priv->alcd_test_active) + return dp83td510_cable_test_get_alcd_status(phydev, finished); + + return dp83td510_cable_test_get_tdr_status(phydev, finished); +} + +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 int dp83td510_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct dp83td510_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + return 0; +} + +static struct phy_driver dp83td510_driver[] = { +{ + PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID), + .name = "TI DP83TD510E", + + .flags = PHY_POLL_CABLE_TEST, + .probe = dp83td510_probe, + .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, + .cable_test_start = dp83td510_cable_test_start, + .cable_test_get_status = dp83td510_cable_test_get_status, + .get_phy_stats = dp83td510_get_phy_stats, + .update_stats = dp83td510_update_stats, + + .get_mse_capability = dp83td510_get_mse_capability, + .get_mse_snapshot = dp83td510_get_mse_snapshot, + + .led_brightness_set = dp83td510_led_brightness_set, + .led_hw_is_supported = dp83td510_led_hw_is_supported, + .led_hw_control_set = dp83td510_led_hw_control_set, + .led_hw_control_get = dp83td510_led_hw_control_get, + .led_polarity_set = dp83td510_led_polarity_set, + + .suspend = genphy_suspend, + .resume = genphy_resume, +} }; +module_phy_driver(dp83td510_driver); + +static const 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 "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/dp83tg720.c b/drivers/net/phy/dp83tg720.c new file mode 100644 index 000000000..391c1d868 --- /dev/null +++ b/drivers/net/phy/dp83tg720.c @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Driver for the Texas Instruments DP83TG720 PHY + * Copyright (c) 2023 Pengutronix, Oleksij Rempel + */ +#include +#include +#include +#include +#include +#include +#include + +#include "open_alliance_helpers.h" + +/* + * DP83TG720 PHY Limitations and Workarounds + * + * The DP83TG720 1000BASE-T1 PHY has several limitations that require + * software-side mitigations. These workarounds are implemented throughout + * this driver. This section documents the known issues and their corresponding + * mitigation strategies. + * + * 1. Unreliable Link Detection and Synchronized Reset Deadlock + * ------------------------------------------------------------ + * After a link loss or during link establishment, the DP83TG720 PHY may fail + * to detect or report link status correctly. As of June 2025, no public + * errata sheet for the DP83TG720 PHY documents this behavior. + * The "DP83TC81x, DP83TG72x Software Implementation Guide" application note + * (SNLA404, available at https://www.ti.com/lit/an/snla404/snla404.pdf) + * recommends performing a soft restart if polling for a link fails to establish + * a connection after 100ms. This procedure is adopted as the workaround for the + * observed link detection issue. + * + * However, in point-to-point setups where both link partners use the same + * driver (e.g. Linux on both sides), a synchronized reset pattern may emerge. + * This leads to a deadlock, where both PHYs reset at the same time and + * continuously miss each other during auto-negotiation. + * + * To address this, the reset procedure includes two components: + * + * - A **fixed minimum delay of 1ms** after a hardware reset. The datasheet + * "DP83TG720S-Q1 1000BASE-T1 Automotive Ethernet PHY with SGMII and RGMII" + * specifies this as the "Post reset stabilization-time prior to MDC preamble + * for register access" (T6.2), ensuring the PHY is ready for MDIO + * operations. + * + * - An **additional asymmetric delay**, empirically chosen based on + * master/slave role. This reduces the risk of synchronized resets on both + * link partners. Values are selected to avoid periodic overlap and ensure + * the link is re-established within a few cycles. + * + * The functions that implement this logic are: + * - dp83tg720_soft_reset() + * - dp83tg720_get_next_update_time() + * + * 2. Polling-Based Link Detection and IRQ Support + * ----------------------------------------------- + * Due to the PHY-specific limitation described in section 1, link-up events + * cannot be reliably detected via interrupts on the DP83TG720. Therefore, + * polling is required to detect transitions from link-down to link-up. + * + * While link-down events *can* be detected via IRQs on this PHY, this driver + * currently does **not** implement interrupt support. As a result, all link + * state changes must be detected using polling. + * + * Polling behavior: + * - When the link is up: slow polling (e.g. 1s). + * - When the link just went down: fast polling for a short time. + * - When the link stays down: fallback to slow polling. + * + * This design balances responsiveness and CPU usage. It sacrifices fast link-up + * times in cases where the link is expected to remain down for extended periods, + * assuming that such systems do not require immediate reactivity. + */ + +/* + * DP83TG720S_POLL_ACTIVE_LINK - Polling interval in milliseconds when the link + * is active. + * DP83TG720S_POLL_NO_LINK - Polling interval in milliseconds when the + * link is down. + * DP83TG720S_FAST_POLL_DURATION_MS - Timeout in milliseconds for no-link + * polling after which polling interval is + * increased. + * DP83TG720S_POLL_SLOW - Slow polling interval when there is no + * link for a prolongued period. + * DP83TG720S_RESET_DELAY_MS_MASTER - Delay after a reset before attempting + * to establish a link again for master phy. + * DP83TG720S_RESET_DELAY_MS_SLAVE - Delay after a reset before attempting + * to establish a link again for slave phy. + * + * These values are not documented or officially recommended by the vendor but + * were determined through empirical testing. They achieve a good balance in + * minimizing the number of reset retries while ensuring reliable link recovery + * within a reasonable timeframe. + */ +#define DP83TG720S_POLL_ACTIVE_LINK 421 +#define DP83TG720S_POLL_NO_LINK 149 +#define DP83TG720S_FAST_POLL_DURATION_MS 6000 +#define DP83TG720S_POLL_SLOW 1117 +#define DP83TG720S_RESET_DELAY_MS_MASTER 97 +#define DP83TG720S_RESET_DELAY_MS_SLAVE 149 + +#define DP83TG720S_PHY_ID 0x2000a284 + +/* MDIO_MMD_VEND2 registers */ +#define DP83TG720S_MII_REG_10 0x10 +#define DP83TG720S_STS_MII_INT BIT(7) +#define DP83TG720S_LINK_STATUS BIT(0) + +/* TDR Configuration Register (0x1E) */ +#define DP83TG720S_TDR_CFG 0x1e +/* 1b = TDR start, 0b = No TDR */ +#define DP83TG720S_TDR_START BIT(15) +/* 1b = TDR auto on link down, 0b = Manual TDR start */ +#define DP83TG720S_CFG_TDR_AUTO_RUN BIT(14) +/* 1b = TDR done, 0b = TDR in progress */ +#define DP83TG720S_TDR_DONE BIT(1) +/* 1b = TDR fail, 0b = TDR success */ +#define DP83TG720S_TDR_FAIL BIT(0) + +#define DP83TG720S_PHY_RESET 0x1f +#define DP83TG720S_HW_RESET BIT(15) + +#define DP83TG720S_LPS_CFG3 0x18c +/* Power modes are documented as bit fields but used as values */ +/* Power Mode 0 is Normal mode */ +#define DP83TG720S_LPS_CFG3_PWR_MODE_0 BIT(0) + +/* Open Aliance 1000BaseT1 compatible HDD.TDR Fault Status Register */ +#define DP83TG720S_TDR_FAULT_STATUS 0x30f + +/* Register 0x0301: TDR Configuration 2 */ +#define DP83TG720S_TDR_CFG2 0x301 + +/* Register 0x0303: TDR Configuration 3 */ +#define DP83TG720S_TDR_CFG3 0x303 + +/* Register 0x0304: TDR Configuration 4 */ +#define DP83TG720S_TDR_CFG4 0x304 + +/* Register 0x0405: Unknown Register */ +#define DP83TG720S_UNKNOWN_0405 0x405 + +#define DP83TG720S_LINK_QUAL_3 0x547 +#define DP83TG720S_LINK_LOSS_CNT_MASK GENMASK(15, 10) + +/* Register 0x0576: TDR Master Link Down Control */ +#define DP83TG720S_TDR_MASTER_LINK_DOWN 0x576 + +#define DP83TG720S_RGMII_DELAY_CTRL 0x602 +/* In RGMII mode, Enable or disable the internal delay for RXD */ +#define DP83TG720S_RGMII_RX_CLK_SEL BIT(1) +/* In RGMII mode, Enable or disable the internal delay for TXD */ +#define DP83TG720S_RGMII_TX_CLK_SEL BIT(0) + +/* + * DP83TG720S_PKT_STAT_x registers correspond to similarly named registers + * in the datasheet (PKT_STAT_1 through PKT_STAT_6). These registers store + * 32-bit or 16-bit counters for TX and RX statistics and must be read in + * sequence to ensure the counters are cleared correctly. + * + * - DP83TG720S_PKT_STAT_1: Contains TX packet count bits [15:0]. + * - DP83TG720S_PKT_STAT_2: Contains TX packet count bits [31:16]. + * - DP83TG720S_PKT_STAT_3: Contains TX error packet count. + * - DP83TG720S_PKT_STAT_4: Contains RX packet count bits [15:0]. + * - DP83TG720S_PKT_STAT_5: Contains RX packet count bits [31:16]. + * - DP83TG720S_PKT_STAT_6: Contains RX error packet count. + * + * Keeping the register names as defined in the datasheet helps maintain + * clarity and alignment with the documentation. + */ +#define DP83TG720S_PKT_STAT_1 0x639 +#define DP83TG720S_PKT_STAT_2 0x63a +#define DP83TG720S_PKT_STAT_3 0x63b +#define DP83TG720S_PKT_STAT_4 0x63c +#define DP83TG720S_PKT_STAT_5 0x63d +#define DP83TG720S_PKT_STAT_6 0x63e + +/* Register 0x083F: Unknown Register */ +#define DP83TG720S_UNKNOWN_083F 0x83f + +#define DP83TG720S_SQI_REG_1 0x871 +#define DP83TG720S_SQI_OUT_WORST GENMASK(7, 5) +#define DP83TG720S_SQI_OUT GENMASK(3, 1) + +#define DP83TG720_SQI_MAX 7 + +struct dp83tg720_stats { + u64 link_loss_cnt; + u64 tx_pkt_cnt; + u64 tx_err_pkt_cnt; + u64 rx_pkt_cnt; + u64 rx_err_pkt_cnt; +}; + +struct dp83tg720_priv { + struct dp83tg720_stats stats; + unsigned long last_link_down_jiffies; +}; + +/** + * dp83tg720_update_stats - Update the PHY statistics for the DP83TD510 PHY. + * @phydev: Pointer to the phy_device structure. + * + * The function reads the PHY statistics registers and updates the statistics + * structure. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83tg720_update_stats(struct phy_device *phydev) +{ + struct dp83tg720_priv *priv = phydev->priv; + u32 count; + int ret; + + /* Read the link loss count */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_LINK_QUAL_3); + if (ret < 0) + return ret; + /* link_loss_cnt */ + count = FIELD_GET(DP83TG720S_LINK_LOSS_CNT_MASK, ret); + priv->stats.link_loss_cnt += count; + + /* The DP83TG720S_PKT_STAT registers are divided into two groups: + * - Group 1 (TX stats): DP83TG720S_PKT_STAT_1 to DP83TG720S_PKT_STAT_3 + * - Group 2 (RX stats): DP83TG720S_PKT_STAT_4 to DP83TG720S_PKT_STAT_6 + * + * Registers in each group are cleared only after reading them in a + * plain sequence (e.g., 1, 2, 3 for Group 1 or 4, 5, 6 for Group 2). + * Any deviation from the sequence, such as reading 1, 2, 1, 2, 3, will + * prevent the group from being cleared. Additionally, the counters + * for a group are frozen as soon as the first register in that group + * is accessed. + */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_PKT_STAT_1); + if (ret < 0) + return ret; + /* tx_pkt_cnt_15_0 */ + count = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_PKT_STAT_2); + if (ret < 0) + return ret; + /* tx_pkt_cnt_31_16 */ + count |= ret << 16; + priv->stats.tx_pkt_cnt += count; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_PKT_STAT_3); + if (ret < 0) + return ret; + /* tx_err_pkt_cnt */ + priv->stats.tx_err_pkt_cnt += ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_PKT_STAT_4); + if (ret < 0) + return ret; + /* rx_pkt_cnt_15_0 */ + count = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_PKT_STAT_5); + if (ret < 0) + return ret; + /* rx_pkt_cnt_31_16 */ + count |= ret << 16; + priv->stats.rx_pkt_cnt += count; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_PKT_STAT_6); + if (ret < 0) + return ret; + /* rx_err_pkt_cnt */ + priv->stats.rx_err_pkt_cnt += ret; + + return 0; +} + +static int dp83tg720_soft_reset(struct phy_device *phydev) +{ + int ret; + + ret = phy_write(phydev, DP83TG720S_PHY_RESET, DP83TG720S_HW_RESET); + if (ret) + return ret; + + /* Include mandatory MDC-access delay (1ms) + extra asymmetric delay to + * avoid synchronized reset deadlock. See section 1 in the top-of-file + * comment block. + */ + if (phydev->master_slave_state == MASTER_SLAVE_STATE_SLAVE) + msleep(DP83TG720S_RESET_DELAY_MS_SLAVE); + else + msleep(DP83TG720S_RESET_DELAY_MS_MASTER); + + return ret; +} + +static void dp83tg720_get_link_stats(struct phy_device *phydev, + struct ethtool_link_ext_stats *link_stats) +{ + struct dp83tg720_priv *priv = phydev->priv; + + link_stats->link_down_events = priv->stats.link_loss_cnt; +} + +static void dp83tg720_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct dp83tg720_priv *priv = phydev->priv; + + stats->tx_packets = priv->stats.tx_pkt_cnt; + stats->tx_errors = priv->stats.tx_err_pkt_cnt; + stats->rx_packets = priv->stats.rx_pkt_cnt; + stats->rx_errors = priv->stats.rx_err_pkt_cnt; +} + +/** + * dp83tg720_cable_test_start - Start the cable test for the DP83TG720 PHY. + * @phydev: Pointer to the phy_device structure. + * + * This sequence is based on the documented procedure for the DP83TG720 PHY. + * + * Returns: 0 on success, a negative error code on failure. + */ +static int dp83tg720_cable_test_start(struct phy_device *phydev) +{ + int ret; + + /* Initialize the PHY to run the TDR test as described in the + * "DP83TG720S-Q1: Configuring for Open Alliance Specification + * Compliance (Rev. B)" application note. + * Most of the registers are not documented. Some of register names + * are guessed by comparing the register offsets with the DP83TD510E. + */ + + /* Force master link down */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, + DP83TG720S_TDR_MASTER_LINK_DOWN, 0x0400); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG2, + 0xa008); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG3, + 0x0928); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG4, + 0x0004); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_UNKNOWN_0405, + 0x6400); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_UNKNOWN_083F, + 0x3003); + if (ret) + return ret; + + /* Start the TDR */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG, + DP83TG720S_TDR_START); + if (ret) + return ret; + + return 0; +} + +/** + * dp83tg720_cable_test_get_status - Get the status of the cable test for the + * DP83TG720 PHY. + * @phydev: Pointer to the phy_device structure. + * @finished: Pointer to a boolean that indicates whether the test is finished. + * + * The function sets the @finished flag to true if the test is complete. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83tg720_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + int ret, stat; + + *finished = false; + + /* Read the TDR status */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG); + if (ret < 0) + return ret; + + /* Check if the TDR test is done */ + if (!(ret & DP83TG720S_TDR_DONE)) + return 0; + + /* Check for TDR test failure */ + if (!(ret & DP83TG720S_TDR_FAIL)) { + int location; + + /* Read fault status */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + DP83TG720S_TDR_FAULT_STATUS); + if (ret < 0) + return ret; + + /* Get fault type */ + stat = oa_1000bt1_get_ethtool_cable_result_code(ret); + + /* Determine fault location */ + location = oa_1000bt1_get_tdr_distance(ret); + if (location > 0) + ethnl_cable_test_fault_length(phydev, + ETHTOOL_A_CABLE_PAIR_A, + location); + } else { + /* Active link partner or other issues */ + stat = ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } + + *finished = true; + + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, stat); + + /* save the current stats before resetting the PHY */ + ret = dp83tg720_update_stats(phydev); + if (ret) + return ret; + + return phy_init_hw(phydev); +} + +static int dp83tg720_config_aneg(struct phy_device *phydev) +{ + int ret; + + /* Autoneg is not supported and this PHY supports only one speed. + * We need to care only about master/slave configuration if it was + * changed by user. + */ + ret = genphy_c45_pma_baset1_setup_master_slave(phydev); + if (ret) + return ret; + + /* Re-read role configuration to make changes visible even if + * the link is in administrative down state. + */ + return genphy_c45_pma_baset1_read_master_slave(phydev); +} + +static int dp83tg720_read_status(struct phy_device *phydev) +{ + u16 phy_sts; + int ret; + + phydev->pause = 0; + phydev->asym_pause = 0; + + /* Most of Clause 45 registers are not present, so we can't use + * genphy_c45_read_status() here. + */ + phy_sts = phy_read(phydev, DP83TG720S_MII_REG_10); + phydev->link = !!(phy_sts & DP83TG720S_LINK_STATUS); + if (!phydev->link) { + /* save the current stats before resetting the PHY */ + ret = dp83tg720_update_stats(phydev); + if (ret) + return ret; + + /* According to the "DP83TC81x, DP83TG72x Software + * Implementation Guide", the PHY needs to be reset after a + * link loss or if no link is created after at least 100ms. + */ + ret = phy_init_hw(phydev); + if (ret) + return ret; + + /* After HW reset we need to restore master/slave configuration. + * genphy_c45_pma_baset1_read_master_slave() call will be done + * by the dp83tg720_config_aneg() function. + */ + ret = dp83tg720_config_aneg(phydev); + if (ret) + return ret; + + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + } else { + /* PMA/PMD control 1 register (Register 1.0) is present, but it + * doesn't contain the link speed information. + * So genphy_c45_read_pma() can't be used here. + */ + ret = genphy_c45_pma_baset1_read_master_slave(phydev); + if (ret) + return ret; + + phydev->duplex = DUPLEX_FULL; + phydev->speed = SPEED_1000; + } + + return 0; +} + +static int dp83tg720_get_sqi(struct phy_device *phydev) +{ + int ret; + + if (!phydev->link) + return 0; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_SQI_REG_1); + if (ret < 0) + return ret; + + return FIELD_GET(DP83TG720S_SQI_OUT, ret); +} + +static int dp83tg720_get_sqi_max(struct phy_device *phydev) +{ + return DP83TG720_SQI_MAX; +} + +static int dp83tg720_config_rgmii_delay(struct phy_device *phydev) +{ + u16 rgmii_delay_mask; + u16 rgmii_delay = 0; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + rgmii_delay = 0; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + rgmii_delay = DP83TG720S_RGMII_RX_CLK_SEL | + DP83TG720S_RGMII_TX_CLK_SEL; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + rgmii_delay = DP83TG720S_RGMII_RX_CLK_SEL; + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + rgmii_delay = DP83TG720S_RGMII_TX_CLK_SEL; + break; + default: + return 0; + } + + rgmii_delay_mask = DP83TG720S_RGMII_RX_CLK_SEL | + DP83TG720S_RGMII_TX_CLK_SEL; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + DP83TG720S_RGMII_DELAY_CTRL, rgmii_delay_mask, + rgmii_delay); +} + +static int dp83tg720_config_init(struct phy_device *phydev) +{ + int ret; + + /* Reset the PHY to recover from a link failure */ + ret = dp83tg720_soft_reset(phydev); + if (ret) + return ret; + + if (phy_interface_is_rgmii(phydev)) { + ret = dp83tg720_config_rgmii_delay(phydev); + if (ret) + return ret; + } + + /* In case the PHY is bootstrapped in managed mode, we need to + * wake it. + */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_LPS_CFG3, + DP83TG720S_LPS_CFG3_PWR_MODE_0); + if (ret) + return ret; + + /* Make role configuration visible for ethtool on init and after + * rest. + */ + return genphy_c45_pma_baset1_read_master_slave(phydev); +} + +static int dp83tg720_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct dp83tg720_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + return 0; +} + +/** + * dp83tg720_get_next_update_time - Return next polling interval for PHY state + * @phydev: Pointer to the phy_device structure + * + * Implements adaptive polling interval logic depending on link state and + * downtime duration. See the "2. Polling-Based Link Detection and IRQ Support" + * section at the top of this file for details. + * + * Return: Time (in jiffies) until the next poll + */ +static unsigned int dp83tg720_get_next_update_time(struct phy_device *phydev) +{ + struct dp83tg720_priv *priv = phydev->priv; + unsigned int next_time_jiffies; + + if (phydev->link) { + priv->last_link_down_jiffies = 0; + + /* When the link is up, use a slower interval (in jiffies) */ + next_time_jiffies = + msecs_to_jiffies(DP83TG720S_POLL_ACTIVE_LINK); + } else { + unsigned long now = jiffies; + + if (!priv->last_link_down_jiffies) + priv->last_link_down_jiffies = now; + + if (time_before(now, priv->last_link_down_jiffies + + msecs_to_jiffies(DP83TG720S_FAST_POLL_DURATION_MS))) { + /* Link recently went down: fast polling */ + next_time_jiffies = + msecs_to_jiffies(DP83TG720S_POLL_NO_LINK); + } else { + /* Link has been down for a while: slow polling */ + next_time_jiffies = + msecs_to_jiffies(DP83TG720S_POLL_SLOW); + } + } + + /* Ensure the polling time is at least one jiffy */ + return max(next_time_jiffies, 1U); +} + +static struct phy_driver dp83tg720_driver[] = { +{ + PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID), + .name = "TI DP83TG720S", + + .flags = PHY_POLL_CABLE_TEST, + .probe = dp83tg720_probe, + .soft_reset = dp83tg720_soft_reset, + .config_aneg = dp83tg720_config_aneg, + .read_status = dp83tg720_read_status, + .get_features = genphy_c45_pma_read_ext_abilities, + .config_init = dp83tg720_config_init, + .get_sqi = dp83tg720_get_sqi, + .get_sqi_max = dp83tg720_get_sqi_max, + .cable_test_start = dp83tg720_cable_test_start, + .cable_test_get_status = dp83tg720_cable_test_get_status, + .get_link_stats = dp83tg720_get_link_stats, + .get_phy_stats = dp83tg720_get_phy_stats, + .update_stats = dp83tg720_update_stats, + .get_next_update_time = dp83tg720_get_next_update_time, + + .suspend = genphy_suspend, + .resume = genphy_resume, +} }; +module_phy_driver(dp83tg720_driver); + +static const struct mdio_device_id __maybe_unused dp83tg720_tbl[] = { + { PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(mdio, dp83tg720_tbl); + +MODULE_DESCRIPTION("Texas Instruments DP83TG720S PHY driver"); +MODULE_AUTHOR("Oleksij Rempel "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/et1011c.c b/drivers/net/phy/et1011c.c new file mode 100644 index 000000000..6cd8d7758 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 const 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..0b83fb30a --- /dev/null +++ b/drivers/net/phy/fixed_phy.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) + * + * Author: Vitaly Bordug + * Anton Vorontsov + * + * Copyright (c) 2006-2007 MontaVista Software, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "swphy.h" + +/* The DSA loop driver may allocate 4 fixed PHY's, and 4 additional + * fixed PHY's for a system should be sufficient. + */ +#define NUM_FP 8 + +struct fixed_phy { + struct phy_device *phydev; + struct fixed_phy_status status; + int (*link_update)(struct net_device *, struct fixed_phy_status *); +}; + +static DECLARE_BITMAP(fixed_phy_ids, NUM_FP); +static struct fixed_phy fmb_fixed_phys[NUM_FP]; +static struct mii_bus *fmb_mii_bus; + +static struct fixed_phy *fixed_phy_find(int addr) +{ + return test_bit(addr, fixed_phy_ids) ? fmb_fixed_phys + addr : NULL; +} + +int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier) +{ + struct phy_device *phydev = dev->phydev; + struct fixed_phy *fp; + + if (!phydev || !phydev->mdio.bus) + return -EINVAL; + + fp = fixed_phy_find(phydev->mdio.addr); + if (!fp) + return -EINVAL; + + fp->status.link = new_carrier; + + return 0; +} +EXPORT_SYMBOL_GPL(fixed_phy_change_carrier); + +static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +{ + struct fixed_phy *fp; + + fp = fixed_phy_find(phy_addr); + if (!fp) + return 0xffff; + + if (fp->link_update) + fp->link_update(fp->phydev->attached_dev, &fp->status); + + return swphy_read_reg(reg_num, &fp->status); +} + +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_phy *fp; + + if (!phydev || !phydev->mdio.bus) + return -EINVAL; + + fp = fixed_phy_find(phydev->mdio.addr); + if (!fp) + return -ENOENT; + + fp->link_update = link_update; + fp->phydev = phydev; + + return 0; +} +EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); + +static void fixed_phy_del(int phy_addr) +{ + struct fixed_phy *fp; + + fp = fixed_phy_find(phy_addr); + if (!fp) + return; + + memset(fp, 0, sizeof(*fp)); + clear_bit(phy_addr, fixed_phy_ids); +} + +static int fixed_phy_get_free_addr(void) +{ + int addr; + + do { + addr = find_first_zero_bit(fixed_phy_ids, NUM_FP); + if (addr == NUM_FP) + return -ENOSPC; + } while (test_and_set_bit(addr, fixed_phy_ids)); + + return addr; +} + +struct phy_device *fixed_phy_register(const struct fixed_phy_status *status, + struct device_node *np) +{ + struct phy_device *phy; + int phy_addr; + int ret; + + ret = swphy_validate_state(status); + if (ret < 0) + return ERR_PTR(ret); + + if (!fmb_mii_bus || fmb_mii_bus->state != MDIOBUS_REGISTERED) + return ERR_PTR(-EPROBE_DEFER); + + /* Get the next available PHY address, up to NUM_FP */ + phy_addr = fixed_phy_get_free_addr(); + if (phy_addr < 0) + return ERR_PTR(phy_addr); + + fmb_fixed_phys[phy_addr].status = *status; + fmb_fixed_phys[phy_addr].status.link = true; + + phy = get_phy_device(fmb_mii_bus, phy_addr, false); + if (IS_ERR(phy)) { + fixed_phy_del(phy_addr); + return ERR_PTR(-EINVAL); + } + + of_node_get(np); + phy->mdio.dev.of_node = np; + phy->is_pseudo_fixed_link = true; + + ret = phy_device_register(phy); + if (ret) { + phy_device_free(phy); + of_node_put(np); + fixed_phy_del(phy_addr); + return ERR_PTR(ret); + } + + return phy; +} +EXPORT_SYMBOL_GPL(fixed_phy_register); + +struct phy_device *fixed_phy_register_100fd(void) +{ + static const struct fixed_phy_status status = { + .speed = SPEED_100, + .duplex = DUPLEX_FULL, + }; + + return fixed_phy_register(&status, NULL); +} +EXPORT_SYMBOL_GPL(fixed_phy_register_100fd); + +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); + phy_device_free(phy); +} +EXPORT_SYMBOL_GPL(fixed_phy_unregister); + +static int __init fixed_mdio_bus_init(void) +{ + int ret; + + fmb_mii_bus = mdiobus_alloc(); + if (!fmb_mii_bus) + return -ENOMEM; + + snprintf(fmb_mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); + fmb_mii_bus->name = "Fixed MDIO Bus"; + 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); + return ret; +} +module_init(fixed_mdio_bus_init); + +static void __exit fixed_mdio_bus_exit(void) +{ + mdiobus_unregister(fmb_mii_bus); + mdiobus_free(fmb_mii_bus); +} +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..c0c4f19cf --- /dev/null +++ b/drivers/net/phy/icplus.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for ICPlus PHYs + * + * Copyright (c) 2007 Freescale Semiconductor, Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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, + const struct phy_driver *phydrv) +{ + return ip101a_g_match_phy_device(phydev, true); +} + +static int ip101g_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + 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++) + ethtool_puts(&data, ip101g_hw_stats[i].name); +} + +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 const 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..afbcec711 --- /dev/null +++ b/drivers/net/phy/intel-xway.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Daniel Schwierzeck + * Copyright (C) 2016 Hauke Mehrtens + */ + +#include +#include +#include +#include +#include + +#define XWAY_MDIO_ERRCNT 0x15 /* error counter */ +#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_ERRCNT_SEL GENMASK(11, 8) +#define XWAY_MDIO_ERRCNT_COUNT GENMASK(7, 0) +#define XWAY_MDIO_ERRCNT_SEL_RXERR 0 + +#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 XWAY_GPHY_MAX_LEDS 3 +#define XWAY_GPHY_LED_INV(idx) BIT(12 + (idx)) +#define XWAY_GPHY_LED_EN(idx) BIT(8 + (idx)) +#define XWAY_GPHY_LED_DA(idx) BIT(idx) +#define XWAY_MMD_LEDxH(idx) (XWAY_MMD_LED0H + 2 * (idx)) +#define XWAY_MMD_LEDxL(idx) (XWAY_MMD_LED0L + 2 * (idx)) + +#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 + +struct xway_gphy_priv { + u64 rx_errors; +}; + +static const int xway_internal_delay[] = {0, 500, 1000, 1500, 2000, 2500, + 3000, 3500}; + +static int xway_gphy_rgmii_init(struct phy_device *phydev) +{ + 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, 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, 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_init_leds(struct phy_device *phydev) +{ + int err; + u32 ledxh; + u32 ledxl; + + /* 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); + + return 0; +} + +static int xway_gphy_config_init(struct phy_device *phydev) +{ + struct device_node *np; + int err; + + /* Mask all interrupts */ + err = phy_write(phydev, XWAY_MDIO_IMASK, 0); + if (err) + return err; + + /* Use default LED configuration if 'leds' node isn't defined */ + np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds"); + if (np) + of_node_put(np); + else + xway_gphy_init_leds(phydev); + + /* Clear all pending interrupts */ + phy_read(phydev, XWAY_MDIO_ISTAT); + + err = xway_gphy_rgmii_init(phydev); + if (err) + return err; + + /* Count MDI RX errors (SymbolErrorDuringCarrier) */ + return phy_write(phydev, XWAY_MDIO_ERRCNT, + FIELD_PREP(XWAY_MDIO_ERRCNT_SEL, XWAY_MDIO_ERRCNT_SEL_RXERR)); +} + +static int xway_gphy_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct xway_gphy_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + 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 int xway_gphy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + int ret; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + /* clear EN and set manual LED state */ + ret = phy_modify(phydev, XWAY_MDIO_LED, + ((value == LED_OFF) ? XWAY_GPHY_LED_EN(index) : 0) | + XWAY_GPHY_LED_DA(index), + (value == LED_OFF) ? 0 : XWAY_GPHY_LED_DA(index)); + if (ret) + return ret; + + /* clear HW LED setup */ + if (value == LED_OFF) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), 0); + if (ret) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), 0); + } else { + return 0; + } +} + +static const unsigned long supported_triggers = (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 xway_gphy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + /* activity triggers are not possible without combination with a link + * trigger. + */ + if (rules & (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) && + !(rules & (BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000)))) + return -EOPNOTSUPP; + + /* All other combinations of the supported triggers are allowed */ + if (rules & ~supported_triggers) + return -EOPNOTSUPP; + + return 0; +} + +static int xway_gphy_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int lval, hval; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + hval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index)); + if (hval < 0) + return hval; + + lval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index)); + if (lval < 0) + return lval; + + if (hval & XWAY_MMD_LEDxH_CON_LINK10) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); + + if (hval & XWAY_MMD_LEDxH_CON_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + + if (hval & XWAY_MMD_LEDxH_CON_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + + if ((hval & XWAY_MMD_LEDxH_CON_LINK10) && + (hval & XWAY_MMD_LEDxH_CON_LINK100) && + (hval & XWAY_MMD_LEDxH_CON_LINK1000)) + *rules |= BIT(TRIGGER_NETDEV_LINK); + + if (lval & XWAY_MMD_LEDxL_PULSE_TXACT) + *rules |= BIT(TRIGGER_NETDEV_TX); + + if (lval & XWAY_MMD_LEDxL_PULSE_RXACT) + *rules |= BIT(TRIGGER_NETDEV_RX); + + return 0; +} + +static int xway_gphy_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 hval = 0, lval = 0; + int ret; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_10)) + hval |= XWAY_MMD_LEDxH_CON_LINK10; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_100)) + hval |= XWAY_MMD_LEDxH_CON_LINK100; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + hval |= XWAY_MMD_LEDxH_CON_LINK1000; + + if (rules & BIT(TRIGGER_NETDEV_TX)) + lval |= XWAY_MMD_LEDxL_PULSE_TXACT; + + if (rules & BIT(TRIGGER_NETDEV_RX)) + lval |= XWAY_MMD_LEDxL_PULSE_RXACT; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), hval); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), lval); + if (ret) + return ret; + + return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_EN(index)); +} + +static int xway_gphy_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + bool force_active_low = false, force_active_high = false; + u32 mode; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + force_active_low = true; + break; + case PHY_LED_ACTIVE_HIGH: + force_active_high = true; + break; + default: + return -EINVAL; + } + } + + if (force_active_low) + return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index)); + + if (force_active_high) + return phy_clear_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index)); + + return -EINVAL; +} + +static int xway_gphy_update_stats(struct phy_device *phydev) +{ + struct xway_gphy_priv *priv = phydev->priv; + int ret; + + /* XWAY_MDIO_ERRCNT: 8-bit read-clear counter, SEL set to RXERR */ + ret = phy_read(phydev, XWAY_MDIO_ERRCNT); + if (ret < 0) + return ret; + + priv->rx_errors += FIELD_GET(XWAY_MDIO_ERRCNT_COUNT, ret); + + return 0; +} + +static void xway_gphy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct xway_gphy_priv *priv = phydev->priv; + + eth_stats->SymbolErrorDuringCarrier = priv->rx_errors; + stats->rx_errors = priv->rx_errors; +} + +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, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .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, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, +}; +module_phy_driver(xway_gphy); + +static const 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 + +/** + * 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..5251a61c8 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* 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 const 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..201dee1a1 --- /dev/null +++ b/drivers/net/phy/marvell-88q2xxx.c @@ -0,0 +1,1154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell 88Q2XXX automotive 100BASE-T1/1000BASE-T1 PHY driver + * + * Derived from Marvell Q222x API + * + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + */ +#include +#include +#include +#include +#include + +#define PHY_ID_88Q2220_REVB0 (MARVELL_PHY_ID_88Q2220 | 0x1) +#define PHY_ID_88Q2220_REVB1 (MARVELL_PHY_ID_88Q2220 | 0x2) +#define PHY_ID_88Q2220_REVB2 (MARVELL_PHY_ID_88Q2220 | 0x3) + +#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_AN_MV_STAT2 32794 +#define MDIO_MMD_AN_MV_STAT2_AN_RESOLVED 0x0800 +#define MDIO_MMD_AN_MV_STAT2_100BT1 0x2000 +#define MDIO_MMD_AN_MV_STAT2_1000BT1 0x4000 + +#define MDIO_MMD_PCS_MV_RESET_CTRL 32768 +#define MDIO_MMD_PCS_MV_RESET_CTRL_TX_DISABLE 0x8 + +#define MDIO_MMD_PCS_MV_INT_EN 32784 +#define MDIO_MMD_PCS_MV_INT_EN_LINK_UP 0x0040 +#define MDIO_MMD_PCS_MV_INT_EN_LINK_DOWN 0x0080 +#define MDIO_MMD_PCS_MV_INT_EN_100BT1 0x1000 + +#define MDIO_MMD_PCS_MV_GPIO_INT_STAT 32785 +#define MDIO_MMD_PCS_MV_GPIO_INT_STAT_LINK_UP 0x0040 +#define MDIO_MMD_PCS_MV_GPIO_INT_STAT_LINK_DOWN 0x0080 +#define MDIO_MMD_PCS_MV_GPIO_INT_STAT_100BT1_GEN 0x1000 + +#define MDIO_MMD_PCS_MV_GPIO_INT_CTRL 32787 +#define MDIO_MMD_PCS_MV_GPIO_INT_CTRL_TRI_DIS 0x0800 + +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL 32790 +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK GENMASK(7, 4) +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK GENMASK(3, 0) +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK 0x0 /* Link established */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_RX_TX 0x1 /* Link established, blink for rx or tx activity */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1 0x2 /* Blink 3x for 1000BT1 link established */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX_ON 0x3 /* Receive or transmit activity */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX 0x4 /* Blink on receive or transmit activity */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_TX 0x5 /* Transmit activity */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_COPPER 0x6 /* Copper Link established */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1_ON 0x7 /* 1000BT1 link established */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_OFF 0x8 /* Force off */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_ON 0x9 /* Force on */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_HIGHZ 0xa /* Force Hi-Z */ +#define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_BLINK 0xb /* Force blink */ + +#define MDIO_MMD_PCS_MV_TEMP_SENSOR1 32833 +#define MDIO_MMD_PCS_MV_TEMP_SENSOR1_RAW_INT 0x0001 +#define MDIO_MMD_PCS_MV_TEMP_SENSOR1_INT 0x0040 +#define MDIO_MMD_PCS_MV_TEMP_SENSOR1_INT_EN 0x0080 + +#define MDIO_MMD_PCS_MV_TEMP_SENSOR2 32834 +#define MDIO_MMD_PCS_MV_TEMP_SENSOR2_DIS_MASK 0xc000 + +#define MDIO_MMD_PCS_MV_TEMP_SENSOR3 32835 +#define MDIO_MMD_PCS_MV_TEMP_SENSOR3_INT_THRESH_MASK 0xff00 +#define MDIO_MMD_PCS_MV_TEMP_SENSOR3_MASK 0x00ff + +#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 + +#define MDIO_MMD_PCS_MV_100BT1_INT_EN 33042 +#define MDIO_MMD_PCS_MV_100BT1_INT_EN_LINKEVENT 0x0400 + +#define MDIO_MMD_PCS_MV_COPPER_INT_STAT 33043 +#define MDIO_MMD_PCS_MV_COPPER_INT_STAT_LINKEVENT 0x0400 + +#define MDIO_MMD_PCS_MV_RX_STAT 33328 + +#define MDIO_MMD_PCS_MV_TDR_RESET 65226 +#define MDIO_MMD_PCS_MV_TDR_RESET_TDR_RST 0x1000 + +#define MDIO_MMD_PCS_MV_TDR_OFF_SHORT_CABLE 65241 + +#define MDIO_MMD_PCS_MV_TDR_OFF_LONG_CABLE 65242 + +#define MDIO_MMD_PCS_MV_TDR_STATUS 65245 +#define MDIO_MMD_PCS_MV_TDR_STATUS_MASK 0x0003 +#define MDIO_MMD_PCS_MV_TDR_STATUS_OFF 0x0001 +#define MDIO_MMD_PCS_MV_TDR_STATUS_ON 0x0002 +#define MDIO_MMD_PCS_MV_TDR_STATUS_DIST_MASK 0xff00 +#define MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_MASK 0x00f0 +#define MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_SHORT 0x0030 +#define MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_OPEN 0x00e0 +#define MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_OK 0x0070 +#define MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_IN_PROGR 0x0080 +#define MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_NOISE 0x0050 + +#define MDIO_MMD_PCS_MV_TDR_OFF_CUTOFF 65246 + +#define MV88Q2XXX_LED_INDEX_TX_ENABLE 0 +#define MV88Q2XXX_LED_INDEX_GPIO 1 + +struct mv88q2xxx_priv { + bool enable_led0; +}; + +struct mmd_val { + int devad; + u32 regnum; + u16 val; +}; + +static const struct mmd_val mv88q2110_init_seq0[] = { + { MDIO_MMD_PCS, 0xffe4, 0x07b5 }, + { MDIO_MMD_PCS, 0xffe4, 0x06b6 }, +}; + +static const struct mmd_val mv88q2110_init_seq1[] = { + { MDIO_MMD_PCS, 0xffde, 0x402f }, + { MDIO_MMD_PCS, 0xfe34, 0x4040 }, + { MDIO_MMD_PCS, 0xfe2a, 0x3c1d }, + { MDIO_MMD_PCS, 0xfe34, 0x0040 }, + { MDIO_MMD_AN, 0x8032, 0x0064 }, + { MDIO_MMD_AN, 0x8031, 0x0a01 }, + { MDIO_MMD_AN, 0x8031, 0x0c01 }, + { MDIO_MMD_PCS, 0xffdb, 0x0010 }, +}; + +static const struct mmd_val mv88q222x_revb0_init_seq0[] = { + { MDIO_MMD_PCS, 0x8033, 0x6801 }, + { MDIO_MMD_AN, MDIO_AN_T1_CTRL, 0x0 }, + { MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_LPOWER | MDIO_PMA_CTRL1_SPEED1000 }, + { MDIO_MMD_PCS, 0xfe1b, 0x48 }, + { MDIO_MMD_PCS, 0xffe4, 0x6b6 }, + { MDIO_MMD_PMAPMD, MDIO_CTRL1, 0x0 }, + { MDIO_MMD_PCS, MDIO_CTRL1, 0x0 }, +}; + +static const struct mmd_val mv88q222x_revb0_init_seq1[] = { + { MDIO_MMD_PCS, 0xfe79, 0x0 }, + { MDIO_MMD_PCS, 0xfe07, 0x125a }, + { MDIO_MMD_PCS, 0xfe09, 0x1288 }, + { MDIO_MMD_PCS, 0xfe08, 0x2588 }, + { MDIO_MMD_PCS, 0xfe11, 0x1105 }, + { MDIO_MMD_PCS, 0xfe72, 0x042c }, + { MDIO_MMD_PCS, 0xfbba, 0xcb2 }, + { MDIO_MMD_PCS, 0xfbbb, 0xc4a }, + { MDIO_MMD_AN, 0x8032, 0x2020 }, + { MDIO_MMD_AN, 0x8031, 0xa28 }, + { MDIO_MMD_AN, 0x8031, 0xc28 }, + { MDIO_MMD_PCS, 0xffdb, 0xfc10 }, + { MDIO_MMD_PCS, 0xfe1b, 0x58 }, + { MDIO_MMD_PCS, 0xfe79, 0x4 }, + { MDIO_MMD_PCS, 0xfe5f, 0xe8 }, + { MDIO_MMD_PCS, 0xfe05, 0x755c }, +}; + +static const struct mmd_val mv88q222x_revb1_init_seq0[] = { + { MDIO_MMD_PCS, 0xffe4, 0x0007 }, + { MDIO_MMD_AN, MDIO_AN_T1_CTRL, 0x0 }, + { MDIO_MMD_PCS, 0xffe3, 0x7000 }, + { MDIO_MMD_PMAPMD, MDIO_CTRL1, 0x0840 }, +}; + +static const struct mmd_val mv88q222x_revb2_init_seq0[] = { + { MDIO_MMD_PCS, 0xffe4, 0x0007 }, + { MDIO_MMD_AN, MDIO_AN_T1_CTRL, 0x0 }, + { MDIO_MMD_PMAPMD, MDIO_CTRL1, 0x0840 }, +}; + +static const struct mmd_val mv88q222x_revb1_revb2_init_seq1[] = { + { MDIO_MMD_PCS, 0xfe07, 0x125a }, + { MDIO_MMD_PCS, 0xfe09, 0x1288 }, + { MDIO_MMD_PCS, 0xfe08, 0x2588 }, + { MDIO_MMD_PCS, 0xfe72, 0x042c }, + { MDIO_MMD_PCS, 0xffe4, 0x0071 }, + { MDIO_MMD_PCS, 0xffe4, 0x0001 }, + { MDIO_MMD_PCS, 0xfe1b, 0x0048 }, + { MDIO_MMD_PMAPMD, 0x0000, 0x0000 }, + { MDIO_MMD_PCS, 0x0000, 0x0000 }, + { MDIO_MMD_PCS, 0xffdb, 0xfc10 }, + { MDIO_MMD_PCS, 0xfe1b, 0x58 }, + { MDIO_MMD_PCS, 0xfcad, 0x030c }, + { MDIO_MMD_PCS, 0x8032, 0x6001 }, + { MDIO_MMD_PCS, 0xfdff, 0x05a5 }, + { MDIO_MMD_PCS, 0xfdec, 0xdbaf }, + { MDIO_MMD_PCS, 0xfcab, 0x1054 }, + { MDIO_MMD_PCS, 0xfcac, 0x1483 }, + { MDIO_MMD_PCS, 0x8033, 0xc801 }, + { MDIO_MMD_AN, 0x8032, 0x2020 }, + { MDIO_MMD_AN, 0x8031, 0xa28 }, + { MDIO_MMD_AN, 0x8031, 0xc28 }, + { MDIO_MMD_PCS, 0xfbba, 0x0cb2 }, + { MDIO_MMD_PCS, 0xfbbb, 0x0c4a }, + { MDIO_MMD_PCS, 0xfe5f, 0xe8 }, + { MDIO_MMD_PCS, 0xfe05, 0x755c }, + { MDIO_MMD_PCS, 0xfa20, 0x002a }, + { MDIO_MMD_PCS, 0xfe11, 0x1105 }, +}; + +static int mv88q2xxx_write_mmd_vals(struct phy_device *phydev, + const struct mmd_val *vals, size_t len) +{ + int ret; + + for (; len; vals++, len--) { + ret = phy_write_mmd(phydev, vals->devad, vals->regnum, + vals->val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int mv88q2xxx_soft_reset(struct phy_device *phydev) +{ + int ret; + int val; + + /* Enable RESET of DCL */ + if (phydev->autoneg == AUTONEG_ENABLE || phydev->speed == SPEED_1000) { + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, 0xfe1b, 0x48); + if (ret < 0) + return ret; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_1000BT1_CTRL, + MDIO_PCS_1000BT1_CTRL_RESET); + if (ret < 0) + return ret; + + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PCS, + MDIO_PCS_1000BT1_CTRL, val, + !(val & MDIO_PCS_1000BT1_CTRL_RESET), + 50000, 600000, true); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, 0xffe4, 0xc); + if (ret < 0) + return ret; + + /* Disable RESET of DCL */ + if (phydev->autoneg == AUTONEG_ENABLE || phydev->speed == SPEED_1000) + return phy_write_mmd(phydev, MDIO_MMD_PCS, 0xfe1b, 0x58); + + return 0; +} + +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. However, when not in polling mode the local and remote + * receiver status are not evaluated due to the Marvell 88Q2xxx APIs. + */ + 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)) || + !phy_polling_mode(phydev)) { + /* The link state is latched low so that momentary link + * drops can be detected. Do not double-read the status + * in polling mode to detect such short link drops 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 = false; + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_100BT1_STAT2); + if (ret < 0) + return ret; + + if (ret & MDIO_MMD_PCS_MV_100BT1_STAT2_LINK) + phydev->link = true; + + return 0; + } else if (!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) +{ + /* 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) + return mv88q2xxx_read_link_gbit(phydev); + else if (phydev->speed == SPEED_100) + return mv88q2xxx_read_link_100m(phydev); + + phydev->link = false; + return 0; +} + +static int mv88q2xxx_read_master_slave_state(struct phy_device *phydev) +{ + int ret; + + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_MMD_AN_MV_STAT); + if (ret < 0) + return ret; + + if (ret & MDIO_MMD_AN_MV_STAT_LOCAL_MASTER) + phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; + else + phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; + + return 0; +} + +static int mv88q2xxx_read_aneg_speed(struct phy_device *phydev) +{ + int ret; + + phydev->speed = SPEED_UNKNOWN; + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_MMD_AN_MV_STAT2); + if (ret < 0) + return ret; + + if (!(ret & MDIO_MMD_AN_MV_STAT2_AN_RESOLVED)) + return 0; + + if (ret & MDIO_MMD_AN_MV_STAT2_100BT1) + phydev->speed = SPEED_100; + else if (ret & MDIO_MMD_AN_MV_STAT2_1000BT1) + phydev->speed = SPEED_1000; + + return 0; +} + +static int mv88q2xxx_read_status(struct phy_device *phydev) +{ + int ret; + + if (phydev->autoneg == AUTONEG_ENABLE) { + /* We have to get the negotiated speed first, otherwise we are + * not able to read the link. + */ + ret = mv88q2xxx_read_aneg_speed(phydev); + if (ret < 0) + return ret; + + ret = mv88q2xxx_read_link(phydev); + if (ret < 0) + return ret; + + ret = genphy_c45_read_lpa(phydev); + if (ret < 0) + return ret; + + ret = genphy_c45_baset1_read_status(phydev); + if (ret < 0) + return ret; + + ret = mv88q2xxx_read_master_slave_state(phydev); + if (ret < 0) + return ret; + + phy_resolve_aneg_linkmode(phydev); + + return 0; + } + + 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; + + return 0; +} + +static int mv88q2xxx_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_config_aneg(phydev); + if (ret) + return ret; + + return phydev->drv->soft_reset(phydev); +} + +static int mv88q2xxx_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, + MDIO_MMD_PCS_MV_RX_STAT); + 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, 0xff, 0xac); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, 0xfc88); + if (ret < 0) + return ret; + } + + return ret & 0x0f; +} + +static int mv88q2xxx_get_sqi_max(struct phy_device *phydev) +{ + return 15; +} + +static int mv88q2xxx_config_intr(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + /* Enable interrupts for 1000BASE-T1 link up and down events + * and enable general interrupts for 100BASE-T1. + */ + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_INT_EN, + MDIO_MMD_PCS_MV_INT_EN_LINK_UP | + MDIO_MMD_PCS_MV_INT_EN_LINK_DOWN | + MDIO_MMD_PCS_MV_INT_EN_100BT1); + if (ret < 0) + return ret; + + /* Enable interrupts for 100BASE-T1 link events */ + return phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_100BT1_INT_EN, + MDIO_MMD_PCS_MV_100BT1_INT_EN_LINKEVENT); + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_INT_EN, 0); + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_100BT1_INT_EN, 0); + } +} + +static irqreturn_t mv88q2xxx_handle_interrupt(struct phy_device *phydev) +{ + bool trigger_machine = false; + int irq; + + /* Before we can acknowledge the 100BT1 general interrupt, that is in + * the 1000BT1 interrupt status register, we have to acknowledge any + * interrupts that are related to it. Therefore we read first the 100BT1 + * interrupt status register, followed by reading the 1000BT1 interrupt + * status register. + */ + + irq = phy_read_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_COPPER_INT_STAT); + if (irq < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + /* Check link status for 100BT1 */ + if (irq & MDIO_MMD_PCS_MV_COPPER_INT_STAT_LINKEVENT) + trigger_machine = true; + + irq = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_GPIO_INT_STAT); + if (irq < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + /* Check link status for 1000BT1 */ + if ((irq & MDIO_MMD_PCS_MV_GPIO_INT_STAT_LINK_UP) || + (irq & MDIO_MMD_PCS_MV_GPIO_INT_STAT_LINK_DOWN)) + trigger_machine = true; + + if (!trigger_machine) + return IRQ_NONE; + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int mv88q2xxx_suspend(struct phy_device *phydev) +{ + int ret; + + /* Disable PHY interrupts */ + if (phy_interrupt_is_valid(phydev)) { + phydev->interrupts = PHY_INTERRUPT_DISABLED; + ret = mv88q2xxx_config_intr(phydev); + if (ret) + return ret; + } + + return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_LPOWER); +} + +static int mv88q2xxx_resume(struct phy_device *phydev) +{ + int ret; + + /* Enable PHY interrupts */ + if (phy_interrupt_is_valid(phydev)) { + phydev->interrupts = PHY_INTERRUPT_ENABLED; + ret = mv88q2xxx_config_intr(phydev); + if (ret) + return ret; + } + + return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_LPOWER); +} + +#if IS_ENABLED(CONFIG_HWMON) +static int mv88q2xxx_enable_temp_sense(struct phy_device *phydev) +{ + return phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_TEMP_SENSOR2, + MDIO_MMD_PCS_MV_TEMP_SENSOR2_DIS_MASK, 0); +} + +static const struct hwmon_channel_info * const mv88q2xxx_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_ALARM), + NULL +}; + +static umode_t mv88q2xxx_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_input: + return 0444; + case hwmon_temp_max: + return 0644; + case hwmon_temp_alarm: + return 0444; + default: + return 0; + } +} + +static int mv88q2xxx_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct phy_device *phydev = dev_get_drvdata(dev); + int ret; + + switch (attr) { + case hwmon_temp_input: + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TEMP_SENSOR3); + if (ret < 0) + return ret; + + ret = FIELD_GET(MDIO_MMD_PCS_MV_TEMP_SENSOR3_MASK, ret); + *val = (ret - 75) * 1000; + return 0; + case hwmon_temp_max: + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TEMP_SENSOR3); + if (ret < 0) + return ret; + + ret = FIELD_GET(MDIO_MMD_PCS_MV_TEMP_SENSOR3_INT_THRESH_MASK, + ret); + *val = (ret - 75) * 1000; + return 0; + case hwmon_temp_alarm: + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TEMP_SENSOR1); + if (ret < 0) + return ret; + + *val = !!(ret & MDIO_MMD_PCS_MV_TEMP_SENSOR1_RAW_INT); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int mv88q2xxx_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct phy_device *phydev = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_temp_max: + val = clamp(val, -75000, 180000); + val = (val / 1000) + 75; + val = FIELD_PREP(MDIO_MMD_PCS_MV_TEMP_SENSOR3_INT_THRESH_MASK, + val); + return phy_modify_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TEMP_SENSOR3, + MDIO_MMD_PCS_MV_TEMP_SENSOR3_INT_THRESH_MASK, + val); + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops mv88q2xxx_hwmon_hwmon_ops = { + .is_visible = mv88q2xxx_hwmon_is_visible, + .read = mv88q2xxx_hwmon_read, + .write = mv88q2xxx_hwmon_write, +}; + +static const struct hwmon_chip_info mv88q2xxx_hwmon_chip_info = { + .ops = &mv88q2xxx_hwmon_hwmon_ops, + .info = mv88q2xxx_hwmon_info, +}; + +static int mv88q2xxx_hwmon_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct device *hwmon; + int ret; + + ret = mv88q2xxx_enable_temp_sense(phydev); + if (ret < 0) + return ret; + + hwmon = devm_hwmon_device_register_with_info(dev, NULL, phydev, + &mv88q2xxx_hwmon_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon); +} + +#else +static int mv88q2xxx_enable_temp_sense(struct phy_device *phydev) +{ + return 0; +} + +static int mv88q2xxx_hwmon_probe(struct phy_device *phydev) +{ + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_OF_MDIO) +static int mv88q2xxx_leds_probe(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct mv88q2xxx_priv *priv = phydev->priv; + struct device_node *leds; + int ret = 0; + u32 index; + + if (!node) + return 0; + + leds = of_get_child_by_name(node, "leds"); + if (!leds) + return 0; + + for_each_available_child_of_node_scoped(leds, led) { + ret = of_property_read_u32(led, "reg", &index); + if (ret) + goto exit; + + if (index > MV88Q2XXX_LED_INDEX_GPIO) { + ret = -EINVAL; + goto exit; + } + + if (index == MV88Q2XXX_LED_INDEX_TX_ENABLE) + priv->enable_led0 = true; + } + +exit: + of_node_put(leds); + + return ret; +} + +#else +static int mv88q2xxx_leds_probe(struct phy_device *phydev) +{ + return 0; +} +#endif + +static int mv88q2xxx_probe(struct phy_device *phydev) +{ + struct mv88q2xxx_priv *priv; + int ret; + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + ret = mv88q2xxx_leds_probe(phydev); + if (ret) + return ret; + + return mv88q2xxx_hwmon_probe(phydev); +} + +static int mv88q2xxx_config_init(struct phy_device *phydev) +{ + struct mv88q2xxx_priv *priv = phydev->priv; + 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; + + /* Configure interrupt with default settings, output is driven low for + * active interrupt and high for inactive. + */ + if (phy_interrupt_is_valid(phydev)) { + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_GPIO_INT_CTRL, + MDIO_MMD_PCS_MV_GPIO_INT_CTRL_TRI_DIS); + if (ret < 0) + return ret; + } + + /* Enable LED function and disable TX disable feature on LED/TX_ENABLE */ + if (priv->enable_led0) { + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_RESET_CTRL, + MDIO_MMD_PCS_MV_RESET_CTRL_TX_DISABLE); + if (ret < 0) + return ret; + } + + /* Enable temperature sense again. There might have been a hard reset + * of the PHY and in this case the register content is restored to + * defaults and we need to enable it again. + */ + ret = mv88q2xxx_enable_temp_sense(phydev); + if (ret < 0) + return ret; + + return 0; +} + +static int mv88q2110_config_init(struct phy_device *phydev) +{ + int ret; + + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q2110_init_seq0, + ARRAY_SIZE(mv88q2110_init_seq0)); + if (ret < 0) + return ret; + + usleep_range(5000, 10000); + + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q2110_init_seq1, + ARRAY_SIZE(mv88q2110_init_seq1)); + if (ret < 0) + return ret; + + return mv88q2xxx_config_init(phydev); +} + +static int mv88q222x_revb0_config_init(struct phy_device *phydev) +{ + int ret; + + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q222x_revb0_init_seq0, + ARRAY_SIZE(mv88q222x_revb0_init_seq0)); + if (ret < 0) + return ret; + + usleep_range(5000, 10000); + + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q222x_revb0_init_seq1, + ARRAY_SIZE(mv88q222x_revb0_init_seq1)); + if (ret < 0) + return ret; + + return mv88q2xxx_config_init(phydev); +} + +static int mv88q222x_revb1_revb2_config_init(struct phy_device *phydev) +{ + bool is_rev_b1 = phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] == PHY_ID_88Q2220_REVB1; + int ret; + + if (is_rev_b1) + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q222x_revb1_init_seq0, + ARRAY_SIZE(mv88q222x_revb1_init_seq0)); + else + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q222x_revb2_init_seq0, + ARRAY_SIZE(mv88q222x_revb2_init_seq0)); + if (ret < 0) + return ret; + + usleep_range(3000, 5000); + + ret = mv88q2xxx_write_mmd_vals(phydev, mv88q222x_revb1_revb2_init_seq1, + ARRAY_SIZE(mv88q222x_revb1_revb2_init_seq1)); + if (ret < 0) + return ret; + + return mv88q2xxx_config_init(phydev); +} + +static int mv88q222x_config_init(struct phy_device *phydev) +{ + if (phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] == PHY_ID_88Q2220_REVB0) + return mv88q222x_revb0_config_init(phydev); + else + return mv88q222x_revb1_revb2_config_init(phydev); +} + +static int mv88q222x_cable_test_start(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TDR_OFF_CUTOFF, 0x0058); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TDR_OFF_LONG_CABLE, 0x00eb); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_TDR_OFF_SHORT_CABLE, 0x010e); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_TDR_RESET, + 0x0d90); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_TDR_STATUS, + MDIO_MMD_PCS_MV_TDR_STATUS_ON); + if (ret < 0) + return ret; + + /* According to the Marvell API the test is finished within 500 ms */ + msleep(500); + + return 0; +} + +static int mv88q222x_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + int ret, status; + u32 dist; + + status = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_TDR_STATUS); + if (status < 0) + return status; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_TDR_RESET, + MDIO_MMD_PCS_MV_TDR_RESET_TDR_RST | 0xd90); + if (ret < 0) + return ret; + + /* Test could not be finished */ + if (FIELD_GET(MDIO_MMD_PCS_MV_TDR_STATUS_MASK, status) != + MDIO_MMD_PCS_MV_TDR_STATUS_OFF) + return -ETIMEDOUT; + + *finished = true; + /* Fault length reported in meters, convert to centimeters */ + dist = FIELD_GET(MDIO_MMD_PCS_MV_TDR_STATUS_DIST_MASK, status) * 100; + switch (status & MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_MASK) { + case MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_OPEN: + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + ETHTOOL_A_CABLE_RESULT_CODE_OPEN); + ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, + dist); + break; + case MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_SHORT: + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT); + ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, + dist); + break; + case MDIO_MMD_PCS_MV_TDR_STATUS_VCT_STAT_OK: + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + ETHTOOL_A_CABLE_RESULT_CODE_OK); + break; + default: + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC); + } + + return 0; +} + +static int mv88q2xxx_led_mode(u8 index, unsigned long rules) +{ + switch (rules) { + case BIT(TRIGGER_NETDEV_LINK): + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK; + case BIT(TRIGGER_NETDEV_LINK_1000): + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1_ON; + case BIT(TRIGGER_NETDEV_TX): + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_TX; + case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX; + case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_RX_TX; + default: + return -EOPNOTSUPP; + } +} + +static int mv88q2xxx_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int mode; + + mode = mv88q2xxx_led_mode(index, rules); + if (mode < 0) + return mode; + + return 0; +} + +static int mv88q2xxx_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int mode; + + mode = mv88q2xxx_led_mode(index, rules); + if (mode < 0) + return mode; + + if (index == MV88Q2XXX_LED_INDEX_TX_ENABLE) + return phy_modify_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_LED_FUNC_CTRL, + MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK, + FIELD_PREP(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK, + mode)); + else + return phy_modify_mmd(phydev, MDIO_MMD_PCS, + MDIO_MMD_PCS_MV_LED_FUNC_CTRL, + MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK, + FIELD_PREP(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK, + mode)); +} + +static int mv88q2xxx_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_LED_FUNC_CTRL); + if (val < 0) + return val; + + if (index == MV88Q2XXX_LED_INDEX_TX_ENABLE) + val = FIELD_GET(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK, val); + else + val = FIELD_GET(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK, val); + + switch (val) { + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK: + *rules = BIT(TRIGGER_NETDEV_LINK); + break; + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1_ON: + *rules = BIT(TRIGGER_NETDEV_LINK_1000); + break; + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_TX: + *rules = BIT(TRIGGER_NETDEV_TX); + break; + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX: + *rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); + break; + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_RX_TX: + *rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX); + break; + default: + *rules = 0; + break; + } + + return 0; +} + +static struct phy_driver mv88q2xxx_driver[] = { + { + .phy_id = MARVELL_PHY_ID_88Q2110, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "mv88q2110", + .probe = mv88q2xxx_probe, + .get_features = mv88q2xxx_get_features, + .config_aneg = mv88q2xxx_config_aneg, + .config_init = mv88q2110_config_init, + .read_status = mv88q2xxx_read_status, + .soft_reset = mv88q2xxx_soft_reset, + .set_loopback = genphy_c45_loopback, + .get_sqi = mv88q2xxx_get_sqi, + .get_sqi_max = mv88q2xxx_get_sqi_max, + }, + { + .phy_id = MARVELL_PHY_ID_88Q2220, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "mv88q2220", + .flags = PHY_POLL_CABLE_TEST, + .probe = mv88q2xxx_probe, + .get_features = mv88q2xxx_get_features, + .config_aneg = mv88q2xxx_config_aneg, + .aneg_done = genphy_c45_aneg_done, + .config_init = mv88q222x_config_init, + .read_status = mv88q2xxx_read_status, + .soft_reset = mv88q2xxx_soft_reset, + .config_intr = mv88q2xxx_config_intr, + .handle_interrupt = mv88q2xxx_handle_interrupt, + .set_loopback = genphy_c45_loopback, + .cable_test_start = mv88q222x_cable_test_start, + .cable_test_get_status = mv88q222x_cable_test_get_status, + .get_sqi = mv88q2xxx_get_sqi, + .get_sqi_max = mv88q2xxx_get_sqi_max, + .suspend = mv88q2xxx_suspend, + .resume = mv88q2xxx_resume, + .led_hw_is_supported = mv88q2xxx_led_hw_is_supported, + .led_hw_control_set = mv88q2xxx_led_hw_control_set, + .led_hw_control_get = mv88q2xxx_led_hw_control_get, + }, +}; + +module_phy_driver(mv88q2xxx_driver); + +static const struct mdio_device_id __maybe_unused mv88q2xxx_tbl[] = { + { MARVELL_PHY_ID_88Q2110, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88Q2220, 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..ba1bbb6c6 --- /dev/null +++ b/drivers/net/phy/marvell-88x2222.c @@ -0,0 +1,604 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +/* 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_configure_serdes(struct phy_port *port, bool enable, + phy_interface_t interface) +{ + struct phy_device *phydev = port_phydev(port); + struct mv2222_data *priv; + int ret = 0; + + priv = phydev->priv; + priv->line_interface = interface; + + if (enable) { + linkmode_and(priv->supported, phydev->supported, port->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); + } + + } else { + linkmode_zero(priv->supported); + } + + return ret; +} + +static void mv2222_port_link_up(struct phy_port *port) +{ + struct phy_device *phydev = port_phydev(port); + struct mv2222_data *priv; + + priv = phydev->priv; + priv->sfp_link = true; +} + +static void mv2222_port_link_down(struct phy_port *port) +{ + struct phy_device *phydev = port_phydev(port); + struct mv2222_data *priv; + + priv = phydev->priv; + priv->sfp_link = false; +} + +static const struct phy_port_ops mv2222_port_ops = { + .link_up = mv2222_port_link_up, + .link_down = mv2222_port_link_down, + .configure_mii = mv2222_configure_serdes, +}; + +static int mv2222_attach_mii_port(struct phy_device *phydev, struct phy_port *port) +{ + port->ops = &mv2222_port_ops; + + __set_bit(PHY_INTERFACE_MODE_10GBASER, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces); + + return 0; +} + +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 0; +} + +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, + .attach_mii_port = mv2222_attach_mii_port, + }, +}; +module_phy_driver(mv2222_drivers); + +static const 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..f71cffa88 --- /dev/null +++ b/drivers/net/phy/marvell.c @@ -0,0 +1,4205 @@ +// 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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_M1011_PHY_STATUS_MDIX BIT(6) + +#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 MII_VCT_TXPINS 0x1A +#define MII_VCT_RXPINS 0x1B +#define MII_VCT_SR 0x1C +#define MII_VCT_TXPINS_ENVCT BIT(15) +#define MII_VCT_TXRXPINS_VCTTST GENMASK(14, 13) +#define MII_VCT_TXRXPINS_VCTTST_SHIFT 13 +#define MII_VCT_TXRXPINS_VCTTST_OK 0 +#define MII_VCT_TXRXPINS_VCTTST_SHORT 1 +#define MII_VCT_TXRXPINS_VCTTST_OPEN 2 +#define MII_VCT_TXRXPINS_VCTTST_FAIL 3 +#define MII_VCT_TXRXPINS_AMPRFLN GENMASK(12, 8) +#define MII_VCT_TXRXPINS_AMPRFLN_SHIFT 8 +#define MII_VCT_TXRXPINS_DISTRFLN GENMASK(7, 0) +#define MII_VCT_TXRXPINS_DISTRFLN_MAX 0xff + +#define M88E3082_PAIR_A BIT(0) +#define M88E3082_PAIR_B BIT(1) + +#define LPA_PAUSE_FIBER 0x180 +#define LPA_PAUSE_ASYM_FIBER 0x100 + +#define NB_FIBER_STATS 1 +#define NB_STAT_MAX 3 + +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 const 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}, +}; + +static_assert(ARRAY_SIZE(marvell_hw_stats) <= NB_STAT_MAX); + +/* "simple" stat list + corresponding marvell_get_*_simple functions are used + * on PHYs without a page register + */ +struct marvell_hw_stat_simple { + const char *string; + u8 reg; + u8 bits; +}; + +static const struct marvell_hw_stat_simple marvell_hw_stats_simple[] = { + { "phy_receive_errors", 21, 16}, +}; + +static_assert(ARRAY_SIZE(marvell_hw_stats_simple) <= NB_STAT_MAX); + +enum { + M88E3082_VCT_OFF, + M88E3082_VCT_PHASE1, + M88E3082_VCT_PHASE2, +}; + +struct marvell_priv { + u64 stats[NB_STAT_MAX]; + char *hwmon_name; + struct device *hwmon_dev; + bool cable_test_tdr; + u32 first; + u32 last; + u32 step; + s8 pair; + u8 vct_phase; +}; + +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 = ,...; + * + * There may be one or more sets of : + * + * 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 unsigned int m88e1111_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + /* In 1000base-X and SGMII modes, the inband mode can be changed + * through the Fibre page BMCR ANENABLE bit. + */ + if (interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_SGMII) + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE | + LINK_INBAND_BYPASS; + + return 0; +} + +static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes) +{ + u16 extsr, bmcr; + int err; + + if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX && + phydev->interface != PHY_INTERFACE_MODE_SGMII) + return -EINVAL; + + if (modes == LINK_INBAND_BYPASS) + extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS; + else + extsr = 0; + + if (modes == LINK_INBAND_DISABLE) + bmcr = 0; + else + bmcr = BMCR_ANENABLE; + + err = phy_modify(phydev, MII_M1111_PHY_EXT_SR, + MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr); + if (err < 0) + return err; + + return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR, + BMCR_ANENABLE, bmcr); +} + +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. + * FIXME: this does not actually enable 1000BaseX auto-negotiation if + * it was previously disabled in the Fiber BMCR! + */ + 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) +{ + 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 + */ + if (phydev->eee_cfg.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 (fiber) { + phydev->mdix = ETH_TP_MDI_INVALID; + } else { + /* The MDI-X state is set regardless of Autoneg being enabled + * and reflects forced MDI-X state as well as auto resolution + */ + if (status & MII_M1011_PHY_STATUS_RESOLVED) + phydev->mdix = status & MII_M1011_PHY_STATUS_MDIX ? + ETH_TP_MDI_X : ETH_TP_MDI; + else + phydev->mdix = ETH_TP_MDI_INVALID; + } + + 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; +} + +/* m88e1510_resume + * + * The 88e1510 PHY has an erratum where the phy downshift counter is not cleared + * after phy being suspended(BMCR_PDOWN set) and then later resumed(BMCR_PDOWN + * cleared). This can cause the link to intermittently downshift to a lower speed. + * + * Disabling and re-enabling the downshift feature clears the counter, allowing + * the PHY to retry gigabit link negotiation up to the programmed retry count + * before downshifting. This behavior has been observed on copper links. + */ +static int m88e1510_resume(struct phy_device *phydev) +{ + int err; + u8 cnt = 0; + + err = marvell_resume(phydev); + if (err < 0) + return err; + + /* read downshift counter value */ + err = m88e1011_get_downshift(phydev, &cnt); + if (err < 0) + return err; + + if (cnt) { + /* downshift disabled */ + err = m88e1011_set_downshift(phydev, 0); + if (err < 0) + return err; + + /* downshift enabled, with previous counter value */ + err = m88e1011_set_downshift(phydev, cnt); + } + + 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 int marvell_get_sset_count_simple(struct phy_device *phydev) +{ + return ARRAY_SIZE(marvell_hw_stats_simple); +} + +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++) + ethtool_puts(&data, marvell_hw_stats[i].string); +} + +static void marvell_get_strings_simple(struct phy_device *phydev, u8 *data) +{ + int count = marvell_get_sset_count_simple(phydev); + int i; + + for (i = 0; i < count; i++) + ethtool_puts(&data, marvell_hw_stats_simple[i].string); +} + +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 u64 marvell_get_stat_simple(struct phy_device *phydev, int i) +{ + struct marvell_hw_stat_simple stat = marvell_hw_stats_simple[i]; + struct marvell_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 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 void marvell_get_stats_simple(struct phy_device *phydev, + struct ethtool_stats *stats, u64 *data) +{ + int count = marvell_get_sset_count_simple(phydev); + int i; + + for (i = 0; i < count; i++) + data[i] = marvell_get_stat_simple(phydev, i); +} + +static int m88e1510_loopback(struct phy_device *phydev, bool enable, int speed) +{ + u16 bmcr_ctl, mscr2_ctl = 0; + int err; + + if (!enable) + return genphy_loopback(phydev, enable, 0); + + if (speed == SPEED_10 || speed == SPEED_100 || speed == SPEED_1000) + phydev->speed = speed; + else if (speed) + return -EINVAL; + + 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 loopback + * mode. + */ + msleep(1000); + } + return err; +} + +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; +} + +static int m88e3082_vct_cable_test_start(struct phy_device *phydev) +{ + struct marvell_priv *priv = phydev->priv; + int ret; + + /* It needs some magic workarounds described in VCT manual for this PHY. + */ + ret = phy_write(phydev, 29, 0x0003); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x6440); + if (ret < 0) + return ret; + + if (priv->vct_phase == M88E3082_VCT_PHASE1) { + ret = phy_write(phydev, 29, 0x000a); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0002); + if (ret < 0) + return ret; + } + + ret = phy_write(phydev, MII_BMCR, + BMCR_RESET | BMCR_SPEED100 | BMCR_FULLDPLX); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MII_VCT_TXPINS, MII_VCT_TXPINS_ENVCT); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 29, 0x0003); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0); + if (ret < 0) + return ret; + + if (priv->vct_phase == M88E3082_VCT_OFF) { + priv->vct_phase = M88E3082_VCT_PHASE1; + priv->pair = 0; + + return 0; + } + + ret = phy_write(phydev, 29, 0x000a); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0); + if (ret < 0) + return ret; + + priv->vct_phase = M88E3082_VCT_PHASE2; + + return 0; +} + +static int m88e3082_vct_cable_test_report_trans(int result, u8 distance) +{ + switch (result) { + case MII_VCT_TXRXPINS_VCTTST_OK: + if (distance == MII_VCT_TXRXPINS_DISTRFLN_MAX) + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + return ETHTOOL_A_CABLE_RESULT_CODE_IMPEDANCE_MISMATCH; + case MII_VCT_TXRXPINS_VCTTST_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + case MII_VCT_TXRXPINS_VCTTST_OPEN: + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + default: + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } +} + +static u32 m88e3082_vct_distrfln_2_cm(u8 distrfln) +{ + if (distrfln < 24) + return 0; + + /* Original function for meters: y = 0.7861x - 18.862 */ + return (7861 * distrfln - 188620) / 100; +} + +static int m88e3082_vct_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + u8 tx_vcttst_res, rx_vcttst_res, tx_distrfln, rx_distrfln; + struct marvell_priv *priv = phydev->priv; + int ret, tx_result, rx_result; + bool done_phase = true; + + *finished = false; + + ret = phy_read(phydev, MII_VCT_TXPINS); + if (ret < 0) + return ret; + else if (ret & MII_VCT_TXPINS_ENVCT) + return 0; + + tx_distrfln = ret & MII_VCT_TXRXPINS_DISTRFLN; + tx_vcttst_res = (ret & MII_VCT_TXRXPINS_VCTTST) >> + MII_VCT_TXRXPINS_VCTTST_SHIFT; + + ret = phy_read(phydev, MII_VCT_RXPINS); + if (ret < 0) + return ret; + + rx_distrfln = ret & MII_VCT_TXRXPINS_DISTRFLN; + rx_vcttst_res = (ret & MII_VCT_TXRXPINS_VCTTST) >> + MII_VCT_TXRXPINS_VCTTST_SHIFT; + + *finished = true; + + switch (priv->vct_phase) { + case M88E3082_VCT_PHASE1: + tx_result = m88e3082_vct_cable_test_report_trans(tx_vcttst_res, + tx_distrfln); + rx_result = m88e3082_vct_cable_test_report_trans(rx_vcttst_res, + rx_distrfln); + + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + tx_result); + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B, + rx_result); + + if (tx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN) { + done_phase = false; + priv->pair |= M88E3082_PAIR_A; + } else if (tx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_A; + u32 cm = m88e3082_vct_distrfln_2_cm(tx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + + if (rx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN) { + done_phase = false; + priv->pair |= M88E3082_PAIR_B; + } else if (rx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_B; + u32 cm = m88e3082_vct_distrfln_2_cm(rx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + + break; + case M88E3082_VCT_PHASE2: + if (priv->pair & M88E3082_PAIR_A && + tx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN && + tx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_A; + u32 cm = m88e3082_vct_distrfln_2_cm(tx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + if (priv->pair & M88E3082_PAIR_B && + rx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN && + rx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_B; + u32 cm = m88e3082_vct_distrfln_2_cm(rx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + + break; + default: + return -EINVAL; + } + + if (!done_phase) { + *finished = false; + return m88e3082_vct_cable_test_start(phydev); + } + if (*finished) + priv->vct_phase = M88E3082_VCT_OFF; + return 0; +} + +static int m88e1111_vct_cable_test_start(struct phy_device *phydev) +{ + int ret; + + ret = marvell_cable_test_start_common(phydev); + if (ret) + return ret; + + /* It needs some magic workarounds described in VCT manual for this PHY. + */ + ret = phy_write(phydev, 29, 0x0018); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x00c2); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x00ca); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x00c2); + if (ret < 0) + return ret; + + ret = phy_write_paged(phydev, MII_MARVELL_COPPER_PAGE, MII_VCT_SR, + MII_VCT_TXPINS_ENVCT); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 29, 0x0018); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0042); + if (ret < 0) + return ret; + + return 0; +} + +static u32 m88e1111_vct_distrfln_2_cm(u8 distrfln) +{ + if (distrfln < 36) + return 0; + + /* Original function for meters: y = 0.8018x - 28.751 */ + return (8018 * distrfln - 287510) / 100; +} + +static int m88e1111_vct_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + u8 vcttst_res, distrfln; + int ret, result; + + *finished = false; + + /* Each pair use one page: A-0, B-1, C-2, D-3 */ + for (u8 i = 0; i < 4; i++) { + ret = phy_read_paged(phydev, i, MII_VCT_SR); + if (ret < 0) + return ret; + else if (i == 0 && ret & MII_VCT_TXPINS_ENVCT) + return 0; + + distrfln = ret & MII_VCT_TXRXPINS_DISTRFLN; + vcttst_res = (ret & MII_VCT_TXRXPINS_VCTTST) >> + MII_VCT_TXRXPINS_VCTTST_SHIFT; + + result = m88e3082_vct_cable_test_report_trans(vcttst_res, + distrfln); + ethnl_cable_test_result(phydev, i, result); + + if (distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u32 cm = m88e1111_vct_distrfln_2_cm(distrfln); + + ethnl_cable_test_fault_length(phydev, i, cm); + } + } + + *finished = true; + 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; + } +} + +/* 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 const struct hwmon_channel_info * const marvell_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_MAX_ALARM), + 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_port_configure_serdes(struct phy_port *port, bool enable, + phy_interface_t interface) +{ + struct phy_device *phydev = port_phydev(port); + struct device *dev; + int oldpage; + int ret = 0; + u16 mode; + + dev = &phydev->mdio.dev; + + if (enable) { + 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; + } + } else { + mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII; + } + + 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 const struct phy_port_ops m88e1510_serdes_port_ops = { + .configure_mii = m88e1510_port_configure_serdes, +}; + +static int m88e1510_attach_mii_port(struct phy_device *phy_device, + struct phy_port *port) +{ + port->ops = &m88e1510_serdes_port_ops; + + __set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_100BASEX, port->interfaces); + + return 0; +} + +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_88E3082, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E308X/88E609X Family", + /* PHY_BASIC_FEATURES */ + .probe = marvell_probe, + .config_init = marvell_config_init, + .aneg_done = marvell_aneg_done, + .read_status = marvell_read_status, + .resume = genphy_resume, + .suspend = genphy_suspend, + .cable_test_start = m88e3082_vct_cable_test_start, + .cable_test_get_status = m88e3082_vct_cable_test_get_status, + }, + { + .phy_id = MARVELL_PHY_ID_88E1112, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1112", + /* PHY_GBIT_FEATURES */ + .probe = marvell_probe, + .inband_caps = m88e1111_inband_caps, + .config_inband = m88e1111_config_inband, + .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 */ + .flags = PHY_POLL_CABLE_TEST, + .probe = marvell_probe, + .inband_caps = m88e1111_inband_caps, + .config_inband = m88e1111_config_inband, + .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, + .cable_test_start = m88e1111_vct_cable_test_start, + .cable_test_get_status = m88e1111_vct_cable_test_get_status, + }, + { + .phy_id = MARVELL_PHY_ID_88E1111_FINISAR, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1111 (Finisar)", + /* PHY_GBIT_FEATURES */ + .probe = marvell_probe, + .inband_caps = m88e1111_inband_caps, + .config_inband = m88e1111_config_inband, + .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 */ + .flags = PHY_POLL_CABLE_TEST, + .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, + .cable_test_start = m88e1111_vct_cable_test_start, + .cable_test_get_status = m88e1111_vct_cable_test_get_status, + }, + { + .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 = marvell_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 = m88e1510_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, + .attach_mii_port = m88e1510_attach_mii_port, + }, + { + .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_88E6250_FAMILY, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E6250 Family", + /* PHY_BASIC_FEATURES */ + .probe = marvell_probe, + .aneg_done = marvell_aneg_done, + .config_intr = marvell_config_intr, + .handle_interrupt = marvell_handle_interrupt, + .resume = genphy_resume, + .suspend = genphy_suspend, + .get_sset_count = marvell_get_sset_count_simple, + .get_strings = marvell_get_strings_simple, + .get_stats = marvell_get_stats_simple, + }, + { + .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 const struct mdio_device_id __maybe_unused marvell_tbl[] = { + { MARVELL_PHY_ID_88E1101, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E3082, 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_88E6250_FAMILY, 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..b40df8215 --- /dev/null +++ b/drivers/net/phy/marvell10g.c @@ -0,0 +1,1486 @@ +// 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 -- + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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_mactype { + bool valid; + bool fixed_interface; + phy_interface_t interface_10g; +}; + +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); + + const struct mv3310_mactype *mactypes; + size_t n_mactypes; + +#ifdef CONFIG_HWMON + int (*hwmon_read_temp_reg)(struct phy_device *phydev); +#endif +}; + +struct mv3310_priv { + DECLARE_BITMAP(supported_interfaces, PHY_INTERFACE_MODE_MAX); + const struct mv3310_mactype *mactype; + + u32 firmware_ver; + bool has_downshift; + + 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 const struct hwmon_channel_info * const mv3310_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + 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_attach_mii_port(struct phy_device *phydev, + struct phy_port *port) +{ + __set_bit(PHY_INTERFACE_MODE_10GBASER, port->interfaces); + return 0; +} + +static int mv3310_attach_mdi_port(struct phy_device *phydev, + struct phy_port *port) +{ + /* This PHY can do combo-ports, i.e. 2 MDI outputs, usually one + * of them going to an SFP and the other one to a RJ45 + * connector. If we don't have any representation for the port + * in DT, and we are dealing with a non-SFP port, then we + * mask the port's capabilities to report BaseT-only modes + */ + if (port->not_described) + return phy_port_restrict_mediums(port, + BIT(ETHTOOL_LINK_MEDIUM_BASET)); + + return 0; +} + +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); + + phydev->max_n_ports = 2; + + return 0; +} + +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 const struct mv3310_mactype mv2110_mactypes[] = { + [MV_PMA_21X0_PORT_CTRL_MACTYPE_USXGMII] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_USXGMII, + }, + [MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_NA, + }, + [MV_PMA_21X0_PORT_CTRL_MACTYPE_5GBASER_NO_SGMII_AN] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_NA, + }, + [MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, +}; + +static const struct mv3310_mactype mv3310_mactypes[] = { + [MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_RXAUI, + }, + [MV_V2_3310_PORT_CTRL_MACTYPE_XAUI_RATE_MATCH] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_XAUI, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI_RATE_MATCH] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_RXAUI, + }, + [MV_V2_3310_PORT_CTRL_MACTYPE_XAUI] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_XAUI, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_NO_SGMII_AN] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_USXGMII] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_USXGMII, + }, +}; + +static const struct mv3310_mactype mv3340_mactypes[] = { + [MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_RXAUI, + }, + [MV_V2_3340_PORT_CTRL_MACTYPE_RXAUI_NO_SGMII_AN] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_RXAUI, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_RXAUI_RATE_MATCH] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_RXAUI, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_NO_SGMII_AN] = { + .valid = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_10GBASER, + }, + [MV_V2_33X0_PORT_CTRL_MACTYPE_USXGMII] = { + .valid = true, + .fixed_interface = true, + .interface_10g = PHY_INTERFACE_MODE_USXGMII, + }, +}; + +static void mv3310_fill_possible_interfaces(struct phy_device *phydev) +{ + struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev); + unsigned long *possible = phydev->possible_interfaces; + const struct mv3310_mactype *mactype = priv->mactype; + + if (mactype->interface_10g != PHY_INTERFACE_MODE_NA) + __set_bit(priv->mactype->interface_10g, possible); + + if (!mactype->fixed_interface) { + __set_bit(PHY_INTERFACE_MODE_5GBASER, possible); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, possible); + __set_bit(PHY_INTERFACE_MODE_SGMII, possible); + } +} + +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; + + if (mactype >= chip->n_mactypes || !chip->mactypes[mactype].valid) { + phydev_err(phydev, "MACTYPE configuration invalid\n"); + return -EINVAL; + } + + priv->mactype = &chip->mactypes[mactype]; + + mv3310_fill_possible_interfaces(phydev); + + /* 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->mactype->fixed_interface) { + phydev->interface = priv->mactype->interface_10g; + 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->mactype->interface_10g; + 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, + + .mactypes = mv3310_mactypes, + .n_mactypes = ARRAY_SIZE(mv3310_mactypes), + +#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, + + .mactypes = mv3340_mactypes, + .n_mactypes = ARRAY_SIZE(mv3340_mactypes), + +#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, + + .mactypes = mv2110_mactypes, + .n_mactypes = ARRAY_SIZE(mv2110_mactypes), + +#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, + + .mactypes = mv2110_mactypes, + .n_mactypes = ARRAY_SIZE(mv2110_mactypes), + +#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, + const struct phy_driver *phydrv) +{ + 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, + const struct phy_driver *phydrv) +{ + 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, + const struct phy_driver *phydrv) +{ + return mv211x_match_phy_device(phydev, true); +} + +static int mv2111_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + 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, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, + }, + { + .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, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, + }, + { + .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, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, + }, + { + .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, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, + }, +}; + +module_phy_driver(mv3310_drivers); + +static const 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-open-alliance.h b/drivers/net/phy/mdio-open-alliance.h new file mode 100644 index 000000000..449d0fb67 --- /dev/null +++ b/drivers/net/phy/mdio-open-alliance.h @@ -0,0 +1,95 @@ +/* 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 + +/* 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 + +/* + * Open Alliance TC14 (10BASE-T1S) - Advanced Diagnostic Features Registers + * + * Refer to the OPEN Alliance documentation: + * https://opensig.org/automotive-ethernet-specifications/ + * + * Specification: + * "10BASE-T1S Advanced Diagnostic PHY Features" + * https://opensig.org/wp-content/uploads/2025/06/OPEN_Alliance_10BASE-T1S_Advanced_PHY_features_for-automotive_Ethernet_V2.1b.pdf + */ +/* Advanced Diagnostic Features Capability Register*/ +#define MDIO_OATC14_ADFCAP 0xcc00 +#define OATC14_ADFCAP_HDD_CAPABILITY GENMASK(10, 8) +#define OATC14_ADFCAP_SQIPLUS_CAPABILITY GENMASK(4, 1) +#define OATC14_ADFCAP_SQI_CAPABILITY BIT(0) + +/* Harness Defect Detection Register */ +#define MDIO_OATC14_HDD 0xcc01 +#define OATC14_HDD_CONTROL BIT(15) +#define OATC14_HDD_READY BIT(14) +#define OATC14_HDD_START_CONTROL BIT(13) +#define OATC14_HDD_VALID BIT(2) +#define OATC14_HDD_SHORT_OPEN_STATUS GENMASK(1, 0) + +/* Dynamic Channel Quality SQI Register */ +#define MDIO_OATC14_DCQ_SQI 0xcc03 +#define OATC14_DCQ_SQI_VALUE GENMASK(2, 0) + +/* Dynamic Channel Quality SQI Plus Register */ +#define MDIO_OATC14_DCQ_SQIPLUS 0xcc04 +#define OATC14_DCQ_SQIPLUS_VALUE GENMASK(7, 0) + +/* SQI is supported using 3 bits means 8 levels (0-7) */ +#define OATC14_SQI_MAX_LEVEL 7 + +/* Bus Short/Open Status: + * 0 0 - no fault; everything is ok. (Default) + * 0 1 - detected as an open or missing termination(s) + * 1 0 - detected as a short or extra termination(s) + * 1 1 - fault but fault type not detectable. More details can be available by + * vender specific register if supported. + */ +enum oatc14_hdd_status { + OATC14_HDD_STATUS_CABLE_OK = 0, + OATC14_HDD_STATUS_OPEN, + OATC14_HDD_STATUS_SHORT, + OATC14_HDD_STATUS_NOT_DETECTABLE, +}; + +#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..00d0e4159 --- /dev/null +++ b/drivers/net/phy/mdio_bus.c @@ -0,0 +1,627 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +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); + +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 + * + * Return: The register value if successful, negative error code on failure + * + * 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 (addr >= PHY_MAX_ADDR) + return -ENXIO; + + 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 + * + * Return: Zero if successful, negative error code on failure + * + * 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 (addr >= PHY_MAX_ADDR) + return -ENXIO; + + 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 + * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition + * + * Read, modify, and if any change, write the register value back to the + * device. + * + * 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 + * + * Return: The register value if successful, negative error code on failure + * + * 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 (addr >= PHY_MAX_ADDR) + return -ENXIO; + + 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 + * + * Return: Zero if successful, negative error code on failure + * + * 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 (addr >= PHY_MAX_ADDR) + return -ENXIO; + + 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 + * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition + * + * 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 + * + * Return: The register value if successful, negative error code on failure + * + * 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 + * + * Return: The register value if successful, negative error code on failure + * + * 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 + * + * Return: The register value if successful, negative error code on failure + * + * 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 + * + * Return: The register value if successful, negative error code on failure + * + * 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 + * + * Return: Zero if successful, negative error code on failure + * + * 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 + * + * Return: Zero if successful, negative error code on failure + * + * 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 + * + * Return: Zero if successful, negative error code on failure + * + * 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 + * + * Return: Zero if successful, negative error code on failure + * + * 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 + * + * Return: 0 on success, negative on any error condition + */ +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 + * + * Return: 0 on success, negative on any error condition + */ +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 + * + * Return: 0 on success, negative on any error condition + */ +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 + * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition + */ +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 + * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition + */ +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); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MDIO bus/device layer"); diff --git a/drivers/net/phy/mdio_bus_provider.c b/drivers/net/phy/mdio_bus_provider.c new file mode 100644 index 000000000..ce3a607a4 --- /dev/null +++ b/drivers/net/phy/mdio_bus_provider.c @@ -0,0 +1,761 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* MDIO Bus provider interface + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phylib-internal.h" + +/** + * 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); + + if (bus->state == MDIOBUS_RELEASED) + fwnode_handle_put(dev_fwnode(d)); + + kfree(bus); +} + +struct mdio_bus_stat_attr { + struct device_attribute attr; + int address; + unsigned int field_offset; +}; + +static struct mdio_bus_stat_attr *to_sattr(struct device_attribute *attr) +{ + return container_of(attr, struct mdio_bus_stat_attr, attr); +} + +static u64 mdio_bus_get_stat(struct mdio_bus_stats *s, unsigned int offset) +{ + const u64_stats_t *stats = (const void *)s + offset; + unsigned int start; + u64 val = 0; + + do { + start = u64_stats_fetch_begin(&s->syncp); + val = u64_stats_read(stats); + } while (u64_stats_fetch_retry(&s->syncp, start)); + + return val; +} + +static ssize_t mdio_bus_stat_field_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mdio_bus_stat_attr *sattr = to_sattr(attr); + struct mii_bus *bus = to_mii_bus(dev); + u64 val = 0; + + if (sattr->address < 0) { + /* get global stats */ + for (int i = 0; i < PHY_MAX_ADDR; i++) + val += mdio_bus_get_stat(&bus->stats[i], + sattr->field_offset); + } else { + val = mdio_bus_get_stat(&bus->stats[sattr->address], + 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_bus_stat_attr *sattr = to_sattr(attr); + struct mdio_device *mdiodev = to_mdio_device(dev); + struct mii_bus *bus = mdiodev->bus; + int addr = mdiodev->addr; + u64 val; + + val = mdio_bus_get_stat(&bus->stats[addr], sattr->field_offset); + + return sysfs_emit(buf, "%llu\n", val); +} + +#define MDIO_BUS_STATS_ATTR(field) \ +static const struct mdio_bus_stat_attr dev_attr_mdio_bus_##field = { \ + .attr = __ATTR(field, 0444, mdio_bus_stat_field_show, NULL), \ + .address = -1, \ + .field_offset = offsetof(struct mdio_bus_stats, field), \ +}; \ +static const struct mdio_bus_stat_attr dev_attr_mdio_bus_device_##field = { \ + .attr = __ATTR(field, 0444, mdio_bus_device_stat_field_show, NULL), \ + .field_offset = offsetof(struct mdio_bus_stats, 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 const struct mdio_bus_stat_attr \ +dev_attr_mdio_bus_addr_##field##_##addr = { \ + .attr = { .attr = { .name = file, .mode = 0444 }, \ + .show = mdio_bus_stat_field_show, \ + }, \ + .address = addr, \ + .field_offset = 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 const struct attribute *const 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_const = mdio_bus_statistics_attrs, +}; +__ATTRIBUTE_GROUPS(mdio_bus_statistics); + +const struct class mdio_bus_class = { + .name = "mdio_bus", + .dev_release = mdiobus_release, + .dev_groups = mdio_bus_statistics_groups, +}; + +/** + * mdio_bus_match - determine if given MDIO driver supports the given + * MDIO device + * @dev: target MDIO device + * @drv: given MDIO driver + * + * Return: 1 if the driver supports the device, 0 otherwise + * + * Description: 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, const struct device_driver *drv) +{ + const 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 const struct attribute *const 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_const = mdio_bus_device_statistics_attrs, +}; +__ATTRIBUTE_GROUPS(mdio_bus_device_statistics); + +const struct bus_type mdio_bus_type = { + .name = "mdio_bus", + .dev_groups = mdio_bus_device_statistics_groups, + .match = mdio_bus_match, + .uevent = mdio_uevent, +}; + +/** + * 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); + +#if IS_ENABLED(CONFIG_OF_MDIO) +/* 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. + * If a PHY package is found, PHY is searched also there. + */ +static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev, + struct device_node *np) +{ + struct device_node *child; + + for_each_available_child_of_node(np, child) { + int addr; + + if (of_node_name_eq(child, "ethernet-phy-package")) { + /* Validate PHY package reg presence */ + if (!of_property_present(child, "reg")) { + of_node_put(child); + return -EINVAL; + } + + if (!of_mdiobus_find_phy(dev, mdiodev, child)) { + /* The refcount for the PHY package will be + * incremented later when PHY join the Package. + */ + of_node_put(child); + return 0; + } + + continue; + } + + 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 0; + } + } + + return -ENODEV; +} + +static void of_mdiobus_link_mdiodev(struct mii_bus *bus, + struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + + if (dev->of_node || !bus->dev.of_node) + return; + + of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node); +} +#endif + +static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45) +{ + struct phy_device *phydev = ERR_PTR(-ENODEV); + struct fwnode_handle *fwnode; + char node_name[16]; + int err; + + phydev = get_phy_device(bus, addr, c45); + if (IS_ERR(phydev)) + return phydev; + +#if IS_ENABLED(CONFIG_OF_MDIO) + /* 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); +#endif + + /* Search for a swnode for the phy in the swnode hierarchy of the bus. + * If there is no swnode for the phy provided, just ignore it. + */ + if (dev_fwnode(&bus->dev) && !dev_fwnode(&phydev->mdio.dev)) { + snprintf(node_name, sizeof(node_name), "ethernet-phy@%d", + addr); + fwnode = fwnode_get_named_child_node(dev_fwnode(&bus->dev), + node_name); + if (fwnode) + device_set_node(&phydev->mdio.dev, fwnode); + } + + 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) +{ + struct phy_device *phydev; + + mdiobus_for_each_phy(bus, phydev) { + u32 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) + fwnode_set_flag(&bus->parent->of_node->fwnode, + 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); + + /* If the bus state is allocated, we're registering a fresh bus + * that may have a fwnode associated with it. Grab a reference + * to the fwnode. This will be dropped when the bus is released. + * If the bus was set to unregistered, it means that the bus was + * previously registered, and we've already grabbed a reference. + */ + if (bus->state == MDIOBUS_ALLOCATED) + fwnode_handle_get(dev_fwnode(&bus->dev)); + + /* 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; + } + + 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; + + 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); + +/** + * mdio_find_bus - Given the name of a mdiobus, find the mii_bus. + * @mdio_name: The name of a mdiobus. + * + * Return: 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. + * + * Return: 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); +#endif diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c new file mode 100644 index 000000000..06151f207 --- /dev/null +++ b/drivers/net/phy/mdio_device.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Framework for MDIO devices, other than PHYs. + * + * Copyright (c) 2016 Andrew Lunn + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "phylib-internal.h" + +/** + * mdio_device_register_reset - Read and initialize the reset properties of + * an mdio device + * @mdiodev: mdio_device structure + * + * Return: Zero if successful, negative error code on failure + */ +static int mdio_device_register_reset(struct mdio_device *mdiodev) +{ + struct reset_control *reset; + + /* 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"); + + reset = reset_control_get_optional_exclusive(&mdiodev->dev, "phy"); + if (IS_ERR(reset)) { + gpiod_put(mdiodev->reset_gpio); + mdiodev->reset_gpio = NULL; + return PTR_ERR(reset); + } + + mdiodev->reset_ctrl = reset; + + /* Read optional firmware properties */ + device_property_read_u32(&mdiodev->dev, "reset-assert-us", + &mdiodev->reset_assert_delay); + device_property_read_u32(&mdiodev->dev, "reset-deassert-us", + &mdiodev->reset_deassert_delay); + + return 0; +} + +/** + * mdio_device_unregister_reset - uninitialize the reset properties of + * an mdio device + * @mdiodev: mdio_device structure + */ +static void mdio_device_unregister_reset(struct mdio_device *mdiodev) +{ + gpiod_put(mdiodev->reset_gpio); + mdiodev->reset_gpio = NULL; + reset_control_put(mdiodev->reset_ctrl); + mdiodev->reset_ctrl = NULL; + mdiodev->reset_assert_delay = 0; + mdiodev->reset_deassert_delay = 0; +} + +void mdio_device_reset(struct mdio_device *mdiodev, int value) +{ + unsigned int d; + + if (!mdiodev->reset_gpio && !mdiodev->reset_ctrl) + return; + + if (mdiodev->reset_state == value) + 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); + + mdiodev->reset_state = value; +} +EXPORT_SYMBOL(mdio_device_reset); + +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)); +} + +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_obj(*mdiodev); + 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; + mdiodev->reset_state = -1; + + 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 + * + * Return: Zero if successful, negative error code on failure + */ +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); + +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 = mdio_device_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; +} + +int mdiobus_unregister_device(struct mdio_device *mdiodev) +{ + if (mdiodev->bus->mdio_map[mdiodev->addr] != mdiodev) + return -EINVAL; + + mdio_device_unregister_reset(mdiodev); + + mdiodev->bus->mdio_map[mdiodev->addr] = NULL; + + return 0; +} + +/** + * 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. + * + * Return: Zero if successful, negative error code on failure + */ +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 + * + * Return: Zero if successful, negative error code on failure + */ +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..7fd3377db --- /dev/null +++ b/drivers/net/phy/mdio_devres.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +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_DESCRIPTION("Network MDIO bus devres helpers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig new file mode 100644 index 000000000..3b9cf82c0 --- /dev/null +++ b/drivers/net/phy/mediatek/Kconfig @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0-only +config MEDIATEK_2P5GE_PHY + tristate "MediaTek 2.5Gb Ethernet PHYs" + depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST + select MTK_NET_PHYLIB + help + Supports MediaTek SoC built-in 2.5Gb Ethernet PHYs. + + This will load necessary firmware and add appropriate time delay. + Accelerate this procedure through internal pbus instead of MDIO + bus. Certain link-up issues will also be fixed here. + +config MEDIATEK_GE_PHY + tristate "MediaTek Gigabit Ethernet PHYs" + select MTK_NET_PHYLIB + help + Supports the MediaTek non-built-in Gigabit Ethernet PHYs. + + Non-built-in Gigabit Ethernet PHYs include mt7530/mt7531. + You may find mt7530 inside mt7621. This driver shares some + common operations with MediaTek SoC built-in Gigabit + Ethernet PHYs. + +config MEDIATEK_FE_SOC_PHY + tristate "MediaTek SoC Fast Ethernet PHYs" + depends on SOC_MT7620 || COMPILE_TEST + help + Support for MediaTek MT7628 built-in Fast Ethernet PHYs. + This driver only sets an initialization bit required for the PHY + to establish 100 Mbps links. All other PHY operations are handled + by the kernel's generic PHY code. + +config MEDIATEK_GE_SOC_PHY + tristate "MediaTek SoC Gigabit Ethernet PHYs" + depends on ARM64 || ECONET || COMPILE_TEST + depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \ + ECONET || COMPILE_TEST + select MTK_NET_PHYLIB + select PHY_PACKAGE + 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. + + Also include support for the built-in Gigabit Ethernet PHYs of + the Airoha AN7581 and AN7583 SoCs and of the EcoNet EN7528 SoC, + which need no efuse calibration data. + +config MTK_NET_PHYLIB + tristate diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile new file mode 100644 index 000000000..6f9cacf7f --- /dev/null +++ b/drivers/net/phy/mediatek/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MEDIATEK_2P5GE_PHY) += mtk-2p5ge.o +obj-$(CONFIG_MEDIATEK_FE_SOC_PHY) += mtk-fe-soc.o +obj-$(CONFIG_MEDIATEK_GE_PHY) += mtk-ge.o +obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mtk-ge-soc.o +obj-$(CONFIG_MTK_NET_PHYLIB) += mtk-phy-lib.o diff --git a/drivers/net/phy/mediatek/mtk-2p5ge.c b/drivers/net/phy/mediatek/mtk-2p5ge.c new file mode 100644 index 000000000..de8a41a18 --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-2p5ge.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include +#include +#include +#include +#include + +#include "mtk.h" + +#define MTK_2P5GPHY_ID_MT7988 0x00339c11 + +#define MT7988_2P5GE_PMB_FW "mediatek/mt7988/i2p5ge-phy-pmb.bin" +#define MT7988_2P5GE_PMB_FW_SIZE 0x20000 +#define MT7988_2P5GE_PMB_FW_BASE 0x0f100000 +#define MT7988_2P5GE_PMB_FW_LEN 0x20000 +#define MTK_2P5GPHY_MCU_CSR_BASE 0x0f0f0000 +#define MTK_2P5GPHY_MCU_CSR_LEN 0x20 +#define MD32_EN_CFG 0x18 +#define MD32_EN BIT(0) + +#define BASE100T_STATUS_EXTEND 0x10 +#define BASE1000T_STATUS_EXTEND 0x11 +#define EXTEND_CTRL_AND_STATUS 0x16 + +#define PHY_AUX_CTRL_STATUS 0x1d +#define PHY_AUX_DPX_MASK GENMASK(5, 5) +#define PHY_AUX_SPEED_MASK GENMASK(4, 2) + +/* Registers on MDIO_MMD_VEND1 */ +#define MTK_PHY_LPI_PCS_DSP_CTRL 0x121 +#define MTK_PHY_LPI_SIG_EN_LO_THRESH100_MASK GENMASK(12, 8) + +#define MTK_PHY_HOST_CMD1 0x800e +#define MTK_PHY_HOST_CMD2 0x800f +/* Registers on Token Ring debug nodes */ +/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */ +#define AUTO_NP_10XEN BIT(6) + +enum { + PHY_AUX_SPD_10 = 0, + PHY_AUX_SPD_100, + PHY_AUX_SPD_1000, + PHY_AUX_SPD_2500, +}; + +static int mt798x_2p5ge_phy_load_fw(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + void __iomem *mcu_csr_base, *pmb_addr; + const struct firmware *fw; + int ret, i; + u32 reg; + + pmb_addr = ioremap(MT7988_2P5GE_PMB_FW_BASE, MT7988_2P5GE_PMB_FW_LEN); + if (!pmb_addr) + return -ENOMEM; + mcu_csr_base = ioremap(MTK_2P5GPHY_MCU_CSR_BASE, + MTK_2P5GPHY_MCU_CSR_LEN); + if (!mcu_csr_base) { + ret = -ENOMEM; + goto free_pmb; + } + + ret = request_firmware_direct(&fw, MT7988_2P5GE_PMB_FW, dev); + if (ret) { + dev_err(dev, "failed to load firmware: %s, ret: %d\n", + MT7988_2P5GE_PMB_FW, ret); + goto free; + } + + if (fw->size != MT7988_2P5GE_PMB_FW_SIZE) { + dev_err(dev, "Firmware size 0x%zx != 0x%x\n", + fw->size, MT7988_2P5GE_PMB_FW_SIZE); + ret = -EINVAL; + goto release_fw; + } + + reg = readw(mcu_csr_base + MD32_EN_CFG); + if (reg & MD32_EN) { + phy_set_bits(phydev, MII_BMCR, BMCR_RESET); + usleep_range(10000, 11000); + } + phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN); + + /* Write magic number to safely stall MCU */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_HOST_CMD1, 0x1100); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_HOST_CMD2, 0x00df); + + for (i = 0; i < MT7988_2P5GE_PMB_FW_SIZE - 1; i += 4) + writel(*((uint32_t *)(fw->data + i)), pmb_addr + i); + + writew(reg & ~MD32_EN, mcu_csr_base + MD32_EN_CFG); + writew(reg | MD32_EN, mcu_csr_base + MD32_EN_CFG); + phy_set_bits(phydev, MII_BMCR, BMCR_RESET); + /* We need a delay here to stabilize initialization of MCU */ + usleep_range(7000, 8000); + + dev_info(dev, "Firmware date code: %x/%x/%x, version: %x.%x\n", + be16_to_cpu(*((__be16 *)(fw->data + + MT7988_2P5GE_PMB_FW_SIZE - 8))), + *(fw->data + MT7988_2P5GE_PMB_FW_SIZE - 6), + *(fw->data + MT7988_2P5GE_PMB_FW_SIZE - 5), + *(fw->data + MT7988_2P5GE_PMB_FW_SIZE - 2), + *(fw->data + MT7988_2P5GE_PMB_FW_SIZE - 1)); + +release_fw: + release_firmware(fw); +free: + iounmap(mcu_csr_base); +free_pmb: + iounmap(pmb_addr); + + return ret; +} + +static int mt798x_2p5ge_phy_config_init(struct phy_device *phydev) +{ + /* Check if PHY interface type is compatible */ + if (phydev->interface != PHY_INTERFACE_MODE_INTERNAL) + return -ENODEV; + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LPI_PCS_DSP_CTRL, + MTK_PHY_LPI_SIG_EN_LO_THRESH100_MASK, 0); + + /* Enable 16-bit next page exchange bit if 1000-BT isn't advertising */ + mtk_tr_modify(phydev, 0x0, 0xf, 0x3c, AUTO_NP_10XEN, + FIELD_PREP(AUTO_NP_10XEN, 0x1)); + + /* Enable HW auto downshift */ + phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED_1, + MTK_PHY_AUX_CTRL_AND_STATUS, + 0, MTK_PHY_ENABLE_DOWNSHIFT); + + return 0; +} + +static int mt798x_2p5ge_phy_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + u32 adv; + int ret; + + ret = genphy_c45_an_config_aneg(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + /* Clause 45 doesn't define 1000BaseT support. Use Clause 22 instead in + * our design. + */ + adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); + ret = phy_modify_changed(phydev, MII_CTRL1000, ADVERTISE_1000FULL, adv); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return genphy_c45_check_and_restart_aneg(phydev, changed); +} + +static int mt798x_2p5ge_phy_get_features(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_pma_read_abilities(phydev); + if (ret) + return ret; + + /* This phy can't handle collision, and neither can (XFI)MAC it's + * connected to. Although it can do HDX handshake, it doesn't support + * CSMA/CD that HDX requires. + */ + linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, + phydev->supported); + + return 0; +} + +static int mt798x_2p5ge_phy_read_status(struct phy_device *phydev) +{ + int ret; + + /* When MDIO_STAT1_LSTATUS is raised genphy_c45_read_link(), this phy + * actually hasn't finished AN. So use CL22's link update function + * instead. + */ + ret = genphy_update_link(phydev); + if (ret) + return ret; + + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + + /* We'll read link speed through vendor specific registers down below. + * So remove phy_resolve_aneg_linkmode (AN on) & genphy_c45_read_pma + * (AN off). + */ + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) { + ret = genphy_c45_read_lpa(phydev); + if (ret < 0) + return ret; + + /* Clause 45 doesn't define 1000BaseT support. Read the link + * partner's 1G advertisement via Clause 22. + */ + 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); + } + + if (phydev->link) { + ret = phy_read(phydev, PHY_AUX_CTRL_STATUS); + if (ret < 0) + return ret; + + switch (FIELD_GET(PHY_AUX_SPEED_MASK, ret)) { + case PHY_AUX_SPD_10: + phydev->speed = SPEED_10; + break; + case PHY_AUX_SPD_100: + phydev->speed = SPEED_100; + break; + case PHY_AUX_SPD_1000: + phydev->speed = SPEED_1000; + break; + case PHY_AUX_SPD_2500: + phydev->speed = SPEED_2500; + break; + } + + phydev->duplex = DUPLEX_FULL; + phydev->rate_matching = RATE_MATCH_PAUSE; + } + + return 0; +} + +static int mt798x_2p5ge_phy_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + return RATE_MATCH_PAUSE; +} + +static const unsigned long supported_triggers = + BIT(TRIGGER_NETDEV_FULL_DUPLEX) | + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + +static int mt798x_2p5ge_phy_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + bool blinking = false; + int err = 0; + + err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking); + if (err < 0) + return err; + + err = mtk_phy_hw_led_blink_set(phydev, index, blinking); + if (err) + return err; + + if (blinking) + mtk_phy_hw_led_on_set(phydev, index, MTK_2P5GPHY_LED_ON_MASK, + false); + + return 0; +} + +static int mt798x_2p5ge_phy_led_brightness_set(struct phy_device *phydev, + u8 index, + enum led_brightness value) +{ + int err; + + err = mtk_phy_hw_led_blink_set(phydev, index, false); + if (err) + return err; + + return mtk_phy_hw_led_on_set(phydev, index, MTK_2P5GPHY_LED_ON_MASK, + (value != LED_OFF)); +} + +static int mt798x_2p5ge_phy_led_hw_is_supported(struct phy_device *phydev, + u8 index, unsigned long rules) +{ + return mtk_phy_led_hw_is_supported(phydev, index, rules, + supported_triggers); +} + +static int mt798x_2p5ge_phy_led_hw_control_get(struct phy_device *phydev, + u8 index, unsigned long *rules) +{ + return mtk_phy_led_hw_ctrl_get(phydev, index, rules, + MTK_2P5GPHY_LED_ON_SET, + MTK_2P5GPHY_LED_RX_BLINK_SET, + MTK_2P5GPHY_LED_TX_BLINK_SET); +}; + +static int mt798x_2p5ge_phy_led_hw_control_set(struct phy_device *phydev, + u8 index, unsigned long rules) +{ + return mtk_phy_led_hw_ctrl_set(phydev, index, rules, + MTK_2P5GPHY_LED_ON_SET, + MTK_2P5GPHY_LED_RX_BLINK_SET, + MTK_2P5GPHY_LED_TX_BLINK_SET); +}; + +static int mt798x_2p5ge_phy_probe(struct phy_device *phydev) +{ + struct mtk_socphy_priv *priv; + struct pinctrl *pinctrl; + int ret; + + switch (phydev->drv->phy_id) { + case MTK_2P5GPHY_ID_MT7988: + /* This built-in 2.5GbE hardware only sets MDIO_DEVS_PMAPMD. + * Set the rest by this driver since PCS/AN/VEND1/VEND2 MDIO + * manageable devices actually exist. + */ + phydev->c45_ids.mmds_present |= MDIO_DEVS_PCS | + MDIO_DEVS_AN | + MDIO_DEVS_VEND1 | + MDIO_DEVS_VEND2; + break; + default: + return -EINVAL; + } + + ret = mt798x_2p5ge_phy_load_fw(phydev); + if (ret < 0) + return ret; + + /* Setup LED. On default, LED0 is on/off when link is up/down. As for + * LED1, it blinks as tx/rx transmission takes place. + */ + phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_ON_CTRL, + MTK_PHY_LED_ON_POLARITY | MTK_2P5GPHY_LED_ON_SET); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_BLINK_CTRL, + MTK_2P5GPHY_LED_TX_BLINK_SET | + MTK_2P5GPHY_LED_RX_BLINK_SET); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED1_ON_CTRL, + MTK_PHY_LED_ON_FDX | MTK_PHY_LED_ON_HDX | + MTK_2P5GPHY_LED_ON_SET); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED1_BLINK_CTRL, + MTK_2P5GPHY_LED_TX_BLINK_SET | + MTK_2P5GPHY_LED_RX_BLINK_SET); + + /* Switch pinctrl after setting polarity to avoid bogus blinking */ + pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "i2p5gbe-led"); + if (IS_ERR(pinctrl)) + dev_err(&phydev->mdio.dev, "Fail to set LED pins!\n"); + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct mtk_socphy_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + mtk_phy_leds_state_init(phydev); + + return 0; +} + +static struct phy_driver mtk_2p5gephy_driver[] = { + { + PHY_ID_MATCH_MODEL(MTK_2P5GPHY_ID_MT7988), + .name = "MediaTek MT7988 2.5GbE PHY", + .probe = mt798x_2p5ge_phy_probe, + .config_init = mt798x_2p5ge_phy_config_init, + .config_aneg = mt798x_2p5ge_phy_config_aneg, + .get_features = mt798x_2p5ge_phy_get_features, + .read_status = mt798x_2p5ge_phy_read_status, + .get_rate_matching = mt798x_2p5ge_phy_get_rate_matching, + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_page = mtk_phy_read_page, + .write_page = mtk_phy_write_page, + .led_blink_set = mt798x_2p5ge_phy_led_blink_set, + .led_brightness_set = mt798x_2p5ge_phy_led_brightness_set, + .led_hw_is_supported = mt798x_2p5ge_phy_led_hw_is_supported, + .led_hw_control_get = mt798x_2p5ge_phy_led_hw_control_get, + .led_hw_control_set = mt798x_2p5ge_phy_led_hw_control_set, + }, +}; + +module_phy_driver(mtk_2p5gephy_driver); + +static struct mdio_device_id __maybe_unused mtk_2p5ge_phy_tbl[] = { + { PHY_ID_MATCH_VENDOR(0x00339c00) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek 2.5Gb Ethernet PHY driver"); +MODULE_AUTHOR("SkyLake Huang "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_2p5ge_phy_tbl); +MODULE_FIRMWARE(MT7988_2P5GE_PMB_FW); diff --git a/drivers/net/phy/mediatek/mtk-fe-soc.c b/drivers/net/phy/mediatek/mtk-fe-soc.c new file mode 100644 index 000000000..0b0d6c1fe --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-fe-soc.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MT7628 Embedded Switch internal Fast Ethernet PHYs + */ +#include +#include + +#define MTK_FPHY_ID_MT7628 0x03a29410 +#define MTK_EXT_PAGE_ACCESS 0x1f + +static int mt7628_phy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, MTK_EXT_PAGE_ACCESS); +} + +static int mt7628_phy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page); +} + +static int mt7628_phy_config_init(struct phy_device *phydev) +{ + /* + * This undocumented bit is required for the PHYs to be able to + * establish 100mbps links. + */ + return phy_modify_paged(phydev, 0x8000, 30, BIT(13), BIT(13)); +} + +static struct phy_driver mtk_soc_fe_phy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628), + .name = "MediaTek MT7628 PHY", + .config_init = mt7628_phy_config_init, + .read_page = mt7628_phy_read_page, + .write_page = mt7628_phy_write_page, + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; + +module_phy_driver(mtk_soc_fe_phy_driver); +static const struct mdio_device_id __maybe_unused mtk_soc_fe_phy_tbl[] = { + { PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek SoC Fast Ethernet PHY driver"); +MODULE_AUTHOR("Joris Vaisvila "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_soc_fe_phy_tbl); diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c new file mode 100644 index 000000000..0041300d4 --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-ge-soc.c @@ -0,0 +1,1585 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../phylib.h" +#include "mtk.h" + +#define MTK_PHY_MAX_LEDS 2 + +#define MTK_GPHY_ID_MT7981 0x03a29461 +#define MTK_GPHY_ID_MT7988 0x03a29481 +#define MTK_GPHY_ID_EN7528 0x03a29491 +#define MTK_GPHY_ID_AN7581 0x03a294c1 +#define MTK_GPHY_ID_AN7583 0xc0ff0420 + +#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 + +/* Registers on Token Ring debug nodes */ +/* ch_addr = 0x0, node_addr = 0x7, data_addr = 0x15 */ +/* NormMseLoThresh */ +#define NORMAL_MSE_LO_THRESH_MASK GENMASK(15, 8) + +/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */ +/* RemAckCntLimitCtrl */ +#define REMOTE_ACK_COUNT_LIMIT_CTRL_MASK GENMASK(2, 1) + +/* ch_addr = 0x1, node_addr = 0xd, data_addr = 0x20 */ +/* VcoSlicerThreshBitsHigh */ +#define VCO_SLICER_THRESH_HIGH_MASK GENMASK(23, 0) + +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x0 */ +/* DfeTailEnableVgaThresh1000 */ +#define DFE_TAIL_EANBLE_VGA_TRHESH_1000 GENMASK(5, 1) + +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x1 */ +/* MrvlTrFix100Kp */ +#define MRVL_TR_FIX_100KP_MASK GENMASK(22, 20) +/* MrvlTrFix100Kf */ +#define MRVL_TR_FIX_100KF_MASK GENMASK(19, 17) +/* MrvlTrFix1000Kp */ +#define MRVL_TR_FIX_1000KP_MASK GENMASK(16, 14) +/* MrvlTrFix1000Kf */ +#define MRVL_TR_FIX_1000KF_MASK GENMASK(13, 11) + +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x12 */ +/* VgaDecRate */ +#define VGA_DECIMATION_RATE_MASK GENMASK(8, 5) + +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */ +/* SlvDSPreadyTime */ +#define SLAVE_DSP_READY_TIME_MASK GENMASK(22, 15) +/* MasDSPreadyTime */ +#define MASTER_DSP_READY_TIME_MASK GENMASK(14, 7) + +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x18 */ +/* EnabRandUpdTrig */ +#define ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER BIT(8) + +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */ +/* ResetSyncOffset */ +#define RESET_SYNC_OFFSET_MASK GENMASK(11, 8) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x0 */ +/* FfeUpdGainForceVal */ +#define FFE_UPDATE_GAIN_FORCE_VAL_MASK GENMASK(9, 7) +/* FfeUpdGainForce */ +#define FFE_UPDATE_GAIN_FORCE BIT(6) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x3 */ +/* TrFreeze */ +#define TR_FREEZE_MASK GENMASK(11, 0) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */ +/* SS: Steady-state, KP: Proportional Gain */ +/* SSTrKp100 */ +#define SS_TR_KP100_MASK GENMASK(21, 19) +/* SSTrKf100 */ +#define SS_TR_KF100_MASK GENMASK(18, 16) +/* SSTrKp1000Mas */ +#define SS_TR_KP1000_MASTER_MASK GENMASK(15, 13) +/* SSTrKf1000Mas */ +#define SS_TR_KF1000_MASTER_MASK GENMASK(12, 10) +/* SSTrKp1000Slv */ +#define SS_TR_KP1000_SLAVE_MASK GENMASK(9, 7) +/* SSTrKf1000Slv */ +#define SS_TR_KF1000_SLAVE_MASK GENMASK(6, 4) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x8 */ +/* clear this bit if wanna select from AFE */ +/* Regsigdet_sel_1000 */ +#define EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE BIT(4) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */ +/* RegEEE_st2TrKf1000 */ +#define EEE1000_STAGE2_TR_KF_MASK GENMASK(13, 11) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xf */ +/* RegEEE_slv_waketr_timer_tar */ +#define SLAVE_WAKETR_TIMER_MASK GENMASK(20, 11) +/* RegEEE_slv_remtx_timer_tar */ +#define SLAVE_REMTX_TIMER_MASK GENMASK(10, 1) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x10 */ +/* RegEEE_slv_wake_int_timer_tar */ +#define SLAVE_WAKEINT_TIMER_MASK GENMASK(10, 1) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x14 */ +/* RegEEE_trfreeze_timer2 */ +#define TR_FREEZE_TIMER2_MASK GENMASK(9, 0) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x1c */ +/* RegEEE100Stg1_tar */ +#define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK GENMASK(8, 0) + +/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x25 */ +/* REGEEE_wake_slv_tr_wait_dfesigdet_en */ +#define WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN BIT(11) + +#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_MASK GENMASK(8, 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_LED1_DEFAULT_POLARITIES BIT(1) + +/* LED basic control register, part of the same LED block as the LED0/LED1 + * control registers above. The air_en8811h driver describes the same + * register as AIR_PHY_LED_BCR. + */ +#define MTK_PHY_LED_BCR 0x21 +#define MTK_PHY_LED_BCR_CLK_EN BIT(3) +#define MTK_PHY_LED_BCR_EXT_CTRL BIT(15) + +#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 +}; + +struct mtk_socphy_shared { + u32 boottrap; + struct mtk_socphy_priv priv[4]; +}; + +/* 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); + if (ret < 0) + return ret; + ret = FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret); + 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) +{ + const int vals_9481[16] = { 10, 6, 6, 10, + 10, 6, 6, 10, + 10, 6, 6, 10, + 10, 6, 6, 10 }; + const int vals_9461[16] = { 7, 1, 4, 7, + 7, 1, 4, 7, + 7, 1, 4, 7, + 7, 1, 4, 7 }; + int bias[16] = {}; + int i; + + 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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_GBE_MASK, + buf[0] + bias[0])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG, + MTK_PHY_DA_TX_I2MPB_A_TBT_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_A_HBT_MASK, + buf[0] + bias[2])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2, + MTK_PHY_DA_TX_I2MPB_A_TST_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_GBE_MASK, + buf[1] + bias[4])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1, + MTK_PHY_DA_TX_I2MPB_B_TBT_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_B_HBT_MASK, + buf[1] + bias[6])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2, + MTK_PHY_DA_TX_I2MPB_B_TST_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_GBE_MASK, + buf[2] + bias[8])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1, + MTK_PHY_DA_TX_I2MPB_C_TBT_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_C_HBT_MASK, + buf[2] + bias[10])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2, + MTK_PHY_DA_TX_I2MPB_C_TST_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_GBE_MASK, + buf[3] + bias[12])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1, + MTK_PHY_DA_TX_I2MPB_D_TBT_MASK, + FIELD_PREP(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, + FIELD_PREP(MTK_PHY_DA_TX_I2MPB_D_HBT_MASK, + buf[3] + bias[14])); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2, + MTK_PHY_DA_TX_I2MPB_D_TST_MASK, + FIELD_PREP(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 = -1; + + 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); + __mtk_tr_modify(phydev, 0x1, 0xf, 0x17, + SLAVE_DSP_READY_TIME_MASK | MASTER_DSP_READY_TIME_MASK, + FIELD_PREP(SLAVE_DSP_READY_TIME_MASK, 0x18) | + FIELD_PREP(MASTER_DSP_READY_TIME_MASK, 0x18)); + + __mtk_tr_set_bits(phydev, 0x1, 0xf, 0x18, + ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER); + + __mtk_tr_modify(phydev, 0x0, 0x7, 0x15, + NORMAL_MSE_LO_THRESH_MASK, + FIELD_PREP(NORMAL_MSE_LO_THRESH_MASK, 0x55)); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0x0, + FFE_UPDATE_GAIN_FORCE_VAL_MASK, + FIELD_PREP(FFE_UPDATE_GAIN_FORCE_VAL_MASK, 0x4) | + FFE_UPDATE_GAIN_FORCE); + + __mtk_tr_clr_bits(phydev, 0x2, 0xd, 0x3, TR_FREEZE_MASK); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0x6, + SS_TR_KP100_MASK | SS_TR_KF100_MASK | + SS_TR_KP1000_MASTER_MASK | SS_TR_KF1000_MASTER_MASK | + SS_TR_KP1000_SLAVE_MASK | SS_TR_KF1000_SLAVE_MASK, + FIELD_PREP(SS_TR_KP100_MASK, 0x5) | + FIELD_PREP(SS_TR_KF100_MASK, 0x6) | + FIELD_PREP(SS_TR_KP1000_MASTER_MASK, 0x5) | + FIELD_PREP(SS_TR_KF1000_MASTER_MASK, 0x6) | + FIELD_PREP(SS_TR_KP1000_SLAVE_MASK, 0x5) | + FIELD_PREP(SS_TR_KF1000_SLAVE_MASK, 0x6)); + + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); +} + +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); + __mtk_tr_modify(phydev, 0x1, 0xf, 0x20, + RESET_SYNC_OFFSET_MASK, + FIELD_PREP(RESET_SYNC_OFFSET_MASK, 0x6)); + + __mtk_tr_modify(phydev, 0x1, 0xf, 0x12, + VGA_DECIMATION_RATE_MASK, + FIELD_PREP(VGA_DECIMATION_RATE_MASK, 0x1)); + + /* MrvlTrFix100Kp = 3, MrvlTrFix100Kf = 2, + * MrvlTrFix1000Kp = 3, MrvlTrFix1000Kf = 2 + */ + __mtk_tr_modify(phydev, 0x1, 0xf, 0x1, + MRVL_TR_FIX_100KP_MASK | MRVL_TR_FIX_100KF_MASK | + MRVL_TR_FIX_1000KP_MASK | MRVL_TR_FIX_1000KF_MASK, + FIELD_PREP(MRVL_TR_FIX_100KP_MASK, 0x3) | + FIELD_PREP(MRVL_TR_FIX_100KF_MASK, 0x2) | + FIELD_PREP(MRVL_TR_FIX_1000KP_MASK, 0x3) | + FIELD_PREP(MRVL_TR_FIX_1000KF_MASK, 0x2)); + + /* VcoSlicerThreshBitsHigh */ + __mtk_tr_modify(phydev, 0x1, 0xd, 0x20, + VCO_SLICER_THRESH_HIGH_MASK, + FIELD_PREP(VCO_SLICER_THRESH_HIGH_MASK, 0x555555)); + 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 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); + + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + __mtk_tr_modify(phydev, 0x1, 0xf, 0x20, + RESET_SYNC_OFFSET_MASK, + FIELD_PREP(RESET_SYNC_OFFSET_MASK, 0x5)); + + /* VgaDecRate is 1 at default on mt7988 */ + + __mtk_tr_modify(phydev, 0x1, 0xf, 0x1, + MRVL_TR_FIX_100KP_MASK | MRVL_TR_FIX_100KF_MASK | + MRVL_TR_FIX_1000KP_MASK | MRVL_TR_FIX_1000KF_MASK, + FIELD_PREP(MRVL_TR_FIX_100KP_MASK, 0x6) | + FIELD_PREP(MRVL_TR_FIX_100KF_MASK, 0x7) | + FIELD_PREP(MRVL_TR_FIX_1000KP_MASK, 0x6) | + FIELD_PREP(MRVL_TR_FIX_1000KF_MASK, 0x7)); + + __mtk_tr_modify(phydev, 0x0, 0xf, 0x3c, + REMOTE_ACK_COUNT_LIMIT_CTRL_MASK, + FIELD_PREP(REMOTE_ACK_COUNT_LIMIT_CTRL_MASK, 0x1)); + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); + + /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 10 */ + 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, 0xa)); + + /* rg_tr_lpf_cnt_val = 1023 */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x3ff); +} + +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)); + + /* Keep MTK_PHY_LPI_SEND_LOC_TIMER as 375 */ + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG239, + MTK_PHY_LPI_TXPCS_LOC_RCV); + + /* This also fixes some IoT issues, such as CH340 */ + 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); + __mtk_tr_clr_bits(phydev, 0x2, 0xd, 0x8, + EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0xd, + EEE1000_STAGE2_TR_KF_MASK, + FIELD_PREP(EEE1000_STAGE2_TR_KF_MASK, 0x2)); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0xf, + SLAVE_WAKETR_TIMER_MASK | SLAVE_REMTX_TIMER_MASK, + FIELD_PREP(SLAVE_WAKETR_TIMER_MASK, 0x6) | + FIELD_PREP(SLAVE_REMTX_TIMER_MASK, 0x14)); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0x10, + SLAVE_WAKEINT_TIMER_MASK, + FIELD_PREP(SLAVE_WAKEINT_TIMER_MASK, 0x8)); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0x14, + TR_FREEZE_TIMER2_MASK, + FIELD_PREP(TR_FREEZE_TIMER2_MASK, 0x24a)); + + __mtk_tr_modify(phydev, 0x2, 0xd, 0x1c, + EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK, + FIELD_PREP(EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK, + 0x10)); + + __mtk_tr_clr_bits(phydev, 0x2, 0xd, 0x25, + WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN); + + __mtk_tr_modify(phydev, 0x1, 0xf, 0x0, + DFE_TAIL_EANBLE_VGA_TRHESH_1000, + FIELD_PREP(DFE_TAIL_EANBLE_VGA_TRHESH_1000, 0x1b)); + 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) +{ + struct nvmem_cell *cell; + int ret = 0; + size_t len; + u32 *buf; + + 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); + nvmem_cell_put(cell); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + 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_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + bool blinking = false; + int err; + + err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking); + if (err < 0) + return err; + + err = mtk_phy_hw_led_blink_set(phydev, index, blinking); + if (err) + return err; + + return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK, + false); +} + +static int mt798x_phy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + int err; + + err = mtk_phy_hw_led_blink_set(phydev, index, false); + if (err) + return err; + + return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK, + (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) +{ + return mtk_phy_led_hw_is_supported(phydev, index, rules, + supported_triggers); +} + +static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + return mtk_phy_led_hw_ctrl_get(phydev, index, rules, + MTK_GPHY_LED_ON_SET, + MTK_GPHY_LED_RX_BLINK_SET, + MTK_GPHY_LED_TX_BLINK_SET); +}; + +static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + return mtk_phy_led_hw_ctrl_set(phydev, index, rules, + MTK_GPHY_LED_ON_SET, + MTK_GPHY_LED_RX_BLINK_SET, + MTK_GPHY_LED_TX_BLINK_SET); +}; + +static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num) +{ + struct mtk_socphy_shared *priv = phy_package_get_priv(phydev); + 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 = phy_package_get_priv(phydev); + struct device_node *pio_np; + 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 practice 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. + */ + pio_np = of_parse_phandle(np, "mediatek,pio", 0); + if (!pio_np) + return -ENODEV; + + regmap = device_node_to_regmap(pio_np); + of_node_put(pio_np); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = regmap_read(regmap, RG_GPIO_MISC_TPBANK0, ®); + if (ret) + return ret; + + shared->boottrap = FIELD_GET(RG_GPIO_MISC_TPBANK0_BOOTMODE, reg); + + return 0; +} + +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 = phy_package_get_priv(phydev); + priv = &shared->priv[phydev->mdio.addr]; + + phydev->priv = priv; + + mtk_phy_leds_state_init(phydev); + + err = mt7988_phy_fix_leds_polarities(phydev); + if (err) + return err; + + /* Disable TX power saving at probing to: + * 1. Meet common mode compliance test criteria + * 2. Make sure that TX-VCM calibration works fine + */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7, + MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3 << 8); + + 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; + + mtk_phy_leds_state_init(phydev); + + return mt798x_phy_calibration(phydev); +} + +static int an7581_phy_probe(struct phy_device *phydev) +{ + struct mtk_socphy_priv *priv; + struct pinctrl *pinctrl; + + /* Toggle pinctrl to enable PHY LED */ + 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"); + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + return 0; +} + +static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + u16 val = 0; + u32 mode; + + if (index >= MTK_PHY_MAX_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + val = MTK_PHY_LED_ON_POLARITY; + break; + case PHY_LED_ACTIVE_HIGH: + break; + default: + return -EINVAL; + } + } + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ? + MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL, + MTK_PHY_LED_ON_POLARITY, val); +} + +static int an7583_phy_config_init(struct phy_device *phydev) +{ + /* BMCR_PDOWN is enabled by default */ + return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN); +} + +static int en7528_phy_config_init(struct phy_device *phydev) +{ + /* The LED controller of the EN7528 powers up with its external + * control disabled, leaving the LED pins dark regardless of what is + * programmed into the LED control registers. Hand the pins over to + * the LED control registers the same way the air_en8811h driver + * does; the mode field of this register is already set out of reset. + */ + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED_BCR, + MTK_PHY_LED_BCR_CLK_EN | + MTK_PHY_LED_BCR_EXT_CTRL); +} + +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_phy_read_page, + .write_page = mtk_phy_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_phy_read_page, + .write_page = mtk_phy_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_EN7528), + .name = "EcoNet EN7528 PHY", + .config_init = en7528_phy_config_init, + .probe = an7581_phy_probe, + .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, + .led_polarity_set = an7581_phy_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581), + .name = "Airoha AN7581 PHY", + .config_intr = genphy_no_config_intr, + .handle_interrupt = genphy_handle_interrupt_no_ack, + .probe = an7581_phy_probe, + .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, + .led_polarity_set = an7581_phy_led_polarity_set, + }, + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583), + .name = "Airoha AN7583 PHY", + .config_init = an7583_phy_config_init, + .probe = an7581_phy_probe, + .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, + .led_polarity_set = an7581_phy_led_polarity_set, + }, +}; + +module_phy_driver(mtk_socphy_driver); + +static const 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) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_EN7528) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek SoC Gigabit Ethernet PHY driver"); +MODULE_AUTHOR("Daniel Golle "); +MODULE_AUTHOR("SkyLake Huang "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_socphy_tbl); diff --git a/drivers/net/phy/mediatek/mtk-ge.c b/drivers/net/phy/mediatek/mtk-ge.c new file mode 100644 index 000000000..96d8ac515 --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-ge.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include + +#include "mtk.h" + +#define MTK_GPHY_ID_MT7530 0x03a29412 +#define MTK_GPHY_ID_MT7531 0x03a29441 + +#define MTK_PHY_PAGE_EXTENDED_2 0x0002 +#define MTK_PHY_PAGE_EXTENDED_3 0x0003 +#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11 0x11 + +#define MTK_PHY_PAGE_EXTENDED_2A30 0x2a30 + +/* Registers on Token Ring debug nodes */ +/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */ +#define SLAVE_DSP_READY_TIME_MASK GENMASK(22, 15) + +/* Registers on MDIO_MMD_VEND1 */ +#define MTK_PHY_GBE_MODE_TX_DELAY_SEL 0x13 +#define MTK_PHY_TEST_MODE_TX_DELAY_SEL 0x14 +#define MTK_TX_DELAY_PAIR_B_MASK GENMASK(10, 8) +#define MTK_TX_DELAY_PAIR_D_MASK GENMASK(2, 0) + +#define MTK_PHY_MCC_CTRL_AND_TX_POWER_CTRL 0xa6 +#define MTK_MCC_NEARECHO_OFFSET_MASK GENMASK(15, 8) + +#define MTK_PHY_RXADC_CTRL_RG7 0xc6 +#define MTK_PHY_DA_AD_BUF_BIAS_LP_MASK GENMASK(9, 8) + +#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG123 0x123 +#define MTK_PHY_LPI_NORM_MSE_LO_THRESH100_MASK GENMASK(15, 8) +#define MTK_PHY_LPI_NORM_MSE_HI_THRESH100_MASK GENMASK(7, 0) + +static void mtk_gephy_config_init(struct phy_device *phydev) +{ + /* Enable HW auto downshift */ + phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED_1, + MTK_PHY_AUX_CTRL_AND_STATUS, + 0, MTK_PHY_ENABLE_DOWNSHIFT); + + /* Increase SlvDPSready time */ + mtk_tr_modify(phydev, 0x1, 0xf, 0x17, SLAVE_DSP_READY_TIME_MASK, + FIELD_PREP(SLAVE_DSP_READY_TIME_MASK, 0x5e)); + + /* Adjust 100_mse_threshold */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG123, + MTK_PHY_LPI_NORM_MSE_LO_THRESH100_MASK | + MTK_PHY_LPI_NORM_MSE_HI_THRESH100_MASK, + FIELD_PREP(MTK_PHY_LPI_NORM_MSE_LO_THRESH100_MASK, + 0xff) | + FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH100_MASK, + 0xff)); + + /* If echo time is narrower than 0x3, it will be regarded as noise */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_MCC_CTRL_AND_TX_POWER_CTRL, + MTK_MCC_NEARECHO_OFFSET_MASK, + FIELD_PREP(MTK_MCC_NEARECHO_OFFSET_MASK, 0x3)); +} + +static int mt7530_phy_probe(struct phy_device *phydev) +{ + /* The MT7530 internal GE PHY has broken EEE: with EEE advertised, + * some link partners fail to establish a stable link (on a 2-pair + * cable, 1000BASE-T training fails and the port loops instead of + * falling back). MediaTek recommends disabling EEE on this PHY. + * Clear the advertisement as early as possible, before anything + * can negotiate EEE with the link partner. + */ + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); +} + +static int mt7530_phy_config_init(struct phy_device *phydev) +{ + int ret; + + mtk_gephy_config_init(phydev); + + /* The probe() clear alone is not durable: phy_init_hw() replays only + * ->config_init after a PHY reset, with the register back at its + * EEE-advertising hardware default, and phy_probe() zeroes + * eee_disabled_modes (of_set_phy_eee_broken()) after ->probe already + * ran. Clear the advertisement again and mark EEE disabled, so that + * neither phylib nor userspace can re-enable it; dp83867 disables + * broken EEE from config_init() the same way. + */ + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); + if (ret) + return ret; + + phy_disable_eee(phydev); + + /* Increase post_update_timer */ + phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, + MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11, 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_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7, + MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, + FIELD_PREP(MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3)); + + /* Set TX Pair delay selection */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_GBE_MODE_TX_DELAY_SEL, + MTK_TX_DELAY_PAIR_B_MASK | MTK_TX_DELAY_PAIR_D_MASK, + FIELD_PREP(MTK_TX_DELAY_PAIR_B_MASK, 0x4) | + FIELD_PREP(MTK_TX_DELAY_PAIR_D_MASK, 0x4)); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TEST_MODE_TX_DELAY_SEL, + MTK_TX_DELAY_PAIR_B_MASK | MTK_TX_DELAY_PAIR_D_MASK, + FIELD_PREP(MTK_TX_DELAY_PAIR_B_MASK, 0x4) | + FIELD_PREP(MTK_TX_DELAY_PAIR_D_MASK, 0x4)); + + return 0; +} + +static struct phy_driver mtk_gephy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530), + .name = "MediaTek MT7530 PHY", + .probe = mt7530_phy_probe, + .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_phy_read_page, + .write_page = mtk_phy_write_page, + }, + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7531), + .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_phy_read_page, + .write_page = mtk_phy_write_page, + }, +}; + +module_phy_driver(mtk_gephy_driver); + +static const struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = { + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7531) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek Gigabit Ethernet PHY driver"); +MODULE_AUTHOR("DENG, Qingfang "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_gephy_tbl); diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c new file mode 100644 index 000000000..608072fbf --- /dev/null +++ b/drivers/net/phy/mediatek/mtk-phy-lib.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +#include + +#include "mtk.h" + +/* Difference between functions with mtk_tr* and __mtk_tr* prefixes is + * mtk_tr* functions: wrapped by page switching operations + * __mtk_tr* functions: no page switching operations + */ + +static void __mtk_tr_access(struct phy_device *phydev, bool read, u8 ch_addr, + u8 node_addr, u8 data_addr) +{ + u16 tr_cmd = BIT(15); /* bit 14 & 0 are reserved */ + + if (read) + tr_cmd |= BIT(13); + + tr_cmd |= (((ch_addr & 0x3) << 11) | + ((node_addr & 0xf) << 7) | + ((data_addr & 0x3f) << 1)); + dev_dbg(&phydev->mdio.dev, "tr_cmd: 0x%x\n", tr_cmd); + __phy_write(phydev, 0x10, tr_cmd); +} + +static void __mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u16 *tr_high, u16 *tr_low) +{ + __mtk_tr_access(phydev, true, ch_addr, node_addr, data_addr); + *tr_low = __phy_read(phydev, 0x11); + *tr_high = __phy_read(phydev, 0x12); + dev_dbg(&phydev->mdio.dev, "tr_high read: 0x%x, tr_low read: 0x%x\n", + *tr_high, *tr_low); +} + +static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 tr_data) +{ + __phy_write(phydev, 0x11, tr_data & 0xffff); + __phy_write(phydev, 0x12, tr_data >> 16); + dev_dbg(&phydev->mdio.dev, "tr_high write: 0x%x, tr_low write: 0x%x\n", + tr_data >> 16, tr_data & 0xffff); + __mtk_tr_access(phydev, false, ch_addr, node_addr, data_addr); +} + +void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 mask, u32 set) +{ + u32 tr_data; + u16 tr_high; + u16 tr_low; + + __mtk_tr_read(phydev, ch_addr, node_addr, data_addr, &tr_high, &tr_low); + tr_data = (tr_high << 16) | tr_low; + tr_data = (tr_data & ~mask) | set; + __mtk_tr_write(phydev, ch_addr, node_addr, data_addr, tr_data); +} +EXPORT_SYMBOL_GPL(__mtk_tr_modify); + +void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 mask, u32 set) +{ + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, mask, set); + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); +} +EXPORT_SYMBOL_GPL(mtk_tr_modify); + +void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 set) +{ + __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, 0, set); +} +EXPORT_SYMBOL_GPL(__mtk_tr_set_bits); + +void __mtk_tr_clr_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 clr) +{ + __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, clr, 0); +} +EXPORT_SYMBOL_GPL(__mtk_tr_clr_bits); + +int mtk_phy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, MTK_EXT_PAGE_ACCESS); +} +EXPORT_SYMBOL_GPL(mtk_phy_read_page); + +int mtk_phy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page); +} +EXPORT_SYMBOL_GPL(mtk_phy_write_page); + +int mtk_phy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules, + unsigned long supported_triggers) +{ + if (index > 1) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~supported_triggers) + return -EOPNOTSUPP; + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_phy_led_hw_is_supported); + +int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index, + unsigned long *rules, u16 on_set, + u16 rx_blink_set, u16 tx_blink_set) +{ + 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 & (on_set | MTK_PHY_LED_ON_FDX | + MTK_PHY_LED_ON_HDX | MTK_PHY_LED_ON_LINKDOWN)) || + (blink & (rx_blink_set | tx_blink_set))) + 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; + + /* TRIGGER_NETDEV_LINK must not be reported together with any of the + * per-speed rules, the netdev trigger rejects that combination. + * on_set holds every speed this LED can indicate and is what + * mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so + * report the speed independent rule only when they are all on. + */ + if ((on & on_set) == on_set) { + *rules |= BIT(TRIGGER_NETDEV_LINK); + } else { + 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_LINK2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + } + + 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 & rx_blink_set) + *rules |= BIT(TRIGGER_NETDEV_RX); + + if (blink & tx_blink_set) + *rules |= BIT(TRIGGER_NETDEV_TX); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_phy_led_hw_ctrl_get); + +int mtk_phy_led_hw_ctrl_set(struct phy_device *phydev, u8 index, + unsigned long rules, u16 on_set, + u16 rx_blink_set, u16 tx_blink_set) +{ + 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_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) + on |= MTK_PHY_LED_ON_LINK2500; + + if (rules & BIT(TRIGGER_NETDEV_RX)) { + if (on & on_set) { + if (on & MTK_PHY_LED_ON_LINK10) + blink |= MTK_PHY_LED_BLINK_10RX; + if (on & MTK_PHY_LED_ON_LINK100) + blink |= MTK_PHY_LED_BLINK_100RX; + if (on & MTK_PHY_LED_ON_LINK1000) + blink |= MTK_PHY_LED_BLINK_1000RX; + if (on & MTK_PHY_LED_ON_LINK2500) + blink |= MTK_PHY_LED_BLINK_2500RX; + } else { + blink |= rx_blink_set; + } + } + + if (rules & BIT(TRIGGER_NETDEV_TX)) { + if (on & on_set) { + if (on & MTK_PHY_LED_ON_LINK10) + blink |= MTK_PHY_LED_BLINK_10TX; + if (on & MTK_PHY_LED_ON_LINK100) + blink |= MTK_PHY_LED_BLINK_100TX; + if (on & MTK_PHY_LED_ON_LINK1000) + blink |= MTK_PHY_LED_BLINK_1000TX; + if (on & MTK_PHY_LED_ON_LINK2500) + blink |= MTK_PHY_LED_BLINK_2500TX; + } else { + blink |= tx_blink_set; + } + } + + 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 | on_set, + 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); +} +EXPORT_SYMBOL_GPL(mtk_phy_led_hw_ctrl_set); + +int mtk_phy_led_num_dly_cfg(u8 index, unsigned long *delay_on, + unsigned long *delay_off, bool *blinking) +{ + if (index > 1) + return -EINVAL; + + if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) { + *blinking = true; + *delay_on = 50; + *delay_off = 50; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_phy_led_num_dly_cfg); + +int mtk_phy_hw_led_on_set(struct phy_device *phydev, u8 index, + u16 led_on_mask, 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, + led_on_mask, + on ? MTK_PHY_LED_ON_FORCE_ON : 0); + else + return 0; +} +EXPORT_SYMBOL_GPL(mtk_phy_hw_led_on_set); + +int mtk_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; +} +EXPORT_SYMBOL_GPL(mtk_phy_hw_led_blink_set); + +void mtk_phy_leds_state_init(struct phy_device *phydev) +{ + int i; + + for (i = 0; i < 2; ++i) + phydev->drv->led_hw_control_get(phydev, i, NULL); +} +EXPORT_SYMBOL_GPL(mtk_phy_leds_state_init); + +MODULE_DESCRIPTION("MediaTek Ethernet PHY driver common"); +MODULE_AUTHOR("Sky Huang "); +MODULE_AUTHOR("Daniel Golle "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h new file mode 100644 index 000000000..79e09727b --- /dev/null +++ b/drivers/net/phy/mediatek/mtk.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Common definition for Mediatek Ethernet PHYs + * Author: SkyLake Huang + * Copyright (c) 2024 MediaTek Inc. + */ + +#ifndef _MTK_EPHY_H_ +#define _MTK_EPHY_H_ + +#define MTK_PHY_AUX_CTRL_AND_STATUS 0x14 +#define MTK_PHY_ENABLE_DOWNSHIFT BIT(4) + +#define MTK_EXT_PAGE_ACCESS 0x1f +#define MTK_PHY_PAGE_EXTENDED_1 0x0001 +#define MTK_PHY_PAGE_STANDARD 0x0000 +#define MTK_PHY_PAGE_EXTENDED_52B5 0x52b5 + +/* Registers on MDIO_MMD_VEND2 */ +#define MTK_PHY_LED0_ON_CTRL 0x24 +#define MTK_PHY_LED1_ON_CTRL 0x26 +#define MTK_GPHY_LED_ON_MASK GENMASK(6, 0) +#define MTK_2P5GPHY_LED_ON_MASK GENMASK(7, 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_LINK2500 BIT(7) +#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_LED_BLINK_2500TX BIT(10) +#define MTK_PHY_LED_BLINK_2500RX BIT(11) + +#define MTK_GPHY_LED_ON_SET (MTK_PHY_LED_ON_LINK1000 | \ + MTK_PHY_LED_ON_LINK100 | \ + MTK_PHY_LED_ON_LINK10) +#define MTK_GPHY_LED_RX_BLINK_SET (MTK_PHY_LED_BLINK_1000RX | \ + MTK_PHY_LED_BLINK_100RX | \ + MTK_PHY_LED_BLINK_10RX) +#define MTK_GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_1000TX | \ + MTK_PHY_LED_BLINK_100TX | \ + MTK_PHY_LED_BLINK_10TX) + +#define MTK_2P5GPHY_LED_ON_SET (MTK_PHY_LED_ON_LINK2500 | \ + MTK_GPHY_LED_ON_SET) +#define MTK_2P5GPHY_LED_RX_BLINK_SET (MTK_PHY_LED_BLINK_2500RX | \ + MTK_GPHY_LED_RX_BLINK_SET) +#define MTK_2P5GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_2500TX | \ + MTK_GPHY_LED_TX_BLINK_SET) + +#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; +}; + +void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 mask, u32 set); +void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 mask, u32 set); +void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 set); +void __mtk_tr_clr_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr, + u8 data_addr, u32 clr); + +int mtk_phy_read_page(struct phy_device *phydev); +int mtk_phy_write_page(struct phy_device *phydev, int page); + +int mtk_phy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules, + unsigned long supported_triggers); +int mtk_phy_led_hw_ctrl_set(struct phy_device *phydev, u8 index, + unsigned long rules, u16 on_set, + u16 rx_blink_set, u16 tx_blink_set); +int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index, + unsigned long *rules, u16 on_set, + u16 rx_blink_set, u16 tx_blink_set); +int mtk_phy_led_num_dly_cfg(u8 index, unsigned long *delay_on, + unsigned long *delay_off, bool *blinking); +int mtk_phy_hw_led_on_set(struct phy_device *phydev, u8 index, + u16 led_on_mask, bool on); +int mtk_phy_hw_led_blink_set(struct phy_device *phydev, u8 index, + bool blinking); +void mtk_phy_leds_state_init(struct phy_device *phydev); + +#endif /* _MTK_EPHY_H_ */ diff --git a/drivers/net/phy/meson-gxl.c b/drivers/net/phy/meson-gxl.c new file mode 100644 index 000000000..962ebbbc1 --- /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 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#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 const 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 "); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c new file mode 100644 index 000000000..ae8307818 --- /dev/null +++ b/drivers/net/phy/micrel.c @@ -0,0 +1,7139 @@ +// 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phylib.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 + +/* KSZ9x31 remote loopback register */ +#define KSZ9x31_REMOTE_LOOPBACK 0x11 +/* This is an undocumented bit of the KSZ9131RNX. + * It was reported by NXP in cooperation with Micrel. + */ +#define KSZ9x31_REMOTE_LOOPBACK_KEEP_PREAMBLE BIT(2) +#define KSZ9x31_REMOTE_LOOPBACK_EN BIT(8) + +#define LAN8814_SKUS 0xB + +#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_FLF BIT(15) +#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) + +#define LAN8814_EEE_STATE 0x38 +#define LAN8814_EEE_STATE_MASK2P5P BIT(10) + +#define LAN8814_PD_CONTROLS 0x9d +#define LAN8814_PD_CONTROLS_PD_MEAS_TIME_MASK GENMASK(3, 0) +#define LAN8814_PD_CONTROLS_PD_MEAS_TIME_VAL 0xb + +/* 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 + +/* Represents 1ppm adjustment in 2^32 format with + * each nsec contains 8 clock cycles. + * The value is calculated as following: (1/1000000)/((2^-32)/8) + */ +#define LAN8841_1PPM_FORMAT 34360 + +#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_COMMON_INT_ENA 0x0204 +#define PTP_COMMON_INT_ENA_GPIO_CAP_EN BIT(2) + +#define PTP_CLOCK_SET_SEC_HI 0x0205 +#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_HI 0x0229 +#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_GPIO_SEL 0x0230 +#define PTP_GPIO_SEL_GPIO_SEL(pin) ((pin) << 8) +#define PTP_GPIO_CAP_MAP_LO 0x0232 + +#define PTP_GPIO_CAP_EN 0x0233 +#define PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(gpio) BIT(gpio) +#define PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(gpio) (BIT(gpio) << 8) + +#define PTP_GPIO_RE_LTC_SEC_HI_CAP 0x0235 +#define PTP_GPIO_RE_LTC_SEC_LO_CAP 0x0236 +#define PTP_GPIO_RE_LTC_NS_HI_CAP 0x0237 +#define PTP_GPIO_RE_LTC_NS_LO_CAP 0x0238 +#define PTP_GPIO_FE_LTC_SEC_HI_CAP 0x0239 +#define PTP_GPIO_FE_LTC_SEC_LO_CAP 0x023A +#define PTP_GPIO_FE_LTC_NS_HI_CAP 0x023B +#define PTP_GPIO_FE_LTC_NS_LO_CAP 0x023C + +#define PTP_GPIO_CAP_STS 0x023D +#define PTP_GPIO_CAP_STS_PTP_GPIO_RE_STS(gpio) BIT(gpio) +#define PTP_GPIO_CAP_STS_PTP_GPIO_FE_STS(gpio) (BIT(gpio) << 8) + +#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) +#define LAN8814_LED_CTRL_2 0x1 +#define LAN8814_LED_CTRL_2_LED1_COM_DIS BIT(8) + +/* 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 + +/* Vendor-specific Clause 22 register, virtualized by KSZ87xx embedded PHYs DSA driver */ +#define MII_KSZ87XX_SHORT_CABLE 0x1a +#define MII_KSZ87XX_LPF_BW 0x1b +#define MII_KSZ87XX_EQ_INIT 0x1c + +/* 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 + +#define LAN8814_PTP_GPIO_NUM 24 +#define LAN8814_PTP_PEROUT_NUM 2 +#define LAN8814_PTP_EXTTS_NUM 3 + +#define LAN8814_BUFFER_TIME 2 + +#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 + +#define LAN8814_GPIO_EN1 0x20 +#define LAN8814_GPIO_EN2 0x21 +#define LAN8814_GPIO_DIR1 0x22 +#define LAN8814_GPIO_DIR2 0x23 +#define LAN8814_GPIO_BUF1 0x24 +#define LAN8814_GPIO_BUF2 0x25 + +#define LAN8814_GPIO_EN_ADDR(pin) \ + ((pin) > 15 ? LAN8814_GPIO_EN1 : LAN8814_GPIO_EN2) +#define LAN8814_GPIO_EN_BIT(pin) BIT(pin) +#define LAN8814_GPIO_DIR_ADDR(pin) \ + ((pin) > 15 ? LAN8814_GPIO_DIR1 : LAN8814_GPIO_DIR2) +#define LAN8814_GPIO_DIR_BIT(pin) BIT(pin) +#define LAN8814_GPIO_BUF_ADDR(pin) \ + ((pin) > 15 ? LAN8814_GPIO_BUF1 : LAN8814_GPIO_BUF2) +#define LAN8814_GPIO_BUF_BIT(pin) BIT(pin) + +#define LAN8814_EVENT_A 0 +#define LAN8814_EVENT_B 1 + +#define LAN8814_PTP_GENERAL_CONFIG 0x0201 +#define LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_MASK(event) \ + ((event) ? GENMASK(11, 8) : GENMASK(7, 4)) +#define LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_SET(event, value) \ + (((value) & GENMASK(3, 0)) << (4 + ((event) << 2))) +#define LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event) \ + ((event) ? BIT(2) : BIT(0)) +#define LAN8814_PTP_GENERAL_CONFIG_POLARITY_X(event) \ + ((event) ? BIT(3) : BIT(1)) + +#define LAN8814_PTP_CLOCK_TARGET_SEC_HI(event) ((event) ? 0x21F : 0x215) +#define LAN8814_PTP_CLOCK_TARGET_SEC_LO(event) ((event) ? 0x220 : 0x216) +#define LAN8814_PTP_CLOCK_TARGET_NS_HI(event) ((event) ? 0x221 : 0x217) +#define LAN8814_PTP_CLOCK_TARGET_NS_LO(event) ((event) ? 0x222 : 0x218) + +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_HI(event) ((event) ? 0x223 : 0x219) +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_LO(event) ((event) ? 0x224 : 0x21A) +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_HI(event) ((event) ? 0x225 : 0x21B) +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_LO(event) ((event) ? 0x226 : 0x21C) + +/* Delay used to get the second part from the LTC */ +#define LAN8841_GET_SEC_LTC_DELAY (500 * NSEC_PER_MSEC) + +#define LAN8842_REV_8832 0x8832 + +#define LAN8814_REV_LAN8814 0x8814 +#define LAN8814_REV_LAN8818 0x8818 + +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; + struct ptp_pin_desc *pin_config; + + /* Lock for ptp_clock */ + 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_phy_stats { + u64 rx_err_pkt_cnt; +}; + +struct kszphy_priv { + struct kszphy_ptp_priv ptp_priv; + const struct kszphy_type *type; + struct clk *clk; + int led_mode; + u16 vct_ctrl1000; + bool rmii_ref_clk_sel; + bool rmii_ref_clk_sel_val; + bool clk_enable; + bool is_ptp_available; + u64 stats[ARRAY_SIZE(kszphy_hw_stats)]; + struct kszphy_phy_stats phy_stats; +}; + +struct lan8842_phy_stats { + u64 rx_packets; + u64 rx_errors; + u64 tx_packets; + u64 tx_errors; +}; + +struct lan8842_priv { + struct lan8842_phy_stats phy_stats; + struct kszphy_ptp_priv ptp_priv; + u16 rev; +}; + +struct lanphy_reg_data { + int page; + u16 addr; + u16 val; +}; + +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, + .cable_diag_reg = KSZ8081_LMD, + .pair_mask = KSZPHY_WIRE_PAIR_MASK, + .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, + const struct phy_driver *phydrv) +{ + 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; + + /* Chip can be powered down by the bootstrap code. */ + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_PDOWN) { + ret = phy_write(phydev, MII_BMCR, ret & ~BMCR_PDOWN); + if (ret < 0) + return ret; + usleep_range(1000, 2000); + } + + 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, + const struct phy_driver *phydrv) +{ + return ksz8051_ksz8795_match_phy_device(phydev, false); +} + +static int ksz8795_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + int ret; + + switch (tuna->id) { + case ETHTOOL_PHY_SHORT_CABLE_PRESET: + ret = phy_read(phydev, MII_KSZ87XX_SHORT_CABLE); + if (ret < 0) + return ret; + *(u8 *)data = ret; + return 0; + case ETHTOOL_PHY_LPF_BW: + ret = phy_read(phydev, MII_KSZ87XX_LPF_BW); + if (ret < 0) + return ret; + *(u32 *)data = ret & 0xff; + return 0; + case ETHTOOL_PHY_DSP_EQ_INIT_VALUE: + ret = phy_read(phydev, MII_KSZ87XX_EQ_INIT); + if (ret < 0) + return ret; + *(u32 *)data = ret & 0xff; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ksz8795_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + u32 val; + + switch (tuna->id) { + case ETHTOOL_PHY_SHORT_CABLE_PRESET: + return phy_write(phydev, MII_KSZ87XX_SHORT_CABLE, + *(const u8 *)data); + case ETHTOOL_PHY_LPF_BW: + val = *(const u32 *)data; + if (val > 0xff) + return -EINVAL; + return phy_write(phydev, MII_KSZ87XX_LPF_BW, (u8)val); + case ETHTOOL_PHY_DSP_EQ_INIT_VALUE: + val = *(const u32 *)data; + if (val > 0xff) + return -EINVAL; + return phy_write(phydev, MII_KSZ87XX_EQ_INIT, (u8)val); + default: + return -EOPNOTSUPP; + } +} + +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 + * delay + */ +#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_set_loopback(struct phy_device *phydev, bool enable, + int speed) +{ + u16 ctl = BMCR_LOOPBACK; + int val; + + if (!enable) + return genphy_loopback(phydev, enable, 0); + + if (speed == SPEED_10 || speed == SPEED_100 || speed == SPEED_1000) + phydev->speed = speed; + else if (speed) + return -EINVAL; + phydev->duplex = DUPLEX_FULL; + + ctl |= mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); + + phy_write(phydev, MII_BMCR, ctl); + + return phy_read_poll_timeout(phydev, MII_BMSR, val, val & BMSR_LSTATUS, + 5000, 500000, true); +} + +/* KSZ9131-specific sequence to enable loopback, registers are undocumented + * in the datasheet but mentionned in the local loopback mode configuration + * steps. + * + * Without taking these steps, the PHY appears to disable its RXC while in + * loopback mode, which may be needed by some MACs such as stmmac. + */ +static int ksz9131_loopback_enable(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x15, 0xeeee); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x16, 0xeeee); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, 0x1c, 0x18, 0xeeee); + if (ret) + return ret; + + return phy_write_mmd(phydev, 0x1c, 0x1b, 0xeeee); +} + +/* KSZ9131 datasheet doesn't state how to deal with the MMD 0x1c registers + * when disabling loopback. + * + * Set them back to their measured initial state when disabling loopback, and + * ignore errors while doing so. + */ +static void ksz9131_loopback_disable(struct phy_device *phydev) +{ + phy_write_mmd(phydev, 0x1c, 0x15, 0x6eff); + phy_write_mmd(phydev, 0x1c, 0x16, 0xe6ff); + phy_write_mmd(phydev, 0x1c, 0x18, 0x43ff); + phy_write_mmd(phydev, 0x1c, 0x1b, 0x07ff); +} + +static int ksz9131_set_loopback(struct phy_device *phydev, bool enable, + int speed) +{ + int ret; + + if (enable) { + ret = ksz9131_loopback_enable(phydev); + if (ret) + return ret; + } + + ret = ksz9031_set_loopback(phydev, enable, speed); + + if (ret || !enable) + ksz9131_loopback_disable(phydev); + + return ret; +} + +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; + + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + + 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; + + if (phydev->dev_flags & PHY_F_KEEP_PREAMBLE_BEFORE_SFD) + ret = phy_modify(phydev, KSZ9x31_REMOTE_LOOPBACK, 0, + KSZ9x31_REMOTE_LOOPBACK_KEEP_PREAMBLE); + + return ret; +} + +#define MII_KSZ9131_AUTO_MDIX 0x1C +#define MII_KSZ9131_AUTO_MDI_SET BIT(7) +#define MII_KSZ9131_AUTO_MDIX_SWAP_OFF BIT(6) +#define MII_KSZ9131_DIG_AXAN_STS 0x14 +#define MII_KSZ9131_DIG_AXAN_STS_LINK_DET BIT(14) +#define MII_KSZ9131_DIG_AXAN_STS_A_SELECT BIT(12) + +static int ksz9131_mdix_update(struct phy_device *phydev) +{ + int ret; + + if (phydev->mdix_ctrl != ETH_TP_MDI_AUTO) { + phydev->mdix = phydev->mdix_ctrl; + } else { + ret = phy_read(phydev, MII_KSZ9131_DIG_AXAN_STS); + if (ret < 0) + return ret; + + if (ret & MII_KSZ9131_DIG_AXAN_STS_LINK_DET) { + if (ret & MII_KSZ9131_DIG_AXAN_STS_A_SELECT) + phydev->mdix = ETH_TP_MDI; + else + phydev->mdix = ETH_TP_MDI_X; + } else { + phydev->mdix = ETH_TP_MDI_INVALID; + } + } + + 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) || + phydev_id_compare(phydev, PHY_ID_KSZ9477)) + 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; + int retries = 20; + int pair, ret, rv; + + *finished = false; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->supported)) + pair_mask = 0xf; /* All pairs */ + else + pair_mask = 0x3; /* Pairs A and B only */ + + /* 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; + + if (phydev->autoneg != AUTONEG_ENABLE) { + /* When autonegotiation is disabled, we need to manually force + * the link state. If we don't do this, the PHY will keep + * sending Fast Link Pulses (FLPs) which are part of the + * autonegotiation process. This is not desired when + * autonegotiation is off. + */ + ret = phy_set_bits(phydev, MII_KSZPHY_CTRL, + KSZ886X_CTRL_FORCE_LINK); + if (ret) + return ret; + } else { + /* If we had previously forced the link state, we need to + * clear KSZ886X_CTRL_FORCE_LINK bit now. Otherwise, the PHY + * will not perform autonegotiation. + */ + ret = phy_clear_bits(phydev, MII_KSZPHY_CTRL, + KSZ886X_CTRL_FORCE_LINK); + 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); +} + +static int ksz9477_mdix_update(struct phy_device *phydev) +{ + if (phydev->mdix_ctrl != ETH_TP_MDI_AUTO) + phydev->mdix = phydev->mdix_ctrl; + else + phydev->mdix = ETH_TP_MDI_INVALID; + + return 0; +} + +static int ksz9477_read_mdix_ctrl(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MII_KSZ9131_AUTO_MDIX); + if (val < 0) + return val; + + if (!(val & MII_KSZ9131_AUTO_MDIX_SWAP_OFF)) + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + else if (val & MII_KSZ9131_AUTO_MDI_SET) + phydev->mdix_ctrl = ETH_TP_MDI; + else + phydev->mdix_ctrl = ETH_TP_MDI_X; + + return 0; +} + +static int ksz9477_read_status(struct phy_device *phydev) +{ + int ret; + + ret = ksz9477_mdix_update(phydev); + if (ret) + return ret; + + return genphy_read_status(phydev); +} + +static int ksz9477_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); +} + +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_phy_errata(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; + } + + return genphy_restart_aneg(phydev); +} + +static int ksz9477_config_init(struct phy_device *phydev) +{ + int err; + + /* Only KSZ9897 family of switches needs this fix. */ + if ((phydev->phy_id & 0xf) == 1) { + err = ksz9477_phy_errata(phydev); + if (err) + return err; + } + + /* Read initial MDI-X config state. So, we do not need to poll it + * later on. + */ + err = ksz9477_read_mdix_ctrl(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++) + ethtool_puts(&data, kszphy_hw_stats[i].string); +} + +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); +} + +/* KSZ9477 PHY RXER Counter. Probably supported by other PHYs like KSZ9313, + * etc. The counter is incremented when the PHY receives a frame with one or + * more symbol errors. The counter is cleared when the register is read. + */ +#define MII_KSZ9477_PHY_RXER_COUNTER 0x15 + +static int kszphy_update_stats(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + int ret; + + ret = phy_read(phydev, MII_KSZ9477_PHY_RXER_COUNTER); + if (ret < 0) + return ret; + + priv->phy_stats.rx_err_pkt_cnt += ret; + + return 0; +} + +static void kszphy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct kszphy_priv *priv = phydev->priv; + + stats->rx_errors = priv->phy_stats.rx_err_pkt_cnt; +} + +/* Base register for Signal Quality Indicator (SQI) - Channel A + * + * MMD Address: MDIO_MMD_PMAPMD (0x01) + * Register: 0xAC (Channel A) + * Each channel (pair) has its own register: + * Channel A: 0xAC + * Channel B: 0xAD + * Channel C: 0xAE + * Channel D: 0xAF + */ +#define KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A 0xac + +/* SQI field mask for bits [14:8] + * + * SQI indicates relative quality of the signal. + * A lower value indicates better signal quality. + */ +#define KSZ9477_MMD_SQI_MASK GENMASK(14, 8) + +#define KSZ9477_MAX_CHANNELS 4 +#define KSZ9477_SQI_MAX 7 + +/* Number of SQI samples to average for a stable result. + * + * Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26) + * For noisy environments, a minimum of 30–50 readings is recommended. + */ +#define KSZ9477_SQI_SAMPLE_COUNT 40 + +/* The hardware SQI register provides a raw value from 0-127, where a lower + * value indicates better signal quality. However, empirical testing has + * shown that only the 0-7 range is relevant for a functional link. A raw + * value of 8 or higher was measured directly before link drop. This aligns + * with the OPEN Alliance recommendation that SQI=0 should represent the + * pre-failure state. + * + * This table provides a non-linear mapping from the useful raw hardware + * values (0-7) to the standard 0-7 SQI scale, where higher is better. + */ +static const u8 ksz_sqi_mapping[] = { + 7, /* raw 0 -> SQI 7 */ + 7, /* raw 1 -> SQI 7 */ + 6, /* raw 2 -> SQI 6 */ + 5, /* raw 3 -> SQI 5 */ + 4, /* raw 4 -> SQI 4 */ + 3, /* raw 5 -> SQI 3 */ + 2, /* raw 6 -> SQI 2 */ + 1, /* raw 7 -> SQI 1 */ +}; + +/** + * kszphy_get_sqi - Read, average, and map Signal Quality Index (SQI) + * @phydev: the PHY device + * + * This function reads and processes the raw Signal Quality Index from the + * PHY. Based on empirical testing, a raw value of 8 or higher indicates a + * pre-failure state and is mapped to SQI 0. Raw values from 0-7 are + * mapped to the standard 0-7 SQI scale via a lookup table. + * + * Return: SQI value (0–7), or a negative errno on failure. + */ +static int kszphy_get_sqi(struct phy_device *phydev) +{ + int sum[KSZ9477_MAX_CHANNELS] = { 0 }; + int worst_sqi = KSZ9477_SQI_MAX; + int i, val, raw_sqi, ch; + u8 channels; + + /* Determine applicable channels based on link speed */ + if (phydev->speed == SPEED_1000) + channels = 4; + else if (phydev->speed == SPEED_100) + channels = 1; + else + return -EOPNOTSUPP; + + /* Sample and accumulate SQI readings for each pair (currently only one). + * + * Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26) + * - The SQI register is updated every 2 µs. + * - Values may fluctuate significantly, even in low-noise environments. + * - For reliable estimation, average a minimum of 30–50 samples + * (recommended for noisy environments) + * - In noisy environments, individual readings are highly unreliable. + * + * We use 40 samples per pair with a delay of 3 µs between each + * read to ensure new values are captured (2 µs update interval). + */ + for (i = 0; i < KSZ9477_SQI_SAMPLE_COUNT; i++) { + for (ch = 0; ch < channels; ch++) { + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + ch); + if (val < 0) + return val; + + raw_sqi = FIELD_GET(KSZ9477_MMD_SQI_MASK, val); + sum[ch] += raw_sqi; + + /* We communicate with the PHY via MDIO via SPI or + * I2C, which is relatively slow. At least slower than + * the update interval of the SQI register. + * So, we can skip the delay between reads. + */ + } + } + + /* Calculate average for each channel and find the worst SQI */ + for (ch = 0; ch < channels; ch++) { + int avg_raw_sqi = sum[ch] / KSZ9477_SQI_SAMPLE_COUNT; + int mapped_sqi; + + /* Handle the pre-fail/failed state first. */ + if (avg_raw_sqi >= ARRAY_SIZE(ksz_sqi_mapping)) + mapped_sqi = 0; + else + /* Use the lookup table for the good signal range. */ + mapped_sqi = ksz_sqi_mapping[avg_raw_sqi]; + + if (mapped_sqi < worst_sqi) + worst_sqi = mapped_sqi; + } + + return worst_sqi; +} + +static int kszphy_get_sqi_max(struct phy_device *phydev) +{ + return KSZ9477_SQI_MAX; +} + +static int kszphy_get_mse_capability(struct phy_device *phydev, + struct phy_mse_capability *cap) +{ + /* Capabilities depend on link mode: + * - 1000BASE-T: per-pair SQI registers exist => expose A..D + * and a WORST selector. + * - 100BASE-TX: HW provides a single MSE/SQI reading in the "channel A" + * register, but with auto MDI-X there is no MDI-X resolution bit, + * so we cannot map that register to a specific wire pair reliably. + * To avoid misleading per-channel data, advertise only LINK. + * Other speeds: no MSE exposure via this driver. + * + * Note: WORST is *not* a hardware selector on this family. + * We expose it because the driver computes it in software + * by scanning per-channel readouts (A..D) and picking the + * maximum average MSE. + */ + if (phydev->speed == SPEED_1000) + cap->supported_caps = PHY_MSE_CAP_CHANNEL_A | + PHY_MSE_CAP_CHANNEL_B | + PHY_MSE_CAP_CHANNEL_C | + PHY_MSE_CAP_CHANNEL_D | + PHY_MSE_CAP_WORST_CHANNEL; + else if (phydev->speed == SPEED_100) + cap->supported_caps = PHY_MSE_CAP_LINK; + else + return -EOPNOTSUPP; + + cap->max_average_mse = FIELD_MAX(KSZ9477_MMD_SQI_MASK); + cap->refresh_rate_ps = 2000000; /* 2 us */ + /* Estimated from link modulation (125 MBd per channel) and documented + * refresh rate of 2 us + */ + cap->num_symbols = 250; + + cap->supported_caps |= PHY_MSE_CAP_AVG; + + return 0; +} + +static int kszphy_get_mse_snapshot(struct phy_device *phydev, + enum phy_mse_channel channel, + struct phy_mse_snapshot *snapshot) +{ + u8 num_channels; + int ret; + + if (phydev->speed == SPEED_1000) + num_channels = 4; + else if (phydev->speed == SPEED_100) + num_channels = 1; + else + return -EOPNOTSUPP; + + if (channel == PHY_MSE_CHANNEL_WORST) { + u32 worst_val = 0; + int i; + + /* WORST is implemented in software: select the maximum + * average MSE across the available per-channel registers. + * Only defined when multiple channels exist (1000BASE-T). + */ + if (num_channels < 2) + return -EOPNOTSUPP; + + for (i = 0; i < num_channels; i++) { + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + i); + if (ret < 0) + return ret; + + ret = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret); + if (ret > worst_val) + worst_val = ret; + } + snapshot->average_mse = worst_val; + } else if (channel == PHY_MSE_CHANNEL_LINK && num_channels == 1) { + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A); + if (ret < 0) + return ret; + snapshot->average_mse = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret); + } else if (channel >= PHY_MSE_CHANNEL_A && + channel <= PHY_MSE_CHANNEL_D) { + /* Per-channel readouts are valid only for 1000BASE-T. */ + if (phydev->speed != SPEED_1000) + return -EOPNOTSUPP; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + channel); + if (ret < 0) + return ret; + snapshot->average_mse = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret); + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +static void kszphy_enable_clk(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + + if (!priv->clk_enable && priv->clk) { + clk_prepare_enable(priv->clk); + priv->clk_enable = true; + } +} + +static void kszphy_disable_clk(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + + if (priv->clk_enable && priv->clk) { + clk_disable_unprepare(priv->clk); + priv->clk_enable = false; + } +} + +static int kszphy_generic_resume(struct phy_device *phydev) +{ + kszphy_enable_clk(phydev); + + return genphy_resume(phydev); +} + +static int kszphy_generic_suspend(struct phy_device *phydev) +{ + int ret; + + ret = genphy_suspend(phydev); + if (ret) + return ret; + + kszphy_disable_clk(phydev); + + return 0; +} + +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 kszphy_generic_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; + + ret = kszphy_generic_resume(phydev); + if (ret) + return ret; + + /* 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; +} + +/* Because of errata DS80000700A, receiver error following software + * power down. Suspend and resume callbacks only disable and enable + * external rmii reference clock. + */ +static int ksz8041_resume(struct phy_device *phydev) +{ + kszphy_enable_clk(phydev); + + return 0; +} + +static int ksz8041_suspend(struct phy_device *phydev) +{ + kszphy_disable_clk(phydev); + + return 0; +} + +static int ksz9477_resume(struct phy_device *phydev) +{ + int ret; + + /* No need to initialize registers if not powered down. */ + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + if (!(ret & BMCR_PDOWN)) + return 0; + + 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); + + /* Only KSZ9897 family of switches needs this fix. */ + if ((phydev->phy_id & 0xf) == 1) { + ret = ksz9477_phy_errata(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 ksz8061_resume(struct phy_device *phydev) +{ + int ret; + + /* This function can be called twice when the Ethernet device is on. */ + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + if (!(ret & BMCR_PDOWN)) + return 0; + + ret = kszphy_generic_resume(phydev); + if (ret) + return ret; + + usleep_range(1000, 2000); + + /* Re-program the value after chip is reset. */ + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_DEVID1, 0xB61A); + 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 ksz8061_suspend(struct phy_device *phydev) +{ + return kszphy_suspend(phydev); +} + +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_optional(&phydev->mdio.dev, "rmii-ref"); + /* NOTE: clk may be NULL if building without CONFIG_HAVE_CLK */ + if (!IS_ERR_OR_NULL(clk)) { + bool rmii_ref_clk_sel_25_mhz; + unsigned long rate; + int err; + + err = clk_prepare_enable(clk); + if (err) { + phydev_err(phydev, "Failed to enable rmii-ref clock\n"); + return err; + } + + rate = clk_get_rate(clk); + clk_disable_unprepare(clk); + + 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; + } + } else if (!clk) { + /* unnamed clock from the generic ethernet-phy binding */ + clk = devm_clk_get_optional(&phydev->mdio.dev, NULL); + } + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + priv->clk = clk; + + 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; +} + +/** + * LAN8814_PAGE_PCS - Selects Extended Page 0. + * + * This page contains timers used for auto-negotiation, debug registers and + * register to configure fast link failure. + */ +#define LAN8814_PAGE_PCS 0 + +/** + * LAN8814_PAGE_AFE_PMA - Selects Extended Page 1. + * + * This page appears to control the Analog Front-End (AFE) and Physical + * Medium Attachment (PMA) layers. It is used to access registers like + * LAN8814_PD_CONTROLS and LAN8814_LINK_QUALITY. + */ +#define LAN8814_PAGE_AFE_PMA 1 + +/** + * LAN8814_PAGE_PCS_DIGITAL - Selects Extended Page 2. + * + * This page seems dedicated to the Physical Coding Sublayer (PCS) and other + * digital logic. It is used for MDI-X alignment (LAN8814_ALIGN_SWAP) and EEE + * state (LAN8814_EEE_STATE) in the LAN8814, and is repurposed for statistics + * and self-test counters in the LAN8842. + */ +#define LAN8814_PAGE_PCS_DIGITAL 2 + +/** + * LAN8814_PAGE_EEE - Selects Extended Page 3. + * + * This page contains EEE registers + */ +#define LAN8814_PAGE_EEE 3 + +/** + * LAN8814_PAGE_COMMON_REGS - Selects Extended Page 4. + * + * This page contains device-common registers that affect the entire chip. + * It includes controls for chip-level resets, strap status, GPIO, + * QSGMII, the shared 1588 PTP block, and the PVT monitor. + */ +#define LAN8814_PAGE_COMMON_REGS 4 + +/** + * LAN8814_PAGE_PORT_REGS - Selects Extended Page 5. + * + * This page contains port-specific registers that must be accessed + * on a per-port basis. It includes controls for port LEDs, QSGMII PCS, + * rate adaptation FIFOs, and the per-port 1588 TSU block. + */ +#define LAN8814_PAGE_PORT_REGS 5 + +/** + * LAN8814_PAGE_POWER_REGS - Selects Extended Page 28. + * + * This page contains analog control registers and power mode registers. + */ +#define LAN8814_PAGE_POWER_REGS 28 + +/** + * LAN8814_PAGE_SYSTEM_CTRL - Selects Extended Page 31. + * + * This page appears to hold fundamental system or global controls. In the + * driver, it is used by the related LAN8804 to access the + * LAN8814_CLOCK_MANAGEMENT register. + */ +#define LAN8814_PAGE_SYSTEM_CTRL 31 + +#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_TX_CONFIG 0x35 +#define LAN8814_QSGMII_TX_CONFIG_QSGMII BIT(3) +#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 lanphy_modify_page_reg(struct phy_device *phydev, int page, u16 addr, + u16 mask, u16 set) +{ + int ret; + + 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)); + ret = __phy_modify_changed(phydev, LAN_EXT_PAGE_ACCESS_ADDRESS_DATA, + mask, set); + phy_unlock_mdio_bus(phydev); + + if (ret < 0) + phydev_err(phydev, "__phy_modify_changed() failed: %pe\n", + ERR_PTR(ret)); + + return ret; +} + +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, LAN8814_PAGE_PORT_REGS, + 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, LAN8814_PAGE_PORT_REGS, + PTP_RX_INGRESS_SEC_HI); + *seconds = (*seconds << 16) | + lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_RX_INGRESS_SEC_LO); + + *nano_seconds = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_RX_INGRESS_NS_HI); + *nano_seconds = ((*nano_seconds & 0x3fff) << 16) | + lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_RX_INGRESS_NS_LO); + + *seq_id = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + 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, LAN8814_PAGE_PORT_REGS, + PTP_TX_EGRESS_SEC_HI); + *seconds = *seconds << 16 | + lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_EGRESS_SEC_LO); + + *nano_seconds = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_EGRESS_NS_HI); + *nano_seconds = ((*nano_seconds & 0x3fff) << 16) | + lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_EGRESS_NS_LO); + + *seq_id = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_MSG_HEADER2); +} + +static int lan8814_ts_info(struct mii_timestamper *mii_ts, struct kernel_ethtool_ts_info *info) +{ + struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); + struct lan8814_shared_priv *shared = phy_package_get_priv(ptp_priv->phydev); + + 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, LAN8814_PAGE_PORT_REGS, + egress ? PTP_TX_MSG_HEADER2 : PTP_RX_MSG_HEADER2); + + /* Read to clear overflow status bit */ + lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_TSU_INT_STS); +} + +static int lan8814_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config) +{ + struct kszphy_ptp_priv *ptp_priv = + container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); + + config->tx_type = ptp_priv->hwts_tx_type; + config->rx_filter = ptp_priv->rx_filter; + + return 0; +} + +static int lan8814_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct kszphy_ptp_priv *ptp_priv = + container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); + struct lan8814_ptp_rx_ts *rx_ts, *tmp; + int txcfg = 0, rxcfg = 0; + int pkt_ts_enable; + + 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; + } + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + case HWTSTAMP_TX_ON: + case HWTSTAMP_TX_ONESTEP_SYNC: + break; + default: + return -ERANGE; + } + + ptp_priv->hwts_tx_type = config->tx_type; + ptp_priv->rx_filter = config->rx_filter; + + 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, LAN8814_PAGE_PORT_REGS, + PTP_RX_PARSE_CONFIG, rxcfg); + lanphy_write_page_reg(ptp_priv->phydev, LAN8814_PAGE_PORT_REGS, + 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, LAN8814_PAGE_PORT_REGS, + PTP_RX_TIMESTAMP_EN, pkt_ts_enable); + lanphy_write_page_reg(ptp_priv->phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_TIMESTAMP_EN, pkt_ts_enable); + + if (ptp_priv->hwts_tx_type == HWTSTAMP_TX_ONESTEP_SYNC) { + lanphy_modify_page_reg(ptp_priv->phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_MOD, + PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_, + PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_); + } else if (ptp_priv->hwts_tx_type == HWTSTAMP_TX_ON) { + lanphy_modify_page_reg(ptp_priv->phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_MOD, + PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_, + 0); + } + + if (config->rx_filter != HWTSTAMP_FILTER_NONE) + lan8814_config_ts_intr(ptp_priv->phydev, true); + else + lan8814_config_ts_intr(ptp_priv->phydev, false); + + /* 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 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 bool 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); + + if (!ptp_header) + return false; + + *sig = (__force u16)(ntohs(ptp_header->sequence_id)); + return true; +} + +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; + + if (!lan8814_get_sig_rx(skb, &skb_sig)) + return ret; + + /* 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, + time64_t sec, u32 nsec) +{ + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_SET_SEC_LO, lower_16_bits(sec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_SET_SEC_MID, upper_16_bits(sec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_SET_SEC_HI, upper_32_bits(sec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_SET_NS_LO, lower_16_bits(nsec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_SET_NS_HI, upper_16_bits(nsec)); + + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_CMD_CTL, + PTP_CMD_CTL_PTP_CLOCK_LOAD_); +} + +static void lan8814_ptp_clock_get(struct phy_device *phydev, + time64_t *sec, u32 *nsec) +{ + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_CMD_CTL, + PTP_CMD_CTL_PTP_CLOCK_READ_); + + *sec = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_READ_SEC_HI); + *sec <<= 16; + *sec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_READ_SEC_MID); + *sec <<= 16; + *sec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_READ_SEC_LO); + + *nsec = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_CLOCK_READ_NS_HI); + *nsec <<= 16; + *nsec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + 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; + time64_t 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_set_target(struct phy_device *phydev, int event, + s64 start_sec, u32 start_nsec) +{ + /* Set the start time */ + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_SEC_LO(event), + lower_16_bits(start_sec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_SEC_HI(event), + upper_16_bits(start_sec)); + + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_NS_LO(event), + lower_16_bits(start_nsec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_NS_HI(event), + upper_16_bits(start_nsec) & 0x3fff); +} + +static void lan8814_ptp_update_target(struct phy_device *phydev, time64_t sec) +{ + lan8814_ptp_set_target(phydev, LAN8814_EVENT_A, + sec + LAN8814_BUFFER_TIME, 0); + lan8814_ptp_set_target(phydev, LAN8814_EVENT_B, + sec + LAN8814_BUFFER_TIME, 0); +} + +static void lan8814_ptp_clock_step(struct phy_device *phydev, + s64 time_step_ns) +{ + u32 nano_seconds_step; + u64 abs_time_step_ns; + time64_t set_seconds; + u32 nano_seconds; + u32 remainder; + s32 seconds; + + if (time_step_ns > 15000000000LL) { + /* convert to clock set */ + lan8814_ptp_clock_get(phydev, &set_seconds, &nano_seconds); + set_seconds += div_u64_rem(time_step_ns, 1000000000LL, + &remainder); + nano_seconds += remainder; + if (nano_seconds >= 1000000000) { + set_seconds++; + nano_seconds -= 1000000000; + } + lan8814_ptp_clock_set(phydev, set_seconds, nano_seconds); + lan8814_ptp_update_target(phydev, set_seconds); + return; + } else if (time_step_ns < -15000000000LL) { + /* convert to clock set */ + time_step_ns = -time_step_ns; + + lan8814_ptp_clock_get(phydev, &set_seconds, &nano_seconds); + set_seconds -= div_u64_rem(time_step_ns, 1000000000LL, + &remainder); + nano_seconds_step = remainder; + if (nano_seconds < nano_seconds_step) { + set_seconds--; + nano_seconds += 1000000000; + } + nano_seconds -= nano_seconds_step; + lan8814_ptp_clock_set(phydev, set_seconds, nano_seconds); + lan8814_ptp_update_target(phydev, set_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) { + u32 nsec; + + 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, LAN8814_PAGE_COMMON_REGS, + PTP_LTC_STEP_ADJ_LO, + adjustment_value_lo); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_LTC_STEP_ADJ_HI, + PTP_LTC_STEP_ADJ_DIR_ | + adjustment_value_hi); + seconds -= ((s32)adjustment_value); + + lan8814_ptp_clock_get(phydev, &set_seconds, &nsec); + set_seconds -= adjustment_value; + lan8814_ptp_update_target(phydev, set_seconds); + } 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, LAN8814_PAGE_COMMON_REGS, + PTP_LTC_STEP_ADJ_LO, + adjustment_value_lo); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_LTC_STEP_ADJ_HI, + adjustment_value_hi); + seconds += ((s32)adjustment_value); + + lan8814_ptp_clock_get(phydev, &set_seconds, &nsec); + set_seconds += adjustment_value; + lan8814_ptp_update_target(phydev, set_seconds); + } + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + 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, LAN8814_PAGE_COMMON_REGS, + PTP_LTC_STEP_ADJ_LO, + nano_seconds_lo); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_LTC_STEP_ADJ_HI, + PTP_LTC_STEP_ADJ_DIR_ | + nano_seconds_hi); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, 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, LAN8814_PAGE_COMMON_REGS, PTP_CLOCK_RATE_ADJ_HI, + kszphy_rate_adj_hi); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_CLOCK_RATE_ADJ_LO, + kszphy_rate_adj_lo); + mutex_unlock(&shared->shared_lock); + + return 0; +} + +static void lan8814_ptp_set_reload(struct phy_device *phydev, int event, + s64 period_sec, u32 period_nsec) +{ + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_LO(event), + lower_16_bits(period_sec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_HI(event), + upper_16_bits(period_sec)); + + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_LO(event), + lower_16_bits(period_nsec)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_HI(event), + upper_16_bits(period_nsec) & 0x3fff); +} + +static void lan8814_ptp_enable_event(struct phy_device *phydev, int event, + int pulse_width) +{ + /* Set the pulse width of the event, + * Make sure that the target clock will be incremented each time when + * local time reaches or pass it + * Set the polarity high + */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, LAN8814_PTP_GENERAL_CONFIG, + LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_MASK(event) | + LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_SET(event, pulse_width) | + LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event) | + LAN8814_PTP_GENERAL_CONFIG_POLARITY_X(event), + LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_SET(event, pulse_width) | + LAN8814_PTP_GENERAL_CONFIG_POLARITY_X(event)); +} + +static void lan8814_ptp_disable_event(struct phy_device *phydev, int event) +{ + /* Set target to too far in the future, effectively disabling it */ + lan8814_ptp_set_target(phydev, event, 0xFFFFFFFF, 0); + + /* And then reload once it reaches the target */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, LAN8814_PTP_GENERAL_CONFIG, + LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event), + LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event)); +} + +static void lan8814_ptp_perout_off(struct phy_device *phydev, int pin) +{ + /* Disable gpio alternate function, + * 1: select as gpio, + * 0: select alt func + */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_EN_ADDR(pin), + LAN8814_GPIO_EN_BIT(pin), + LAN8814_GPIO_EN_BIT(pin)); + + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_DIR_ADDR(pin), + LAN8814_GPIO_DIR_BIT(pin), + 0); + + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_BUF_ADDR(pin), + LAN8814_GPIO_BUF_BIT(pin), + 0); +} + +static void lan8814_ptp_perout_on(struct phy_device *phydev, int pin) +{ + /* Set as gpio output */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_DIR_ADDR(pin), + LAN8814_GPIO_DIR_BIT(pin), + LAN8814_GPIO_DIR_BIT(pin)); + + /* Enable gpio 0:for alternate function, 1:gpio */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_EN_ADDR(pin), + LAN8814_GPIO_EN_BIT(pin), + 0); + + /* Set buffer type to push pull */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_BUF_ADDR(pin), + LAN8814_GPIO_BUF_BIT(pin), + LAN8814_GPIO_BUF_BIT(pin)); +} + +static int lan8814_ptp_perout(struct ptp_clock_info *ptpci, + struct ptp_clock_request *rq, int on) +{ + struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv, + ptp_clock_info); + struct phy_device *phydev = shared->phydev; + struct timespec64 ts_on, ts_period; + s64 on_nsec, period_nsec; + int pulse_width; + int pin, event; + + mutex_lock(&shared->shared_lock); + event = rq->perout.index; + pin = ptp_find_pin(shared->ptp_clock, PTP_PF_PEROUT, event); + if (pin < 0 || pin >= LAN8814_PTP_PEROUT_NUM) { + mutex_unlock(&shared->shared_lock); + return -EBUSY; + } + + if (!on) { + lan8814_ptp_perout_off(phydev, pin); + lan8814_ptp_disable_event(phydev, event); + mutex_unlock(&shared->shared_lock); + return 0; + } + + 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)); + mutex_unlock(&shared->shared_lock); + return -EOPNOTSUPP; + } + + if (on_nsec >= period_nsec) { + pr_warn_ratelimited("%s: pulse width must be smaller than period\n", + phydev_name(phydev)); + mutex_unlock(&shared->shared_lock); + 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; + } + + /* Configure to pulse every period */ + lan8814_ptp_enable_event(phydev, event, pulse_width); + lan8814_ptp_set_target(phydev, event, rq->perout.start.sec, + rq->perout.start.nsec); + lan8814_ptp_set_reload(phydev, event, rq->perout.period.sec, + rq->perout.period.nsec); + lan8814_ptp_perout_on(phydev, pin); + mutex_unlock(&shared->shared_lock); + + return 0; +} + +static void lan8814_ptp_extts_on(struct phy_device *phydev, int pin, u32 flags) +{ + /* Set as gpio input */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_DIR_ADDR(pin), + LAN8814_GPIO_DIR_BIT(pin), + 0); + + /* Map the pin to ltc pin 0 of the capture map registers */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_CAP_MAP_LO, pin, pin); + + /* Enable capture on the edges of the ltc pin */ + if (flags & PTP_RISING_EDGE) + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_CAP_EN, + PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(0), + PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(0)); + if (flags & PTP_FALLING_EDGE) + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_CAP_EN, + PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(0), + PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(0)); + + /* Enable interrupt top interrupt */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_COMMON_INT_ENA, + PTP_COMMON_INT_ENA_GPIO_CAP_EN, + PTP_COMMON_INT_ENA_GPIO_CAP_EN); +} + +static void lan8814_ptp_extts_off(struct phy_device *phydev, int pin) +{ + /* Set as gpio out */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_DIR_ADDR(pin), + LAN8814_GPIO_DIR_BIT(pin), + LAN8814_GPIO_DIR_BIT(pin)); + + /* Enable alternate, 0:for alternate function, 1:gpio */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_EN_ADDR(pin), + LAN8814_GPIO_EN_BIT(pin), + 0); + + /* Clear the mapping of pin to registers 0 of the capture registers */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_CAP_MAP_LO, + GENMASK(3, 0), + 0); + + /* Disable capture on both of the edges */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_GPIO_CAP_EN, + PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(pin) | + PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(pin), + 0); + + /* Disable interrupt top interrupt */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_COMMON_INT_ENA, + PTP_COMMON_INT_ENA_GPIO_CAP_EN, + 0); +} + +static int lan8814_ptp_extts(struct ptp_clock_info *ptpci, + struct ptp_clock_request *rq, int on) +{ + struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv, + ptp_clock_info); + struct phy_device *phydev = shared->phydev; + int pin; + + pin = ptp_find_pin(shared->ptp_clock, PTP_PF_EXTTS, + rq->extts.index); + if (pin == -1 || pin != LAN8814_PTP_EXTTS_NUM) + return -EINVAL; + + mutex_lock(&shared->shared_lock); + if (on) + lan8814_ptp_extts_on(phydev, pin, rq->extts.flags); + else + lan8814_ptp_extts_off(phydev, pin); + + mutex_unlock(&shared->shared_lock); + + return 0; +} + +static int lan8814_ptpci_enable(struct ptp_clock_info *ptpci, + struct ptp_clock_request *rq, int on) +{ + switch (rq->type) { + case PTP_CLK_REQ_PEROUT: + return lan8814_ptp_perout(ptpci, rq, on); + case PTP_CLK_REQ_EXTTS: + return lan8814_ptp_extts(ptpci, rq, on); + default: + return -EINVAL; + } +} + +static int lan8814_ptpci_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: + /* Only pins 0 and 1 can generate perout signals. And for pin 0 + * there is only chan 0 (event A) and for pin 1 there is only + * chan 1 (event B) + */ + if (pin >= LAN8814_PTP_PEROUT_NUM || pin != chan) + return -1; + break; + case PTP_PF_EXTTS: + if (pin != LAN8814_PTP_EXTTS_NUM) + return -1; + break; + default: + return -1; + } + + return 0; +} + +static bool 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); + + if (!ptp_header) + return false; + + *sig = (__force u16)(ntohs(ptp_header->sequence_id)); + return true; +} + +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) { + if (!lan8814_get_sig_tx(skb, &skb_sig)) + continue; + + 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, LAN8814_PAGE_PORT_REGS, + 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) { + if (!lan8814_get_sig_rx(skb, &skb_sig)) + continue; + + 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_obj(*rx_ts); + 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, LAN8814_PAGE_PORT_REGS, + 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 lan8814_gpio_process_cap(struct lan8814_shared_priv *shared) +{ + struct phy_device *phydev = shared->phydev; + struct ptp_clock_event ptp_event = {0}; + unsigned long nsec; + s64 sec; + u16 tmp; + + /* This is 0 because whatever was the input pin it was mapped it to + * ltc gpio pin 0 + */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_GPIO_SEL, + PTP_GPIO_SEL_GPIO_SEL(0), + PTP_GPIO_SEL_GPIO_SEL(0)); + + tmp = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_CAP_STS); + if (!(tmp & PTP_GPIO_CAP_STS_PTP_GPIO_RE_STS(0)) && + !(tmp & PTP_GPIO_CAP_STS_PTP_GPIO_FE_STS(0))) + return -1; + + if (tmp & BIT(0)) { + sec = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_RE_LTC_SEC_HI_CAP); + sec <<= 16; + sec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_RE_LTC_SEC_LO_CAP); + + nsec = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_RE_LTC_NS_HI_CAP) & 0x3fff; + nsec <<= 16; + nsec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_RE_LTC_NS_LO_CAP); + } else { + sec = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_FE_LTC_SEC_HI_CAP); + sec <<= 16; + sec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_FE_LTC_SEC_LO_CAP); + + nsec = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_FE_LTC_NS_HI_CAP) & 0x3fff; + nsec <<= 16; + nsec |= lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + PTP_GPIO_RE_LTC_NS_LO_CAP); + } + + ptp_event.index = 0; + ptp_event.timestamp = ktime_set(sec, nsec); + ptp_event.type = PTP_CLOCK_EXTTS; + ptp_clock_event(shared->ptp_clock, &ptp_event); + + return 0; +} + +static int lan8814_handle_gpio_interrupt(struct phy_device *phydev, u16 status) +{ + struct lan8814_shared_priv *shared = phy_package_get_priv(phydev); + int ret; + + mutex_lock(&shared->shared_lock); + ret = lan8814_gpio_process_cap(shared); + mutex_unlock(&shared->shared_lock); + + return ret; +} + +static int lan8804_config_init(struct phy_device *phydev) +{ + /* MDI-X setting for swap A,B transmit */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, LAN8804_ALIGN_SWAP, + LAN8804_ALIGN_TX_A_B_SWAP_MASK, + LAN8804_ALIGN_TX_A_B_SWAP); + + /* Make sure that the PHY will not stop generating the clock when the + * link partner goes down + */ + lanphy_write_page_reg(phydev, LAN8814_PAGE_SYSTEM_CTRL, + LAN8814_CLOCK_MANAGEMENT, 0x27e); + lanphy_read_page_reg(phydev, LAN8814_PAGE_AFE_PMA, 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; +} + +/* Check if the PHY has 1588 support. There are multiple skus of the PHY and + * some of them support PTP while others don't support it. This function will + * return true is the sku supports it, otherwise will return false. + */ +static bool lan8814_has_ptp(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + + return priv->is_ptp_available; +} + +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; + } + + if (!lan8814_has_ptp(phydev)) + return ret; + + while (true) { + irq_status = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TSU_INT_STS); + if (!irq_status) + break; + + lan8814_handle_ptp_interrupt(phydev, irq_status); + ret = IRQ_HANDLED; + } + + if (!lan8814_handle_gpio_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, LAN8814_PAGE_COMMON_REGS, 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; + + if (!IS_ENABLED(CONFIG_PTP_1588_CLOCK) || + !IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING)) + return; + + if (!lan8814_has_ptp(phydev)) + return; + + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + TSU_HARD_RESET, TSU_HARD_RESET_); + + lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_TX_MOD, + PTP_TX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_, + PTP_TX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_); + + lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_RX_MOD, + PTP_RX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_, + PTP_RX_MOD_BAD_UDPV4_CHKSUM_FORCE_FCS_DIS_); + + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_RX_PARSE_CONFIG, 0); + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_PARSE_CONFIG, 0); + + /* Removing default registers configs related to L2 and IP */ + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_PARSE_L2_ADDR_EN, 0); + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_RX_PARSE_L2_ADDR_EN, 0); + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TX_PARSE_IP_ADDR_EN, 0); + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_RX_PARSE_IP_ADDR_EN, 0); + + /* Disable checking for minorVersionPTP field */ + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_RX_VERSION, + PTP_MAX_VERSION(0xff) | PTP_MIN_VERSION(0x0)); + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, 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_set = lan8814_hwtstamp_set; + ptp_priv->mii_ts.hwtstamp_get = lan8814_hwtstamp_get; + ptp_priv->mii_ts.ts_info = lan8814_ts_info; + + phydev->mii_ts = &ptp_priv->mii_ts; + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; +} + +static int __lan8814_ptp_probe_once(struct phy_device *phydev, char *pin_name, + int gpios) +{ + struct lan8814_shared_priv *shared = phy_package_get_priv(phydev); + + shared->phydev = phydev; + + /* Initialise shared lock for clock*/ + mutex_init(&shared->shared_lock); + + shared->pin_config = devm_kmalloc_array(&phydev->mdio.dev, + gpios, + sizeof(*shared->pin_config), + GFP_KERNEL); + if (!shared->pin_config) + return -ENOMEM; + + for (int i = 0; i < gpios; i++) { + struct ptp_pin_desc *ptp_pin = &shared->pin_config[i]; + + memset(ptp_pin, 0, sizeof(*ptp_pin)); + snprintf(ptp_pin->name, + sizeof(ptp_pin->name), "%s_%02d", pin_name, i); + ptp_pin->index = i; + ptp_pin->func = PTP_PF_NONE; + } + + 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 = LAN8814_PTP_EXTTS_NUM; + shared->ptp_clock_info.n_pins = gpios; + shared->ptp_clock_info.pps = 0; + shared->ptp_clock_info.supported_extts_flags = PTP_RISING_EDGE | + PTP_FALLING_EDGE | + PTP_STRICT_FLAGS; + shared->ptp_clock_info.supported_perout_flags = PTP_PEROUT_DUTY_CYCLE; + shared->ptp_clock_info.pin_config = shared->pin_config; + shared->ptp_clock_info.n_per_out = LAN8814_PTP_PEROUT_NUM; + 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_info.enable = lan8814_ptpci_enable; + shared->ptp_clock_info.verify = lan8814_ptpci_verify; + + 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 %pe\n", + 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"); + + /* 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, LAN8814_PAGE_COMMON_REGS, + LTC_HARD_RESET, LTC_HARD_RESET_); + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_OPERATING_MODE, + PTP_OPERATING_MODE_STANDALONE_); + + /* Enable ptp to run LTC clock for ptp and gpio 1PPS operation */ + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, PTP_CMD_CTL, + PTP_CMD_CTL_PTP_ENABLE_); + + return 0; +} + +static int lan8814_ptp_probe_once(struct phy_device *phydev) +{ + if (!lan8814_has_ptp(phydev)) + return 0; + + return __lan8814_ptp_probe_once(phydev, "lan8814_ptp_pin", + LAN8814_PTP_GPIO_NUM); +} + +static void lan8814_setup_led(struct phy_device *phydev, int val) +{ + int temp; + + temp = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + 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, LAN8814_PAGE_PORT_REGS, + LAN8814_LED_CTRL_1, temp); +} + +static int lan8814_config_init(struct phy_device *phydev) +{ + struct kszphy_priv *lan8814 = phydev->priv; + int ret; + + if (phy_package_init_once(phydev)) + /* Reset the PHY */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_QSGMII_SOFT_RESET, + LAN8814_QSGMII_SOFT_RESET_BIT, + LAN8814_QSGMII_SOFT_RESET_BIT); + + /* Based on the interface type select how the advertise ability is + * encoded, to set as SGMII or as USGMII. + */ + if (phydev->interface == PHY_INTERFACE_MODE_QSGMII) + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_QSGMII_TX_CONFIG, + LAN8814_QSGMII_TX_CONFIG_QSGMII, + LAN8814_QSGMII_TX_CONFIG_QSGMII); + else + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_QSGMII_TX_CONFIG, + LAN8814_QSGMII_TX_CONFIG_QSGMII, + 0); + + if (ret < 0) + return ret; + + /* MDI-X setting for swap A,B transmit */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, LAN8814_ALIGN_SWAP, + LAN8814_ALIGN_TX_A_B_SWAP_MASK, + LAN8814_ALIGN_TX_A_B_SWAP); + + 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 void lan8814_clear_2psp_bit(struct phy_device *phydev) +{ + /* It was noticed that when traffic is passing through the PHY and the + * cable is removed then the LED was still on even though there is no + * link + */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, LAN8814_EEE_STATE, + LAN8814_EEE_STATE_MASK2P5P, + 0); +} + +static void lan8814_update_meas_time(struct phy_device *phydev) +{ + /* By setting the measure time to a value of 0xb this will allow cables + * longer than 100m to be used. This configuration can be used + * regardless of the mode of operation of the PHY + */ + lanphy_modify_page_reg(phydev, LAN8814_PAGE_AFE_PMA, LAN8814_PD_CONTROLS, + LAN8814_PD_CONTROLS_PD_MEAS_TIME_MASK, + LAN8814_PD_CONTROLS_PD_MEAS_TIME_VAL); +} + +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, LAN8814_PAGE_COMMON_REGS, 0) & 0x1F; + devm_phy_package_join(&phydev->mdio.dev, phydev, + addr, sizeof(struct lan8814_shared_priv)); + + /* There are lan8814 SKUs that don't support PTP. Make sure that for + * those skus no PTP device is created. Here we check if the SKU + * supports PTP. + */ + err = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_SKUS); + if (err < 0) + return err; + + priv->is_ptp_available = err == LAN8814_REV_LAN8814 || + err == LAN8814_REV_LAN8818; + + if (phy_package_probe_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); + + /* Errata workarounds */ + lan8814_clear_2psp_bit(phydev); + lan8814_update_meas_time(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 improvement 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 < 0) + 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); + if (err < 0) + return err; + + /* Getting a positive value doesn't mean that is an error, it + * just indicates what was the status. Therefore make sure to + * clear the value and say that there is no error. + */ + err = 0; + } + + 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 kernel_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) + 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_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); + struct phy_device *phydev = ptp_priv->phydev; + int txcfg = 0, rxcfg = 0; + int pkt_ts_enable; + + 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; + } + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + case HWTSTAMP_TX_ON: + case HWTSTAMP_TX_ONESTEP_SYNC: + break; + default: + return -ERANGE; + } + + ptp_priv->hwts_tx_type = config->tx_type; + ptp_priv->rx_filter = config->rx_filter; + + /* 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 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 = LAN8841_1PPM_FORMAT * (upper_16_bits(scaled_ppm)); + rate += (LAN8841_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; + + 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 input */ + 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, + .supported_perout_flags = PTP_PEROUT_DUTY_CYCLE, +}; + +#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: %pe\n", + 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_set = lan8841_hwtstamp_set; + ptp_priv->mii_ts.hwtstamp_get = lan8814_hwtstamp_get; + ptp_priv->mii_ts.ts_info = lan8841_ts_info; + + phydev->mii_ts = &ptp_priv->mii_ts; + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; + + return 0; +} + +static int lan8804_resume(struct phy_device *phydev) +{ + return kszphy_resume(phydev); +} + +static int lan8804_suspend(struct phy_device *phydev) +{ + return kszphy_generic_suspend(phydev); +} + +static int lan8841_resume(struct phy_device *phydev) +{ + return kszphy_generic_resume(phydev); +} + +static int lan8841_suspend(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv; + + if (ptp_priv->ptp_clock) + ptp_cancel_worker_sync(ptp_priv->ptp_clock); + + return kszphy_generic_suspend(phydev); +} + +static int ksz9131_resume(struct phy_device *phydev) +{ + if (phydev->suspended && phy_interface_is_rgmii(phydev)) + ksz9131_config_rgmii_delay(phydev); + + return kszphy_resume(phydev); +} + +#define LAN8842_PTP_GPIO_NUM 16 + +static int lan8842_ptp_probe_once(struct phy_device *phydev) +{ + return __lan8814_ptp_probe_once(phydev, "lan8842_ptp_pin", + LAN8842_PTP_GPIO_NUM); +} + +#define LAN8842_STRAP_REG 0 /* 0x0 */ +#define LAN8842_STRAP_REG_PHYADDR_MASK GENMASK(4, 0) +#define LAN8842_SKU_REG 11 /* 0x0b */ +#define LAN8842_SELF_TEST 14 /* 0x0e */ +#define LAN8842_SELF_TEST_RX_CNT_ENA BIT(8) +#define LAN8842_SELF_TEST_TX_CNT_ENA BIT(4) + +static int lan8842_probe(struct phy_device *phydev) +{ + struct lan8842_priv *priv; + int addr; + int ret; + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + /* Similar to lan8814 this PHY has a pin which needs to be pulled down + * to enable to pass any traffic through it. Therefore use the same + * function as lan8814 + */ + ret = lan8814_release_coma_mode(phydev); + if (ret) + return ret; + + /* Enable to count the RX and TX packets */ + ret = lanphy_write_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, + LAN8842_SELF_TEST, + LAN8842_SELF_TEST_RX_CNT_ENA | + LAN8842_SELF_TEST_TX_CNT_ENA); + if (ret < 0) + return ret; + + /* Revision lan8832 doesn't have support for PTP, therefore don't add + * any PTP clocks + */ + ret = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8842_SKU_REG); + if (ret < 0) + return ret; + + priv->rev = ret; + if (priv->rev == LAN8842_REV_8832) + return 0; + + /* As the lan8814 and lan8842 has the same IP for the PTP block, the + * only difference is the number of the GPIOs, then make sure that the + * lan8842 initialized also the shared data pointer as this is used in + * all the PTP functions for lan8814. The lan8842 doesn't have multiple + * PHYs in the same package. + */ + addr = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8842_STRAP_REG); + if (addr < 0) + return addr; + addr &= LAN8842_STRAP_REG_PHYADDR_MASK; + + ret = devm_phy_package_join(&phydev->mdio.dev, phydev, addr, + sizeof(struct lan8814_shared_priv)); + if (ret) + return ret; + + if (phy_package_init_once(phydev)) { + ret = lan8842_ptp_probe_once(phydev); + if (ret) + return ret; + } + + lan8814_ptp_init(phydev); + + return 0; +} + +#define LAN8814_POWER_MGMT_MODE_3_ANEG_MDI 0x13 +#define LAN8814_POWER_MGMT_MODE_4_ANEG_MDIX 0x14 +#define LAN8814_POWER_MGMT_MODE_5_10BT_MDI 0x15 +#define LAN8814_POWER_MGMT_MODE_6_10BT_MDIX 0x16 +#define LAN8814_POWER_MGMT_MODE_7_100BT_TRAIN 0x17 +#define LAN8814_POWER_MGMT_MODE_8_100BT_MDI 0x18 +#define LAN8814_POWER_MGMT_MODE_9_100BT_EEE_MDI_TX 0x19 +#define LAN8814_POWER_MGMT_MODE_10_100BT_EEE_MDI_RX 0x1a +#define LAN8814_POWER_MGMT_MODE_11_100BT_MDIX 0x1b +#define LAN8814_POWER_MGMT_MODE_12_100BT_EEE_MDIX_TX 0x1c +#define LAN8814_POWER_MGMT_MODE_13_100BT_EEE_MDIX_RX 0x1d +#define LAN8814_POWER_MGMT_MODE_14_100BTX_EEE_TX_RX 0x1e + +#define LAN8814_POWER_MGMT_DLLPD_D BIT(0) +#define LAN8814_POWER_MGMT_ADCPD_D BIT(1) +#define LAN8814_POWER_MGMT_PGAPD_D BIT(2) +#define LAN8814_POWER_MGMT_TXPD_D BIT(3) +#define LAN8814_POWER_MGMT_DLLPD_C BIT(4) +#define LAN8814_POWER_MGMT_ADCPD_C BIT(5) +#define LAN8814_POWER_MGMT_PGAPD_C BIT(6) +#define LAN8814_POWER_MGMT_TXPD_C BIT(7) +#define LAN8814_POWER_MGMT_DLLPD_B BIT(8) +#define LAN8814_POWER_MGMT_ADCPD_B BIT(9) +#define LAN8814_POWER_MGMT_PGAPD_B BIT(10) +#define LAN8814_POWER_MGMT_TXPD_B BIT(11) +#define LAN8814_POWER_MGMT_DLLPD_A BIT(12) +#define LAN8814_POWER_MGMT_ADCPD_A BIT(13) +#define LAN8814_POWER_MGMT_PGAPD_A BIT(14) +#define LAN8814_POWER_MGMT_TXPD_A BIT(15) + +#define LAN8814_POWER_MGMT_C_D (LAN8814_POWER_MGMT_DLLPD_D | \ + LAN8814_POWER_MGMT_ADCPD_D | \ + LAN8814_POWER_MGMT_PGAPD_D | \ + LAN8814_POWER_MGMT_DLLPD_C | \ + LAN8814_POWER_MGMT_ADCPD_C | \ + LAN8814_POWER_MGMT_PGAPD_C) + +#define LAN8814_POWER_MGMT_B_C_D (LAN8814_POWER_MGMT_C_D | \ + LAN8814_POWER_MGMT_DLLPD_B | \ + LAN8814_POWER_MGMT_ADCPD_B | \ + LAN8814_POWER_MGMT_PGAPD_B) + +#define LAN8814_POWER_MGMT_VAL1 (LAN8814_POWER_MGMT_C_D | \ + LAN8814_POWER_MGMT_ADCPD_B | \ + LAN8814_POWER_MGMT_PGAPD_B | \ + LAN8814_POWER_MGMT_ADCPD_A | \ + LAN8814_POWER_MGMT_PGAPD_A) + +#define LAN8814_POWER_MGMT_VAL2 LAN8814_POWER_MGMT_C_D + +#define LAN8814_POWER_MGMT_VAL3 (LAN8814_POWER_MGMT_C_D | \ + LAN8814_POWER_MGMT_DLLPD_B | \ + LAN8814_POWER_MGMT_ADCPD_B | \ + LAN8814_POWER_MGMT_PGAPD_A) + +#define LAN8814_POWER_MGMT_VAL4 (LAN8814_POWER_MGMT_B_C_D | \ + LAN8814_POWER_MGMT_ADCPD_A | \ + LAN8814_POWER_MGMT_PGAPD_A) + +#define LAN8814_POWER_MGMT_VAL5 LAN8814_POWER_MGMT_B_C_D + +#define LAN8814_EEE_WAKE_TX_TIMER 0x0e +#define LAN8814_EEE_WAKE_TX_TIMER_MAX_VAL 0x1f + +static const struct lanphy_reg_data short_center_tap_errata[] = { + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_3_ANEG_MDI, + LAN8814_POWER_MGMT_VAL1 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_4_ANEG_MDIX, + LAN8814_POWER_MGMT_VAL1 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_5_10BT_MDI, + LAN8814_POWER_MGMT_VAL1 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_6_10BT_MDIX, + LAN8814_POWER_MGMT_VAL1 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_7_100BT_TRAIN, + LAN8814_POWER_MGMT_VAL2 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_8_100BT_MDI, + LAN8814_POWER_MGMT_VAL3 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_9_100BT_EEE_MDI_TX, + LAN8814_POWER_MGMT_VAL3 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_10_100BT_EEE_MDI_RX, + LAN8814_POWER_MGMT_VAL4 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_11_100BT_MDIX, + LAN8814_POWER_MGMT_VAL5 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_12_100BT_EEE_MDIX_TX, + LAN8814_POWER_MGMT_VAL5 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_13_100BT_EEE_MDIX_RX, + LAN8814_POWER_MGMT_VAL4 }, + { LAN8814_PAGE_POWER_REGS, + LAN8814_POWER_MGMT_MODE_14_100BTX_EEE_TX_RX, + LAN8814_POWER_MGMT_VAL4 }, +}; + +static const struct lanphy_reg_data waketx_timer_errata[] = { + { LAN8814_PAGE_EEE, + LAN8814_EEE_WAKE_TX_TIMER, + LAN8814_EEE_WAKE_TX_TIMER_MAX_VAL }, +}; + +static int lanphy_write_reg_data(struct phy_device *phydev, + const struct lanphy_reg_data *data, + size_t num) +{ + int ret = 0; + + while (num--) { + ret = lanphy_write_page_reg(phydev, data->page, data->addr, + data->val); + if (ret) + break; + } + + return ret; +} + +static int lan8842_erratas(struct phy_device *phydev) +{ + int ret; + + ret = lanphy_write_reg_data(phydev, short_center_tap_errata, + ARRAY_SIZE(short_center_tap_errata)); + if (ret) + return ret; + + return lanphy_write_reg_data(phydev, waketx_timer_errata, + ARRAY_SIZE(waketx_timer_errata)); +} + +static int lan8842_config_init(struct phy_device *phydev) +{ + int ret; + + /* Reset the PHY */ + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_QSGMII_SOFT_RESET, + LAN8814_QSGMII_SOFT_RESET_BIT, + LAN8814_QSGMII_SOFT_RESET_BIT); + if (ret < 0) + return ret; + + /* Apply the erratas for this device */ + ret = lan8842_erratas(phydev); + if (ret < 0) + return ret; + + /* Even if the GPIOs are set to control the LEDs the behaviour of the + * LEDs is wrong, they are not blinking when there is traffic. + * To fix this it is required to set extended LED mode + */ + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + LAN8814_LED_CTRL_1, + LAN8814_LED_CTRL_1_KSZ9031_LED_MODE_, 0); + if (ret < 0) + return ret; + + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + LAN8814_LED_CTRL_2, + LAN8814_LED_CTRL_2_LED1_COM_DIS, + LAN8814_LED_CTRL_2_LED1_COM_DIS); + if (ret < 0) + return ret; + + /* To allow the PHY to control the LEDs the GPIOs of the PHY should have + * a function mode and not the GPIO. Apparently by default the value is + * GPIO and not function even though the datasheet it says that it is + * function. Therefore set this value. + */ + return lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_GPIO_EN2, 0); +} + +#define LAN8842_INTR_CTRL_REG 52 /* 0x34 */ + +static int lan8842_config_intr(struct phy_device *phydev) +{ + int err; + + lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8842_INTR_CTRL_REG, + 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 | LAN8814_INT_FLF); + } else { + err = phy_write(phydev, LAN8814_INTC, 0); + if (err) + return err; + + err = lan8814_ack_interrupt(phydev); + } + + return err; +} + +static unsigned int lan8842_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + /* Inband configuration can be enabled or disabled using the registers + * PCS1G_ANEG_CONFIG. + */ + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; +} + +static int lan8842_config_inband(struct phy_device *phydev, unsigned int modes) +{ + bool enable; + + if (modes == LINK_INBAND_DISABLE) + enable = false; + else + enable = true; + + /* Disable or enable in-band autoneg with PCS Host side + * It has the same address as lan8814 + */ + return lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + LAN8814_QSGMII_PCS1G_ANEG_CONFIG, + LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA, + enable ? LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA : 0); +} + +static void lan8842_handle_ptp_interrupt(struct phy_device *phydev, u16 status) +{ + struct kszphy_ptp_priv *ptp_priv; + struct lan8842_priv *priv; + + priv = phydev->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 irqreturn_t lan8842_handle_interrupt(struct phy_device *phydev) +{ + struct lan8842_priv *priv = phydev->priv; + 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 | LAN8814_INT_FLF)) { + phy_trigger_machine(phydev); + ret = IRQ_HANDLED; + } + + /* Phy revision lan8832 doesn't have support for PTP therefore there is + * not need to check the PTP and GPIO interrupts + */ + if (priv->rev == LAN8842_REV_8832) + goto out; + + while (true) { + irq_status = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, + PTP_TSU_INT_STS); + if (!irq_status) + break; + + lan8842_handle_ptp_interrupt(phydev, irq_status); + ret = IRQ_HANDLED; + } + + if (!lan8814_handle_gpio_interrupt(phydev, irq_status)) + ret = IRQ_HANDLED; + +out: + return ret; +} + +static u64 lan8842_get_stat(struct phy_device *phydev, int count, int *regs) +{ + u64 ret = 0; + int val; + + for (int j = 0; j < count; ++j) { + val = lanphy_read_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, + regs[j]); + if (val < 0) + return U64_MAX; + + ret <<= 16; + ret += val; + } + return ret; +} + +static int lan8842_update_stats(struct phy_device *phydev) +{ + struct lan8842_priv *priv = phydev->priv; + int rx_packets_regs[] = {88, 61, 60}; + int rx_errors_regs[] = {63, 62}; + int tx_packets_regs[] = {89, 85, 84}; + int tx_errors_regs[] = {87, 86}; + + priv->phy_stats.rx_packets = lan8842_get_stat(phydev, + ARRAY_SIZE(rx_packets_regs), + rx_packets_regs); + priv->phy_stats.rx_errors = lan8842_get_stat(phydev, + ARRAY_SIZE(rx_errors_regs), + rx_errors_regs); + priv->phy_stats.tx_packets = lan8842_get_stat(phydev, + ARRAY_SIZE(tx_packets_regs), + tx_packets_regs); + priv->phy_stats.tx_errors = lan8842_get_stat(phydev, + ARRAY_SIZE(tx_errors_regs), + tx_errors_regs); + + return 0; +} + +#define LAN8842_FLF 15 /* 0x0e */ +#define LAN8842_FLF_ENA BIT(1) +#define LAN8842_FLF_ENA_LINK_DOWN BIT(0) + +static int lan8842_get_fast_down(struct phy_device *phydev, u8 *msecs) +{ + int ret; + + ret = lanphy_read_page_reg(phydev, LAN8814_PAGE_PCS, LAN8842_FLF); + if (ret < 0) + return ret; + + if (ret & LAN8842_FLF_ENA) + *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_ON; + else + *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF; + + return 0; +} + +static int lan8842_set_fast_down(struct phy_device *phydev, const u8 *msecs) +{ + u16 flf; + + switch (*msecs) { + case ETHTOOL_PHY_FAST_LINK_DOWN_OFF: + flf = 0; + break; + case ETHTOOL_PHY_FAST_LINK_DOWN_ON: + flf = LAN8842_FLF_ENA | LAN8842_FLF_ENA_LINK_DOWN; + break; + default: + return -EINVAL; + } + + return lanphy_modify_page_reg(phydev, LAN8814_PAGE_PCS, + LAN8842_FLF, + LAN8842_FLF_ENA | + LAN8842_FLF_ENA_LINK_DOWN, flf); +} + +static int lan8842_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_FAST_LINK_DOWN: + return lan8842_get_fast_down(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int lan8842_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_FAST_LINK_DOWN: + return lan8842_set_fast_down(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static void lan8842_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct lan8842_priv *priv = phydev->priv; + + stats->rx_packets = priv->phy_stats.rx_packets; + stats->rx_errors = priv->phy_stats.rx_errors; + stats->tx_packets = priv->phy_stats.tx_packets; + stats->tx_errors = priv->phy_stats.tx_errors; +} + +#define LAN9645X_CTRL_REG 0x1f +#define LAN9645X_CTRL_REG_SW_SOFT_RST BIT(1) + +#define LAN9645X_DAC_ICAS_AMP_POWER_DOWN 0x47 +#define LAN9645X_BTRX_QBIAS_POWER_DOWN 0x46 +#define LAN9645X_TX_LOW_I_CH_CD_POWER_MGMT 0x45 +#define LAN9645X_TX_LOW_I_CH_B_POWER_MGMT 0x44 +#define LAN9645X_TX_LOW_I_CH_A_POWER_MGMT 0x43 + +static const struct lanphy_reg_data force_dac_tx_errata[] = { + /* Force channel A/B/C/D TX on */ + { LAN8814_PAGE_POWER_REGS, + LAN9645X_DAC_ICAS_AMP_POWER_DOWN, + 0 }, + /* Force channel A/B/C/D QBias on */ + { LAN8814_PAGE_POWER_REGS, + LAN9645X_BTRX_QBIAS_POWER_DOWN, + 0xaa }, + /* Tx low I on channel C/D overwrite */ + { LAN8814_PAGE_POWER_REGS, + LAN9645X_TX_LOW_I_CH_CD_POWER_MGMT, + 0xbfff }, + /* Channel B low I overwrite */ + { LAN8814_PAGE_POWER_REGS, + LAN9645X_TX_LOW_I_CH_B_POWER_MGMT, + 0xabbf }, + /* Channel A low I overwrite */ + { LAN8814_PAGE_POWER_REGS, + LAN9645X_TX_LOW_I_CH_A_POWER_MGMT, + 0xbd3f }, +}; + +static int lan9645x_config_init(struct phy_device *phydev) +{ + int ret; + + /* Apply erratas from previous generations. */ + ret = lan8842_erratas(phydev); + if (ret < 0) + return ret; + + /* Apply errata for an issue where bringing a port down, can cause a few + * CRC errors for traffic flowing through adjacent ports. + */ + return lanphy_write_reg_data(phydev, force_dac_tx_errata, + ARRAY_SIZE(force_dac_tx_errata)); +} + +static int lan9645x_suspend(struct phy_device *phydev) +{ + int ret, val; + + /* Force link down before software power down (SPD), by doing software + * soft reset. This resets the PHY, but keeps all register configuration + * intact. The bit self clears. + * + * This is needed as a workaround for an issue where performing SPD on a + * port can bring adjacent ports down, when there is traffic flowing + * through the ports. + */ + ret = phy_set_bits(phydev, LAN9645X_CTRL_REG, + LAN9645X_CTRL_REG_SW_SOFT_RST); + if (ret) + return ret; + + ret = phy_read_poll_timeout(phydev, LAN9645X_CTRL_REG, val, + !(val & LAN9645X_CTRL_REG_SW_SOFT_RST), + 3000, 100000, true); + if (ret) + return ret; + + return genphy_suspend(phydev); +} + +static int lan9645x_config_intr(struct phy_device *phydev) +{ + int err; + + /* enable / disable interrupts */ + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + /* This is an internal PHY of lan9645x and is not possible to + * change the polarity of irq sources in the OIC (CPU_INTR) + * found in lan9645x. Therefore change the polarity of the + * interrupt in the PHY from being active low instead of active + * high. + */ + err = phy_write(phydev, LAN8804_CONTROL, + LAN8804_CONTROL_INTR_POLARITY); + if (err) + return err; + + /* 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. + */ + err = phy_write(phydev, LAN8804_OUTPUT_CONTROL, + LAN8804_OUTPUT_CONTROL_INTR_BUFFER); + if (err) + return err; + + err = lan8814_ack_interrupt(phydev); + if (err) + return err; + + err = phy_write(phydev, LAN8814_INTC, + LAN8814_INT_LINK | LAN8814_INT_FLF); + } else { + err = phy_write(phydev, LAN8814_INTC, 0); + if (err) + return err; + + err = lan8814_ack_interrupt(phydev); + } + + return err; +} + +static irqreturn_t lan9645x_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 & (LAN8814_INT_LINK | LAN8814_INT_FLF)) { + phy_trigger_machine(phydev); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static struct phy_driver ksphy_driver[] = { +{ + PHY_ID_MATCH_MODEL(PHY_ID_KS8737), + .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_MATCH_MODEL(PHY_ID_KSZ8041), + .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, + .suspend = ksz8041_suspend, + .resume = ksz8041_resume, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_KSZ8041RNLI), + .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_MATCH_MODEL(PHY_ID_KSZ8081), + .name = "Micrel KSZ8081 or KSZ8091", + .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_MATCH_MODEL(PHY_ID_KSZ8061), + .name = "Micrel KSZ8061", + /* PHY_BASIC_FEATURES */ + .probe = kszphy_probe, + .config_init = ksz8061_config_init, + .soft_reset = genphy_soft_reset, + .config_intr = kszphy_config_intr, + .handle_interrupt = kszphy_handle_interrupt, + .suspend = ksz8061_suspend, + .resume = ksz8061_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_MATCH_MODEL(PHY_ID_KSZ9031), + .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, + .set_loopback = ksz9031_set_loopback, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_LAN8814), + .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, + .inband_caps = lan8842_inband_caps, + .config_inband = lan8842_config_inband, + .handle_interrupt = lan8814_handle_interrupt, + .cable_test_start = lan8814_cable_test_start, + .cable_test_get_status = ksz886x_cable_test_get_status, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_LAN8804), + .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 = lan8804_suspend, + .resume = lan8804_resume, + .config_intr = lan8804_config_intr, + .handle_interrupt = lan8804_handle_interrupt, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_LAN8841), + .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 = lan8841_resume, + .cable_test_start = lan8814_cable_test_start, + .cable_test_get_status = ksz886x_cable_test_get_status, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_LAN8842), + .name = "Microchip LAN8842 Gigabit PHY", + .flags = PHY_POLL_CABLE_TEST, + .driver_data = &lan8814_type, + .probe = lan8842_probe, + .config_init = lan8842_config_init, + .config_intr = lan8842_config_intr, + .inband_caps = lan8842_inband_caps, + .config_inband = lan8842_config_inband, + .handle_interrupt = lan8842_handle_interrupt, + .get_phy_stats = lan8842_get_phy_stats, + .update_stats = lan8842_update_stats, + .get_tunable = lan8842_get_tunable, + .set_tunable = lan8842_set_tunable, + .cable_test_start = lan8814_cable_test_start, + .cable_test_get_status = ksz886x_cable_test_get_status, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_LAN9645X), + .name = "Microchip LAN9645X Gigabit PHY", + .config_init = lan9645x_config_init, + .driver_data = &ksz9021_type, + .probe = kszphy_probe, + .soft_reset = genphy_soft_reset, + .suspend = lan9645x_suspend, + .resume = genphy_resume, + .config_intr = lan9645x_config_intr, + .handle_interrupt = lan9645x_handle_interrupt, + .get_tunable = lan8842_get_tunable, + .set_tunable = lan8842_set_tunable, + .get_phy_stats = lan8842_get_phy_stats, + .update_stats = lan8842_update_stats, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_KSZ9131), + .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 = ksz9131_resume, + .cable_test_start = ksz9x31_cable_test_start, + .cable_test_get_status = ksz9x31_cable_test_get_status, + .get_features = ksz9477_get_features, + .set_loopback = ksz9131_set_loopback, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_KSZ8873MLL), + .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_MATCH_MODEL(PHY_ID_KSZ886X), + .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, + .get_tunable = ksz8795_get_tunable, + .set_tunable = ksz8795_set_tunable, + .suspend = genphy_suspend, + .resume = genphy_resume, +}, { + PHY_ID_MATCH_MODEL(PHY_ID_KSZ9477), + .name = "Microchip KSZ9477", + .probe = kszphy_probe, + /* PHY_GBIT_FEATURES */ + .config_init = ksz9477_config_init, + .config_intr = kszphy_config_intr, + .config_aneg = ksz9477_config_aneg, + .read_status = ksz9477_read_status, + .handle_interrupt = kszphy_handle_interrupt, + .suspend = genphy_suspend, + .resume = ksz9477_resume, + .get_phy_stats = kszphy_get_phy_stats, + .update_stats = kszphy_update_stats, + .cable_test_start = ksz9x31_cable_test_start, + .cable_test_get_status = ksz9x31_cable_test_get_status, + .get_sqi = kszphy_get_sqi, + .get_sqi_max = kszphy_get_sqi_max, + .get_mse_capability = kszphy_get_mse_capability, + .get_mse_snapshot = kszphy_get_mse_snapshot, +} }; + +module_phy_driver(ksphy_driver); + +MODULE_DESCRIPTION("Micrel PHY driver"); +MODULE_AUTHOR("David J. Choi"); +MODULE_LICENSE("GPL"); + +static const struct mdio_device_id __maybe_unused micrel_tbl[] = { + { PHY_ID_KSZ9021, 0x000ffffe }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ9031) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ9131) }, + { PHY_ID_KSZ8001, 0x00fffffc }, + { PHY_ID_MATCH_MODEL(PHY_ID_KS8737) }, + { PHY_ID_KSZ8021, 0x00ffffff }, + { PHY_ID_KSZ8031, 0x00ffffff }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8041) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8041RNLI) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8051) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8061) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8081) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ8873MLL) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ886X) }, + { PHY_ID_MATCH_MODEL(PHY_ID_KSZ9477) }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN8814) }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN8804) }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN8841) }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN8842) }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN9645X) }, + { } +}; + +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..33bc633bd --- /dev/null +++ b/drivers/net/phy/microchip.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Microchip Technology + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_ID_LAN937X_TX 0x0007c190 + +#define LAN937X_MODE_CTRL_STATUS_REG 0x11 +#define LAN937X_AUTOMDIX_EN BIT(7) +#define LAN937X_MDI_MODE BIT(6) + +#define DRIVER_AUTHOR "WOOJUNG HUH " +#define DRIVER_DESC "Microchip LAN88XX/LAN937X TX PHY driver" + +struct lan88xx_priv { + int chip_id; + int chip_rev; + __u32 wolopts; + u8 downshift_cnt; +}; + +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_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_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val; + + val = phy_read_paged(phydev, 1, LAN78XX_PHY_CTRL3); + if (val < 0) + return val; + + if (!(val & LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT)) { + *data = DOWNSHIFT_DEV_DISABLE; + return 0; + } + + *data = FIELD_GET(LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK, val) + 2; + + return 0; +} + +static int lan88xx_set_downshift(struct phy_device *phydev, u8 cnt) +{ + u32 mask = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK | + LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT; + + if (cnt == DOWNSHIFT_DEV_DISABLE) + return phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3, + LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT, 0); + + if (cnt == DOWNSHIFT_DEV_DEFAULT_COUNT) + cnt = 2; + + if (cnt < 2 || cnt > 5) + return -EINVAL; + + return phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3, mask, + FIELD_PREP(LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK, + cnt - 2) | + LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT); +} + +static int lan88xx_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return lan88xx_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int lan88xx_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + struct lan88xx_priv *priv = phydev->priv; + int ret; + + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + ret = lan88xx_set_downshift(phydev, *(const u8 *)data); + if (!ret) + priv->downshift_cnt = *(const u8 *)data; + return ret; + default: + return -EOPNOTSUPP; + } +} + +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; + priv->downshift_cnt = 2; + + 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) +{ + struct lan88xx_priv *priv = phydev->priv; + int val, err; + + /*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); + + err = lan88xx_set_downshift(phydev, priv->downshift_cnt); + if (err < 0) + return err; + + 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; + int ret; + + /* Reset PHY to ensure MII_LPA provides up-to-date information. This + * issue is reproducible only after parallel detection, as described + * in IEEE 802.3-2022, Section 28.2.3.1 ("Parallel detection function"), + * where the link partner does not support auto-negotiation. + */ + if (phydev->state == PHY_NOLINK) { + ret = phy_init_hw(phydev); + if (ret < 0) + goto link_change_notify_failed; + + ret = _phy_start_aneg(phydev); + if (ret < 0) + goto link_change_notify_failed; + } + + /* 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->state == PHY_NOLINK && !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); + } + + return; + +link_change_notify_failed: + phydev_err(phydev, "Link change process failed %pe\n", ERR_PTR(ret)); +} + +/** + * lan937x_tx_read_mdix_status - Read the MDIX status for the LAN937x TX PHY. + * @phydev: Pointer to the phy_device structure. + * + * This function reads the MDIX status of the LAN937x TX PHY and sets the + * mdix_ctrl and mdix fields of the phy_device structure accordingly. + * Note that MDIX status is not supported in AUTO mode, and will be set + * to invalid in such cases. + * + * Return: 0 on success, a negative error code on failure. + */ +static int lan937x_tx_read_mdix_status(struct phy_device *phydev) +{ + int ret; + + ret = phy_read(phydev, LAN937X_MODE_CTRL_STATUS_REG); + if (ret < 0) + return ret; + + if (ret & LAN937X_AUTOMDIX_EN) { + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + /* MDI/MDIX status is unknown */ + phydev->mdix = ETH_TP_MDI_INVALID; + } else if (ret & LAN937X_MDI_MODE) { + phydev->mdix_ctrl = ETH_TP_MDI_X; + phydev->mdix = ETH_TP_MDI_X; + } else { + phydev->mdix_ctrl = ETH_TP_MDI; + phydev->mdix = ETH_TP_MDI; + } + + return 0; +} + +/** + * lan937x_tx_read_status - Read the status for the LAN937x TX PHY. + * @phydev: Pointer to the phy_device structure. + * + * This function reads the status of the LAN937x TX PHY and updates the + * phy_device structure accordingly. + * + * Return: 0 on success, a negative error code on failure. + */ +static int lan937x_tx_read_status(struct phy_device *phydev) +{ + int ret; + + ret = genphy_read_status(phydev); + if (ret < 0) + return ret; + + return lan937x_tx_read_mdix_status(phydev); +} + +/** + * lan937x_tx_set_mdix - Set the MDIX mode for the LAN937x TX PHY. + * @phydev: Pointer to the phy_device structure. + * + * This function configures the MDIX mode of the LAN937x TX PHY based on the + * mdix_ctrl field of the phy_device structure. The MDIX mode can be set to + * MDI (straight-through), MDIX (crossover), or AUTO (auto-MDIX). If the mode + * is not recognized, it returns 0 without making any changes. + * + * Return: 0 on success, a negative error code on failure. + */ +static int lan937x_tx_set_mdix(struct phy_device *phydev) +{ + u16 val; + + switch (phydev->mdix_ctrl) { + case ETH_TP_MDI: + val = 0; + break; + case ETH_TP_MDI_X: + val = LAN937X_MDI_MODE; + break; + case ETH_TP_MDI_AUTO: + val = LAN937X_AUTOMDIX_EN; + break; + default: + return 0; + } + + return phy_modify(phydev, LAN937X_MODE_CTRL_STATUS_REG, + LAN937X_AUTOMDIX_EN | LAN937X_MDI_MODE, val); +} + +/** + * lan937x_tx_config_aneg - Configure auto-negotiation and fixed modes for the + * LAN937x TX PHY. + * @phydev: Pointer to the phy_device structure. + * + * This function configures the MDIX mode for the LAN937x TX PHY and then + * proceeds to configure the auto-negotiation or fixed mode settings + * based on the phy_device structure. + * + * Return: 0 on success, a negative error code on failure. + */ +static int lan937x_tx_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = lan937x_tx_set_mdix(phydev); + if (ret < 0) + return ret; + + return genphy_config_aneg(phydev); +} + +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. + * These PHYs are integrated in LAN7800 and LAN7850 USB/Ethernet + * controllers. + */ + .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, + .soft_reset = genphy_soft_reset, + + /* Interrupt handling is broken, do not define related + * functions to force polling. + */ + + .suspend = lan88xx_suspend, + .resume = genphy_resume, + .set_wol = lan88xx_set_wol, + .read_page = lan88xx_read_page, + .write_page = lan88xx_write_page, + .get_tunable = lan88xx_get_tunable, + .set_tunable = lan88xx_set_tunable, +}, +{ + PHY_ID_MATCH_MODEL(PHY_ID_LAN937X_TX), + .name = "Microchip LAN937x TX", + .suspend = genphy_suspend, + .resume = genphy_resume, + .config_aneg = lan937x_tx_config_aneg, + .read_status = lan937x_tx_read_status, +} }; + +module_phy_driver(microchip_phy_driver); + +static const struct mdio_device_id __maybe_unused microchip_tbl[] = { + { 0x0007c132, 0xfffffff2 }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN937X_TX) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, microchip_tbl); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/microchip_rds_ptp.c b/drivers/net/phy/microchip_rds_ptp.c new file mode 100644 index 000000000..2ad95d5c9 --- /dev/null +++ b/drivers/net/phy/microchip_rds_ptp.c @@ -0,0 +1,1330 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 Microchip Technology + +#include "microchip_rds_ptp.h" + +static int mchp_rds_phy_read_mmd(struct mchp_rds_ptp_clock *clock, + u32 offset, enum mchp_rds_ptp_base base) +{ + struct phy_device *phydev = clock->phydev; + u32 addr; + + addr = (offset + ((base == MCHP_RDS_PTP_PORT) ? BASE_PORT(clock) : + BASE_CLK(clock))); + + return phy_read_mmd(phydev, PTP_MMD(clock), addr); +} + +static int mchp_rds_phy_write_mmd(struct mchp_rds_ptp_clock *clock, + u32 offset, enum mchp_rds_ptp_base base, + u16 val) +{ + struct phy_device *phydev = clock->phydev; + u32 addr; + + addr = (offset + ((base == MCHP_RDS_PTP_PORT) ? BASE_PORT(clock) : + BASE_CLK(clock))); + + return phy_write_mmd(phydev, PTP_MMD(clock), addr, val); +} + +static int mchp_rds_phy_modify_mmd(struct mchp_rds_ptp_clock *clock, + u32 offset, enum mchp_rds_ptp_base base, + u16 mask, u16 val) +{ + struct phy_device *phydev = clock->phydev; + u32 addr; + + addr = (offset + ((base == MCHP_RDS_PTP_PORT) ? BASE_PORT(clock) : + BASE_CLK(clock))); + + return phy_modify_mmd(phydev, PTP_MMD(clock), addr, mask, val); +} + +static int mchp_rds_phy_set_bits_mmd(struct mchp_rds_ptp_clock *clock, + u32 offset, enum mchp_rds_ptp_base base, + u16 val) +{ + struct phy_device *phydev = clock->phydev; + u32 addr; + + addr = (offset + ((base == MCHP_RDS_PTP_PORT) ? BASE_PORT(clock) : + BASE_CLK(clock))); + + return phy_set_bits_mmd(phydev, PTP_MMD(clock), addr, val); +} + +static int mchp_get_pulsewidth(struct phy_device *phydev, + struct ptp_perout_request *perout_request, + int *pulse_width) +{ + struct timespec64 ts_period; + s64 ts_on_nsec, period_nsec; + struct timespec64 ts_on; + static const s64 sup_on_necs[] = { + 100, /* 100ns */ + 500, /* 500ns */ + 1000, /* 1us */ + 5000, /* 5us */ + 10000, /* 10us */ + 50000, /* 50us */ + 100000, /* 100us */ + 500000, /* 500us */ + 1000000, /* 1ms */ + 5000000, /* 5ms */ + 10000000, /* 10ms */ + 50000000, /* 50ms */ + 100000000, /* 100ms */ + 200000000, /* 200ms */ + }; + + ts_period.tv_sec = perout_request->period.sec; + ts_period.tv_nsec = perout_request->period.nsec; + + ts_on.tv_sec = perout_request->on.sec; + ts_on.tv_nsec = perout_request->on.nsec; + ts_on_nsec = timespec64_to_ns(&ts_on); + period_nsec = timespec64_to_ns(&ts_period); + + if (period_nsec < 200) { + phydev_warn(phydev, "perout period small, minimum is 200ns\n"); + return -EOPNOTSUPP; + } + + for (int i = 0; i < ARRAY_SIZE(sup_on_necs); i++) { + if (ts_on_nsec <= sup_on_necs[i]) { + *pulse_width = i; + break; + } + } + + phydev_info(phydev, "pulse width is %d\n", *pulse_width); + return 0; +} + +static int mchp_general_event_config(struct mchp_rds_ptp_clock *clock, + int pulse_width) +{ + int general_config; + + general_config = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_GEN_CFG, + MCHP_RDS_PTP_CLOCK); + if (general_config < 0) + return general_config; + + general_config &= ~MCHP_RDS_PTP_GEN_CFG_LTC_EVT_MASK; + general_config |= MCHP_RDS_PTP_GEN_CFG_LTC_EVT_SET(pulse_width); + general_config &= ~MCHP_RDS_PTP_GEN_CFG_RELOAD_ADD; + general_config |= MCHP_RDS_PTP_GEN_CFG_POLARITY; + + return mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_GEN_CFG, + MCHP_RDS_PTP_CLOCK, general_config); +} + +static int mchp_set_clock_reload(struct mchp_rds_ptp_clock *clock, + s64 period_sec, u32 period_nsec) +{ + int rc; + + rc = mchp_rds_phy_write_mmd(clock, + MCHP_RDS_PTP_CLK_TRGT_RELOAD_SEC_LO, + MCHP_RDS_PTP_CLOCK, + lower_16_bits(period_sec)); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, + MCHP_RDS_PTP_CLK_TRGT_RELOAD_SEC_HI, + MCHP_RDS_PTP_CLOCK, + upper_16_bits(period_sec)); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, + MCHP_RDS_PTP_CLK_TRGT_RELOAD_NS_LO, + MCHP_RDS_PTP_CLOCK, + lower_16_bits(period_nsec)); + if (rc < 0) + return rc; + + return mchp_rds_phy_write_mmd(clock, + MCHP_RDS_PTP_CLK_TRGT_RELOAD_NS_HI, + MCHP_RDS_PTP_CLOCK, + upper_16_bits(period_nsec) & 0x3fff); +} + +static int mchp_set_clock_target(struct mchp_rds_ptp_clock *clock, + s64 start_sec, u32 start_nsec) +{ + int rc; + + /* Set the start time */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_CLK_TRGT_SEC_LO, + MCHP_RDS_PTP_CLOCK, + lower_16_bits(start_sec)); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_CLK_TRGT_SEC_HI, + MCHP_RDS_PTP_CLOCK, + upper_16_bits(start_sec)); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_CLK_TRGT_NS_LO, + MCHP_RDS_PTP_CLOCK, + lower_16_bits(start_nsec)); + if (rc < 0) + return rc; + + return mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_CLK_TRGT_NS_HI, + MCHP_RDS_PTP_CLOCK, + upper_16_bits(start_nsec) & 0x3fff); +} + +static int mchp_rds_ptp_perout_off(struct mchp_rds_ptp_clock *clock) +{ + u16 general_config; + int rc; + + /* Set target to too far in the future, effectively disabling it */ + rc = mchp_set_clock_target(clock, 0xFFFFFFFF, 0); + if (rc < 0) + return rc; + + general_config = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_GEN_CFG, + MCHP_RDS_PTP_CLOCK); + general_config |= MCHP_RDS_PTP_GEN_CFG_RELOAD_ADD; + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_GEN_CFG, + MCHP_RDS_PTP_CLOCK, general_config); + if (rc < 0) + return rc; + + clock->mchp_rds_ptp_event = -1; + + return 0; +} + +static bool mchp_get_event(struct mchp_rds_ptp_clock *clock, int pin) +{ + if (clock->mchp_rds_ptp_event < 0 && pin == clock->event_pin) { + clock->mchp_rds_ptp_event = pin; + return true; + } + + return false; +} + +static int mchp_rds_ptp_perout(struct ptp_clock_info *ptpci, + struct ptp_perout_request *perout, int on) +{ + struct mchp_rds_ptp_clock *clock = container_of(ptpci, + struct mchp_rds_ptp_clock, + caps); + struct phy_device *phydev = clock->phydev; + int ret, event_pin, pulsewidth; + + event_pin = ptp_find_pin(clock->ptp_clock, PTP_PF_PEROUT, + perout->index); + if (event_pin != clock->event_pin) + return -EINVAL; + + if (!on) { + ret = mchp_rds_ptp_perout_off(clock); + return ret; + } + + if (!mchp_get_event(clock, event_pin)) + return -EINVAL; + + ret = mchp_get_pulsewidth(phydev, perout, &pulsewidth); + if (ret < 0) + return ret; + + /* Configure to pulse every period */ + ret = mchp_general_event_config(clock, pulsewidth); + if (ret < 0) + return ret; + + ret = mchp_set_clock_target(clock, perout->start.sec, + perout->start.nsec); + if (ret < 0) + return ret; + + return mchp_set_clock_reload(clock, perout->period.sec, + perout->period.nsec); +} + +static int mchp_rds_ptpci_enable(struct ptp_clock_info *ptpci, + struct ptp_clock_request *request, int on) +{ + switch (request->type) { + case PTP_CLK_REQ_PEROUT: + return mchp_rds_ptp_perout(ptpci, &request->perout, on); + default: + return -EINVAL; + } +} + +static int mchp_rds_ptpci_verify(struct ptp_clock_info *ptpci, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + struct mchp_rds_ptp_clock *clock = container_of(ptpci, + struct mchp_rds_ptp_clock, + caps); + + if (!(pin == clock->event_pin && chan == 0)) + return -1; + + switch (func) { + case PTP_PF_NONE: + case PTP_PF_PEROUT: + break; + default: + return -1; + } + + return 0; +} + +static int mchp_rds_ptp_flush_fifo(struct mchp_rds_ptp_clock *clock, + enum mchp_rds_ptp_fifo_dir dir) +{ + int rc; + + if (dir == MCHP_RDS_PTP_EGRESS_FIFO) + skb_queue_purge(&clock->tx_queue); + else + skb_queue_purge(&clock->rx_queue); + + for (int i = 0; i < MCHP_RDS_PTP_FIFO_SIZE; ++i) { + rc = mchp_rds_phy_read_mmd(clock, + dir == MCHP_RDS_PTP_EGRESS_FIFO ? + MCHP_RDS_PTP_TX_MSG_HDR2 : + MCHP_RDS_PTP_RX_MSG_HDR2, + MCHP_RDS_PTP_PORT); + if (rc < 0) + return rc; + } + return mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_INT_STS, + MCHP_RDS_PTP_PORT); +} + +static int mchp_rds_ptp_config_intr(struct mchp_rds_ptp_clock *clock, + bool enable) +{ + /* Enable or disable ptp interrupts */ + return mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_INT_EN, + MCHP_RDS_PTP_PORT, + enable ? MCHP_RDS_PTP_INT_ALL_MSK : 0); +} + +static void mchp_rds_ptp_txtstamp(struct mii_timestamper *mii_ts, + struct sk_buff *skb, int type) +{ + struct mchp_rds_ptp_clock *clock = container_of(mii_ts, + struct mchp_rds_ptp_clock, + mii_ts); + + switch (clock->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(&clock->tx_queue, skb); + break; + case HWTSTAMP_TX_OFF: + default: + kfree_skb(skb); + break; + } +} + +static bool mchp_rds_ptp_get_sig_rx(struct sk_buff *skb, u16 *sig) +{ + struct ptp_header *ptp_header; + int type; + + skb_push(skb, ETH_HLEN); + type = ptp_classify_raw(skb); + if (type == PTP_CLASS_NONE) + return false; + + ptp_header = ptp_parse_header(skb, type); + if (!ptp_header) + return false; + + skb_pull_inline(skb, ETH_HLEN); + + *sig = (__force u16)(ntohs(ptp_header->sequence_id)); + + return true; +} + +static bool mchp_rds_ptp_match_skb(struct mchp_rds_ptp_clock *clock, + struct mchp_rds_ptp_rx_ts *rx_ts) +{ + struct skb_shared_hwtstamps *shhwtstamps; + struct sk_buff *skb, *skb_tmp; + unsigned long flags; + bool rc = false; + u16 skb_sig; + + spin_lock_irqsave(&clock->rx_queue.lock, flags); + skb_queue_walk_safe(&clock->rx_queue, skb, skb_tmp) { + if (!mchp_rds_ptp_get_sig_rx(skb, &skb_sig)) + continue; + + if (skb_sig != rx_ts->seq_id) + continue; + + __skb_unlink(skb, &clock->rx_queue); + + rc = true; + break; + } + spin_unlock_irqrestore(&clock->rx_queue.lock, flags); + + if (rc) { + shhwtstamps = skb_hwtstamps(skb); + shhwtstamps->hwtstamp = ktime_set(rx_ts->seconds, rx_ts->nsec); + netif_rx(skb); + } + + return rc; +} + +static void mchp_rds_ptp_match_rx_ts(struct mchp_rds_ptp_clock *clock, + struct mchp_rds_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 (!mchp_rds_ptp_match_skb(clock, rx_ts)) { + spin_lock_irqsave(&clock->rx_ts_lock, flags); + list_add(&rx_ts->list, &clock->rx_ts_list); + spin_unlock_irqrestore(&clock->rx_ts_lock, flags); + } else { + kfree(rx_ts); + } +} + +static void mchp_rds_ptp_match_rx_skb(struct mchp_rds_ptp_clock *clock, + struct sk_buff *skb) +{ + struct mchp_rds_ptp_rx_ts *rx_ts, *tmp, *rx_ts_var = NULL; + struct skb_shared_hwtstamps *shhwtstamps; + unsigned long flags; + u16 skb_sig; + + if (!mchp_rds_ptp_get_sig_rx(skb, &skb_sig)) + return; + + /* Iterate over all RX timestamps and match it with the received skbs */ + spin_lock_irqsave(&clock->rx_ts_lock, flags); + list_for_each_entry_safe(rx_ts, tmp, &clock->rx_ts_list, list) { + /* Check if we found the signature we were looking for. */ + if (skb_sig != rx_ts->seq_id) + continue; + + shhwtstamps = skb_hwtstamps(skb); + shhwtstamps->hwtstamp = ktime_set(rx_ts->seconds, rx_ts->nsec); + netif_rx(skb); + + rx_ts_var = rx_ts; + + break; + } + spin_unlock_irqrestore(&clock->rx_ts_lock, flags); + + if (rx_ts_var) { + list_del(&rx_ts_var->list); + kfree(rx_ts_var); + } else { + skb_queue_tail(&clock->rx_queue, skb); + } +} + +static bool mchp_rds_ptp_rxtstamp(struct mii_timestamper *mii_ts, + struct sk_buff *skb, int type) +{ + struct mchp_rds_ptp_clock *clock = container_of(mii_ts, + struct mchp_rds_ptp_clock, + mii_ts); + + if (clock->rx_filter == HWTSTAMP_FILTER_NONE || + type == PTP_CLASS_NONE) + return false; + + if ((type & clock->version) == 0 || (type & clock->layer) == 0) + return false; + + /* Here if match occurs skb is sent to application, If not skb is added + * to queue and sending skb to application will get handled when + * interrupt occurs i.e., it get handles in interrupt handler. By + * any means skb will reach the application so we should not return + * false here if skb doesn't matches. + */ + mchp_rds_ptp_match_rx_skb(clock, skb); + + return true; +} + +static int mchp_rds_ptp_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config) +{ + struct mchp_rds_ptp_clock *clock = + container_of(mii_ts, struct mchp_rds_ptp_clock, + mii_ts); + config->tx_type = clock->hwts_tx_type; + config->rx_filter = clock->rx_filter; + + return 0; +} + +static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct mchp_rds_ptp_clock *clock = + container_of(mii_ts, struct mchp_rds_ptp_clock, + mii_ts); + struct mchp_rds_ptp_rx_ts *rx_ts, *tmp; + int txcfg = 0, rxcfg = 0; + unsigned long flags; + int rc; + + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + clock->layer = 0; + clock->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: + clock->layer = PTP_CLASS_L4; + clock->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: + clock->layer = PTP_CLASS_L2; + clock->version = PTP_CLASS_V2; + break; + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + clock->layer = PTP_CLASS_L4 | PTP_CLASS_L2; + clock->version = PTP_CLASS_V2; + break; + default: + return -ERANGE; + } + + switch (config->tx_type) { + case HWTSTAMP_TX_ONESTEP_SYNC: + case HWTSTAMP_TX_ON: + case HWTSTAMP_TX_OFF: + break; + default: + return -ERANGE; + } + + /* Setup parsing of the frames and enable the timestamping for ptp + * frames + */ + if (clock->layer & PTP_CLASS_L2) { + rxcfg = MCHP_RDS_PTP_PARSE_CONFIG_LAYER2_EN; + txcfg = MCHP_RDS_PTP_PARSE_CONFIG_LAYER2_EN; + } + if (clock->layer & PTP_CLASS_L4) { + rxcfg |= MCHP_RDS_PTP_PARSE_CONFIG_IPV4_EN | + MCHP_RDS_PTP_PARSE_CONFIG_IPV6_EN; + txcfg |= MCHP_RDS_PTP_PARSE_CONFIG_IPV4_EN | + MCHP_RDS_PTP_PARSE_CONFIG_IPV6_EN; + } + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_RX_PARSE_CONFIG, + MCHP_RDS_PTP_PORT, rxcfg); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TX_PARSE_CONFIG, + MCHP_RDS_PTP_PORT, txcfg); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_RX_TIMESTAMP_EN, + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_TIMESTAMP_EN_ALL); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TX_TIMESTAMP_EN, + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_TIMESTAMP_EN_ALL); + if (rc < 0) + return rc; + + if (config->tx_type == HWTSTAMP_TX_ONESTEP_SYNC) + /* Enable / disable of the TX timestamp in the SYNC frames */ + rc = mchp_rds_phy_modify_mmd(clock, MCHP_RDS_PTP_TX_MOD, + MCHP_RDS_PTP_PORT, + MCHP_RDS_TX_MOD_PTP_SYNC_TS_INSERT, + MCHP_RDS_TX_MOD_PTP_SYNC_TS_INSERT); + else + rc = mchp_rds_phy_modify_mmd(clock, MCHP_RDS_PTP_TX_MOD, + MCHP_RDS_PTP_PORT, + MCHP_RDS_TX_MOD_PTP_SYNC_TS_INSERT, + (u16)~MCHP_RDS_TX_MOD_PTP_SYNC_TS_INSERT); + + if (rc < 0) + return rc; + + /* In case of multiple starts and stops, these needs to be cleared */ + spin_lock_irqsave(&clock->rx_ts_lock, flags); + list_for_each_entry_safe(rx_ts, tmp, &clock->rx_ts_list, list) { + list_del(&rx_ts->list); + kfree(rx_ts); + } + spin_unlock_irqrestore(&clock->rx_ts_lock, flags); + + rc = mchp_rds_ptp_flush_fifo(clock, MCHP_RDS_PTP_INGRESS_FIFO); + if (rc < 0) + return rc; + + rc = mchp_rds_ptp_flush_fifo(clock, MCHP_RDS_PTP_EGRESS_FIFO); + if (rc < 0) + return rc; + + /* Now enable the timestamping interrupts */ + rc = mchp_rds_ptp_config_intr(clock, + config->rx_filter != HWTSTAMP_FILTER_NONE); + if (rc < 0) + return rc; + + clock->hwts_tx_type = config->tx_type; + clock->rx_filter = config->rx_filter; + + return 0; +} + +static int mchp_rds_ptp_ts_info(struct mii_timestamper *mii_ts, + struct kernel_ethtool_ts_info *info) +{ + struct mchp_rds_ptp_clock *clock = container_of(mii_ts, + struct mchp_rds_ptp_clock, + mii_ts); + + info->phc_index = ptp_clock_index(clock->ptp_clock); + + info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON) | + BIT(HWTSTAMP_TX_ONESTEP_SYNC); + + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) | + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | + BIT(HWTSTAMP_FILTER_PTP_V2_EVENT); + + return 0; +} + +static int mchp_rds_ptp_ltc_adjtime(struct ptp_clock_info *info, s64 delta) +{ + struct mchp_rds_ptp_clock *clock = container_of(info, + struct mchp_rds_ptp_clock, + caps); + struct timespec64 ts; + bool add = true; + int rc = 0; + u32 nsec; + s32 sec; + + /* 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 compensate 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; + + info->gettime64(info, &ts); + + now = ktime_to_ns(timespec64_to_ktime(ts)); + ts = ns_to_timespec64(now + delta); + + info->settime64(info, &ts); + return 0; + } + sec = div_u64_rem(abs(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(&clock->ptp_lock); + if (sec) { + sec = abs(sec); + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_STEP_ADJ_LO, + MCHP_RDS_PTP_CLOCK, sec); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_set_bits_mmd(clock, MCHP_RDS_PTP_STEP_ADJ_HI, + MCHP_RDS_PTP_CLOCK, + ((add ? + MCHP_RDS_PTP_STEP_ADJ_HI_DIR : + 0) | ((sec >> 16) & + GENMASK(13, 0)))); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_set_bits_mmd(clock, MCHP_RDS_PTP_CMD_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_CMD_CTL_LTC_STEP_SEC); + if (rc < 0) + goto out_unlock; + } + + if (nsec) { + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_STEP_ADJ_LO, + MCHP_RDS_PTP_CLOCK, + nsec & GENMASK(15, 0)); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_STEP_ADJ_HI, + MCHP_RDS_PTP_CLOCK, + (nsec >> 16) & GENMASK(13, 0)); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_set_bits_mmd(clock, MCHP_RDS_PTP_CMD_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_CMD_CTL_LTC_STEP_NSEC); + } + + mutex_unlock(&clock->ptp_lock); + info->gettime64(info, &ts); + mutex_lock(&clock->ptp_lock); + + /* Target update is required for pulse generation on events that + * are enabled + */ + if (clock->mchp_rds_ptp_event >= 0) + mchp_set_clock_target(clock, + ts.tv_sec + MCHP_RDS_PTP_BUFFER_TIME, 0); +out_unlock: + mutex_unlock(&clock->ptp_lock); + + return rc; +} + +static int mchp_rds_ptp_ltc_adjfine(struct ptp_clock_info *info, + long scaled_ppm) +{ + struct mchp_rds_ptp_clock *clock = container_of(info, + struct mchp_rds_ptp_clock, + caps); + u16 rate_lo, rate_hi; + bool faster = true; + u32 rate; + int rc; + + if (!scaled_ppm) + return 0; + + if (scaled_ppm < 0) { + scaled_ppm = -scaled_ppm; + faster = false; + } + + rate = MCHP_RDS_PTP_1PPM_FORMAT * (upper_16_bits(scaled_ppm)); + rate += (MCHP_RDS_PTP_1PPM_FORMAT * (lower_16_bits(scaled_ppm))) >> 16; + + rate_lo = rate & GENMASK(15, 0); + rate_hi = (rate >> 16) & GENMASK(13, 0); + + if (faster) + rate_hi |= MCHP_RDS_PTP_LTC_RATE_ADJ_HI_DIR; + + mutex_lock(&clock->ptp_lock); + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_RATE_ADJ_HI, + MCHP_RDS_PTP_CLOCK, rate_hi); + if (rc < 0) + goto error; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_RATE_ADJ_LO, + MCHP_RDS_PTP_CLOCK, rate_lo); + if (rc > 0) + rc = 0; +error: + mutex_unlock(&clock->ptp_lock); + + return rc; +} + +static int mchp_rds_ptp_ltc_gettime64(struct ptp_clock_info *info, + struct timespec64 *ts) +{ + struct mchp_rds_ptp_clock *clock = container_of(info, + struct mchp_rds_ptp_clock, + caps); + time64_t secs; + int rc = 0; + s64 nsecs; + + mutex_lock(&clock->ptp_lock); + /* Set read bit to 1 to save current values of 1588 local time counter + * into PTP LTC seconds and nanoseconds registers. + */ + rc = mchp_rds_phy_set_bits_mmd(clock, MCHP_RDS_PTP_CMD_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_CMD_CTL_CLOCK_READ); + if (rc < 0) + goto out_unlock; + + /* Get LTC clock values */ + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_LTC_READ_SEC_HI, + MCHP_RDS_PTP_CLOCK); + if (rc < 0) + goto out_unlock; + secs = rc << 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_LTC_READ_SEC_MID, + MCHP_RDS_PTP_CLOCK); + if (rc < 0) + goto out_unlock; + secs |= rc; + secs <<= 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_LTC_READ_SEC_LO, + MCHP_RDS_PTP_CLOCK); + if (rc < 0) + goto out_unlock; + secs |= rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_LTC_READ_NS_HI, + MCHP_RDS_PTP_CLOCK); + if (rc < 0) + goto out_unlock; + nsecs = (rc & GENMASK(13, 0)); + nsecs <<= 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_LTC_READ_NS_LO, + MCHP_RDS_PTP_CLOCK); + if (rc < 0) + goto out_unlock; + nsecs |= rc; + + set_normalized_timespec64(ts, secs, nsecs); + + if (rc > 0) + rc = 0; +out_unlock: + mutex_unlock(&clock->ptp_lock); + + return rc; +} + +static int mchp_rds_ptp_ltc_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + struct mchp_rds_ptp_clock *clock = container_of(info, + struct mchp_rds_ptp_clock, + caps); + int rc; + + mutex_lock(&clock->ptp_lock); + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_SEC_LO, + MCHP_RDS_PTP_CLOCK, + lower_16_bits(ts->tv_sec)); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_SEC_MID, + MCHP_RDS_PTP_CLOCK, + upper_16_bits(ts->tv_sec)); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_SEC_HI, + MCHP_RDS_PTP_CLOCK, + upper_32_bits(ts->tv_sec) & GENMASK(15, 0)); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_NS_LO, + MCHP_RDS_PTP_CLOCK, + lower_16_bits(ts->tv_nsec)); + if (rc < 0) + goto out_unlock; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LTC_NS_HI, + MCHP_RDS_PTP_CLOCK, + upper_16_bits(ts->tv_nsec) & GENMASK(13, 0)); + if (rc < 0) + goto out_unlock; + + /* Set load bit to 1 to write PTP LTC seconds and nanoseconds + * registers to 1588 local time counter. + */ + rc = mchp_rds_phy_set_bits_mmd(clock, MCHP_RDS_PTP_CMD_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_CMD_CTL_CLOCK_LOAD); + if (rc > 0) + rc = 0; +out_unlock: + mutex_unlock(&clock->ptp_lock); + + return rc; +} + +static bool mchp_rds_ptp_get_sig_tx(struct sk_buff *skb, u16 *sig) +{ + struct ptp_header *ptp_header; + int type; + + type = ptp_classify_raw(skb); + if (type == PTP_CLASS_NONE) + return false; + + ptp_header = ptp_parse_header(skb, type); + if (!ptp_header) + return false; + + *sig = (__force u16)(ntohs(ptp_header->sequence_id)); + + return true; +} + +static void mchp_rds_ptp_match_tx_skb(struct mchp_rds_ptp_clock *clock, + u32 seconds, u32 nsec, u16 seq_id) +{ + struct skb_shared_hwtstamps shhwtstamps; + struct sk_buff *skb, *skb_tmp; + unsigned long flags; + bool rc = false; + u16 skb_sig; + + spin_lock_irqsave(&clock->tx_queue.lock, flags); + skb_queue_walk_safe(&clock->tx_queue, skb, skb_tmp) { + if (!mchp_rds_ptp_get_sig_tx(skb, &skb_sig)) + continue; + + if (skb_sig != seq_id) + continue; + + __skb_unlink(skb, &clock->tx_queue); + rc = true; + break; + } + spin_unlock_irqrestore(&clock->tx_queue.lock, flags); + + if (rc) { + shhwtstamps.hwtstamp = ktime_set(seconds, nsec); + skb_complete_tx_timestamp(skb, &shhwtstamps); + } +} + +static struct mchp_rds_ptp_rx_ts + *mchp_rds_ptp_get_rx_ts(struct mchp_rds_ptp_clock *clock) +{ + struct phy_device *phydev = clock->phydev; + struct mchp_rds_ptp_rx_ts *rx_ts = NULL; + u32 sec, nsec; + int rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_RX_INGRESS_NS_HI, + MCHP_RDS_PTP_PORT); + if (rc < 0) + goto error; + if (!(rc & MCHP_RDS_PTP_RX_INGRESS_NS_HI_TS_VALID)) { + phydev_err(phydev, "RX Timestamp is not valid!\n"); + goto error; + } + nsec = (rc & GENMASK(13, 0)) << 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_RX_INGRESS_NS_LO, + MCHP_RDS_PTP_PORT); + if (rc < 0) + goto error; + nsec |= rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_RX_INGRESS_SEC_HI, + MCHP_RDS_PTP_PORT); + if (rc < 0) + goto error; + sec = rc << 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_RX_INGRESS_SEC_LO, + MCHP_RDS_PTP_PORT); + if (rc < 0) + goto error; + sec |= rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_RX_MSG_HDR2, + MCHP_RDS_PTP_PORT); + if (rc < 0) + goto error; + + rx_ts = kmalloc_obj(*rx_ts); + if (!rx_ts) + return NULL; + + rx_ts->seconds = sec; + rx_ts->nsec = nsec; + rx_ts->seq_id = rc; + +error: + return rx_ts; +} + +static void mchp_rds_ptp_process_rx_ts(struct mchp_rds_ptp_clock *clock) +{ + int caps; + + do { + struct mchp_rds_ptp_rx_ts *rx_ts; + + rx_ts = mchp_rds_ptp_get_rx_ts(clock); + if (rx_ts) + mchp_rds_ptp_match_rx_ts(clock, rx_ts); + + caps = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_CAP_INFO, + MCHP_RDS_PTP_PORT); + if (caps < 0) + return; + } while (MCHP_RDS_PTP_RX_TS_CNT(caps) > 0); +} + +static bool mchp_rds_ptp_get_tx_ts(struct mchp_rds_ptp_clock *clock, + u32 *sec, u32 *nsec, u16 *seq) +{ + int rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_TX_EGRESS_NS_HI, + MCHP_RDS_PTP_PORT); + if (rc < 0) + return false; + if (!(rc & MCHP_RDS_PTP_TX_EGRESS_NS_HI_TS_VALID)) + return false; + *nsec = (rc & GENMASK(13, 0)) << 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_TX_EGRESS_NS_LO, + MCHP_RDS_PTP_PORT); + if (rc < 0) + return false; + *nsec = *nsec | rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_TX_EGRESS_SEC_HI, + MCHP_RDS_PTP_PORT); + if (rc < 0) + return false; + *sec = rc << 16; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_TX_EGRESS_SEC_LO, + MCHP_RDS_PTP_PORT); + if (rc < 0) + return false; + *sec = *sec | rc; + + rc = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_TX_MSG_HDR2, + MCHP_RDS_PTP_PORT); + if (rc < 0) + return false; + + *seq = rc; + + return true; +} + +static void mchp_rds_ptp_process_tx_ts(struct mchp_rds_ptp_clock *clock) +{ + int caps; + + do { + u32 sec, nsec; + u16 seq; + + if (mchp_rds_ptp_get_tx_ts(clock, &sec, &nsec, &seq)) + mchp_rds_ptp_match_tx_skb(clock, sec, nsec, seq); + + caps = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_CAP_INFO, + MCHP_RDS_PTP_PORT); + if (caps < 0) + return; + } while (MCHP_RDS_PTP_TX_TS_CNT(caps) > 0); +} + +int mchp_rds_ptp_top_config_intr(struct mchp_rds_ptp_clock *clock, + u16 reg, u16 val, bool clear) +{ + if (clear) + return phy_clear_bits_mmd(clock->phydev, PTP_MMD(clock), reg, + val); + else + return phy_set_bits_mmd(clock->phydev, PTP_MMD(clock), reg, + val); +} +EXPORT_SYMBOL_GPL(mchp_rds_ptp_top_config_intr); + +irqreturn_t mchp_rds_ptp_handle_interrupt(struct mchp_rds_ptp_clock *clock) +{ + int irq_sts; + + /* To handle rogue interrupt scenarios */ + if (!clock) + return IRQ_NONE; + + do { + irq_sts = mchp_rds_phy_read_mmd(clock, MCHP_RDS_PTP_INT_STS, + MCHP_RDS_PTP_PORT); + if (irq_sts < 0) + return IRQ_NONE; + + if (irq_sts & MCHP_RDS_PTP_INT_RX_TS_EN) + mchp_rds_ptp_process_rx_ts(clock); + + if (irq_sts & MCHP_RDS_PTP_INT_TX_TS_EN) + mchp_rds_ptp_process_tx_ts(clock); + + if (irq_sts & MCHP_RDS_PTP_INT_TX_TS_OVRFL_EN) + mchp_rds_ptp_flush_fifo(clock, + MCHP_RDS_PTP_EGRESS_FIFO); + + if (irq_sts & MCHP_RDS_PTP_INT_RX_TS_OVRFL_EN) + mchp_rds_ptp_flush_fifo(clock, + MCHP_RDS_PTP_INGRESS_FIFO); + } while (irq_sts & (MCHP_RDS_PTP_INT_RX_TS_EN | + MCHP_RDS_PTP_INT_TX_TS_EN | + MCHP_RDS_PTP_INT_TX_TS_OVRFL_EN | + MCHP_RDS_PTP_INT_RX_TS_OVRFL_EN)); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(mchp_rds_ptp_handle_interrupt); + +static int mchp_rds_ptp_init(struct mchp_rds_ptp_clock *clock) +{ + int rc; + + /* Disable PTP */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_CMD_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_CMD_CTL_DIS); + if (rc < 0) + return rc; + + /* Disable TSU */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TSU_GEN_CONFIG, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + /* Clear PTP interrupt status registers */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TSU_HARD_RESET, + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_TSU_HARDRESET); + if (rc < 0) + return rc; + + /* Predictor enable */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_LATENCY_CORRECTION_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_LATENCY_SETTING); + if (rc < 0) + return rc; + + /* Configure PTP operational mode */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_OP_MODE, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_OP_MODE_STANDALONE); + if (rc < 0) + return rc; + + /* Reference clock configuration */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_REF_CLK_CFG, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_REF_CLK_CFG_SET); + if (rc < 0) + return rc; + + /* Classifier configurations */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_RX_PARSE_CONFIG, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TX_PARSE_CONFIG, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TX_PARSE_L2_ADDR_EN, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_RX_PARSE_L2_ADDR_EN, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_RX_PARSE_IPV4_ADDR_EN, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TX_PARSE_IPV4_ADDR_EN, + MCHP_RDS_PTP_PORT, 0); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_RX_VERSION, + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_MAX_VERSION(0xff) | + MCHP_RDS_PTP_MIN_VERSION(0x0)); + if (rc < 0) + return rc; + + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TX_VERSION, + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_MAX_VERSION(0xff) | + MCHP_RDS_PTP_MIN_VERSION(0x0)); + if (rc < 0) + return rc; + + /* Enable TSU */ + rc = mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_TSU_GEN_CONFIG, + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_TSU_GEN_CFG_TSU_EN); + if (rc < 0) + return rc; + + /* Enable PTP */ + return mchp_rds_phy_write_mmd(clock, MCHP_RDS_PTP_CMD_CTL, + MCHP_RDS_PTP_CLOCK, + MCHP_RDS_PTP_CMD_CTL_EN); +} + +struct mchp_rds_ptp_clock *mchp_rds_ptp_probe(struct phy_device *phydev, u8 mmd, + u16 clk_base_addr, + u16 port_base_addr) +{ + struct mchp_rds_ptp_clock *clock; + int rc; + + clock = devm_kzalloc(&phydev->mdio.dev, sizeof(*clock), GFP_KERNEL); + if (!clock) + return ERR_PTR(-ENOMEM); + + clock->port_base_addr = port_base_addr; + clock->clk_base_addr = clk_base_addr; + clock->mmd = mmd; + + mutex_init(&clock->ptp_lock); + clock->pin_config = devm_kmalloc_array(&phydev->mdio.dev, + MCHP_RDS_PTP_N_PIN, + sizeof(*clock->pin_config), + GFP_KERNEL); + if (!clock->pin_config) + return ERR_PTR(-ENOMEM); + + for (int i = 0; i < MCHP_RDS_PTP_N_PIN; ++i) { + struct ptp_pin_desc *p = &clock->pin_config[i]; + + memset(p, 0, sizeof(*p)); + snprintf(p->name, sizeof(p->name), "pin%d", i); + p->index = i; + p->func = PTP_PF_NONE; + } + /* Register PTP clock */ + clock->caps.owner = THIS_MODULE; + snprintf(clock->caps.name, 30, "%s", phydev->drv->name); + clock->caps.max_adj = MCHP_RDS_PTP_MAX_ADJ; + clock->caps.n_ext_ts = 0; + clock->caps.pps = 0; + clock->caps.n_pins = MCHP_RDS_PTP_N_PIN; + clock->caps.n_per_out = MCHP_RDS_PTP_N_PEROUT; + clock->caps.supported_perout_flags = PTP_PEROUT_DUTY_CYCLE; + clock->caps.pin_config = clock->pin_config; + clock->caps.adjfine = mchp_rds_ptp_ltc_adjfine; + clock->caps.adjtime = mchp_rds_ptp_ltc_adjtime; + clock->caps.gettime64 = mchp_rds_ptp_ltc_gettime64; + clock->caps.settime64 = mchp_rds_ptp_ltc_settime64; + clock->caps.enable = mchp_rds_ptpci_enable; + clock->caps.verify = mchp_rds_ptpci_verify; + clock->caps.getcrosststamp = NULL; + clock->ptp_clock = ptp_clock_register(&clock->caps, + &phydev->mdio.dev); + if (IS_ERR(clock->ptp_clock)) + return ERR_PTR(-EINVAL); + + /* Check if PHC support is missing at the configuration level */ + if (!clock->ptp_clock) + return NULL; + + /* Initialize the SW */ + skb_queue_head_init(&clock->tx_queue); + skb_queue_head_init(&clock->rx_queue); + INIT_LIST_HEAD(&clock->rx_ts_list); + spin_lock_init(&clock->rx_ts_lock); + + clock->mii_ts.rxtstamp = mchp_rds_ptp_rxtstamp; + clock->mii_ts.txtstamp = mchp_rds_ptp_txtstamp; + clock->mii_ts.hwtstamp_set = mchp_rds_ptp_hwtstamp_set; + clock->mii_ts.hwtstamp_get = mchp_rds_ptp_hwtstamp_get; + clock->mii_ts.ts_info = mchp_rds_ptp_ts_info; + + phydev->mii_ts = &clock->mii_ts; + + clock->mchp_rds_ptp_event = -1; + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; + + clock->phydev = phydev; + + rc = mchp_rds_ptp_init(clock); + if (rc < 0) + return ERR_PTR(rc); + + return clock; +} +EXPORT_SYMBOL_GPL(mchp_rds_ptp_probe); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MICROCHIP PHY RDS PTP driver"); +MODULE_AUTHOR("Divya Koppera"); diff --git a/drivers/net/phy/microchip_rds_ptp.h b/drivers/net/phy/microchip_rds_ptp.h new file mode 100644 index 000000000..25af68337 --- /dev/null +++ b/drivers/net/phy/microchip_rds_ptp.h @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2024 Microchip Technology + */ + +#ifndef _MICROCHIP_RDS_PTP_H +#define _MICROCHIP_RDS_PTP_H + +#include +#include +#include +#include +#include +#include + +#define MCHP_RDS_PTP_CMD_CTL 0x0 +#define MCHP_RDS_PTP_CMD_CTL_LTC_STEP_NSEC BIT(6) +#define MCHP_RDS_PTP_CMD_CTL_LTC_STEP_SEC BIT(5) +#define MCHP_RDS_PTP_CMD_CTL_CLOCK_LOAD BIT(4) +#define MCHP_RDS_PTP_CMD_CTL_CLOCK_READ BIT(3) +#define MCHP_RDS_PTP_CMD_CTL_EN BIT(1) +#define MCHP_RDS_PTP_CMD_CTL_DIS BIT(0) + +#define MCHP_RDS_PTP_REF_CLK_CFG 0x2 +#define MCHP_RDS_PTP_REF_CLK_SRC_250MHZ 0x0 +#define MCHP_RDS_PTP_REF_CLK_PERIOD_OVERRIDE BIT(9) +#define MCHP_RDS_PTP_REF_CLK_PERIOD 4 +#define MCHP_RDS_PTP_REF_CLK_CFG_SET (MCHP_RDS_PTP_REF_CLK_SRC_250MHZ |\ + MCHP_RDS_PTP_REF_CLK_PERIOD_OVERRIDE |\ + MCHP_RDS_PTP_REF_CLK_PERIOD) + +#define MCHP_RDS_PTP_LTC_SEC_HI 0x5 +#define MCHP_RDS_PTP_LTC_SEC_MID 0x6 +#define MCHP_RDS_PTP_LTC_SEC_LO 0x7 +#define MCHP_RDS_PTP_LTC_NS_HI 0x8 +#define MCHP_RDS_PTP_LTC_NS_LO 0x9 +#define MCHP_RDS_PTP_LTC_RATE_ADJ_HI 0xc +#define MCHP_RDS_PTP_LTC_RATE_ADJ_HI_DIR BIT(15) +#define MCHP_RDS_PTP_LTC_RATE_ADJ_LO 0xd +#define MCHP_RDS_PTP_STEP_ADJ_HI 0x12 +#define MCHP_RDS_PTP_STEP_ADJ_HI_DIR BIT(15) +#define MCHP_RDS_PTP_STEP_ADJ_LO 0x13 +#define MCHP_RDS_PTP_LTC_READ_SEC_HI 0x29 +#define MCHP_RDS_PTP_LTC_READ_SEC_MID 0x2a +#define MCHP_RDS_PTP_LTC_READ_SEC_LO 0x2b +#define MCHP_RDS_PTP_LTC_READ_NS_HI 0x2c +#define MCHP_RDS_PTP_LTC_READ_NS_LO 0x2d +#define MCHP_RDS_PTP_OP_MODE 0x41 +#define MCHP_RDS_PTP_OP_MODE_DIS 0 +#define MCHP_RDS_PTP_OP_MODE_STANDALONE 1 +#define MCHP_RDS_PTP_LATENCY_CORRECTION_CTL 0x44 +#define MCHP_RDS_PTP_PREDICTOR_EN BIT(6) +#define MCHP_RDS_PTP_TX_PRED_DIS BIT(1) +#define MCHP_RDS_PTP_RX_PRED_DIS BIT(0) +#define MCHP_RDS_PTP_LATENCY_SETTING (MCHP_RDS_PTP_PREDICTOR_EN | \ + MCHP_RDS_PTP_TX_PRED_DIS | \ + MCHP_RDS_PTP_RX_PRED_DIS) + +#define MCHP_RDS_PTP_INT_EN 0x0 +#define MCHP_RDS_PTP_INT_STS 0x01 +#define MCHP_RDS_PTP_INT_TX_TS_OVRFL_EN BIT(3) +#define MCHP_RDS_PTP_INT_TX_TS_EN BIT(2) +#define MCHP_RDS_PTP_INT_RX_TS_OVRFL_EN BIT(1) +#define MCHP_RDS_PTP_INT_RX_TS_EN BIT(0) +#define MCHP_RDS_PTP_INT_ALL_MSK (MCHP_RDS_PTP_INT_TX_TS_OVRFL_EN | \ + MCHP_RDS_PTP_INT_TX_TS_EN | \ + MCHP_RDS_PTP_INT_RX_TS_OVRFL_EN |\ + MCHP_RDS_PTP_INT_RX_TS_EN) + +#define MCHP_RDS_PTP_CAP_INFO 0x2e +#define MCHP_RDS_PTP_TX_TS_CNT(v) (((v) & GENMASK(11, 8)) >> 8) +#define MCHP_RDS_PTP_RX_TS_CNT(v) ((v) & GENMASK(3, 0)) + +#define MCHP_RDS_PTP_RX_PARSE_CONFIG 0x42 +#define MCHP_RDS_PTP_RX_PARSE_L2_ADDR_EN 0x44 +#define MCHP_RDS_PTP_RX_PARSE_IPV4_ADDR_EN 0x45 + +#define MCHP_RDS_PTP_RX_TIMESTAMP_CONFIG 0x4e +#define MCHP_RDS_PTP_RX_TIMESTAMP_CONFIG_PTP_FCS_DIS BIT(0) + +#define MCHP_RDS_PTP_RX_VERSION 0x48 +#define MCHP_RDS_PTP_RX_TIMESTAMP_EN 0x4d + +#define MCHP_RDS_PTP_RX_INGRESS_NS_HI 0x54 +#define MCHP_RDS_PTP_RX_INGRESS_NS_HI_TS_VALID BIT(15) + +#define MCHP_RDS_PTP_RX_INGRESS_NS_LO 0x55 +#define MCHP_RDS_PTP_RX_INGRESS_SEC_HI 0x56 +#define MCHP_RDS_PTP_RX_INGRESS_SEC_LO 0x57 +#define MCHP_RDS_PTP_RX_MSG_HDR2 0x59 + +#define MCHP_RDS_PTP_TX_PARSE_CONFIG 0x82 +#define MCHP_RDS_PTP_PARSE_CONFIG_LAYER2_EN BIT(0) +#define MCHP_RDS_PTP_PARSE_CONFIG_IPV4_EN BIT(1) +#define MCHP_RDS_PTP_PARSE_CONFIG_IPV6_EN BIT(2) + +#define MCHP_RDS_PTP_TX_PARSE_L2_ADDR_EN 0x84 +#define MCHP_RDS_PTP_TX_PARSE_IPV4_ADDR_EN 0x85 + +#define MCHP_RDS_PTP_TX_VERSION 0x88 +#define MCHP_RDS_PTP_MAX_VERSION(x) (((x) & GENMASK(7, 0)) << 8) +#define MCHP_RDS_PTP_MIN_VERSION(x) ((x) & GENMASK(7, 0)) + +#define MCHP_RDS_PTP_TX_TIMESTAMP_EN 0x8d +#define MCHP_RDS_PTP_TIMESTAMP_EN_SYNC BIT(0) +#define MCHP_RDS_PTP_TIMESTAMP_EN_DREQ BIT(1) +#define MCHP_RDS_PTP_TIMESTAMP_EN_PDREQ BIT(2) +#define MCHP_RDS_PTP_TIMESTAMP_EN_PDRES BIT(3) +#define MCHP_RDS_PTP_TIMESTAMP_EN_ALL (MCHP_RDS_PTP_TIMESTAMP_EN_SYNC |\ + MCHP_RDS_PTP_TIMESTAMP_EN_DREQ |\ + MCHP_RDS_PTP_TIMESTAMP_EN_PDREQ |\ + MCHP_RDS_PTP_TIMESTAMP_EN_PDRES) + +#define MCHP_RDS_PTP_TX_TIMESTAMP_CONFIG 0x8e +#define MCHP_RDS_PTP_TX_TIMESTAMP_CONFIG_PTP_FCS_DIS BIT(0) + +#define MCHP_RDS_PTP_TX_MOD 0x8f +#define MCHP_RDS_TX_MOD_PTP_SYNC_TS_INSERT BIT(12) + +#define MCHP_RDS_PTP_TX_EGRESS_NS_HI 0x94 +#define MCHP_RDS_PTP_TX_EGRESS_NS_HI_TS_VALID BIT(15) + +#define MCHP_RDS_PTP_TX_EGRESS_NS_LO 0x95 +#define MCHP_RDS_PTP_TX_EGRESS_SEC_HI 0x96 +#define MCHP_RDS_PTP_TX_EGRESS_SEC_LO 0x97 +#define MCHP_RDS_PTP_TX_MSG_HDR2 0x99 + +#define MCHP_RDS_PTP_TSU_GEN_CONFIG 0xc0 +#define MCHP_RDS_PTP_TSU_GEN_CFG_TSU_EN BIT(0) + +#define MCHP_RDS_PTP_TSU_HARD_RESET 0xc1 +#define MCHP_RDS_PTP_TSU_HARDRESET BIT(0) + +#define MCHP_RDS_PTP_CLK_TRGT_SEC_HI 0x15 +#define MCHP_RDS_PTP_CLK_TRGT_SEC_LO 0x16 +#define MCHP_RDS_PTP_CLK_TRGT_NS_HI 0x17 +#define MCHP_RDS_PTP_CLK_TRGT_NS_LO 0x18 + +#define MCHP_RDS_PTP_CLK_TRGT_RELOAD_SEC_HI 0x19 +#define MCHP_RDS_PTP_CLK_TRGT_RELOAD_SEC_LO 0x1a +#define MCHP_RDS_PTP_CLK_TRGT_RELOAD_NS_HI 0x1b +#define MCHP_RDS_PTP_CLK_TRGT_RELOAD_NS_LO 0x1c + +#define MCHP_RDS_PTP_GEN_CFG 0x01 +#define MCHP_RDS_PTP_GEN_CFG_LTC_EVT_MASK GENMASK(11, 8) + +#define MCHP_RDS_PTP_GEN_CFG_LTC_EVT_SET(value) (((value) & 0xF) << 4) +#define MCHP_RDS_PTP_GEN_CFG_RELOAD_ADD BIT(0) +#define MCHP_RDS_PTP_GEN_CFG_POLARITY BIT(1) + +/* Represents 1ppm adjustment in 2^32 format with + * each nsec contains 4 clock cycles in 250MHz. + * The value is calculated as following: (1/1000000)/((2^-32)/4) + */ +#define MCHP_RDS_PTP_1PPM_FORMAT 17179 +#define MCHP_RDS_PTP_FIFO_SIZE 8 +#define MCHP_RDS_PTP_MAX_ADJ 31249999 + +#define MCHP_RDS_PTP_BUFFER_TIME 2 +#define MCHP_RDS_PTP_N_PIN 4 +#define MCHP_RDS_PTP_N_PEROUT 1 + +#define BASE_CLK(p) ((p)->clk_base_addr) +#define BASE_PORT(p) ((p)->port_base_addr) +#define PTP_MMD(p) ((p)->mmd) + +enum mchp_rds_ptp_base { + MCHP_RDS_PTP_PORT, + MCHP_RDS_PTP_CLOCK +}; + +enum mchp_rds_ptp_fifo_dir { + MCHP_RDS_PTP_INGRESS_FIFO, + MCHP_RDS_PTP_EGRESS_FIFO +}; + +struct mchp_rds_ptp_clock { + struct mii_timestamper mii_ts; + struct phy_device *phydev; + struct ptp_clock *ptp_clock; + + struct sk_buff_head tx_queue; + struct sk_buff_head rx_queue; + struct list_head rx_ts_list; + + struct ptp_clock_info caps; + + /* Lock for Rx ts fifo */ + spinlock_t rx_ts_lock; + int hwts_tx_type; + + enum hwtstamp_rx_filters rx_filter; + int layer; + int version; + u16 port_base_addr; + u16 clk_base_addr; + + /* Lock for phc */ + struct mutex ptp_lock; + u8 mmd; + int mchp_rds_ptp_event; + int event_pin; + struct ptp_pin_desc *pin_config; +}; + +struct mchp_rds_ptp_rx_ts { + struct list_head list; + u32 seconds; + u32 nsec; + u16 seq_id; +}; + +#if IS_ENABLED(CONFIG_MICROCHIP_PHY_RDS_PTP) + +struct mchp_rds_ptp_clock *mchp_rds_ptp_probe(struct phy_device *phydev, u8 mmd, + u16 clk_base, u16 port_base); + +int mchp_rds_ptp_top_config_intr(struct mchp_rds_ptp_clock *clock, + u16 reg, u16 val, bool enable); + +irqreturn_t mchp_rds_ptp_handle_interrupt(struct mchp_rds_ptp_clock *clock); + +#else + +static inline struct mchp_rds_ptp_clock *mchp_rds_ptp_probe(struct phy_device + *phydev, u8 mmd, + u16 clk_base, + u16 port_base) +{ + return NULL; +} + +static inline int mchp_rds_ptp_top_config_intr(struct mchp_rds_ptp_clock *clock, + u16 reg, u16 val, bool enable) +{ + return 0; +} + +static inline irqreturn_t mchp_rds_ptp_handle_interrupt(struct + mchp_rds_ptp_clock + * clock) +{ + return IRQ_NONE; +} + +#endif //CONFIG_MICROCHIP_PHY_RDS_PTP + +#endif //_MICROCHIP_RDS_PTP_H diff --git a/drivers/net/phy/microchip_t1.c b/drivers/net/phy/microchip_t1.c new file mode 100644 index 000000000..3292b2235 --- /dev/null +++ b/drivers/net/phy/microchip_t1.c @@ -0,0 +1,2176 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Microchip Technology + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "microchip_rds_ptp.h" + +#define PHY_ID_LAN87XX 0x0007c150 +#define PHY_ID_LAN937X 0x0007c180 +#define PHY_ID_LAN887X 0x0007c1f0 + +#define MCHP_RDS_PTP_LTC_BASE_ADDR 0xe000 +#define MCHP_RDS_PTP_PORT_BASE_ADDR (MCHP_RDS_PTP_LTC_BASE_ADDR + 0x800) + +/* 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 + +/* Chiptop registers */ +#define LAN887X_PMA_EXT_ABILITY_2 0x12 +#define LAN887X_PMA_EXT_ABILITY_2_1000T1 BIT(1) +#define LAN887X_PMA_EXT_ABILITY_2_100T1 BIT(0) + +/* DSP 100M registers */ +#define LAN887x_CDR_CONFIG1_100 0x0405 +#define LAN887x_LOCK1_EQLSR_CONFIG_100 0x0411 +#define LAN887x_SLV_HD_MUFAC_CONFIG_100 0x0417 +#define LAN887x_PLOCK_MUFAC_CONFIG_100 0x041c +#define LAN887x_PROT_DISABLE_100 0x0425 +#define LAN887x_KF_LOOP_SAT_CONFIG_100 0x0454 + +/* DSP 1000M registers */ +#define LAN887X_LOCK1_EQLSR_CONFIG 0x0811 +#define LAN887X_LOCK3_EQLSR_CONFIG 0x0813 +#define LAN887X_PROT_DISABLE 0x0825 +#define LAN887X_FFE_GAIN6 0x0843 +#define LAN887X_FFE_GAIN7 0x0844 +#define LAN887X_FFE_GAIN8 0x0845 +#define LAN887X_FFE_GAIN9 0x0846 +#define LAN887X_ECHO_DELAY_CONFIG 0x08ec +#define LAN887X_FFE_MAX_CONFIG 0x08ee + +/* PCS 1000M registers */ +#define LAN887X_SCR_CONFIG_3 0x8043 +#define LAN887X_INFO_FLD_CONFIG_5 0x8048 + +/* T1 afe registers */ +#define LAN887X_ZQCAL_CONTROL_1 0x8080 +#define LAN887X_AFE_PORT_TESTBUS_CTRL2 0x8089 +#define LAN887X_AFE_PORT_TESTBUS_CTRL4 0x808b +#define LAN887X_AFE_PORT_TESTBUS_CTRL6 0x808d +#define LAN887X_TX_AMPLT_1000T1_REG 0x80b0 +#define LAN887X_INIT_COEFF_DFE1_100 0x0422 + +/* PMA registers */ +#define LAN887X_DSP_PMA_CONTROL 0x810e +#define LAN887X_DSP_PMA_CONTROL_LNK_SYNC BIT(4) + +/* PCS 100M registers */ +#define LAN887X_IDLE_ERR_TIMER_WIN 0x8204 +#define LAN887X_IDLE_ERR_CNT_THRESH 0x8213 + +/* Misc registers */ +#define LAN887X_REG_REG26 0x001a +#define LAN887X_REG_REG26_HW_INIT_SEQ_EN BIT(8) + +/* Mis registers */ +#define LAN887X_MIS_CFG_REG0 0xa00 +#define LAN887X_MIS_CFG_REG0_RCLKOUT_DIS BIT(5) +#define LAN887X_MIS_CFG_REG0_MAC_MODE_SEL GENMASK(1, 0) + +#define LAN887X_MAC_MODE_RGMII 0x01 +#define LAN887X_MAC_MODE_SGMII 0x03 + +#define LAN887X_MIS_DLL_CFG_REG0 0xa01 +#define LAN887X_MIS_DLL_CFG_REG1 0xa02 + +#define LAN887X_MIS_DLL_DELAY_EN BIT(15) +#define LAN887X_MIS_DLL_EN BIT(0) +#define LAN887X_MIS_DLL_CONF (LAN887X_MIS_DLL_DELAY_EN |\ + LAN887X_MIS_DLL_EN) + +#define LAN887X_MIS_CFG_REG2 0xa03 +#define LAN887X_MIS_CFG_REG2_FE_LPBK_EN BIT(2) + +#define LAN887X_MIS_PKT_STAT_REG0 0xa06 +#define LAN887X_MIS_PKT_STAT_REG1 0xa07 +#define LAN887X_MIS_PKT_STAT_REG3 0xa09 +#define LAN887X_MIS_PKT_STAT_REG4 0xa0a +#define LAN887X_MIS_PKT_STAT_REG5 0xa0b +#define LAN887X_MIS_PKT_STAT_REG6 0xa0c + +/* Chiptop common registers */ +#define LAN887X_COMMON_LED3_LED2 0xc05 +#define LAN887X_COMMON_LED2_MODE_SEL_MASK GENMASK(4, 0) +#define LAN887X_LED_LINK_ACT_ANY_SPEED 0x0 + +/* MX chip top registers */ +#define LAN887X_CHIP_HARD_RST 0xf03e +#define LAN887X_CHIP_HARD_RST_RESET BIT(0) + +#define LAN887X_CHIP_SOFT_RST 0xf03f +#define LAN887X_CHIP_SOFT_RST_RESET BIT(0) + +#define LAN887X_SGMII_CTL 0xf01a +#define LAN887X_SGMII_CTL_SGMII_MUX_EN BIT(0) + +#define LAN887X_SGMII_PCS_CFG 0xf034 +#define LAN887X_SGMII_PCS_CFG_PCS_ENA BIT(9) + +#define LAN887X_EFUSE_READ_DAT9 0xf209 +#define LAN887X_EFUSE_READ_DAT9_SGMII_DIS BIT(9) +#define LAN887X_EFUSE_READ_DAT9_MAC_MODE GENMASK(1, 0) + +#define LAN887X_CALIB_CONFIG_100 0x437 +#define LAN887X_CALIB_CONFIG_100_CBL_DIAG_USE_LOCAL_SMPL BIT(5) +#define LAN887X_CALIB_CONFIG_100_CBL_DIAG_STB_SYNC_MODE BIT(4) +#define LAN887X_CALIB_CONFIG_100_CBL_DIAG_CLK_ALGN_MODE BIT(3) +#define LAN887X_CALIB_CONFIG_100_VAL \ + (LAN887X_CALIB_CONFIG_100_CBL_DIAG_CLK_ALGN_MODE |\ + LAN887X_CALIB_CONFIG_100_CBL_DIAG_STB_SYNC_MODE |\ + LAN887X_CALIB_CONFIG_100_CBL_DIAG_USE_LOCAL_SMPL) + +#define LAN887X_MAX_PGA_GAIN_100 0x44f +#define LAN887X_MIN_PGA_GAIN_100 0x450 +#define LAN887X_START_CBL_DIAG_100 0x45a +#define LAN887X_CBL_DIAG_DONE BIT(1) +#define LAN887X_CBL_DIAG_START BIT(0) +#define LAN887X_CBL_DIAG_STOP 0x0 + +#define LAN887X_CBL_DIAG_TDR_THRESH_100 0x45b +#define LAN887X_CBL_DIAG_AGC_THRESH_100 0x45c +#define LAN887X_CBL_DIAG_MIN_WAIT_CONFIG_100 0x45d +#define LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100 0x45e +#define LAN887X_CBL_DIAG_CYC_CONFIG_100 0x45f +#define LAN887X_CBL_DIAG_TX_PULSE_CONFIG_100 0x460 +#define LAN887X_CBL_DIAG_MIN_PGA_GAIN_100 0x462 +#define LAN887X_CBL_DIAG_AGC_GAIN_100 0x497 +#define LAN887X_CBL_DIAG_POS_PEAK_VALUE_100 0x499 +#define LAN887X_CBL_DIAG_NEG_PEAK_VALUE_100 0x49a +#define LAN887X_CBL_DIAG_POS_PEAK_TIME_100 0x49c +#define LAN887X_CBL_DIAG_NEG_PEAK_TIME_100 0x49d + +#define MICROCHIP_CABLE_NOISE_MARGIN 20 +#define MICROCHIP_CABLE_TIME_MARGIN 89 +#define MICROCHIP_CABLE_MIN_TIME_DIFF 96 +#define MICROCHIP_CABLE_MAX_TIME_DIFF \ + (MICROCHIP_CABLE_MIN_TIME_DIFF + MICROCHIP_CABLE_TIME_MARGIN) + +#define LAN887X_INT_STS 0xf000 +#define LAN887X_INT_MSK 0xf001 +#define LAN887X_INT_MSK_P1588_MOD_INT_MSK BIT(3) +#define LAN887X_INT_MSK_T1_PHY_INT_MSK BIT(2) +#define LAN887X_INT_MSK_LINK_UP_MSK BIT(1) +#define LAN887X_INT_MSK_LINK_DOWN_MSK BIT(0) + +#define LAN887X_MX_CHIP_TOP_REG_CONTROL1 0xF002 +#define LAN887X_MX_CHIP_TOP_REG_CONTROL1_EVT_EN BIT(8) + +#define LAN887X_MX_CHIP_TOP_LINK_MSK (LAN887X_INT_MSK_LINK_UP_MSK |\ + LAN887X_INT_MSK_LINK_DOWN_MSK) + +#define LAN887X_MX_CHIP_TOP_ALL_MSK (LAN887X_INT_MSK_T1_PHY_INT_MSK |\ + LAN887X_MX_CHIP_TOP_LINK_MSK) + +#define LAN887X_COEFF_PWR_DN_CONFIG_100 0x0404 +#define LAN887X_COEFF_PWR_DN_CONFIG_100_V 0x16d6 +#define LAN887X_SQI_CONFIG_100 0x042e +#define LAN887X_SQI_CONFIG_100_V 0x9572 +#define LAN887X_SQI_MSE_100 0x483 + +#define LAN887X_POKE_PEEK_100 0x040d +#define LAN887X_POKE_PEEK_100_EN BIT(0) + +#define LAN887X_COEFF_MOD_CONFIG 0x080d +#define LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN BIT(8) + +#define LAN887X_DCQ_SQI_STATUS 0x08b2 + +/* SQI raw samples count */ +#define SQI_SAMPLES 200 + +/* Samples percentage considered for SQI calculation */ +#define SQI_INLINERS_PERCENT 60 + +/* Samples count considered for SQI calculation */ +#define SQI_INLIERS_NUM (SQI_SAMPLES * SQI_INLINERS_PERCENT / 100) + +/* Start offset of samples */ +#define SQI_INLIERS_START ((SQI_SAMPLES - SQI_INLIERS_NUM) / 2) + +/* End offset of samples */ +#define SQI_INLIERS_END (SQI_INLIERS_START + SQI_INLIERS_NUM) + +#define DRIVER_AUTHOR "Nisar Sayed " +#define DRIVER_DESC "Microchip LAN87XX/LAN937x/LAN887x T1 PHY driver" + +/* TEST_MODE_NORMAL: Non-hybrid results to calculate cable status(open/short/ok) + * TEST_MODE_HYBRID: Hybrid results to calculate distance to fault + */ +enum cable_diag_mode { + TEST_MODE_NORMAL, + TEST_MODE_HYBRID +}; + +/* CD_TEST_INIT: Cable test is initated + * CD_TEST_DONE: Cable test is done + */ +enum cable_diag_state { + CD_TEST_INIT, + CD_TEST_DONE +}; + +struct access_ereg_val { + u8 mode; + u8 bank; + u8 offset; + u16 val; + u16 mask; +}; + +struct lan887x_hw_stat { + const char *string; + u8 mmd; + u16 reg; + u8 bits; +}; + +static const struct lan887x_hw_stat lan887x_hw_stats[] = { + { "TX Good Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG0, 14}, + { "RX Good Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG1, 14}, + { "RX ERR Count detected by PCS", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG3, 16}, + { "TX CRC ERR Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG4, 8}, + { "RX CRC ERR Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG5, 8}, + { "RX ERR Count for SGMII MII2GMII", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG6, 8}, +}; + +struct lan887x_regwr_map { + u8 mmd; + u16 reg; + u16 val; +}; + +struct lan887x_priv { + u64 stats[ARRAY_SIZE(lan887x_hw_stats)]; + struct mchp_rds_ptp_clock *clock; + bool init_done; +}; + +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 phy_init_hw(phydev); +} + +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; + + return genphy_read_status_fixed(phydev); +} + +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 int lan887x_rgmii_init(struct phy_device *phydev) +{ + int ret; + + /* SGMII mux disable */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_SGMII_CTL, + LAN887X_SGMII_CTL_SGMII_MUX_EN); + if (ret < 0) + return ret; + + /* Select MAC_MODE as RGMII */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_CFG_REG0, + LAN887X_MIS_CFG_REG0_MAC_MODE_SEL, + LAN887X_MAC_MODE_RGMII); + if (ret < 0) + return ret; + + /* Disable PCS */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_SGMII_PCS_CFG, + LAN887X_SGMII_PCS_CFG_PCS_ENA); + if (ret < 0) + return ret; + + /* LAN887x Errata: RGMII rx clock active in SGMII mode + * Disabled it for SGMII mode + * Re-enabling it for RGMII mode + */ + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_MIS_CFG_REG0, + LAN887X_MIS_CFG_REG0_RCLKOUT_DIS); +} + +static int lan887x_sgmii_init(struct phy_device *phydev) +{ + int ret; + + /* SGMII mux enable */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_SGMII_CTL, + LAN887X_SGMII_CTL_SGMII_MUX_EN); + if (ret < 0) + return ret; + + /* Select MAC_MODE as SGMII */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_CFG_REG0, + LAN887X_MIS_CFG_REG0_MAC_MODE_SEL, + LAN887X_MAC_MODE_SGMII); + if (ret < 0) + return ret; + + /* LAN887x Errata: RGMII rx clock active in SGMII mode. + * So disabling it for SGMII mode + */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_CFG_REG0, + LAN887X_MIS_CFG_REG0_RCLKOUT_DIS); + if (ret < 0) + return ret; + + /* Enable PCS */ + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SGMII_PCS_CFG, + LAN887X_SGMII_PCS_CFG_PCS_ENA); +} + +static int lan887x_config_rgmii_en(struct phy_device *phydev) +{ + int txc; + int rxc; + int ret; + + ret = lan887x_rgmii_init(phydev); + if (ret < 0) + return ret; + + /* Control bit to enable/disable TX DLL delay line in signal path */ + txc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG0); + if (txc < 0) + return txc; + + /* Control bit to enable/disable RX DLL delay line in signal path */ + rxc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG1); + if (rxc < 0) + return rxc; + + /* Configures the phy to enable RX/TX delay + * RGMII - TX & RX delays are either added by MAC or not needed, + * phy should not add + * RGMII_ID - Configures phy to enable TX & RX delays, MAC shouldn't add + * RGMII_RX_ID - Configures the PHY to enable the RX delay. + * The MAC shouldn't add the RX delay + * RGMII_TX_ID - Configures the PHY to enable the TX delay. + * The MAC shouldn't add the TX delay in this case + */ + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + txc &= ~LAN887X_MIS_DLL_CONF; + rxc &= ~LAN887X_MIS_DLL_CONF; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + txc |= LAN887X_MIS_DLL_CONF; + rxc |= LAN887X_MIS_DLL_CONF; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + txc &= ~LAN887X_MIS_DLL_CONF; + rxc |= LAN887X_MIS_DLL_CONF; + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + txc |= LAN887X_MIS_DLL_CONF; + rxc &= ~LAN887X_MIS_DLL_CONF; + break; + default: + WARN_ONCE(1, "Invalid phydev interface %d\n", phydev->interface); + return 0; + } + + /* Configures the PHY to enable/disable RX delay in signal path */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG1, + LAN887X_MIS_DLL_CONF, rxc); + if (ret < 0) + return ret; + + /* Configures the PHY to enable/disable the TX delay in signal path */ + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG0, + LAN887X_MIS_DLL_CONF, txc); +} + +static int lan887x_config_phy_interface(struct phy_device *phydev) +{ + int interface_mode; + int sgmii_dis; + int ret; + + /* Read sku efuse data for interfaces supported by sku */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_EFUSE_READ_DAT9); + if (ret < 0) + return ret; + + /* If interface_mode is 1 then efuse sets RGMII operations. + * If interface mode is 3 then efuse sets SGMII operations. + */ + interface_mode = ret & LAN887X_EFUSE_READ_DAT9_MAC_MODE; + /* SGMII disable is set for RGMII operations */ + sgmii_dis = ret & LAN887X_EFUSE_READ_DAT9_SGMII_DIS; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + /* Reject RGMII settings for SGMII only sku */ + ret = -EOPNOTSUPP; + + if (!((interface_mode & LAN887X_MAC_MODE_SGMII) == + LAN887X_MAC_MODE_SGMII)) + ret = lan887x_config_rgmii_en(phydev); + break; + case PHY_INTERFACE_MODE_SGMII: + /* Reject SGMII setting for RGMII only sku */ + ret = -EOPNOTSUPP; + + if (!sgmii_dis) + ret = lan887x_sgmii_init(phydev); + break; + default: + /* Reject setting for unsupported interfaces */ + ret = -EOPNOTSUPP; + } + + return ret; +} + +static int lan887x_get_features(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_pma_read_abilities(phydev); + if (ret < 0) + return ret; + + /* Enable twisted pair */ + linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, phydev->supported); + + /* First patch only supports 100Mbps and 1000Mbps force-mode. + * T1 Auto-Negotiation (Clause 98 of IEEE 802.3) will be added later. + */ + linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported); + + return 0; +} + +static int lan887x_phy_init(struct phy_device *phydev) +{ + struct lan887x_priv *priv = phydev->priv; + int ret; + + if (!priv->init_done && phy_interrupt_is_valid(phydev)) { + priv->clock = mchp_rds_ptp_probe(phydev, MDIO_MMD_VEND1, + MCHP_RDS_PTP_LTC_BASE_ADDR, + MCHP_RDS_PTP_PORT_BASE_ADDR); + if (IS_ERR(priv->clock)) + return PTR_ERR(priv->clock); + + /* Enable pin mux for EVT */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_MX_CHIP_TOP_REG_CONTROL1, + LAN887X_MX_CHIP_TOP_REG_CONTROL1_EVT_EN, + LAN887X_MX_CHIP_TOP_REG_CONTROL1_EVT_EN); + + /* Initialize pin numbers specific to PEROUT */ + priv->clock->event_pin = 3; + + priv->init_done = true; + } + + /* Clear loopback */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_MIS_CFG_REG2, + LAN887X_MIS_CFG_REG2_FE_LPBK_EN); + if (ret < 0) + return ret; + + /* Configure default behavior of led to link and activity for any + * speed + */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_COMMON_LED3_LED2, + LAN887X_COMMON_LED2_MODE_SEL_MASK, + LAN887X_LED_LINK_ACT_ANY_SPEED); + if (ret < 0) + return ret; + + /* PHY interface setup */ + return lan887x_config_phy_interface(phydev); +} + +static int lan887x_phy_config(struct phy_device *phydev, + const struct lan887x_regwr_map *reg_map, int cnt) +{ + int ret; + + for (int i = 0; i < cnt; i++) { + ret = phy_write_mmd(phydev, reg_map[i].mmd, + reg_map[i].reg, reg_map[i].val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int lan887x_phy_setup(struct phy_device *phydev) +{ + static const struct lan887x_regwr_map phy_cfg[] = { + /* PORT_AFE writes */ + {MDIO_MMD_PMAPMD, LAN887X_ZQCAL_CONTROL_1, 0x4008}, + {MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL2, 0x0000}, + {MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL6, 0x0040}, + /* 100T1_PCS_VENDOR writes */ + {MDIO_MMD_PCS, LAN887X_IDLE_ERR_CNT_THRESH, 0x0008}, + {MDIO_MMD_PCS, LAN887X_IDLE_ERR_TIMER_WIN, 0x800d}, + /* 100T1 DSP writes */ + {MDIO_MMD_VEND1, LAN887x_CDR_CONFIG1_100, 0x0ab1}, + {MDIO_MMD_VEND1, LAN887x_LOCK1_EQLSR_CONFIG_100, 0x5274}, + {MDIO_MMD_VEND1, LAN887x_SLV_HD_MUFAC_CONFIG_100, 0x0d74}, + {MDIO_MMD_VEND1, LAN887x_PLOCK_MUFAC_CONFIG_100, 0x0aea}, + {MDIO_MMD_VEND1, LAN887x_PROT_DISABLE_100, 0x0360}, + {MDIO_MMD_VEND1, LAN887x_KF_LOOP_SAT_CONFIG_100, 0x0c30}, + /* 1000T1 DSP writes */ + {MDIO_MMD_VEND1, LAN887X_LOCK1_EQLSR_CONFIG, 0x2a78}, + {MDIO_MMD_VEND1, LAN887X_LOCK3_EQLSR_CONFIG, 0x1368}, + {MDIO_MMD_VEND1, LAN887X_PROT_DISABLE, 0x1354}, + {MDIO_MMD_VEND1, LAN887X_FFE_GAIN6, 0x3C84}, + {MDIO_MMD_VEND1, LAN887X_FFE_GAIN7, 0x3ca5}, + {MDIO_MMD_VEND1, LAN887X_FFE_GAIN8, 0x3ca5}, + {MDIO_MMD_VEND1, LAN887X_FFE_GAIN9, 0x3ca5}, + {MDIO_MMD_VEND1, LAN887X_ECHO_DELAY_CONFIG, 0x0024}, + {MDIO_MMD_VEND1, LAN887X_FFE_MAX_CONFIG, 0x227f}, + /* 1000T1 PCS writes */ + {MDIO_MMD_PCS, LAN887X_SCR_CONFIG_3, 0x1e00}, + {MDIO_MMD_PCS, LAN887X_INFO_FLD_CONFIG_5, 0x0fa1}, + }; + + return lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg)); +} + +static int lan887x_100M_setup(struct phy_device *phydev) +{ + int ret; + + /* (Re)configure the speed/mode dependent T1 settings */ + if (phydev->master_slave_set == MASTER_SLAVE_CFG_MASTER_FORCE || + phydev->master_slave_set == MASTER_SLAVE_CFG_MASTER_PREFERRED){ + static const struct lan887x_regwr_map phy_cfg[] = { + {MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL4, 0x00b8}, + {MDIO_MMD_PMAPMD, LAN887X_TX_AMPLT_1000T1_REG, 0x0038}, + {MDIO_MMD_VEND1, LAN887X_INIT_COEFF_DFE1_100, 0x000f}, + }; + + ret = lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg)); + } else { + static const struct lan887x_regwr_map phy_cfg[] = { + {MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL4, 0x0038}, + {MDIO_MMD_VEND1, LAN887X_INIT_COEFF_DFE1_100, 0x0014}, + }; + + ret = lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg)); + } + if (ret < 0) + return ret; + + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26, + LAN887X_REG_REG26_HW_INIT_SEQ_EN); +} + +static int lan887x_1000M_setup(struct phy_device *phydev) +{ + static const struct lan887x_regwr_map phy_cfg[] = { + {MDIO_MMD_PMAPMD, LAN887X_TX_AMPLT_1000T1_REG, 0x003f}, + {MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL4, 0x00b8}, + }; + int ret; + + /* (Re)configure the speed/mode dependent T1 settings */ + ret = lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg)); + if (ret < 0) + return ret; + + return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, LAN887X_DSP_PMA_CONTROL, + LAN887X_DSP_PMA_CONTROL_LNK_SYNC); +} + +static int lan887x_link_setup(struct phy_device *phydev) +{ + int ret = -EINVAL; + + if (phydev->speed == SPEED_1000) + ret = lan887x_1000M_setup(phydev); + else if (phydev->speed == SPEED_100) + ret = lan887x_100M_setup(phydev); + + return ret; +} + +/* LAN887x Errata: speed configuration changes require soft reset + * and chip soft reset + */ +static int lan887x_phy_reset(struct phy_device *phydev) +{ + int ret, val; + + /* Clear 1000M link sync */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, LAN887X_DSP_PMA_CONTROL, + LAN887X_DSP_PMA_CONTROL_LNK_SYNC); + if (ret < 0) + return ret; + + /* Clear 100M link sync */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26, + LAN887X_REG_REG26_HW_INIT_SEQ_EN); + if (ret < 0) + return ret; + + /* Chiptop soft-reset to allow the speed/mode change */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_CHIP_SOFT_RST, + LAN887X_CHIP_SOFT_RST_RESET); + if (ret < 0) + return ret; + + /* CL22 soft-reset to let the link re-train */ + ret = phy_modify(phydev, MII_BMCR, BMCR_RESET, BMCR_RESET); + if (ret < 0) + return ret; + + /* Wait for reset complete or timeout if > 10ms */ + return phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET), + 5000, 10000, true); +} + +static int lan887x_phy_reconfig(struct phy_device *phydev) +{ + int ret; + + linkmode_zero(phydev->advertising); + + ret = genphy_c45_pma_setup_forced(phydev); + if (ret < 0) + return ret; + + return lan887x_link_setup(phydev); +} + +static int lan887x_config_aneg(struct phy_device *phydev) +{ + int ret; + + /* LAN887x Errata: speed configuration changes require soft reset + * and chip soft reset + */ + ret = lan887x_phy_reset(phydev); + if (ret < 0) + return ret; + + return lan887x_phy_reconfig(phydev); +} + +static int lan887x_probe(struct phy_device *phydev) +{ + struct lan887x_priv *priv; + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->init_done = false; + phydev->priv = priv; + + return lan887x_phy_setup(phydev); +} + +static u64 lan887x_get_stat(struct phy_device *phydev, int i) +{ + struct lan887x_hw_stat stat = lan887x_hw_stats[i]; + struct lan887x_priv *priv = phydev->priv; + int val; + u64 ret; + + if (stat.mmd) + val = phy_read_mmd(phydev, stat.mmd, stat.reg); + else + 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 lan887x_get_stats(struct phy_device *phydev, + struct ethtool_stats *stats, u64 *data) +{ + for (int i = 0; i < ARRAY_SIZE(lan887x_hw_stats); i++) + data[i] = lan887x_get_stat(phydev, i); +} + +static int lan887x_get_sset_count(struct phy_device *phydev) +{ + return ARRAY_SIZE(lan887x_hw_stats); +} + +static void lan887x_get_strings(struct phy_device *phydev, u8 *data) +{ + for (int i = 0; i < ARRAY_SIZE(lan887x_hw_stats); i++) + ethtool_puts(&data, lan887x_hw_stats[i].string); +} + +static int lan887x_config_intr(struct phy_device *phydev) +{ + struct lan887x_priv *priv = phydev->priv; + int rc; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + /* Clear the interrupt status before enabling interrupts */ + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_INT_STS); + if (rc < 0) + return rc; + + /* Unmask for enabling interrupt */ + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_INT_MSK, + (u16)~LAN887X_MX_CHIP_TOP_ALL_MSK); + } else { + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_INT_MSK, + GENMASK(15, 0)); + if (rc < 0) + return rc; + + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_INT_STS); + } + if (rc < 0) + return rc; + + if (phy_is_default_hwtstamp(phydev)) { + return mchp_rds_ptp_top_config_intr(priv->clock, + LAN887X_INT_MSK, + LAN887X_INT_MSK_P1588_MOD_INT_MSK, + (phydev->interrupts == + PHY_INTERRUPT_ENABLED)); + } + + return 0; +} + +static irqreturn_t lan887x_handle_interrupt(struct phy_device *phydev) +{ + struct lan887x_priv *priv = phydev->priv; + int rc = IRQ_NONE; + int irq_status; + + irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_INT_STS); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (irq_status & LAN887X_MX_CHIP_TOP_LINK_MSK) { + phy_trigger_machine(phydev); + rc = IRQ_HANDLED; + } + + if (irq_status & LAN887X_INT_MSK_P1588_MOD_INT_MSK) + rc = mchp_rds_ptp_handle_interrupt(priv->clock); + + return rc; +} + +static int lan887x_cd_reset(struct phy_device *phydev, + enum cable_diag_state cd_done) +{ + u16 val; + int rc; + + /* Chip hard-reset */ + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_CHIP_HARD_RST, + LAN887X_CHIP_HARD_RST_RESET); + if (rc < 0) + return rc; + + /* Wait for reset to complete */ + rc = phy_read_poll_timeout(phydev, MII_PHYSID2, val, + ((val & GENMASK(15, 4)) == + (PHY_ID_LAN887X & GENMASK(15, 4))), + 5000, 50000, true); + if (rc < 0) + return rc; + + if (cd_done == CD_TEST_DONE) { + /* Cable diagnostics complete. Restore PHY. */ + rc = lan887x_phy_setup(phydev); + if (rc < 0) + return rc; + + rc = lan887x_phy_init(phydev); + if (rc < 0) + return rc; + + rc = lan887x_config_intr(phydev); + if (rc < 0) + return rc; + + rc = lan887x_phy_reconfig(phydev); + if (rc < 0) + return rc; + } + + return 0; +} + +static int lan887x_cable_test_prep(struct phy_device *phydev, + enum cable_diag_mode mode) +{ + static const struct lan887x_regwr_map values[] = { + {MDIO_MMD_VEND1, LAN887X_MAX_PGA_GAIN_100, 0x1f}, + {MDIO_MMD_VEND1, LAN887X_MIN_PGA_GAIN_100, 0x0}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_TDR_THRESH_100, 0x1}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_AGC_THRESH_100, 0x3c}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MIN_WAIT_CONFIG_100, 0x0}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100, 0x46}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_CYC_CONFIG_100, 0x5a}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_TX_PULSE_CONFIG_100, 0x44d5}, + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MIN_PGA_GAIN_100, 0x0}, + + }; + int rc; + + rc = lan887x_cd_reset(phydev, CD_TEST_INIT); + if (rc < 0) + return rc; + + /* Forcing DUT to master mode, as we don't care about + * mode during diagnostics + */ + rc = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, + MDIO_PMA_PMD_BT1_CTRL_CFG_MST); + if (rc < 0) + return rc; + + rc = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 0x80b0, 0x0038); + if (rc < 0) + return rc; + + rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CALIB_CONFIG_100, 0, + LAN887X_CALIB_CONFIG_100_VAL); + if (rc < 0) + return rc; + + for (int i = 0; i < ARRAY_SIZE(values); i++) { + rc = phy_write_mmd(phydev, values[i].mmd, values[i].reg, + values[i].val); + if (rc < 0) + return rc; + + if (mode && + values[i].reg == LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100) { + rc = phy_write_mmd(phydev, values[i].mmd, + values[i].reg, 0xa); + if (rc < 0) + return rc; + } + } + + if (mode == TEST_MODE_HYBRID) { + rc = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, + LAN887X_AFE_PORT_TESTBUS_CTRL4, + BIT(0), BIT(0)); + if (rc < 0) + return rc; + } + + /* HW_INIT 100T1, Get DUT running in 100T1 mode */ + rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26, + LAN887X_REG_REG26_HW_INIT_SEQ_EN, + LAN887X_REG_REG26_HW_INIT_SEQ_EN); + if (rc < 0) + return rc; + + /* Cable diag requires hard reset and is sensitive regarding the delays. + * Hard reset is expected into and out of cable diag. + * Wait for 50ms + */ + msleep(50); + + /* Start cable diag */ + return phy_write_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_START_CBL_DIAG_100, + LAN887X_CBL_DIAG_START); +} + +static int lan887x_cable_test_chk(struct phy_device *phydev, + enum cable_diag_mode mode) +{ + int val; + int rc; + + if (mode == TEST_MODE_HYBRID) { + /* Cable diag requires hard reset and is sensitive regarding the delays. + * Hard reset is expected into and out of cable diag. + * Wait for cable diag to complete. + * Minimum wait time is 50ms if the condition is not a match. + */ + rc = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + LAN887X_START_CBL_DIAG_100, val, + ((val & LAN887X_CBL_DIAG_DONE) == + LAN887X_CBL_DIAG_DONE), + 50000, 500000, false); + if (rc < 0) + return rc; + } else { + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_START_CBL_DIAG_100); + if (rc < 0) + return rc; + + if ((rc & LAN887X_CBL_DIAG_DONE) != LAN887X_CBL_DIAG_DONE) + return -EAGAIN; + } + + /* Stop cable diag */ + return phy_write_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_START_CBL_DIAG_100, + LAN887X_CBL_DIAG_STOP); +} + +static int lan887x_cable_test_start(struct phy_device *phydev) +{ + int rc, ret; + + rc = lan887x_cable_test_prep(phydev, TEST_MODE_NORMAL); + if (rc < 0) { + ret = lan887x_cd_reset(phydev, CD_TEST_DONE); + if (ret < 0) + return ret; + + return rc; + } + + return 0; +} + +static int lan887x_cable_test_report(struct phy_device *phydev) +{ + int pos_peak_cycle, pos_peak_cycle_hybrid, pos_peak_in_phases; + int pos_peak_time, pos_peak_time_hybrid, neg_peak_time; + int neg_peak_cycle, neg_peak_in_phases; + int pos_peak_in_phases_hybrid; + int gain_idx, gain_idx_hybrid; + int pos_peak_phase_hybrid; + int pos_peak, neg_peak; + int distance; + int detect; + int length; + int ret; + int rc; + + /* Read non-hybrid results */ + gain_idx = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_AGC_GAIN_100); + if (gain_idx < 0) { + rc = gain_idx; + goto error; + } + + pos_peak = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_POS_PEAK_VALUE_100); + if (pos_peak < 0) { + rc = pos_peak; + goto error; + } + + neg_peak = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_NEG_PEAK_VALUE_100); + if (neg_peak < 0) { + rc = neg_peak; + goto error; + } + + pos_peak_time = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_POS_PEAK_TIME_100); + if (pos_peak_time < 0) { + rc = pos_peak_time; + goto error; + } + + neg_peak_time = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_NEG_PEAK_TIME_100); + if (neg_peak_time < 0) { + rc = neg_peak_time; + goto error; + } + + /* Calculate non-hybrid values */ + pos_peak_cycle = (pos_peak_time >> 7) & 0x7f; + pos_peak_in_phases = (pos_peak_cycle * 96) + (pos_peak_time & 0x7f); + neg_peak_cycle = (neg_peak_time >> 7) & 0x7f; + neg_peak_in_phases = (neg_peak_cycle * 96) + (neg_peak_time & 0x7f); + + /* Deriving the status of cable */ + if (pos_peak > MICROCHIP_CABLE_NOISE_MARGIN && + neg_peak > MICROCHIP_CABLE_NOISE_MARGIN && gain_idx >= 0) { + if (pos_peak_in_phases > neg_peak_in_phases && + ((pos_peak_in_phases - neg_peak_in_phases) >= + MICROCHIP_CABLE_MIN_TIME_DIFF) && + ((pos_peak_in_phases - neg_peak_in_phases) < + MICROCHIP_CABLE_MAX_TIME_DIFF) && + pos_peak_in_phases > 0) { + detect = LAN87XX_CABLE_TEST_SAME_SHORT; + } else if (neg_peak_in_phases > pos_peak_in_phases && + ((neg_peak_in_phases - pos_peak_in_phases) >= + MICROCHIP_CABLE_MIN_TIME_DIFF) && + ((neg_peak_in_phases - pos_peak_in_phases) < + MICROCHIP_CABLE_MAX_TIME_DIFF) && + neg_peak_in_phases > 0) { + detect = LAN87XX_CABLE_TEST_OPEN; + } else { + detect = LAN87XX_CABLE_TEST_OK; + } + } else { + detect = LAN87XX_CABLE_TEST_OK; + } + + if (detect == LAN87XX_CABLE_TEST_OK) { + distance = 0; + goto get_len; + } + + /* Re-initialize PHY and start cable diag test */ + rc = lan887x_cable_test_prep(phydev, TEST_MODE_HYBRID); + if (rc < 0) + goto cd_stop; + + /* Wait for cable diag test completion */ + rc = lan887x_cable_test_chk(phydev, TEST_MODE_HYBRID); + if (rc < 0) + goto cd_stop; + + /* Read hybrid results */ + gain_idx_hybrid = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_AGC_GAIN_100); + if (gain_idx_hybrid < 0) { + rc = gain_idx_hybrid; + goto error; + } + + pos_peak_time_hybrid = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_CBL_DIAG_POS_PEAK_TIME_100); + if (pos_peak_time_hybrid < 0) { + rc = pos_peak_time_hybrid; + goto error; + } + + /* Calculate hybrid values to derive cable length to fault */ + pos_peak_cycle_hybrid = (pos_peak_time_hybrid >> 7) & 0x7f; + pos_peak_phase_hybrid = pos_peak_time_hybrid & 0x7f; + pos_peak_in_phases_hybrid = pos_peak_cycle_hybrid * 96 + + pos_peak_phase_hybrid; + + /* Distance to fault calculation. + * distance = (peak_in_phases - peak_in_phases_hybrid) * + * propagationconstant. + * constant to convert number of phases to meters + * propagationconstant = 0.015953 + * (0.6811 * 2.9979 * 156.2499 * 0.0001 * 0.5) + * Applying constant 1.5953 as ethtool further devides by 100 to + * convert to meters. + */ + if (detect == LAN87XX_CABLE_TEST_OPEN) { + distance = (((pos_peak_in_phases - pos_peak_in_phases_hybrid) + * 15953) / 10000); + } else if (detect == LAN87XX_CABLE_TEST_SAME_SHORT) { + distance = (((neg_peak_in_phases - pos_peak_in_phases_hybrid) + * 15953) / 10000); + } else { + distance = 0; + } + +get_len: + rc = lan887x_cd_reset(phydev, CD_TEST_DONE); + if (rc < 0) + return rc; + + length = ((u32)distance & GENMASK(15, 0)); + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + lan87xx_cable_test_report_trans(detect)); + ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, length); + + return 0; + +cd_stop: + /* Stop cable diag */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_START_CBL_DIAG_100, + LAN887X_CBL_DIAG_STOP); + if (ret < 0) + return ret; + +error: + /* Cable diag test failed */ + ret = lan887x_cd_reset(phydev, CD_TEST_DONE); + if (ret < 0) + return ret; + + /* Return error in failure case */ + return rc; +} + +static int lan887x_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + int rc; + + rc = lan887x_cable_test_chk(phydev, TEST_MODE_NORMAL); + if (rc < 0) { + /* Let PHY statemachine poll again */ + if (rc == -EAGAIN) + return 0; + return rc; + } + + /* Cable diag test complete */ + *finished = true; + + /* Retrieve test status and cable length to fault */ + return lan887x_cable_test_report(phydev); +} + +/* Compare block to sort in ascending order */ +static int sqi_compare(const void *a, const void *b) +{ + return *(u16 *)a - *(u16 *)b; +} + +static int lan887x_get_sqi_100M(struct phy_device *phydev) +{ + u16 rawtable[SQI_SAMPLES]; + u32 sqiavg = 0; + u8 sqinum = 0; + int rc, i; + + /* Configuration of SQI 100M */ + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_COEFF_PWR_DN_CONFIG_100, + LAN887X_COEFF_PWR_DN_CONFIG_100_V); + if (rc < 0) + return rc; + + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SQI_CONFIG_100, + LAN887X_SQI_CONFIG_100_V); + if (rc < 0) + return rc; + + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SQI_CONFIG_100); + if (rc != LAN887X_SQI_CONFIG_100_V) + return -EINVAL; + + rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_POKE_PEEK_100, + LAN887X_POKE_PEEK_100_EN, + LAN887X_POKE_PEEK_100_EN); + if (rc < 0) + return rc; + + /* Required before reading register + * otherwise it will return high value + */ + msleep(50); + + /* Link check before raw readings */ + rc = genphy_c45_read_link(phydev); + if (rc < 0) + return rc; + + if (!phydev->link) + return -ENETDOWN; + + /* Get 200 SQI raw readings */ + for (i = 0; i < SQI_SAMPLES; i++) { + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_POKE_PEEK_100, + LAN887X_POKE_PEEK_100_EN); + if (rc < 0) + return rc; + + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_SQI_MSE_100); + if (rc < 0) + return rc; + + rawtable[i] = (u16)rc; + } + + /* Link check after raw readings */ + rc = genphy_c45_read_link(phydev); + if (rc < 0) + return rc; + + if (!phydev->link) + return -ENETDOWN; + + /* Sort SQI raw readings in ascending order */ + sort(rawtable, SQI_SAMPLES, sizeof(u16), sqi_compare, NULL); + + /* Keep inliers and discard outliers */ + for (i = SQI_INLIERS_START; i < SQI_INLIERS_END; i++) + sqiavg += rawtable[i]; + + /* Handle invalid samples */ + if (sqiavg != 0) { + /* Get SQI average */ + sqiavg /= SQI_INLIERS_NUM; + + if (sqiavg < 75) + sqinum = 7; + else if (sqiavg < 94) + sqinum = 6; + else if (sqiavg < 119) + sqinum = 5; + else if (sqiavg < 150) + sqinum = 4; + else if (sqiavg < 189) + sqinum = 3; + else if (sqiavg < 237) + sqinum = 2; + else if (sqiavg < 299) + sqinum = 1; + else + sqinum = 0; + } + + return sqinum; +} + +static int lan887x_get_sqi(struct phy_device *phydev) +{ + int rc, val; + + if (phydev->speed != SPEED_1000 && + phydev->speed != SPEED_100) + return -ENETDOWN; + + if (phydev->speed == SPEED_100) + return lan887x_get_sqi_100M(phydev); + + /* Writing DCQ_COEFF_EN to trigger a SQI read */ + rc = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + LAN887X_COEFF_MOD_CONFIG, + LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN); + if (rc < 0) + return rc; + + /* Wait for DCQ done */ + rc = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + LAN887X_COEFF_MOD_CONFIG, val, ((val & + LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN) != + LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN), + 10, 200, true); + if (rc < 0) + return rc; + + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_DCQ_SQI_STATUS); + if (rc < 0) + return rc; + + return FIELD_GET(T1_DCQ_SQI_MSK, rc); +} + +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, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_LAN887X), + .name = "Microchip LAN887x T1 PHY", + .flags = PHY_POLL_CABLE_TEST, + .probe = lan887x_probe, + .get_features = lan887x_get_features, + .config_init = lan887x_phy_init, + .config_aneg = lan887x_config_aneg, + .get_stats = lan887x_get_stats, + .get_sset_count = lan887x_get_sset_count, + .get_strings = lan887x_get_strings, + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_status = genphy_c45_read_status, + .cable_test_start = lan887x_cable_test_start, + .cable_test_get_status = lan887x_cable_test_get_status, + .config_intr = lan887x_config_intr, + .handle_interrupt = lan887x_handle_interrupt, + .get_sqi = lan887x_get_sqi, + .get_sqi_max = lan87xx_get_sqi_max, + .set_loopback = genphy_c45_loopback, + } +}; + +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) }, + { PHY_ID_MATCH_MODEL(PHY_ID_LAN887X) }, + { } +}; + +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..73c23d311 --- /dev/null +++ b/drivers/net/phy/microchip_t1s.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Microchip 10BASE-T1S PHYs + * + * Support: Microchip Phys: + * lan8670/1/2 Rev.B1/C1/C2/D0 + * lan8650/1 Rev.B0/B1 Internal PHYs + */ + +#include +#include +#include + +#define PHY_ID_LAN867X_REVB1 0x0007C162 +#define PHY_ID_LAN867X_REVC1 0x0007C164 +#define PHY_ID_LAN867X_REVC2 0x0007C165 +#define PHY_ID_LAN867X_REVD0 0x0007C166 +/* Both Rev.B0 and B1 clause 22 PHYID's are same due to B1 chip limitation */ +#define PHY_ID_LAN865X_REVB 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 + +/* Collision Detector Control 0 Register */ +#define LAN86XX_REG_COL_DET_CTRL0 0x0087 +#define COL_DET_CTRL0_ENABLE_BIT_MASK BIT(15) +#define COL_DET_ENABLE BIT(15) +#define COL_DET_DISABLE 0x0000 + +/* LAN8670/1/2 Rev.D0 Link Status Selection Register */ +#define LAN867X_REG_LINK_STATUS_CTRL 0x0012 +#define LINK_STATUS_CONFIGURATION GENMASK(12, 11) +#define LINK_STATUS_SEMAPHORE BIT(0) + +/* Link Status Configuration */ +#define LINK_STATUS_CONFIG_PLCA_STATUS 0x1 +#define LINK_STATUS_CONFIG_SEMAPHORE 0x2 + +#define LINK_STATUS_SEMAPHORE_SET 0x1 + +#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/B1 configuration parameters from AN1760 + * As per the Configuration Application Note AN1760 published in the below link, + * https://www.microchip.com/en-us/application-notes/an1760 + * Revision F (DS60001760G - June 2024) + */ +static const u32 lan865x_revb_fixup_registers[17] = { + 0x00D0, 0x00E0, 0x00E9, 0x00F5, + 0x00F4, 0x00F8, 0x00F9, 0x0081, + 0x0091, 0x0043, 0x0044, 0x0045, + 0x0053, 0x0054, 0x0055, 0x0040, + 0x0050, +}; + +static const u16 lan865x_revb_fixup_values[17] = { + 0x3F31, 0xC000, 0x9E50, 0x1CF8, + 0xC020, 0xB900, 0x4E53, 0x0080, + 0x9660, 0x00FF, 0xFFFF, 0x0000, + 0x00FF, 0xFFFF, 0x0000, 0x0002, + 0x0002, +}; + +static const u16 lan865x_revb_fixup_cfg_regs[2] = { + 0x0084, 0x008A, +}; + +static const u32 lan865x_revb_sqi_fixup_regs[12] = { + 0x00B0, 0x00B1, 0x00B2, 0x00B3, + 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, +}; + +static const u16 lan865x_revb_sqi_fixup_values[12] = { + 0x0103, 0x0910, 0x1D26, 0x002A, + 0x0103, 0x070D, 0x1720, 0x0027, + 0x0509, 0x0E13, 0x1C25, 0x002B, +}; + +static const u16 lan865x_revb_sqi_fixup_cfg_regs[3] = { + 0x00AD, 0x00AE, 0x00AF, +}; + +/* LAN867x Rev.D0 configuration parameters from AN1699 + * As per the Configuration Application Note AN1699 published in the below link, + * https://www.microchip.com/en-us/application-notes/an1699 + * Revision G (DS60001699G - October 2025) + */ +static const u16 lan867x_revd0_fixup_regs[8] = { + 0x0037, 0x008A, 0x0118, 0x00D6, + 0x0082, 0x00FD, 0x00FD, 0x0091, +}; + +static const u16 lan867x_revd0_fixup_values[8] = { + 0x0800, 0xBFC0, 0x029C, 0x1001, + 0x001C, 0x0C0B, 0x8C07, 0x9660, +}; + +/* 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_revb_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[]) +{ + const u16 fixup_regs[2] = {0x0004, 0x0008}; + int ret; + + for (int i = 0; i < ARRAY_SIZE(fixup_regs); i++) { + ret = lan865x_revb_indirect_read(phydev, fixup_regs[i]); + if (ret < 0) + return ret; + + /* 5-bit signed value, sign extend */ + ret &= GENMASK(4, 0); + if (ret & BIT(4)) + offsets[i] = ret | 0xE0; + else + offsets[i] = ret; + } + + return 0; +} + +static int lan865x_read_cfg_params(struct phy_device *phydev, + const u16 cfg_regs[], u16 cfg_params[], + u8 count) +{ + int ret; + + for (int i = 0; i < count; i++) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + 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, + const u16 cfg_regs[], u16 cfg_params[], + u8 count) +{ + int ret; + + for (int i = 0; i < count; i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, cfg_regs[i], + cfg_params[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lan865x_setup_cfgparam(struct phy_device *phydev, s8 offsets[]) +{ + u16 cfg_results[ARRAY_SIZE(lan865x_revb_fixup_cfg_regs)]; + u16 cfg_params[ARRAY_SIZE(lan865x_revb_fixup_cfg_regs)]; + int ret; + + ret = lan865x_read_cfg_params(phydev, lan865x_revb_fixup_cfg_regs, + cfg_params, ARRAY_SIZE(cfg_params)); + if (ret) + return ret; + + cfg_results[0] = FIELD_PREP(GENMASK(15, 10), 9 + offsets[0]) | + FIELD_PREP(GENMASK(9, 4), 14 + offsets[0]) | + 0x03; + cfg_results[1] = FIELD_PREP(GENMASK(15, 10), 40 + offsets[1]); + + return lan865x_write_cfg_params(phydev, lan865x_revb_fixup_cfg_regs, + cfg_results, ARRAY_SIZE(cfg_results)); +} + +static int lan865x_setup_sqi_cfgparam(struct phy_device *phydev, s8 offsets[]) +{ + u16 cfg_results[ARRAY_SIZE(lan865x_revb_sqi_fixup_cfg_regs)]; + u16 cfg_params[ARRAY_SIZE(lan865x_revb_sqi_fixup_cfg_regs)]; + int ret; + + ret = lan865x_read_cfg_params(phydev, lan865x_revb_sqi_fixup_cfg_regs, + cfg_params, ARRAY_SIZE(cfg_params)); + if (ret) + return ret; + + cfg_results[0] = FIELD_PREP(GENMASK(13, 8), 5 + offsets[0]) | + (9 + offsets[0]); + cfg_results[1] = FIELD_PREP(GENMASK(13, 8), 9 + offsets[0]) | + (14 + offsets[0]); + cfg_results[2] = FIELD_PREP(GENMASK(13, 8), 17 + offsets[0]) | + (22 + offsets[0]); + + return lan865x_write_cfg_params(phydev, lan865x_revb_sqi_fixup_cfg_regs, + cfg_results, ARRAY_SIZE(cfg_results)); +} + +static int lan865x_revb_config_init(struct phy_device *phydev) +{ + s8 offsets[2]; + int ret; + + /* Reference to AN1760 + * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8650-1-Configuration-60001760.pdf + */ + ret = lan865x_generate_cfg_offsets(phydev, offsets); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(lan865x_revb_fixup_registers); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb_fixup_registers[i], + lan865x_revb_fixup_values[i]); + if (ret) + return ret; + + if (i == 1) { + ret = lan865x_setup_cfgparam(phydev, offsets); + if (ret) + return ret; + } + } + + ret = lan865x_setup_sqi_cfgparam(phydev, offsets); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(lan865x_revb_sqi_fixup_regs); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb_sqi_fixup_regs[i], + lan865x_revb_sqi_fixup_values[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lan867x_check_reset_complete(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; + } + } + + return 0; +} + +static int lan867x_revc_config_init(struct phy_device *phydev) +{ + s8 offsets[2]; + int ret; + + ret = lan867x_check_reset_complete(phydev); + if (ret) + return ret; + + ret = lan865x_generate_cfg_offsets(phydev, offsets); + if (ret) + return ret; + + /* LAN867x Rev.C1/C2 configuration settings are equal to the first 9 + * configuration settings and all the sqi fixup settings from LAN865x + * Rev.B0/B1. So the same fixup registers and values from LAN865x + * Rev.B0/B1 are used for LAN867x Rev.C1/C2 to avoid duplication. + * Refer the below links for the comparison. + * https://www.microchip.com/en-us/application-notes/an1760 + * Revision F (DS60001760G - June 2024) + * https://www.microchip.com/en-us/application-notes/an1699 + * Revision E (DS60001699F - June 2024) + */ + for (int i = 0; i < 9; i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb_fixup_registers[i], + lan865x_revb_fixup_values[i]); + if (ret) + return ret; + + if (i == 1) { + ret = lan865x_setup_cfgparam(phydev, offsets); + if (ret) + return ret; + } + } + + ret = lan865x_setup_sqi_cfgparam(phydev, offsets); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(lan865x_revb_sqi_fixup_regs); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb_sqi_fixup_regs[i], + lan865x_revb_sqi_fixup_values[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lan867x_revb1_config_init(struct phy_device *phydev) +{ + int err; + + err = lan867x_check_reset_complete(phydev); + if (err) + return err; + + /* 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 lan867x_revd0_link_active_selection(struct phy_device *phydev, + bool plca_enabled) +{ + u16 value; + + if (plca_enabled) { + /* 0x1 - When PLCA is enabled: link status reflects plca_status. + */ + value = FIELD_PREP(LINK_STATUS_CONFIGURATION, + LINK_STATUS_CONFIG_PLCA_STATUS); + } else { + /* 0x2 - Link status is controlled by the value written into the + * LINK_STATUS_SEMAPHORE bit written. Here the link semaphore + * bit is written with 0x1 to set the link always active in + * CSMA/CD mode as it doesn't support autoneg. + */ + value = FIELD_PREP(LINK_STATUS_CONFIGURATION, + LINK_STATUS_CONFIG_SEMAPHORE) | + FIELD_PREP(LINK_STATUS_SEMAPHORE, + LINK_STATUS_SEMAPHORE_SET); + } + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, + LAN867X_REG_LINK_STATUS_CTRL, value); +} + +/* As per LAN8650/1 Rev.B0/B1 AN1760 (Revision F (DS60001760G - June 2024)) and + * LAN8670/1/2 Rev.C1/C2 AN1699 (Revision E (DS60001699F - June 2024)), under + * normal operation, the device should be operated in PLCA mode. Disabling + * collision detection is recommended to allow the device to operate in noisy + * environments or when reflections and other inherent transmission line + * distortion cause poor signal quality. Collision detection must be re-enabled + * if the device is configured to operate in CSMA/CD mode. + * + * AN1760: https://www.microchip.com/en-us/application-notes/an1760 + * AN1699: https://www.microchip.com/en-us/application-notes/an1699 + */ +static int lan86xx_plca_set_cfg(struct phy_device *phydev, + const struct phy_plca_cfg *plca_cfg) +{ + int ret; + + /* Link status selection must be configured for LAN8670/1/2 Rev.D0 */ + if (phydev->phy_id == PHY_ID_LAN867X_REVD0) { + ret = lan867x_revd0_link_active_selection(phydev, + plca_cfg->enabled); + if (ret) + return ret; + } + + ret = genphy_c45_plca_set_cfg(phydev, plca_cfg); + if (ret) + return ret; + + if (plca_cfg->enabled) + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + LAN86XX_REG_COL_DET_CTRL0, + COL_DET_CTRL0_ENABLE_BIT_MASK, + COL_DET_DISABLE); + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_COL_DET_CTRL0, + COL_DET_CTRL0_ENABLE_BIT_MASK, COL_DET_ENABLE); +} + +static int lan867x_revd0_config_init(struct phy_device *phydev) +{ + int ret; + + ret = lan867x_check_reset_complete(phydev); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(lan867x_revd0_fixup_regs); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan867x_revd0_fixup_regs[i], + lan867x_revd0_fixup_values[i]); + if (ret) + return ret; + } + + /* Initially the PHY will be in CSMA/CD mode by default. So it is + * required to set the link always active as it doesn't support + * autoneg. + */ + return lan867x_revd0_link_active_selection(phydev, false); +} + +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_LAN867X_REVC1), + .name = "LAN867X Rev.C1", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .config_init = lan867x_revc_config_init, + .read_status = lan86xx_read_status, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = lan86xx_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC2), + .name = "LAN867X Rev.C2", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .config_init = lan867x_revc_config_init, + .read_status = lan86xx_read_status, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = lan86xx_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVD0), + .name = "LAN867X Rev.D0", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .config_init = lan867x_revd0_config_init, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = lan86xx_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + .cable_test_start = genphy_c45_oatc14_cable_test_start, + .cable_test_get_status = genphy_c45_oatc14_cable_test_get_status, + .get_sqi = genphy_c45_oatc14_get_sqi, + .get_sqi_max = genphy_c45_oatc14_get_sqi_max, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB), + .name = "LAN865X Rev.B0/B1 Internal Phy", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .config_init = lan865x_revb_config_init, + .read_status = lan86xx_read_status, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = lan86xx_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, +}; + +module_phy_driver(microchip_t1s_driver); + +static const struct mdio_device_id __maybe_unused tbl[] = { + { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC1) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC2) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVD0) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB) }, + { } +}; + +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..3ad10e9e3 --- /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 +// + +#include + +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_obj(*desc); + 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..c5a2cda8d --- /dev/null +++ b/drivers/net/phy/motorcomm.c @@ -0,0 +1,3180 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Motorcomm 8511/8521/8522/8531/8531S/8821 PHY driver. + * + * Author: Peter Geis + * Author: Frank + */ + +#include +#include +#include +#include +#include +#include + +#define PHY_ID_YT8511 0x0000010a +#define PHY_ID_YT8521 0x0000011a +#define PHY_ID_YT8522 0x4f51e928 +#define PHY_ID_YT8531 0x4f51e91b +#define PHY_ID_YT8531S 0x4f51e91a +#define PHY_ID_YT8821 0x4f51ea19 +/* YT8521/YT8531S/YT8821 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_MASK ((0x3 << 14) | BIT(9)) +#define YTPHY_SSR_SPEED_10M ((0x0 << 14)) +#define YTPHY_SSR_SPEED_100M ((0x1 << 14)) +#define YTPHY_SSR_SPEED_1000M ((0x2 << 14)) +#define YTPHY_SSR_SPEED_10G ((0x3 << 14)) +#define YTPHY_SSR_SPEED_2500M ((0x0 << 14) | BIT(9)) +#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 + +/* LED CONFIG */ +#define YT8521_MAX_LEDS 3 +#define YT8521_LED0_CFG_REG 0xA00C +#define YT8521_LED1_CFG_REG 0xA00D +#define YT8521_LED2_CFG_REG 0xA00E +#define YT8521_LED_ACT_BLK_IND BIT(13) +#define YT8521_LED_FDX_ON_EN BIT(12) +#define YT8521_LED_HDX_ON_EN BIT(11) +#define YT8521_LED_TXACT_BLK_EN BIT(10) +#define YT8521_LED_RXACT_BLK_EN BIT(9) +#define YT8521_LED_1000_ON_EN BIT(6) +#define YT8521_LED_100_ON_EN BIT(5) +#define YT8521_LED_10_ON_EN BIT(4) + +#define YT8522_EXTREG_SLEEP_CONTROL 0x2027 +#define YT8522_EN_SLEEP_SW BIT(15) + +#define YT8522_EXTENDED_COMBO_CTRL 0x4000 +#define YT8522_RXDV_SEL BIT(4) +#define YT8522_RMII_EN BIT(1) + +#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 + +#define YT8821_SDS_EXT_CSR_CTRL_REG 0x23 +#define YT8821_SDS_EXT_CSR_VCO_LDO_EN BIT(15) +#define YT8821_SDS_EXT_CSR_VCO_BIAS_LPF_EN BIT(8) + +#define YT8821_UTP_EXT_PI_CTRL_REG 0x56 +#define YT8821_UTP_EXT_PI_RST_N_FIFO BIT(5) +#define YT8821_UTP_EXT_PI_TX_CLK_SEL_AFE BIT(4) +#define YT8821_UTP_EXT_PI_RX_CLK_3_SEL_AFE BIT(3) +#define YT8821_UTP_EXT_PI_RX_CLK_2_SEL_AFE BIT(2) +#define YT8821_UTP_EXT_PI_RX_CLK_1_SEL_AFE BIT(1) +#define YT8821_UTP_EXT_PI_RX_CLK_0_SEL_AFE BIT(0) + +#define YT8821_UTP_EXT_VCT_CFG6_CTRL_REG 0x97 +#define YT8821_UTP_EXT_FECHO_AMP_TH_HUGE GENMASK(15, 8) + +#define YT8821_UTP_EXT_ECHO_CTRL_REG 0x336 +#define YT8821_UTP_EXT_TRACE_LNG_GAIN_THR_1000 GENMASK(14, 8) + +#define YT8821_UTP_EXT_GAIN_CTRL_REG 0x340 +#define YT8821_UTP_EXT_TRACE_MED_GAIN_THR_1000 GENMASK(6, 0) + +#define YT8821_UTP_EXT_RPDN_CTRL_REG 0x34E +#define YT8821_UTP_EXT_RPDN_BP_FFE_LNG_2500 BIT(15) +#define YT8821_UTP_EXT_RPDN_BP_FFE_SHT_2500 BIT(7) +#define YT8821_UTP_EXT_RPDN_IPR_SHT_2500 GENMASK(6, 0) + +#define YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG 0x36A +#define YT8821_UTP_EXT_TH_20DB_2500 GENMASK(15, 0) + +#define YT8821_UTP_EXT_TRACE_CTRL_REG 0x372 +#define YT8821_UTP_EXT_TRACE_LNG_GAIN_THE_2500 GENMASK(14, 8) +#define YT8821_UTP_EXT_TRACE_MED_GAIN_THE_2500 GENMASK(6, 0) + +#define YT8821_UTP_EXT_ALPHA_IPR_CTRL_REG 0x374 +#define YT8821_UTP_EXT_ALPHA_SHT_2500 GENMASK(14, 8) +#define YT8821_UTP_EXT_IPR_LNG_2500 GENMASK(6, 0) + +#define YT8821_UTP_EXT_PLL_CTRL_REG 0x450 +#define YT8821_UTP_EXT_PLL_SPARE_CFG GENMASK(7, 0) + +#define YT8821_UTP_EXT_DAC_IMID_CH_2_3_CTRL_REG 0x466 +#define YT8821_UTP_EXT_DAC_IMID_CH_3_10_ORG GENMASK(14, 8) +#define YT8821_UTP_EXT_DAC_IMID_CH_2_10_ORG GENMASK(6, 0) + +#define YT8821_UTP_EXT_DAC_IMID_CH_0_1_CTRL_REG 0x467 +#define YT8821_UTP_EXT_DAC_IMID_CH_1_10_ORG GENMASK(14, 8) +#define YT8821_UTP_EXT_DAC_IMID_CH_0_10_ORG GENMASK(6, 0) + +#define YT8821_UTP_EXT_DAC_IMSB_CH_2_3_CTRL_REG 0x468 +#define YT8821_UTP_EXT_DAC_IMSB_CH_3_10_ORG GENMASK(14, 8) +#define YT8821_UTP_EXT_DAC_IMSB_CH_2_10_ORG GENMASK(6, 0) + +#define YT8821_UTP_EXT_DAC_IMSB_CH_0_1_CTRL_REG 0x469 +#define YT8821_UTP_EXT_DAC_IMSB_CH_1_10_ORG GENMASK(14, 8) +#define YT8821_UTP_EXT_DAC_IMSB_CH_0_10_ORG GENMASK(6, 0) + +#define YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG 0x4B3 +#define YT8821_UTP_EXT_MU_COARSE_FR_F_FFE GENMASK(14, 12) +#define YT8821_UTP_EXT_MU_COARSE_FR_F_FBE GENMASK(10, 8) + +#define YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG 0x4B5 +#define YT8821_UTP_EXT_MU_FINE_FR_F_FFE GENMASK(14, 12) +#define YT8821_UTP_EXT_MU_FINE_FR_F_FBE GENMASK(10, 8) + +#define YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG 0x4D2 +#define YT8821_UTP_EXT_VGA_LPF1_CAP_OTHER GENMASK(7, 4) +#define YT8821_UTP_EXT_VGA_LPF1_CAP_2500 GENMASK(3, 0) + +#define YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG 0x4D3 +#define YT8821_UTP_EXT_VGA_LPF2_CAP_OTHER GENMASK(7, 4) +#define YT8821_UTP_EXT_VGA_LPF2_CAP_2500 GENMASK(3, 0) + +#define YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG 0x660 +#define YT8821_UTP_EXT_NFR_TX_ABILITY BIT(3) +/* Extended Register end */ + +#define YTPHY_DTS_OUTPUT_CLK_DIS 0 +#define YTPHY_DTS_OUTPUT_CLK_25M 25000000 +#define YTPHY_DTS_OUTPUT_CLK_125M 125000000 + +#define YT8821_CHIP_MODE_AUTO_BX2500_SGMII 0 +#define YT8821_CHIP_MODE_FORCE_BX2500 1 + +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 *dev = &phydev->mdio.dev; + int tb_size_half = tb_size / 2; + u32 val; + int i; + + if (device_property_read_u32(dev, 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; + case PHY_INTERFACE_MODE_GMII: + if (phydev->drv->phy_id != PHY_ID_YT8531S) + return -EOPNOTSUPP; + return 0; + 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(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 *dev = &phydev->mdio.dev; + u32 ds_field_low, ds_field_hi, val; + int ret, ds; + + /* set rgmii rx clk driver strength */ + if (!device_property_read_u32(dev, "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(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 (!device_property_read_u32(dev, "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(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 *dev = &phydev->mdio.dev; + struct yt8521_priv *priv; + struct clk *clk; + 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; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable PHY clock\n"); + + 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 (device_property_read_u32(dev, "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 *dev = &phydev->mdio.dev; + struct clk *clk; + u16 mask, val; + u32 freq; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get and enable PHY clock\n"); + + if (device_property_read_u32(dev, "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_MASK; + + 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 *dev = &phydev->mdio.dev; + 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 (device_property_read_bool(dev, "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 (device_property_read_bool(dev, "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; + } + + if (phy_interface_is_rgmii(phydev) && + phydev_id_compare(phydev, PHY_ID_YT8531S)) + ret = yt8531_set_ds(phydev); + +err_restore_page: + return phy_restore_page(phydev, old_page, ret); +} + +static const unsigned long supported_trgs = (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 yt8521_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= YT8521_MAX_LEDS) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~supported_trgs) + return -EOPNOTSUPP; + + return 0; +} + +static int yt8521_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + + if (index >= YT8521_MAX_LEDS) + return -EINVAL; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules)) { + val |= YT8521_LED_10_ON_EN; + val |= YT8521_LED_100_ON_EN; + val |= YT8521_LED_1000_ON_EN; + } + + if (test_bit(TRIGGER_NETDEV_LINK_10, &rules)) + val |= YT8521_LED_10_ON_EN; + + if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) + val |= YT8521_LED_100_ON_EN; + + if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) + val |= YT8521_LED_1000_ON_EN; + + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) + val |= YT8521_LED_FDX_ON_EN; + + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) + val |= YT8521_LED_HDX_ON_EN; + + if (test_bit(TRIGGER_NETDEV_TX, &rules) || + test_bit(TRIGGER_NETDEV_RX, &rules)) + val |= YT8521_LED_ACT_BLK_IND; + + if (test_bit(TRIGGER_NETDEV_TX, &rules)) + val |= YT8521_LED_TXACT_BLK_EN; + + if (test_bit(TRIGGER_NETDEV_RX, &rules)) + val |= YT8521_LED_RXACT_BLK_EN; + + return ytphy_write_ext(phydev, YT8521_LED0_CFG_REG + index, val); +} + +static int yt8521_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index >= YT8521_MAX_LEDS) + return -EINVAL; + + val = ytphy_read_ext(phydev, YT8521_LED0_CFG_REG + index); + if (val < 0) + return val; + + if (val & YT8521_LED_TXACT_BLK_EN || val & YT8521_LED_ACT_BLK_IND) + __set_bit(TRIGGER_NETDEV_TX, rules); + + if (val & YT8521_LED_RXACT_BLK_EN || val & YT8521_LED_ACT_BLK_IND) + __set_bit(TRIGGER_NETDEV_RX, rules); + + if (val & YT8521_LED_FDX_ON_EN) + __set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules); + + if (val & YT8521_LED_HDX_ON_EN) + __set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules); + + if (val & YT8521_LED_1000_ON_EN) + __set_bit(TRIGGER_NETDEV_LINK_1000, rules); + + if (val & YT8521_LED_100_ON_EN) + __set_bit(TRIGGER_NETDEV_LINK_100, rules); + + if (val & YT8521_LED_10_ON_EN) + __set_bit(TRIGGER_NETDEV_LINK_10, rules); + + return 0; +} + +static int yt8531_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + int ret; + + ret = ytphy_rgmii_clk_delay_config_with_lock(phydev); + if (ret < 0) + return ret; + + if (device_property_read_bool(dev, "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 (device_property_read_bool(dev, "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; + } + + phy_lock_mdio_bus(phydev); + ret = yt8531_set_ds(phydev); + phy_unlock_mdio_bus(phydev); + if (ret < 0) + return ret; + + return 0; +} + +static int yt8522_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + int ret, val; + + val = ytphy_read_ext_with_lock(phydev, YT8522_EXTENDED_COMBO_CTRL); + if (val < 0) + return val; + + if (val & YT8522_RMII_EN) { + val |= YT8522_RXDV_SEL; + ret = ytphy_write_ext_with_lock(phydev, + YT8522_EXTENDED_COMBO_CTRL, + val); + if (ret < 0) + return ret; + } + + if (device_property_read_bool(dev, "motorcomm,auto-sleep-disabled")) { + /* disable auto sleep */ + ret = ytphy_modify_ext_with_lock(phydev, + YT8522_EXTREG_SLEEP_CONTROL, + YT8522_EN_SLEEP_SW, 0); + 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 *dev = &phydev->mdio.dev; + 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 (device_property_read_bool(dev, "motorcomm,tx-clk-adj-enabled")) + tx_clk_adj_enabled = true; + + if (!tx_clk_adj_enabled) + return; + + if (device_property_read_bool(dev, "motorcomm,tx-clk-10-inverted")) + tx_clk_10_inverted = true; + if (device_property_read_bool(dev, "motorcomm,tx-clk-100-inverted")) + tx_clk_100_inverted = true; + if (device_property_read_bool(dev, "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; +} + +/** + * yt8821_get_features - read mmd register to get 2.5G capability + * @phydev: target phy_device struct + * + * Returns: 0 or negative errno code + */ +static int yt8821_get_features(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_pma_read_ext_abilities(phydev); + if (ret < 0) + return ret; + + return genphy_read_abilities(phydev); +} + +/** + * yt8821_get_rate_matching - read register to get phy chip mode + * @phydev: target phy_device struct + * @iface: PHY data interface type + * + * Returns: rate matching type or negative errno code + */ +static int yt8821_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + int val; + + val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG); + if (val < 0) + return val; + + if (FIELD_GET(YT8521_CCR_MODE_SEL_MASK, val) == + YT8821_CHIP_MODE_FORCE_BX2500) + return RATE_MATCH_PAUSE; + + return RATE_MATCH_NONE; +} + +/** + * yt8821_aneg_done() - determines the auto negotiation result + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0(no link)or 1(utp link) or negative errno code + */ +static int yt8821_aneg_done(struct phy_device *phydev) +{ + return yt8521_aneg_done_paged(phydev, YT8521_RSSR_UTP_SPACE); +} + +/** + * yt8821_serdes_init() - serdes init + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_serdes_init(struct phy_device *phydev) +{ + int old_page; + int ret = 0; + u16 mask; + u16 set; + + old_page = phy_select_page(phydev, YT8521_RSSR_FIBER_SPACE); + if (old_page < 0) { + phydev_err(phydev, "Failed to select page: %d\n", + old_page); + goto err_restore_page; + } + + ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_SDS_EXT_CSR_VCO_LDO_EN | + YT8821_SDS_EXT_CSR_VCO_BIAS_LPF_EN; + set = YT8821_SDS_EXT_CSR_VCO_LDO_EN; + ret = ytphy_modify_ext(phydev, YT8821_SDS_EXT_CSR_CTRL_REG, mask, + set); + +err_restore_page: + return phy_restore_page(phydev, old_page, ret); +} + +/** + * yt8821_utp_init() - utp init + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_utp_init(struct phy_device *phydev) +{ + int old_page; + int ret = 0; + u16 mask; + u16 save; + u16 set; + + old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE); + if (old_page < 0) { + phydev_err(phydev, "Failed to select page: %d\n", + old_page); + goto err_restore_page; + } + + mask = YT8821_UTP_EXT_RPDN_BP_FFE_LNG_2500 | + YT8821_UTP_EXT_RPDN_BP_FFE_SHT_2500 | + YT8821_UTP_EXT_RPDN_IPR_SHT_2500; + set = YT8821_UTP_EXT_RPDN_BP_FFE_LNG_2500 | + YT8821_UTP_EXT_RPDN_BP_FFE_SHT_2500; + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_RPDN_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_VGA_LPF1_CAP_OTHER | + YT8821_UTP_EXT_VGA_LPF1_CAP_2500; + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG, + mask, 0); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_VGA_LPF2_CAP_OTHER | + YT8821_UTP_EXT_VGA_LPF2_CAP_2500; + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG, + mask, 0); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_TRACE_LNG_GAIN_THE_2500 | + YT8821_UTP_EXT_TRACE_MED_GAIN_THE_2500; + set = FIELD_PREP(YT8821_UTP_EXT_TRACE_LNG_GAIN_THE_2500, 0x5a) | + FIELD_PREP(YT8821_UTP_EXT_TRACE_MED_GAIN_THE_2500, 0x3c); + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_TRACE_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_IPR_LNG_2500; + set = FIELD_PREP(YT8821_UTP_EXT_IPR_LNG_2500, 0x6c); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_ALPHA_IPR_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_TRACE_LNG_GAIN_THR_1000; + set = FIELD_PREP(YT8821_UTP_EXT_TRACE_LNG_GAIN_THR_1000, 0x2a); + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_ECHO_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_TRACE_MED_GAIN_THR_1000; + set = FIELD_PREP(YT8821_UTP_EXT_TRACE_MED_GAIN_THR_1000, 0x22); + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_GAIN_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_TH_20DB_2500; + set = FIELD_PREP(YT8821_UTP_EXT_TH_20DB_2500, 0x8000); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_MU_COARSE_FR_F_FFE | + YT8821_UTP_EXT_MU_COARSE_FR_F_FBE; + set = FIELD_PREP(YT8821_UTP_EXT_MU_COARSE_FR_F_FFE, 0x7) | + FIELD_PREP(YT8821_UTP_EXT_MU_COARSE_FR_F_FBE, 0x7); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_MU_FINE_FR_F_FFE | + YT8821_UTP_EXT_MU_FINE_FR_F_FBE; + set = FIELD_PREP(YT8821_UTP_EXT_MU_FINE_FR_F_FFE, 0x2) | + FIELD_PREP(YT8821_UTP_EXT_MU_FINE_FR_F_FBE, 0x2); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + /* save YT8821_UTP_EXT_PI_CTRL_REG's val for use later */ + ret = ytphy_read_ext(phydev, YT8821_UTP_EXT_PI_CTRL_REG); + if (ret < 0) + goto err_restore_page; + + save = ret; + + mask = YT8821_UTP_EXT_PI_TX_CLK_SEL_AFE | + YT8821_UTP_EXT_PI_RX_CLK_3_SEL_AFE | + YT8821_UTP_EXT_PI_RX_CLK_2_SEL_AFE | + YT8821_UTP_EXT_PI_RX_CLK_1_SEL_AFE | + YT8821_UTP_EXT_PI_RX_CLK_0_SEL_AFE; + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_PI_CTRL_REG, + mask, 0); + if (ret < 0) + goto err_restore_page; + + /* restore YT8821_UTP_EXT_PI_CTRL_REG's val */ + ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_PI_CTRL_REG, save); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_FECHO_AMP_TH_HUGE; + set = FIELD_PREP(YT8821_UTP_EXT_FECHO_AMP_TH_HUGE, 0x38); + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_VCT_CFG6_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_NFR_TX_ABILITY; + set = YT8821_UTP_EXT_NFR_TX_ABILITY; + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_PLL_SPARE_CFG; + set = FIELD_PREP(YT8821_UTP_EXT_PLL_SPARE_CFG, 0xe9); + ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_PLL_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_DAC_IMID_CH_3_10_ORG | + YT8821_UTP_EXT_DAC_IMID_CH_2_10_ORG; + set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_3_10_ORG, 0x64) | + FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_2_10_ORG, 0x64); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_DAC_IMID_CH_2_3_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_DAC_IMID_CH_1_10_ORG | + YT8821_UTP_EXT_DAC_IMID_CH_0_10_ORG; + set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_1_10_ORG, 0x64) | + FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_0_10_ORG, 0x64); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_DAC_IMID_CH_0_1_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_DAC_IMSB_CH_3_10_ORG | + YT8821_UTP_EXT_DAC_IMSB_CH_2_10_ORG; + set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_3_10_ORG, 0x64) | + FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_2_10_ORG, 0x64); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_DAC_IMSB_CH_2_3_CTRL_REG, + mask, set); + if (ret < 0) + goto err_restore_page; + + mask = YT8821_UTP_EXT_DAC_IMSB_CH_1_10_ORG | + YT8821_UTP_EXT_DAC_IMSB_CH_0_10_ORG; + set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_1_10_ORG, 0x64) | + FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_0_10_ORG, 0x64); + ret = ytphy_modify_ext(phydev, + YT8821_UTP_EXT_DAC_IMSB_CH_0_1_CTRL_REG, + mask, set); + +err_restore_page: + return phy_restore_page(phydev, old_page, ret); +} + +/** + * yt8821_auto_sleep_config() - phy auto sleep config + * @phydev: a pointer to a &struct phy_device + * @enable: true enable auto sleep, false disable auto sleep + * + * Returns: 0 or negative errno code + */ +static int yt8821_auto_sleep_config(struct phy_device *phydev, + bool enable) +{ + int old_page; + int ret = 0; + + old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE); + if (old_page < 0) { + phydev_err(phydev, "Failed to select page: %d\n", + old_page); + goto err_restore_page; + } + + ret = ytphy_modify_ext(phydev, + YT8521_EXTREG_SLEEP_CONTROL1_REG, + YT8521_ESC1R_SLEEP_SW, + enable ? 1 : 0); + +err_restore_page: + return phy_restore_page(phydev, old_page, ret); +} + +/** + * yt8821_soft_reset() - soft reset utp and serdes + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_soft_reset(struct phy_device *phydev) +{ + return ytphy_modify_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG, + YT8521_CCR_SW_RST, 0); +} + +/** + * yt8821_config_init() - phy initializatioin + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_config_init(struct phy_device *phydev) +{ + u8 mode = YT8821_CHIP_MODE_AUTO_BX2500_SGMII; + int ret; + u16 set; + + if (phydev->interface == PHY_INTERFACE_MODE_2500BASEX) + mode = YT8821_CHIP_MODE_FORCE_BX2500; + + set = FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, mode); + ret = ytphy_modify_ext_with_lock(phydev, + YT8521_CHIP_CONFIG_REG, + YT8521_CCR_MODE_SEL_MASK, + set); + if (ret < 0) + return ret; + + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + phydev->possible_interfaces); + + if (mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) { + __set_bit(PHY_INTERFACE_MODE_SGMII, + phydev->possible_interfaces); + + phydev->rate_matching = RATE_MATCH_NONE; + } else if (mode == YT8821_CHIP_MODE_FORCE_BX2500) { + phydev->rate_matching = RATE_MATCH_PAUSE; + } + + ret = yt8821_serdes_init(phydev); + if (ret < 0) + return ret; + + ret = yt8821_utp_init(phydev); + if (ret < 0) + return ret; + + /* disable auto sleep */ + ret = yt8821_auto_sleep_config(phydev, false); + if (ret < 0) + return ret; + + /* soft reset */ + return yt8821_soft_reset(phydev); +} + +/** + * yt8821_adjust_status() - update speed and duplex to phydev + * @phydev: a pointer to a &struct phy_device + * @val: read from YTPHY_SPECIFIC_STATUS_REG + */ +static void yt8821_adjust_status(struct phy_device *phydev, int val) +{ + int speed, duplex; + int speed_mode; + + duplex = FIELD_GET(YTPHY_SSR_DUPLEX, val); + speed_mode = val & YTPHY_SSR_SPEED_MASK; + switch (speed_mode) { + case YTPHY_SSR_SPEED_10M: + speed = SPEED_10; + break; + case YTPHY_SSR_SPEED_100M: + speed = SPEED_100; + break; + case YTPHY_SSR_SPEED_1000M: + speed = SPEED_1000; + break; + case YTPHY_SSR_SPEED_2500M: + speed = SPEED_2500; + break; + default: + speed = SPEED_UNKNOWN; + break; + } + + phydev->speed = speed; + phydev->duplex = duplex; +} + +/** + * yt8821_update_interface() - update interface per current speed + * @phydev: a pointer to a &struct phy_device + */ +static void yt8821_update_interface(struct phy_device *phydev) +{ + if (!phydev->link) + return; + + switch (phydev->speed) { + 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: + phydev_warn(phydev, "phy speed err :%d\n", phydev->speed); + break; + } +} + +/** + * yt8821_read_status() - determines the negotiated speed and duplex + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_read_status(struct phy_device *phydev) +{ + int link; + int ret; + int val; + + ret = ytphy_write_ext_with_lock(phydev, + YT8521_REG_SPACE_SELECT_REG, + YT8521_RSSR_UTP_SPACE); + if (ret < 0) + return ret; + + ret = genphy_read_status(phydev); + if (ret < 0) + return ret; + + if (phydev->autoneg_complete) { + ret = genphy_c45_read_lpa(phydev); + if (ret < 0) + return ret; + } + + ret = phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG); + if (ret < 0) + return ret; + + val = ret; + + link = val & YTPHY_SSR_LINK; + if (link) + yt8821_adjust_status(phydev, val); + + if (link) { + if (phydev->link == 0) + phydev_dbg(phydev, + "%s, phy addr: %d, link up\n", + __func__, phydev->mdio.addr); + phydev->link = 1; + } else { + if (phydev->link == 1) + phydev_dbg(phydev, + "%s, phy addr: %d, link down\n", + __func__, phydev->mdio.addr); + phydev->link = 0; + } + + val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG); + if (val < 0) + return val; + + if (FIELD_GET(YT8521_CCR_MODE_SEL_MASK, val) == + YT8821_CHIP_MODE_AUTO_BX2500_SGMII) + yt8821_update_interface(phydev); + + return 0; +} + +/** + * yt8821_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. + * + * Returns: 0 or negative errno code + */ +static int yt8821_modify_utp_fiber_bmcr(struct phy_device *phydev, + u16 mask, u16 set) +{ + int ret; + + ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_UTP_SPACE, + mask, set); + if (ret < 0) + return ret; + + return yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_FIBER_SPACE, + mask, set); +} + +/** + * yt8821_suspend() - suspend the hardware + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_suspend(struct phy_device *phydev) +{ + int wol_config; + + 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 yt8821_modify_utp_fiber_bmcr(phydev, 0, BMCR_PDOWN); +} + +/** + * yt8821_resume() - resume the hardware + * @phydev: a pointer to a &struct phy_device + * + * Returns: 0 or negative errno code + */ +static int yt8821_resume(struct phy_device *phydev) +{ + int wol_config; + int ret; + + /* disable auto sleep */ + ret = yt8821_auto_sleep_config(phydev, false); + 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 yt8821_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0); +} + +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, + .led_hw_is_supported = yt8521_led_hw_is_supported, + .led_hw_control_set = yt8521_led_hw_control_set, + .led_hw_control_get = yt8521_led_hw_control_get, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_YT8522), + .name = "YT8522 100 Megabit Ethernet", + .config_aneg = genphy_config_aneg, + .config_init = yt8522_config_init, + .suspend = genphy_suspend, + .resume = genphy_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, + .led_hw_is_supported = yt8521_led_hw_is_supported, + .led_hw_control_set = yt8521_led_hw_control_set, + .led_hw_control_get = yt8521_led_hw_control_get, + }, + { + 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, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_YT8821), + .name = "YT8821 2.5Gbps PHY", + .get_features = yt8821_get_features, + .read_page = yt8521_read_page, + .write_page = yt8521_write_page, + .get_wol = ytphy_get_wol, + .set_wol = ytphy_set_wol, + .config_aneg = genphy_config_aneg, + .aneg_done = yt8821_aneg_done, + .config_init = yt8821_config_init, + .get_rate_matching = yt8821_get_rate_matching, + .read_status = yt8821_read_status, + .soft_reset = yt8821_soft_reset, + .suspend = yt8821_suspend, + .resume = yt8821_resume, + }, +}; + +module_phy_driver(motorcomm_phy_drvs); + +MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821 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_YT8522) }, + { PHY_ID_MATCH_EXACT(PHY_ID_YT8531) }, + { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) }, + { PHY_ID_MATCH_EXACT(PHY_ID_YT8821) }, + { /* 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..65c9d7bd9 --- /dev/null +++ b/drivers/net/phy/mscc/mscc.h @@ -0,0 +1,523 @@ +/* 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_PHY_LED_BEHAVIOR 30 +#define LED_COMBINE_DIS_MASK(x) BIT(x) +#define LED_COMBINE_DIS(x, dis) (((dis) ? 1 : 0) << (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_PCS_CTRL 16 +#define MSCC_PHY_SERDES_ANEG BIT(7) + +#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 0x000707e1 +#define PHY_ID_VSC8572 0x000704d0 +#define PHY_ID_VSC8574 0x000704a0 +#define PHY_ID_VSC8575 0x000707d1 +#define PHY_ID_VSC8582 0x000707b1 +#define PHY_ID_VSC8584 0x000707c1 +#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_skb_cb { + u32 ns; +}; + +#define VSC8531_SKB_CB(skb) \ + ((struct vsc8531_skb_cb *)((skb)->cb)) + +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; + + /* list of skbs that were received and need timestamp information but it + * didn't received it yet + */ + struct sk_buff_head rx_skbs_list; +}; + +/* 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. + */ + +enum vsc85xx_global_phy { + VSC88XX_BASE_ADDR = 0, +}; + +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); +void vsc8584_ptp_deinit(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 void vsc8584_ptp_deinit(struct phy_device *phydev) +{ +} +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..2ae5eed6d --- /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 +#include + +#include + +#include + +#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 aes_enckey aes; + int ret; + + ret = aes_prepareenckey(&aes, key, key_len); + if (ret) + return ret; + + aes_encrypt(&aes, hkey, input); + memzero_explicit(&aes, sizeof(aes)); + 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_obj(*flow); + 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 + +#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..2b9fb8a67 --- /dev/null +++ b/drivers/net/phy/mscc/mscc_main.c @@ -0,0 +1,3003 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../phylib.h" +#include "mscc_serdes.h" +#include "mscc.h" + +struct vsc85xx_probe_config { + const struct vsc85xx_hw_stat *hw_stats; + size_t shared_size; + size_t nstats; + u16 supp_led_modes; + u8 nleds; + bool check_rate_magic; + bool use_package; + bool has_ptp; +}; + +static const u32 vsc85xx_default_led_modes_4[] = { + VSC8531_LINK_1000_ACTIVITY, + VSC8531_LINK_100_ACTIVITY, + VSC8531_LINK_ACTIVITY, + VSC8531_DUPLEX_COLLISION +}; + +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++) + ethtool_puts(&data, priv->hw_stats[i].string); +} + +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) +{ + u16 mask = LED_MODE_SEL_MASK(led_num); + u16 val = LED_MODE_SEL(led_num, mode); + + return phy_modify(phydev, MSCC_PHY_LED_MODE_SEL, mask, val); +} + +static int vsc85xx_led_combine_disable_set(struct phy_device *phydev, + u8 led_num, bool combine_disable) +{ + u16 val = LED_COMBINE_DIS(led_num, combine_disable); + u16 mask = LED_COMBINE_DIS_MASK(led_num); + + return phy_modify(phydev, MSCC_PHY_LED_BEHAVIOR, mask, val); +} + +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, + const 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); + 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, 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, 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, VSC88XX_BASE_ADDR, 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, VSC88XX_BASE_ADDR, 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)) { + 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 unsigned int vsc85xx_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + if (interface != PHY_INTERFACE_MODE_SGMII && + interface != PHY_INTERFACE_MODE_QSGMII) + return 0; + + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; +} + +static int vsc85xx_config_inband(struct phy_device *phydev, unsigned int modes) +{ + u16 reg_val = 0; + + if (modes == LINK_INBAND_ENABLE) + reg_val = MSCC_PHY_SERDES_ANEG; + + return phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED_3, + MSCC_PHY_SERDES_PCS_CTRL, MSCC_PHY_SERDES_ANEG, + reg_val); +} + +static int vsc85xx_probe_common(struct phy_device *phydev, + const struct vsc85xx_probe_config *cfg, + const u32 *default_led_mode) +{ + struct vsc8531_private *vsc8531; + struct device_node *np; + int ret; + + vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL); + if (!vsc8531) + return -ENOMEM; + + phydev->priv = vsc8531; + + /* Check rate magic if needed (only for non-package PHYs) */ + if (cfg->check_rate_magic) { + ret = vsc85xx_edge_rate_magic_get(phydev); + if (ret < 0) + return ret; + + vsc8531->rate_magic = ret; + } + + /* Set up package if needed */ + if (cfg->use_package) { + vsc8584_get_base_addr(phydev); + ret = devm_phy_package_join(&phydev->mdio.dev, phydev, + vsc8531->base_addr, + cfg->shared_size); + if (ret) + return ret; + } + + /* Configure LED settings */ + vsc8531->nleds = cfg->nleds; + vsc8531->supp_led_modes = cfg->supp_led_modes; + + /* Configure hardware stats */ + vsc8531->hw_stats = cfg->hw_stats; + vsc8531->nstats = cfg->nstats; + vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats, + sizeof(u64), GFP_KERNEL); + if (!vsc8531->stats) + return -ENOMEM; + + /* PTP setup for VSC8584 */ + if (cfg->has_ptp) { + 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; + } + + /* + * Check for LED configuration in device tree if available + * or fall back to default `vsc8531,led-x-mode` DT properties. + */ + np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds"); + if (np) { + of_node_put(np); + + /* Force to defaults */ + for (unsigned int i = 0; i < vsc8531->nleds; i++) + vsc8531->leds_mode[i] = default_led_mode[i]; + + return 0; + } + + /* Parse LED modes from device tree */ + return vsc85xx_dt_led_modes_get(phydev, default_led_mode); +} + +static int vsc85xx_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + struct vsc8531_private *vsc8531 = phydev->priv; + + if (index >= vsc8531->nleds) + return -EINVAL; + + return vsc85xx_led_cntl_set(phydev, index, value == LED_OFF ? + VSC8531_FORCE_LED_OFF : VSC8531_FORCE_LED_ON); +} + +static int vsc85xx_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + static const unsigned long supported = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + struct vsc8531_private *vsc8531 = phydev->priv; + + if (index >= vsc8531->nleds) + return -EINVAL; + + if (rules & ~supported) + return -EOPNOTSUPP; + + return 0; +} + +static int vsc85xx_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + struct vsc8531_private *vsc8531 = phydev->priv; + u8 mode, behavior; + int rc; + + if (index >= vsc8531->nleds) + return -EINVAL; + + rc = phy_read(phydev, MSCC_PHY_LED_MODE_SEL); + if (rc < 0) + return rc; + mode = (rc & LED_MODE_SEL_MASK(index)) >> LED_MODE_SEL_POS(index); + + rc = phy_read(phydev, MSCC_PHY_LED_BEHAVIOR); + if (rc < 0) + return rc; + behavior = (rc & LED_COMBINE_DIS_MASK(index)) >> index; + + switch (mode) { + case VSC8531_LINK_ACTIVITY: + case VSC8531_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_1000_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_100_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_10_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_100_1000_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_10_1000_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_10_100_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK); + break; + + default: + *rules = 0; + break; + } + + if (!behavior && *rules) + *rules |= BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX); + + return 0; +} + +static int vsc85xx_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + struct vsc8531_private *vsc8531 = phydev->priv; + u8 mode = VSC8531_FORCE_LED_ON; + bool combine_disable = false; + bool has_rx, has_tx; + int ret; + + if (index >= vsc8531->nleds) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK)) + mode = VSC8531_LINK_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10)) + mode = VSC8531_LINK_10_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_100)) + mode = VSC8531_LINK_100_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_1000)) + mode = VSC8531_LINK_1000_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_100) && + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + mode = VSC8531_LINK_100_1000_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10) && + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + mode = VSC8531_LINK_10_1000_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10) && + rules & BIT(TRIGGER_NETDEV_LINK_100)) + mode = VSC8531_LINK_10_100_ACTIVITY; + + /* + * The VSC85xx PHYs provides an option to control LED behavior. By + * default, the LEDx combine function is enabled, meaning the LED + * will be on when there is link/activity or duplex/collision. If + * the combine function is disabled, the LED will be on only for + * link or duplex. + * + * To control this behavior, we check the selected rules. If both + * RX and TX activity are not selected, the LED combine function + * is disabled; otherwise, it remains enabled. + */ + has_rx = !!(rules & BIT(TRIGGER_NETDEV_RX)); + has_tx = !!(rules & BIT(TRIGGER_NETDEV_TX)); + if (!has_rx && !has_tx) + combine_disable = true; + + ret = vsc85xx_led_combine_disable_set(phydev, index, combine_disable); + if (ret < 0) + return ret; + + return vsc85xx_led_cntl_set(phydev, index, mode); +} + +static int vsc8514_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc8514_cfg = { + .nleds = 4, + .supp_led_modes = VSC85XX_SUPP_LED_MODES, + .hw_stats = vsc85xx_hw_stats, + .nstats = ARRAY_SIZE(vsc85xx_hw_stats), + .use_package = true, + .shared_size = 0, + .has_ptp = false, + .check_rate_magic = false, + }; + + return vsc85xx_probe_common(phydev, &vsc8514_cfg, vsc85xx_default_led_modes_4); +} + +static int vsc8574_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc8574_cfg = { + .nleds = 4, + .supp_led_modes = VSC8584_SUPP_LED_MODES, + .hw_stats = vsc8584_hw_stats, + .nstats = ARRAY_SIZE(vsc8584_hw_stats), + .use_package = true, + .shared_size = 0, + .has_ptp = false, + .check_rate_magic = false, + }; + + return vsc85xx_probe_common(phydev, &vsc8574_cfg, vsc85xx_default_led_modes_4); +} + +static int vsc8584_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc8584_cfg = { + .nleds = 4, + .supp_led_modes = VSC8584_SUPP_LED_MODES, + .hw_stats = vsc8584_hw_stats, + .nstats = ARRAY_SIZE(vsc8584_hw_stats), + .use_package = true, + .shared_size = sizeof(struct vsc85xx_shared_private), + .has_ptp = true, + .check_rate_magic = false, + }; + + return vsc85xx_probe_common(phydev, &vsc8584_cfg, vsc85xx_default_led_modes_4); +} + +static int vsc85xx_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc85xx_cfg = { + .nleds = 2, + .supp_led_modes = VSC85XX_SUPP_LED_MODES, + .hw_stats = vsc85xx_hw_stats, + .nstats = ARRAY_SIZE(vsc85xx_hw_stats), + .use_package = false, + .has_ptp = false, + .check_rate_magic = true, + }; + + return vsc85xx_probe_common(phydev, &vsc85xx_cfg, vsc85xx_default_led_modes_4); +} + +static void vsc85xx_remove(struct phy_device *phydev) +{ + vsc8584_ptp_deinit(phydev); +} + +/* 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, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + PHY_ID_MATCH_EXACT(PHY_ID_VSC856X), + .name = "Microsemi GE VSC856X SyncE", + /* 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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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, + .remove = &vsc85xx_remove, + .probe = &vsc8584_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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + .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 = vsc8584_handle_interrupt, + .config_intr = &vsc85xx_config_intr, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .remove = &vsc85xx_remove, + .probe = &vsc8584_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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + PHY_ID_MATCH_EXACT(PHY_ID_VSC8575), + .name = "Microsemi GE VSC8575 SyncE", + /* 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, + .remove = &vsc85xx_remove, + .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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + PHY_ID_MATCH_EXACT(PHY_ID_VSC8582), + .name = "Microsemi GE VSC8582 SyncE", + /* 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, + .remove = &vsc85xx_remove, + .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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +}, +{ + PHY_ID_MATCH_EXACT(PHY_ID_VSC8584), + .name = "Microsemi GE VSC8584 SyncE", + /* 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, + .remove = &vsc85xx_remove, + .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, + .inband_caps = vsc85xx_inband_caps, + .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, +} + +}; + +module_phy_driver(vsc85xx_driver); + +static const 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..4865eac74 --- /dev/null +++ b/drivers/net/phy/mscc/mscc_ptp.c @@ -0,0 +1,1645 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../phylib.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_lockless(&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 vsc8531_private *priv = phydev->priv; + struct vsc85xx_shared_private *shared; + u32 val; + + shared = phy_package_get_priv(phydev); + + 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 vsc8531_private *priv = phydev->priv; + struct vsc85xx_shared_private *shared; + u32 val; + + shared = phy_package_get_priv(phydev); + + 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; + val |= ANA_ETH1_FLOW_ADDR_MATCH2_ANY_UNICAST; + 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); + if (enable) + val |= 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_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct vsc8531_private *vsc8531 = + container_of(mii_ts, struct vsc8531_private, mii_ts); + + cfg->tx_type = vsc8531->ptp->tx_type; + cfg->rx_filter = vsc8531->ptp->rx_filter; + + return 0; +} + +static int vsc85xx_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) +{ + struct vsc8531_private *vsc8531 = + container_of(mii_ts, struct vsc8531_private, mii_ts); + struct phy_device *phydev = vsc8531->ptp->phydev; + bool one_step = false; + u32 val; + + switch (cfg->tx_type) { + case HWTSTAMP_TX_ONESTEP_SYNC: + one_step = true; + break; + case HWTSTAMP_TX_ON: + break; + case HWTSTAMP_TX_OFF: + skb_queue_purge(&vsc8531->ptp->tx_queue); + 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); + + /* 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 0; +} + +static int vsc85xx_ts_info(struct mii_timestamper *mii_ts, + struct kernel_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) + goto out; + + if (vsc8531->ptp->tx_type == HWTSTAMP_TX_OFF) + goto out; + + if (vsc8531->ptp->tx_type == HWTSTAMP_TX_ONESTEP_SYNC) + if (ptp_msg_is_sync(skb, type)) + goto out; + + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + + skb_queue_tail(&vsc8531->ptp->tx_queue, skb); + return; + +out: + kfree_skb(skb); +} + +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 vsc85xx_ptphdr *ptphdr; + unsigned long ns; + + if (!vsc8531->ptp->configured) + return false; + + if (vsc8531->ptp->rx_filter == HWTSTAMP_FILTER_NONE || + type == PTP_CLASS_NONE) + return false; + + ptphdr = get_ptp_header_rx(skb, vsc8531->ptp->rx_filter); + if (!ptphdr) + return false; + + ns = ntohl(ptphdr->rsrvd2); + + VSC8531_SKB_CB(skb)->ns = ns; + skb_queue_tail(&vsc8531->rx_skbs_list, skb); + + ptp_schedule_worker(vsc8531->ptp->ptp_clock, 0); + + return true; +} + +static long vsc85xx_do_aux_work(struct ptp_clock_info *info) +{ + struct vsc85xx_ptp *ptp = container_of(info, struct vsc85xx_ptp, caps); + struct skb_shared_hwtstamps *shhwtstamps = NULL; + struct phy_device *phydev = ptp->phydev; + struct vsc8531_private *priv = phydev->priv; + struct sk_buff_head received; + struct sk_buff *rx_skb; + struct timespec64 ts; + unsigned long flags; + + __skb_queue_head_init(&received); + spin_lock_irqsave(&priv->rx_skbs_list.lock, flags); + skb_queue_splice_tail_init(&priv->rx_skbs_list, &received); + spin_unlock_irqrestore(&priv->rx_skbs_list.lock, flags); + + vsc85xx_gettime(info, &ts); + while ((rx_skb = __skb_dequeue(&received)) != NULL) { + shhwtstamps = skb_hwtstamps(rx_skb); + memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps)); + + if (ts.tv_nsec < VSC8531_SKB_CB(rx_skb)->ns) + ts.tv_sec--; + + shhwtstamps->hwtstamp = ktime_set(ts.tv_sec, + VSC8531_SKB_CB(rx_skb)->ns); + netif_rx(rx_skb); + } + + return -1; +} + +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, + .do_aux_work = &vsc85xx_do_aux_work, +}; + +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) +{ + 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); + + return 0; +} + +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; +} + +void vsc8584_ptp_deinit(struct phy_device *phydev) +{ + struct vsc8531_private *vsc8531 = phydev->priv; + + if (vsc8531->ptp->ptp_clock) { + ptp_clock_unregister(vsc8531->ptp->ptp_clock); + skb_queue_purge(&vsc8531->rx_skbs_list); + skb_queue_purge(&vsc8531->ptp->tx_queue); + } +} + +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); + skb_queue_head_init(&vsc8531->rx_skbs_list); + skb_queue_head_init(&vsc8531->ptp->tx_queue); + + /* 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); + } + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; + + vsc8531->ptp->phydev = phydev; + + vsc8531->mii_ts.rxtstamp = vsc85xx_rxtstamp; + vsc8531->mii_ts.txtstamp = vsc85xx_txtstamp; + vsc8531->mii_ts.hwtstamp_set = vsc85xx_hwtstamp_set; + vsc8531->mii_ts.hwtstamp_get = vsc85xx_hwtstamp_get; + 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); +} + +int vsc8584_ptp_probe_once(struct phy_device *phydev) +{ + struct vsc85xx_shared_private *shared = phy_package_get_priv(phydev); + + /* 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..ae9ad925b --- /dev/null +++ b/drivers/net/phy/mscc/mscc_ptp.h @@ -0,0 +1,473 @@ +/* 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_ANY_UNICAST 0x200000 +#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 + * License: Dual MIT/GPL + * Copyright (c) 2021 Microsemi Corporation + */ + +#include +#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-86110.c b/drivers/net/phy/mxl-86110.c new file mode 100644 index 000000000..42a5fe3f1 --- /dev/null +++ b/drivers/net/phy/mxl-86110.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PHY driver for Maxlinear MXL86110 + * + * Copyright 2023 MaxLinear Inc. + * + */ + +#include +#include +#include +#include +#include +#include + +/* PHY ID */ +#define PHY_ID_MXL86110 0xc1335580 +#define PHY_ID_MXL86111 0xc1335588 + +/* required to access extended registers */ +#define MXL86110_EXTD_REG_ADDR_OFFSET 0x1E +#define MXL86110_EXTD_REG_ADDR_DATA 0x1F +#define PHY_IRQ_ENABLE_REG 0x12 +#define PHY_IRQ_ENABLE_REG_WOL BIT(6) + +/* different pages for EXTD access for MXL86111 */ +/* SerDes/PHY Control Access Register - COM_EXT_SMI_SDS_PHY */ +#define MXL86111_EXT_SMI_SDS_PHY_REG 0xA000 +#define MXL86111_EXT_SMI_SDS_PHYSPACE_MASK BIT(1) +#define MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE (0x1 << 1) +#define MXL86111_EXT_SMI_SDS_PHYUTP_SPACE (0x0 << 1) +#define MXL86111_EXT_SMI_SDS_PHY_AUTO 0xff + +/* SyncE Configuration Register - COM_EXT_SYNCE_CFG */ +#define MXL86110_EXT_SYNCE_CFG_REG 0xA012 +#define MXL86110_EXT_SYNCE_CFG_CLK_FRE_SEL BIT(4) +#define MXL86110_EXT_SYNCE_CFG_EN_SYNC_E_DURING_LNKDN BIT(5) +#define MXL86110_EXT_SYNCE_CFG_EN_SYNC_E BIT(6) +#define MXL86110_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK GENMASK(3, 1) +#define MXL86110_EXT_SYNCE_CFG_CLK_SRC_SEL_125M_PLL 0 +#define MXL86110_EXT_SYNCE_CFG_CLK_SRC_SEL_25M 4 + +/* MAC Address registers */ +#define MXL86110_EXT_MAC_ADDR_CFG1 0xA007 +#define MXL86110_EXT_MAC_ADDR_CFG2 0xA008 +#define MXL86110_EXT_MAC_ADDR_CFG3 0xA009 + +#define MXL86110_EXT_WOL_CFG_REG 0xA00A +#define MXL86110_WOL_CFG_WOL_MASK BIT(3) + +/* RGMII register */ +#define MXL86110_EXT_RGMII_CFG1_REG 0xA003 +/* delay can be adjusted in steps of about 150ps */ +#define MXL86110_EXT_RGMII_CFG1_RX_NO_DELAY (0x0 << 10) +/* Closest value to 2000 ps */ +#define MXL86110_EXT_RGMII_CFG1_RX_DELAY_1950PS (0xD << 10) +#define MXL86110_EXT_RGMII_CFG1_RX_DELAY_MASK GENMASK(13, 10) + +#define MXL86110_EXT_RGMII_CFG1_TX_1G_DELAY_1950PS (0xD << 0) +#define MXL86110_EXT_RGMII_CFG1_TX_1G_DELAY_MASK GENMASK(3, 0) + +#define MXL86110_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_1950PS (0xD << 4) +#define MXL86110_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK GENMASK(7, 4) + +#define MXL86110_EXT_RGMII_CFG1_FULL_MASK \ + ((MXL86110_EXT_RGMII_CFG1_RX_DELAY_MASK) | \ + (MXL86110_EXT_RGMII_CFG1_TX_1G_DELAY_MASK) | \ + (MXL86110_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK)) + +/* EXT Sleep Control register */ +#define MXL86110_UTP_EXT_SLEEP_CTRL_REG 0x27 +#define MXL86110_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_OFF 0 +#define MXL86110_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_MASK BIT(15) + +/* RGMII In-Band Status and MDIO Configuration Register */ +#define MXL86110_EXT_RGMII_MDIO_CFG 0xA005 +#define MXL86110_RGMII_MDIO_CFG_EPA0_MASK GENMASK(6, 6) +#define MXL86110_EXT_RGMII_MDIO_CFG_EBA_MASK GENMASK(5, 5) +#define MXL86110_EXT_RGMII_MDIO_CFG_BA_MASK GENMASK(4, 0) + +#define MXL86110_MAX_LEDS 3 +/* LED registers and defines */ +#define MXL86110_COM_EXT_LED_GEN_CFG 0xA00B +# define MXL86110_COM_EXT_LED_GEN_CFG_LFM(x) ((BIT(0) | BIT(1)) << (3 * (x))) +# define MXL86110_COM_EXT_LED_GEN_CFG_LFME(x) (BIT(0) << (3 * (x))) +# define MXL86110_COM_EXT_LED_GEN_CFG_LFE(x) (BIT(2) << (3 * (x))) + +#define MXL86110_LED0_CFG_REG 0xA00C +#define MXL86110_LED1_CFG_REG 0xA00D +#define MXL86110_LED2_CFG_REG 0xA00E + +#define MXL86110_LEDX_CFG_BLINK BIT(13) +#define MXL86110_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON BIT(12) +#define MXL86110_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON BIT(11) +#define MXL86110_LEDX_CFG_LINK_UP_TX_ACT_ON BIT(10) +#define MXL86110_LEDX_CFG_LINK_UP_RX_ACT_ON BIT(9) +#define MXL86110_LEDX_CFG_LINK_UP_TX_ON BIT(8) +#define MXL86110_LEDX_CFG_LINK_UP_RX_ON BIT(7) +#define MXL86110_LEDX_CFG_LINK_UP_1GB_ON BIT(6) +#define MXL86110_LEDX_CFG_LINK_UP_100MB_ON BIT(5) +#define MXL86110_LEDX_CFG_LINK_UP_10MB_ON BIT(4) +#define MXL86110_LEDX_CFG_LINK_UP_COLLISION BIT(3) +#define MXL86110_LEDX_CFG_LINK_UP_1GB_BLINK BIT(2) +#define MXL86110_LEDX_CFG_LINK_UP_100MB_BLINK BIT(1) +#define MXL86110_LEDX_CFG_LINK_UP_10MB_BLINK BIT(0) + +#define MXL86110_LED_BLINK_CFG_REG 0xA00F +#define MXL86110_LED_BLINK_CFG_FREQ_MODE1_2HZ 0 +#define MXL86110_LED_BLINK_CFG_FREQ_MODE1_4HZ BIT(0) +#define MXL86110_LED_BLINK_CFG_FREQ_MODE1_8HZ BIT(1) +#define MXL86110_LED_BLINK_CFG_FREQ_MODE1_16HZ (BIT(1) | BIT(0)) +#define MXL86110_LED_BLINK_CFG_FREQ_MODE2_2HZ 0 +#define MXL86110_LED_BLINK_CFG_FREQ_MODE2_4HZ BIT(2) +#define MXL86110_LED_BLINK_CFG_FREQ_MODE2_8HZ BIT(3) +#define MXL86110_LED_BLINK_CFG_FREQ_MODE2_16HZ (BIT(3) | BIT(2)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_50_ON 0 +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_67_ON (BIT(4)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_75_ON (BIT(5)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_83_ON (BIT(5) | BIT(4)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_50_OFF (BIT(6)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_33_ON (BIT(6) | BIT(4)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_25_ON (BIT(6) | BIT(5)) +#define MXL86110_LED_BLINK_CFG_DUTY_CYCLE_17_ON (BIT(6) | BIT(5) | BIT(4)) + +/* Chip Configuration Register - COM_EXT_CHIP_CFG */ +#define MXL86110_EXT_CHIP_CFG_REG 0xA001 +#define MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK GENMASK(2, 0) +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII 0 +#define MXL86111_EXT_CHIP_CFG_MODE_FIBER_TO_RGMII 1 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_FIBER_TO_RGMII 2 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_SGMII 3 +#define MXL86111_EXT_CHIP_CFG_MODE_SGPHY_TO_RGMAC 4 +#define MXL86111_EXT_CHIP_CFG_MODE_SGMAC_TO_RGPHY 5 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_AUTO 6 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_FORCE 7 + +#define MXL86111_EXT_CHIP_CFG_CLDO_MASK GENMASK(5, 4) +#define MXL86111_EXT_CHIP_CFG_CLDO_3V3 0 +#define MXL86111_EXT_CHIP_CFG_CLDO_2V5 1 +#define MXL86111_EXT_CHIP_CFG_CLDO_1V8_2 2 +#define MXL86111_EXT_CHIP_CFG_CLDO_1V8_3 3 +#define MXL86111_EXT_CHIP_CFG_CLDO_SHIFT 4 +#define MXL86111_EXT_CHIP_CFG_ELDO BIT(6) +#define MXL86110_EXT_CHIP_CFG_RXDLY_ENABLE BIT(8) +#define MXL86110_EXT_CHIP_CFG_SW_RST_N_MODE BIT(15) + +/* Specific Status Register - PHY_STAT */ +#define MXL86111_PHY_STAT_REG 0x11 +#define MXL86111_PHY_STAT_SPEED_MASK GENMASK(15, 14) +#define MXL86111_PHY_STAT_SPEED_OFFSET 14 +#define MXL86111_PHY_STAT_SPEED_10M 0x0 +#define MXL86111_PHY_STAT_SPEED_100M 0x1 +#define MXL86111_PHY_STAT_SPEED_1000M 0x2 +#define MXL86111_PHY_STAT_DPX_OFFSET 13 +#define MXL86111_PHY_STAT_DPX BIT(13) +#define MXL86111_PHY_STAT_LSRT BIT(10) + +/* 3 phy reg page modes,auto mode combines utp and fiber mode*/ +#define MXL86111_MODE_FIBER 0x1 +#define MXL86111_MODE_UTP 0x2 +#define MXL86111_MODE_AUTO 0x3 + +/* FIBER Auto-Negotiation link partner ability - SDS_AN_LPA */ +#define MXL86111_SDS_AN_LPA_PAUSE (0x3 << 7) +#define MXL86111_SDS_AN_LPA_ASYM_PAUSE (0x2 << 7) + +/* Miscellaneous Control Register - COM_EXT _MISC_CFG */ +#define MXL86111_EXT_MISC_CONFIG_REG 0xa006 +#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL BIT(0) +#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_1000BX (0x1 << 0) +#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_100BX (0x0 << 0) + +/* Phy fiber Link timer cfg2 Register - EXT_SDS_LINK_TIMER_CFG2 */ +#define MXL86111_EXT_SDS_LINK_TIMER_CFG2_REG 0xA5 +#define MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN BIT(15) + +/* default values of PHY register, required for Dual Media mode */ +#define MII_BMSR_DEFAULT_VAL 0x7949 +#define MII_ESTATUS_DEFAULT_VAL 0x2000 + +/* Timeout in ms for PHY SW reset check in STD_CTRL/SDS_CTRL */ +#define BMCR_RESET_TIMEOUT 500 + +/* PL P1 requires optimized RGMII timing for 1.8V RGMII voltage + */ +#define MXL86111_PL_P1 0x500 + +/** + * __mxl86110_write_extended_reg() - write to a PHY's extended register + * @phydev: pointer to the PHY device structure + * @regnum: register number to write + * @val: value to write to @regnum + * + * Unlocked version of mxl86110_write_extended_reg + * + * Note: This function assumes the caller already holds the MDIO bus lock + * or otherwise has exclusive access to the PHY. + * + * Return: 0 or negative error code + */ +static int __mxl86110_write_extended_reg(struct phy_device *phydev, + u16 regnum, u16 val) +{ + int ret; + + ret = __phy_write(phydev, MXL86110_EXTD_REG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + + return __phy_write(phydev, MXL86110_EXTD_REG_ADDR_DATA, val); +} + +/** + * __mxl86110_read_extended_reg - Read a PHY's extended register + * @phydev: pointer to the PHY device structure + * @regnum: extended register number to read (address written to reg 30) + * + * Unlocked version of mxl86110_read_extended_reg + * + * Reads the content of a PHY extended register using the MaxLinear + * 2-step access mechanism: write the register address to reg 30 (0x1E), + * then read the value from reg 31 (0x1F). + * + * Note: This function assumes the caller already holds the MDIO bus lock + * or otherwise has exclusive access to the PHY. + * + * Return: 16-bit register value on success, or negative errno code on failure. + */ +static int __mxl86110_read_extended_reg(struct phy_device *phydev, u16 regnum) +{ + int ret; + + ret = __phy_write(phydev, MXL86110_EXTD_REG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + return __phy_read(phydev, MXL86110_EXTD_REG_ADDR_DATA); +} + +/** + * __mxl86110_modify_extended_reg() - modify bits of a PHY's extended register + * @phydev: pointer to the PHY device structure + * @regnum: register number to write + * @mask: bit mask of bits to clear + * @set: bit mask of bits to set + * + * Note: register value = (old register value & ~mask) | set. + * This function assumes the caller already holds the MDIO bus lock + * or otherwise has exclusive access to the PHY. + * + * Return: 0 or negative error code + */ +static int __mxl86110_modify_extended_reg(struct phy_device *phydev, + u16 regnum, u16 mask, u16 set) +{ + int ret; + + ret = __phy_write(phydev, MXL86110_EXTD_REG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + + return __phy_modify(phydev, MXL86110_EXTD_REG_ADDR_DATA, mask, set); +} + +/** + * mxl86110_write_extended_reg() - Write to a PHY's extended register + * @phydev: pointer to the PHY device structure + * @regnum: register number to write + * @val: value to write to @regnum + * + * This function writes to an extended register of the PHY using the + * MaxLinear two-step access method (reg 0x1E/0x1F). It handles acquiring + * and releasing the MDIO bus lock internally. + * + * Return: 0 or negative error code + */ +static int mxl86110_write_extended_reg(struct phy_device *phydev, + u16 regnum, u16 val) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __mxl86110_write_extended_reg(phydev, regnum, val); + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxl86110_read_extended_reg() - Read a PHY's extended register + * @phydev: pointer to the PHY device structure + * @regnum: extended register number to read + * + * This function reads from an extended register of the PHY using the + * MaxLinear two-step access method (reg 0x1E/0x1F). It handles acquiring + * and releasing the MDIO bus lock internally. + * + * Return: 16-bit register value on success, or negative errno code on failure + */ +static int mxl86110_read_extended_reg(struct phy_device *phydev, u16 regnum) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __mxl86110_read_extended_reg(phydev, regnum); + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxl86110_modify_extended_reg() - modify bits of a PHY's extended register + * @phydev: pointer to the PHY device structure + * @regnum: register number to write + * @mask: bit mask of bits to clear + * @set: bit mask of bits to set + * + * Note: register value = (old register value & ~mask) | set. + * + * Return: 0 or negative error code + */ +static int mxl86110_modify_extended_reg(struct phy_device *phydev, + u16 regnum, u16 mask, u16 set) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret = __mxl86110_modify_extended_reg(phydev, regnum, mask, set); + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxl86110_get_wol() - report if wake-on-lan is enabled + * @phydev: pointer to the phy_device + * @wol: a pointer to a &struct ethtool_wolinfo + */ +static void mxl86110_get_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + int val; + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + val = mxl86110_read_extended_reg(phydev, MXL86110_EXT_WOL_CFG_REG); + if (val >= 0 && (val & MXL86110_WOL_CFG_WOL_MASK)) + wol->wolopts |= WAKE_MAGIC; +} + +/** + * mxl86110_set_wol() - enable/disable wake-on-lan + * @phydev: pointer to the phy_device + * @wol: a pointer to a &struct ethtool_wolinfo + * + * Configures the WOL Magic Packet MAC + * + * Return: 0 or negative errno code + */ +static int mxl86110_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + struct net_device *netdev; + const unsigned char *mac; + int ret = 0; + + phy_lock_mdio_bus(phydev); + + if (wol->wolopts & WAKE_MAGIC) { + netdev = phydev->attached_dev; + if (!netdev) { + ret = -ENODEV; + goto out; + } + + /* Configure the MAC address of the WOL magic packet */ + mac = netdev->dev_addr; + ret = __mxl86110_write_extended_reg(phydev, + MXL86110_EXT_MAC_ADDR_CFG1, + ((mac[0] << 8) | mac[1])); + if (ret < 0) + goto out; + + ret = __mxl86110_write_extended_reg(phydev, + MXL86110_EXT_MAC_ADDR_CFG2, + ((mac[2] << 8) | mac[3])); + if (ret < 0) + goto out; + + ret = __mxl86110_write_extended_reg(phydev, + MXL86110_EXT_MAC_ADDR_CFG3, + ((mac[4] << 8) | mac[5])); + if (ret < 0) + goto out; + + ret = __mxl86110_modify_extended_reg(phydev, + MXL86110_EXT_WOL_CFG_REG, + MXL86110_WOL_CFG_WOL_MASK, + MXL86110_WOL_CFG_WOL_MASK); + if (ret < 0) + goto out; + + /* Enables Wake-on-LAN interrupt in the PHY. */ + ret = __phy_modify(phydev, PHY_IRQ_ENABLE_REG, 0, + PHY_IRQ_ENABLE_REG_WOL); + if (ret < 0) + goto out; + + phydev_dbg(phydev, + "%s, MAC Addr: %02X:%02X:%02X:%02X:%02X:%02X\n", + __func__, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else { + ret = __mxl86110_modify_extended_reg(phydev, + MXL86110_EXT_WOL_CFG_REG, + MXL86110_WOL_CFG_WOL_MASK, + 0); + if (ret < 0) + goto out; + + /* Disables Wake-on-LAN interrupt in the PHY. */ + ret = __phy_modify(phydev, PHY_IRQ_ENABLE_REG, + PHY_IRQ_ENABLE_REG_WOL, 0); + } + +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +static const unsigned long supported_trgs = (BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_HALF_DUPLEX) | + BIT(TRIGGER_NETDEV_FULL_DUPLEX) | + BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX)); + +static int mxl86110_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= MXL86110_MAX_LEDS) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~supported_trgs) + return -EOPNOTSUPP; + + return 0; +} + +static int mxl86110_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index >= MXL86110_MAX_LEDS) + return -EINVAL; + + val = mxl86110_read_extended_reg(phydev, + MXL86110_LED0_CFG_REG + index); + if (val < 0) + return val; + + if (val & MXL86110_LEDX_CFG_LINK_UP_TX_ACT_ON) + *rules |= BIT(TRIGGER_NETDEV_TX); + + if (val & MXL86110_LEDX_CFG_LINK_UP_RX_ACT_ON) + *rules |= BIT(TRIGGER_NETDEV_RX); + + if (val & MXL86110_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON) + *rules |= BIT(TRIGGER_NETDEV_HALF_DUPLEX); + + if (val & MXL86110_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON) + *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX); + + if (val & MXL86110_LEDX_CFG_LINK_UP_10MB_ON) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); + + if (val & MXL86110_LEDX_CFG_LINK_UP_100MB_ON) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + + if (val & MXL86110_LEDX_CFG_LINK_UP_1GB_ON) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + + return 0; +} + +static int mxl86110_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + int ret; + + if (index >= MXL86110_MAX_LEDS) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10)) + val |= MXL86110_LEDX_CFG_LINK_UP_10MB_ON; + + if (rules & BIT(TRIGGER_NETDEV_LINK_100)) + val |= MXL86110_LEDX_CFG_LINK_UP_100MB_ON; + + if (rules & BIT(TRIGGER_NETDEV_LINK_1000)) + val |= MXL86110_LEDX_CFG_LINK_UP_1GB_ON; + + if (rules & BIT(TRIGGER_NETDEV_TX)) + val |= MXL86110_LEDX_CFG_LINK_UP_TX_ACT_ON; + + if (rules & BIT(TRIGGER_NETDEV_RX)) + val |= MXL86110_LEDX_CFG_LINK_UP_RX_ACT_ON; + + if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX)) + val |= MXL86110_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON; + + if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX)) + val |= MXL86110_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON; + + if (rules & BIT(TRIGGER_NETDEV_TX) || + rules & BIT(TRIGGER_NETDEV_RX)) + val |= MXL86110_LEDX_CFG_BLINK; + + ret = mxl86110_write_extended_reg(phydev, + MXL86110_LED0_CFG_REG + index, val); + if (ret) + return ret; + + /* clear manual control bit */ + ret = mxl86110_modify_extended_reg(phydev, + MXL86110_COM_EXT_LED_GEN_CFG, + MXL86110_COM_EXT_LED_GEN_CFG_LFE(index), + 0); + + return ret; +} + +static int mxl86110_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + u16 mask, set; + int ret; + + if (index >= MXL86110_MAX_LEDS) + return -EINVAL; + + /* force manual control */ + set = MXL86110_COM_EXT_LED_GEN_CFG_LFE(index); + /* clear previous force mode */ + mask = MXL86110_COM_EXT_LED_GEN_CFG_LFM(index); + + /* force LED to be permanently on */ + if (value != LED_OFF) + set |= MXL86110_COM_EXT_LED_GEN_CFG_LFME(index); + + ret = mxl86110_modify_extended_reg(phydev, + MXL86110_COM_EXT_LED_GEN_CFG, + mask, set); + + return ret; +} + +/** + * mxl86110_synce_clk_cfg() - applies syncE/clk output configuration + * @phydev: pointer to the phy_device + * + * Note: This function assumes the caller already holds the MDIO bus lock + * or otherwise has exclusive access to the PHY. + * + * Return: 0 or negative errno code + */ +static int mxl86110_synce_clk_cfg(struct phy_device *phydev) +{ + u16 mask = 0, val = 0; + + /* + * Configures the clock output to its default + * setting as per the datasheet. + * This results in a 25MHz clock output being selected in the + * COM_EXT_SYNCE_CFG register for SyncE configuration. + */ + val = MXL86110_EXT_SYNCE_CFG_EN_SYNC_E | + FIELD_PREP(MXL86110_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK, + MXL86110_EXT_SYNCE_CFG_CLK_SRC_SEL_25M); + mask = MXL86110_EXT_SYNCE_CFG_EN_SYNC_E | + MXL86110_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK | + MXL86110_EXT_SYNCE_CFG_CLK_FRE_SEL; + + /* Write clock output configuration */ + return __mxl86110_modify_extended_reg(phydev, + MXL86110_EXT_SYNCE_CFG_REG, + mask, val); +} + +/** + * mxl86110_broadcast_cfg - Configure MDIO broadcast setting for PHY + * @phydev: Pointer to the PHY device structure + * + * This function configures the MDIO broadcast behavior of the MxL86110 PHY. + * Currently, broadcast mode is explicitly disabled by clearing the EPA0 bit + * in the RGMII_MDIO_CFG extended register. + * + * Note: This function assumes the caller already holds the MDIO bus lock + * or otherwise has exclusive access to the PHY. + * + * Return: 0 on success or a negative errno code on failure. + */ +static int mxl86110_broadcast_cfg(struct phy_device *phydev) +{ + return __mxl86110_modify_extended_reg(phydev, + MXL86110_EXT_RGMII_MDIO_CFG, + MXL86110_RGMII_MDIO_CFG_EPA0_MASK, + 0); +} + +/** + * mxl86110_enable_led_activity_blink - Enable LEDs activity blink on PHY + * @phydev: Pointer to the PHY device structure + * + * Configure all PHY LEDs to blink on traffic activity regardless of whether + * they are ON or OFF. This behavior allows each LED to serve as a pure activity + * indicator, independently of its use as a link status indicator. + * + * By default, each LED blinks only when it is also in the ON state. + * This function modifies the appropriate registers (LABx fields) + * to enable blinking even when the LEDs are OFF, to allow the LED to be used + * as a traffic indicator without requiring it to also serve + * as a link status LED. + * + * Note: Any further LED customization can be performed via the + * /sys/class/leds interface; the functions led_hw_is_supported, + * led_hw_control_get, and led_hw_control_set are used + * to support this mechanism. + * + * This function assumes the caller already holds the MDIO bus lock + * or otherwise has exclusive access to the PHY. + * + * Return: 0 on success or a negative errno code on failure. + */ +static int mxl86110_enable_led_activity_blink(struct phy_device *phydev) +{ + int i, ret = 0; + + for (i = 0; i < MXL86110_MAX_LEDS; i++) { + ret = __mxl86110_modify_extended_reg(phydev, + MXL86110_LED0_CFG_REG + i, + 0, + MXL86110_LEDX_CFG_BLINK); + if (ret < 0) + break; + } + + return ret; +} + +/** + * mxl86110_config_rgmii_delay() - configure RGMII delays + * @phydev: pointer to the phy_device + * + * Return: 0 or negative errno code + */ +static int mxl86110_config_rgmii_delay(struct phy_device *phydev) +{ + int ret; + u16 val; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + val = 0; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + val = MXL86110_EXT_RGMII_CFG1_RX_DELAY_1950PS; + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + val = MXL86110_EXT_RGMII_CFG1_TX_1G_DELAY_1950PS | + MXL86110_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_1950PS; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + val = MXL86110_EXT_RGMII_CFG1_TX_1G_DELAY_1950PS | + MXL86110_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_1950PS | + MXL86110_EXT_RGMII_CFG1_RX_DELAY_1950PS; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = __mxl86110_modify_extended_reg(phydev, + MXL86110_EXT_RGMII_CFG1_REG, + MXL86110_EXT_RGMII_CFG1_FULL_MASK, + val); + if (ret < 0) + goto out; + + /* Configure RXDLY (RGMII Rx Clock Delay) to disable + * the default additional delay value on RX_CLK + * (2 ns for 125 MHz, 8 ns for 25 MHz/2.5 MHz) + * and use just the digital one selected before + */ + ret = __mxl86110_modify_extended_reg(phydev, + MXL86110_EXT_CHIP_CFG_REG, + MXL86110_EXT_CHIP_CFG_RXDLY_ENABLE, + 0); + if (ret < 0) + goto out; + +out: + return ret; +} + +/** + * mxl86110_config_init() - initialize the MXL86110 PHY + * @phydev: pointer to the phy_device + * + * Return: 0 or negative errno code + */ +static int mxl86110_config_init(struct phy_device *phydev) +{ + int ret; + + phy_lock_mdio_bus(phydev); + + /* configure syncE / clk output */ + ret = mxl86110_synce_clk_cfg(phydev); + if (ret < 0) + goto out; + + ret = mxl86110_config_rgmii_delay(phydev); + if (ret < 0) + goto out; + + ret = mxl86110_enable_led_activity_blink(phydev); + if (ret < 0) + goto out; + + ret = mxl86110_broadcast_cfg(phydev); + +out: + phy_unlock_mdio_bus(phydev); + return ret; +} + +/** + * mxl86111_probe() - validate bootstrap chip config and set UTP page + * @phydev: pointer to the phy_device + * + * Return: 0 or negative errno code + */ +static int mxl86111_probe(struct phy_device *phydev) +{ + int chip_config; + u16 reg_page; + int ret; + + chip_config = mxl86110_read_extended_reg(phydev, MXL86110_EXT_CHIP_CFG_REG); + if (chip_config < 0) + return chip_config; + + switch (chip_config & MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK) { + case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_SGMII: + case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII: + phydev->port = PORT_TP; + reg_page = MXL86111_EXT_SMI_SDS_PHYUTP_SPACE; + break; + default: + return -EOPNOTSUPP; + } + + ret = mxl86110_write_extended_reg(phydev, + MXL86111_EXT_SMI_SDS_PHY_REG, + reg_page); + if (ret < 0) + return ret; + + return 0; +} + +/** + * mxl86111_config_init() - initialize the MXL86111 PHY + * @phydev: pointer to the phy_device + * + * Return: 0 or negative errno code + */ +static int mxl86111_config_init(struct phy_device *phydev) +{ + int ret; + + phy_lock_mdio_bus(phydev); + + /* configure syncE / clk output */ + ret = mxl86110_synce_clk_cfg(phydev); + if (ret < 0) + goto out; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_100BASEX: + ret = __mxl86110_modify_extended_reg(phydev, + MXL86111_EXT_MISC_CONFIG_REG, + MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL, + MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_100BX); + if (ret < 0) + goto out; + break; + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_SGMII: + ret = __mxl86110_modify_extended_reg(phydev, + MXL86111_EXT_MISC_CONFIG_REG, + MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL, + MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_1000BX); + if (ret < 0) + goto out; + break; + default: + /* RGMII modes */ + ret = mxl86110_config_rgmii_delay(phydev); + if (ret < 0) + goto out; + ret = __mxl86110_modify_extended_reg(phydev, MXL86110_EXT_RGMII_CFG1_REG, + MXL86110_EXT_RGMII_CFG1_FULL_MASK, ret); + + /* PL P1 requires optimized RGMII timing for 1.8V RGMII voltage + */ + ret = __mxl86110_read_extended_reg(phydev, 0xf); + if (ret < 0) + goto out; + + if (ret == MXL86111_PL_P1) { + ret = __mxl86110_read_extended_reg(phydev, MXL86110_EXT_CHIP_CFG_REG); + if (ret < 0) + goto out; + + /* check if LDO is in 1.8V mode */ + switch (FIELD_GET(MXL86111_EXT_CHIP_CFG_CLDO_MASK, ret)) { + case MXL86111_EXT_CHIP_CFG_CLDO_1V8_3: + case MXL86111_EXT_CHIP_CFG_CLDO_1V8_2: + ret = __mxl86110_write_extended_reg(phydev, 0xa010, 0xabff); + if (ret < 0) + goto out; + break; + default: + break; + } + } + break; + } + + ret = mxl86110_enable_led_activity_blink(phydev); + if (ret < 0) + goto out; + + ret = mxl86110_broadcast_cfg(phydev); +out: + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxl86111_read_page() - read reg page + * @phydev: pointer to the phy_device + * + * Return: current reg space of mxl86111 or negative errno code + */ +static int mxl86111_read_page(struct phy_device *phydev) +{ + int page; + + page = __mxl86110_read_extended_reg(phydev, MXL86111_EXT_SMI_SDS_PHY_REG); + if (page < 0) + return page; + + return page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK; +}; + +/** + * mxl86111_write_page() - Set reg page + * @phydev: pointer to the phy_device + * @page: The reg page to set + * + * Return: 0 or negative errno code + */ +static int mxl86111_write_page(struct phy_device *phydev, int page) +{ + return __mxl86110_modify_extended_reg(phydev, MXL86111_EXT_SMI_SDS_PHY_REG, + MXL86111_EXT_SMI_SDS_PHYSPACE_MASK, page); +}; + +static int mxl86111_config_inband(struct phy_device *phydev, unsigned int modes) +{ + int ret; + + ret = phy_modify_paged(phydev, MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE, + MII_BMCR, BMCR_ANENABLE, + (modes == LINK_INBAND_DISABLE) ? 0 : BMCR_ANENABLE); + if (ret < 0) + goto out; + + phy_lock_mdio_bus(phydev); + + ret = __mxl86110_modify_extended_reg(phydev, MXL86111_EXT_SDS_LINK_TIMER_CFG2_REG, + MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN, + (modes == LINK_INBAND_DISABLE) ? 0 : + MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN); + if (ret < 0) + goto out; + + ret = __mxl86110_modify_extended_reg(phydev, MXL86110_EXT_CHIP_CFG_REG, + MXL86110_EXT_CHIP_CFG_SW_RST_N_MODE, 0); + if (ret < 0) + goto out; + + /* For fiber forced mode, power down/up to re-aneg */ + if (modes != LINK_INBAND_DISABLE) { + __phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN); + usleep_range(1000, 1050); + __phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0); + } + +out: + phy_unlock_mdio_bus(phydev); + + return ret; +} + +static unsigned int mxl86111_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_100BASEX: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_SGMII: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + default: + return 0; + } +} + +static struct phy_driver mxl_phy_drvs[] = { + { + PHY_ID_MATCH_EXACT(PHY_ID_MXL86110), + .name = "MXL86110 Gigabit Ethernet", + .config_init = mxl86110_config_init, + .suspend = genphy_suspend, + .resume = genphy_resume, + .soft_reset = genphy_soft_reset, + .get_wol = mxl86110_get_wol, + .set_wol = mxl86110_set_wol, + .led_brightness_set = mxl86110_led_brightness_set, + .led_hw_is_supported = mxl86110_led_hw_is_supported, + .led_hw_control_get = mxl86110_led_hw_control_get, + .led_hw_control_set = mxl86110_led_hw_control_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_MXL86111), + .name = "MXL86111 Gigabit Ethernet", + .probe = mxl86111_probe, + .config_init = mxl86111_config_init, + .get_wol = mxl86110_get_wol, + .set_wol = mxl86110_set_wol, + .inband_caps = mxl86111_inband_caps, + .config_inband = mxl86111_config_inband, + .read_page = mxl86111_read_page, + .write_page = mxl86111_write_page, + .led_brightness_set = mxl86110_led_brightness_set, + .led_hw_is_supported = mxl86110_led_hw_is_supported, + .led_hw_control_get = mxl86110_led_hw_control_get, + .led_hw_control_set = mxl86110_led_hw_control_set, + }, +}; + +module_phy_driver(mxl_phy_drvs); + +static const struct mdio_device_id __maybe_unused mxl_tbl[] = { + { PHY_ID_MATCH_EXACT(PHY_ID_MXL86110) }, + { PHY_ID_MATCH_EXACT(PHY_ID_MXL86111) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, mxl_tbl); + +MODULE_DESCRIPTION("MaxLinear MXL86110/MXL86111 PHY driver"); +MODULE_AUTHOR("Stefano Radaelli"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c new file mode 100644 index 000000000..0da2d4e9d --- /dev/null +++ b/drivers/net/phy/mxl-gpy.c @@ -0,0 +1,1503 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2021 Maxlinear Corporation + * Copyright (C) 2020 Intel Corporation + * + * Drivers for Maxlinear Ethernet GPY + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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_ID_MXL86211C 0xC1335400 +#define PHY_ID_MXL86252 0xC1335520 +#define PHY_ID_MXL86282 0xC1335500 + +#define PHY_CTL1 0x13 +#define PHY_CTL1_MDICD BIT(3) +#define PHY_CTL1_MDIAB BIT(2) +#define PHY_CTL1_AMDIX BIT(0) +#define PHY_ERRCNT 0x15 /* Error counter */ +#define PHY_MIISTAT 0x18 /* MII state */ +#define PHY_IMASK 0x19 /* interrupt mask */ +#define PHY_ISTAT 0x1A /* interrupt status */ +#define PHY_LED 0x1B /* LEDs */ +#define PHY_FWV 0x1E /* firmware version */ + +#define PHY_ERRCNT_SEL GENMASK(11, 8) +#define PHY_ERRCNT_COUNT GENMASK(7, 0) +#define PHY_ERRCNT_SEL_RXERR 0 + +#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 GPY_MAX_LEDS 4 +#define PHY_LED_POLARITY(idx) BIT(12 + (idx)) +#define PHY_LED_HWCONTROL(idx) BIT(8 + (idx)) +#define PHY_LED_ON(idx) BIT(idx) + +#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 + +/* LED */ +#define VSPEC1_LED(idx) (1 + (idx)) +#define VSPEC1_LED_BLINKS GENMASK(15, 12) +#define VSPEC1_LED_PULSE GENMASK(11, 8) +#define VSPEC1_LED_CON GENMASK(7, 4) +#define VSPEC1_LED_BLINKF GENMASK(3, 0) + +#define VSPEC1_LED_LINK10 BIT(0) +#define VSPEC1_LED_LINK100 BIT(1) +#define VSPEC1_LED_LINK1000 BIT(2) +#define VSPEC1_LED_LINK2500 BIT(3) + +#define VSPEC1_LED_TXACT BIT(0) +#define VSPEC1_LED_RXACT BIT(1) +#define VSPEC1_LED_COL BIT(2) +#define VSPEC1_LED_NO_CON BIT(3) + +/* 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; + u32 wolopts; + u64 rx_errors; + + /* 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 int mxl862x2_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct phy_device *phydev = dev_get_drvdata(dev); + long tmp; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_TEMP_STA); + if (ret < 0) + return ret; + if (!ret) + return -ENODATA; + + tmp = (s16)ret; + tmp *= 78125; + tmp /= 10000; + + *value = tmp; + + 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_ops mxl862x2_hwmon_hwmon_ops = { + .is_visible = gpy_hwmon_is_visible, + .read = mxl862x2_hwmon_read, +}; + +static const struct hwmon_chip_info gpy_hwmon_chip_info = { + .ops = &gpy_hwmon_hwmon_ops, + .info = gpy_hwmon_info, +}; + +static const struct hwmon_chip_info mxl862x2_hwmon_chip_info = { + .ops = &mxl862x2_hwmon_hwmon_ops, + .info = gpy_hwmon_info, +}; + +static int gpy_hwmon_register(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + const struct hwmon_chip_info *info; + struct device *hwmon_dev; + + if (phy_id_compare_model(phydev->phy_id, PHY_ID_MXL86252) || + phy_id_compare_model(phydev->phy_id, PHY_ID_MXL86282)) + info = &mxl862x2_hwmon_chip_info; + else + info = &gpy_hwmon_chip_info; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, NULL, phydev, + 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_ack_interrupt(struct phy_device *phydev) +{ + int ret; + + /* Clear all pending interrupts */ + ret = phy_read(phydev, PHY_ISTAT); + return ret < 0 ? ret : 0; +} + +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) +{ + /* Count MDI RX errors (SymbolErrorDuringCarrier) */ + return phy_write(phydev, PHY_ERRCNT, + FIELD_PREP(PHY_ERRCNT_SEL, PHY_ERRCNT_SEL_RXERR)); +} + +static int gpy21x_config_init(struct phy_device *phydev) +{ + __set_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces); + __set_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces); + + return gpy_config_init(phydev); +} + +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 0; + + /* 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; + break; + } + + return 0; +} + +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; + + if (phydev->speed == SPEED_2500 || phydev->speed == SPEED_1000) { + ret = genphy_read_master_slave(phydev); + if (ret < 0) + return ret; + } + + ret = gpy_update_mdix(phydev); + if (ret < 0) + return ret; + } + + return 0; +} + +static int gpy_config_intr(struct phy_device *phydev) +{ + struct gpy_priv *priv = phydev->priv; + u16 mask = 0; + int ret; + + ret = gpy_ack_interrupt(phydev); + if (ret) + return ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + mask = PHY_IMASK_MASK; + + if (priv->wolopts & WAKE_MAGIC) + mask |= PHY_IMASK_WOL; + + if (priv->wolopts & WAKE_PHY) + mask |= PHY_IMASK_LSTC; + + 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; + struct gpy_priv *priv = phydev->priv; + 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; + + priv->wolopts |= WAKE_MAGIC; + } else { + /* Disable magic packet matching */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, + VPSPEC2_WOL_CTL, + WOL_EN); + if (ret < 0) + return ret; + + /* Disable the WOL interrupt */ + ret = phy_clear_bits(phydev, PHY_IMASK, PHY_IMASK_WOL); + if (ret < 0) + return ret; + + priv->wolopts &= ~WAKE_MAGIC; + } + + 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); + + priv->wolopts |= WAKE_PHY; + return 0; + } + + priv->wolopts &= ~WAKE_PHY; + /* 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) +{ + struct gpy_priv *priv = phydev->priv; + + wol->supported = WAKE_MAGIC | WAKE_PHY; + wol->wolopts = priv->wolopts; +} + +static int gpy_loopback(struct phy_device *phydev, bool enable, int speed) +{ + struct gpy_priv *priv = phydev->priv; + u16 set = 0; + int ret; + + if (enable) { + u64 now = get_jiffies_64(); + + if (speed) + return -EOPNOTSUPP; + + /* 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, int speed) +{ + struct gpy_priv *priv = phydev->priv; + + if (enable) + return gpy_loopback(phydev, enable, speed); + + if (priv->fw_minor > 0x76) + return gpy_loopback(phydev, 0, 0); + + return genphy_soft_reset(phydev); +} + +static int gpy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + int ret; + + if (index >= GPY_MAX_LEDS) + return -EINVAL; + + /* clear HWCONTROL and set manual LED state */ + ret = phy_modify(phydev, PHY_LED, + ((value == LED_OFF) ? PHY_LED_HWCONTROL(index) : 0) | + PHY_LED_ON(index), + (value == LED_OFF) ? 0 : PHY_LED_ON(index)); + if (ret) + return ret; + + /* ToDo: set PWM brightness */ + + /* clear HW LED setup */ + if (value == LED_OFF) + return phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), 0); + else + return 0; +} + +static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX)); + +static int gpy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= GPY_MAX_LEDS) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~supported_triggers) + return -EOPNOTSUPP; + + return 0; +} + +static int gpy_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index >= GPY_MAX_LEDS) + return -EINVAL; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index)); + if (val < 0) + return val; + + if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK10) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); + + if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + + if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + + if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + + if (FIELD_GET(VSPEC1_LED_CON, val) == (VSPEC1_LED_LINK10 | + VSPEC1_LED_LINK100 | + VSPEC1_LED_LINK1000 | + VSPEC1_LED_LINK2500)) + *rules |= BIT(TRIGGER_NETDEV_LINK); + + if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_TXACT) + *rules |= BIT(TRIGGER_NETDEV_TX); + + if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_RXACT) + *rules |= BIT(TRIGGER_NETDEV_RX); + + return 0; +} + +static int gpy_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + int ret; + + if (index >= GPY_MAX_LEDS) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_10)) + val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK10); + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_100)) + val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK100); + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK1000); + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_2500)) + val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK2500); + + if (rules & BIT(TRIGGER_NETDEV_TX)) + val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_TXACT); + + if (rules & BIT(TRIGGER_NETDEV_RX)) + val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_RXACT); + + /* allow RX/TX pulse without link indication */ + if ((rules & BIT(TRIGGER_NETDEV_TX) || rules & BIT(TRIGGER_NETDEV_RX)) && + !(val & VSPEC1_LED_CON)) + val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_NO_CON) | VSPEC1_LED_CON; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), val); + if (ret) + return ret; + + return phy_set_bits(phydev, PHY_LED, PHY_LED_HWCONTROL(index)); +} + +static int gpy_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + bool force_active_low = false, force_active_high = false; + u32 mode; + + if (index >= GPY_MAX_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + force_active_low = true; + break; + case PHY_LED_ACTIVE_HIGH: + force_active_high = true; + break; + default: + return -EINVAL; + } + } + + if (force_active_low) + return phy_set_bits(phydev, PHY_LED, PHY_LED_POLARITY(index)); + + if (force_active_high) + return phy_clear_bits(phydev, PHY_LED, PHY_LED_POLARITY(index)); + + return -EINVAL; +} + +static unsigned int gpy_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + case PHY_INTERFACE_MODE_2500BASEX: + return LINK_INBAND_DISABLE; + default: + return 0; + } +} + +static int gpy_config_inband(struct phy_device *phydev, unsigned int modes) +{ + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL, + VSPEC1_SGMII_ANEN_ANRS, + (modes == LINK_INBAND_DISABLE) ? 0 : + VSPEC1_SGMII_ANEN_ANRS); +} + +static int gpy_update_stats(struct phy_device *phydev) +{ + struct gpy_priv *priv = phydev->priv; + int ret; + + /* PHY_ERRCNT: 8-bit read-clear counter, SEL set to RXERR */ + ret = phy_read(phydev, PHY_ERRCNT); + if (ret < 0) + return ret; + + priv->rx_errors += FIELD_GET(PHY_ERRCNT_COUNT, ret); + + return 0; +} + +static void gpy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct gpy_priv *priv = phydev->priv; + + eth_stats->SymbolErrorDuringCarrier = priv->rx_errors; + stats->rx_errors = priv->rx_errors; +} + +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, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + .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, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + 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, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + .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 = gpy21x_config_init, + .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_GPY211C), + .name = "Maxlinear Ethernet GPY211C", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy21x_config_init, + .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + .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 = gpy21x_config_init, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_GPY212C), + .name = "Maxlinear Ethernet GPY212C", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy21x_config_init, + .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + .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 = gpy21x_config_init, + .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_GPY215C), + .name = "Maxlinear Ethernet GPY215C", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy21x_config_init, + .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + 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, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + 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, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + 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, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C), + .name = "Maxlinear Ethernet MxL86211C", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy_config_init, + .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, + .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, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_MXL86252), + .name = "MaxLinear Ethernet MxL86252", + .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, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_MXL86282), + .name = "MaxLinear Ethernet MxL86282", + .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, + .update_stats = gpy_update_stats, + .get_phy_stats = gpy_get_phy_stats, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + }, +}; +module_phy_driver(gpy_drivers); + +static const 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)}, + {PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C)}, + {PHY_ID_MATCH_MODEL(PHY_ID_MXL86252)}, + {PHY_ID_MATCH_MODEL(PHY_ID_MXL86282)}, + { } +}; +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..7f3ff3228 --- /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 + * Maintainer: Giuseppe Cavallaro + * + * Copyright (c) 2008 STMicroelectronics Limited + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +/* 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 const 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..cabdd83c6 --- /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 +#include +#include +#include +#include +#include +#include + +#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 const 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-macsec.c b/drivers/net/phy/nxp-c45-tja11xx-macsec.c new file mode 100644 index 000000000..63e3934be --- /dev/null +++ b/drivers/net/phy/nxp-c45-tja11xx-macsec.c @@ -0,0 +1,1725 @@ +// SPDX-License-Identifier: GPL-2.0 +/* NXP C45 PTP PHY driver interface + * Copyright 2023 NXP + * Author: Radu Pirea + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nxp-c45-tja11xx.h" + +#define MACSEC_REG_SIZE 32 +#define TX_SC_MAX 4 + +#define TX_SC_BIT(secy_id) BIT(MACSEC_REG_SIZE - (secy_id) - 1) + +#define VEND1_MACSEC_BASE 0x9000 + +#define MACSEC_CFG 0x0000 +#define MACSEC_CFG_BYPASS BIT(1) +#define MACSEC_CFG_S0I BIT(0) + +#define MACSEC_TPNET 0x0044 +#define PN_WRAP_THRESHOLD 0xffffffff + +#define MACSEC_RXSCA 0x0080 +#define MACSEC_RXSCKA 0x0084 + +#define MACSEC_TXSCA 0x00C0 +#define MACSEC_TXSCKA 0x00C4 + +#define MACSEC_RXSC_SCI_1H 0x0100 + +#define MACSEC_RXSC_CFG 0x0128 +#define MACSEC_RXSC_CFG_XPN BIT(25) +#define MACSEC_RXSC_CFG_AES_256 BIT(24) +#define MACSEC_RXSC_CFG_SCI_EN BIT(11) +#define MACSEC_RXSC_CFG_RP BIT(10) +#define MACSEC_RXSC_CFG_VF_MASK GENMASK(9, 8) +#define MACSEC_RXSC_CFG_VF_OFF 8 + +#define MACSEC_RPW 0x012C + +#define MACSEC_RXSA_A_CS 0x0180 +#define MACSEC_RXSA_A_NPN 0x0184 +#define MACSEC_RXSA_A_XNPN 0x0188 +#define MACSEC_RXSA_A_LNPN 0x018C +#define MACSEC_RXSA_A_LXNPN 0x0190 + +#define MACSEC_RXSA_B_CS 0x01C0 +#define MACSEC_RXSA_B_NPN 0x01C4 +#define MACSEC_RXSA_B_XNPN 0x01C8 +#define MACSEC_RXSA_B_LNPN 0x01CC +#define MACSEC_RXSA_B_LXNPN 0x01D0 + +#define MACSEC_RXSA_CS_AN_OFF 1 +#define MACSEC_RXSA_CS_EN BIT(0) + +#define MACSEC_TXSC_SCI_1H 0x0200 +#define MACSEC_TXSC_CFG 0x0228 +#define MACSEC_TXSC_CFG_XPN BIT(25) +#define MACSEC_TXSC_CFG_AES_256 BIT(24) +#define MACSEC_TXSC_CFG_AN_MASK GENMASK(19, 18) +#define MACSEC_TXSC_CFG_AN_OFF 18 +#define MACSEC_TXSC_CFG_ASA BIT(17) +#define MACSEC_TXSC_CFG_SCE BIT(16) +#define MACSEC_TXSC_CFG_ENCRYPT BIT(4) +#define MACSEC_TXSC_CFG_PROTECT BIT(3) +#define MACSEC_TXSC_CFG_SEND_SCI BIT(2) +#define MACSEC_TXSC_CFG_END_STATION BIT(1) +#define MACSEC_TXSC_CFG_SCB BIT(0) + +#define MACSEC_TXSA_A_CS 0x0280 +#define MACSEC_TXSA_A_NPN 0x0284 +#define MACSEC_TXSA_A_XNPN 0x0288 + +#define MACSEC_TXSA_B_CS 0x02C0 +#define MACSEC_TXSA_B_NPN 0x02C4 +#define MACSEC_TXSA_B_XNPN 0x02C8 + +#define MACSEC_SA_CS_A BIT(31) + +#define MACSEC_EVR 0x0400 +#define MACSEC_EVER 0x0404 + +#define MACSEC_RXSA_A_KA 0x0700 +#define MACSEC_RXSA_A_SSCI 0x0720 +#define MACSEC_RXSA_A_SALT 0x0724 + +#define MACSEC_RXSA_B_KA 0x0740 +#define MACSEC_RXSA_B_SSCI 0x0760 +#define MACSEC_RXSA_B_SALT 0x0764 + +#define MACSEC_TXSA_A_KA 0x0780 +#define MACSEC_TXSA_A_SSCI 0x07A0 +#define MACSEC_TXSA_A_SALT 0x07A4 + +#define MACSEC_TXSA_B_KA 0x07C0 +#define MACSEC_TXSA_B_SSCI 0x07E0 +#define MACSEC_TXSA_B_SALT 0x07E4 + +#define MACSEC_UPFR0D2 0x0A08 +#define MACSEC_UPFR0M1 0x0A10 +#define MACSEC_OVP BIT(12) + +#define MACSEC_UPFR0M2 0x0A14 +#define ETYPE_MASK 0xffff + +#define MACSEC_UPFR0R 0x0A18 +#define MACSEC_UPFR_EN BIT(0) + +#define ADPTR_CNTRL 0x0F00 +#define ADPTR_CNTRL_CONFIG_EN BIT(14) +#define ADPTR_CNTRL_ADPTR_EN BIT(12) +#define ADPTR_TX_TAG_CNTRL 0x0F0C +#define ADPTR_TX_TAG_CNTRL_ENA BIT(31) + +#define TX_SC_FLT_BASE 0x800 +#define TX_SC_FLT_SIZE 0x10 +#define TX_FLT_BASE(flt_id) (TX_SC_FLT_BASE + \ + TX_SC_FLT_SIZE * (flt_id)) + +#define TX_SC_FLT_OFF_MAC_DA_SA 0x04 +#define TX_SC_FLT_OFF_MAC_SA 0x08 +#define TX_SC_FLT_OFF_MAC_CFG 0x0C +#define TX_SC_FLT_BY_SA BIT(14) +#define TX_SC_FLT_EN BIT(8) + +#define TX_SC_FLT_MAC_DA_SA(base) ((base) + TX_SC_FLT_OFF_MAC_DA_SA) +#define TX_SC_FLT_MAC_SA(base) ((base) + TX_SC_FLT_OFF_MAC_SA) +#define TX_SC_FLT_MAC_CFG(base) ((base) + TX_SC_FLT_OFF_MAC_CFG) + +#define ADAPTER_EN BIT(6) +#define MACSEC_EN BIT(5) + +#define MACSEC_INOV1HS 0x0140 +#define MACSEC_INOV2HS 0x0144 +#define MACSEC_INOD1HS 0x0148 +#define MACSEC_INOD2HS 0x014C +#define MACSEC_RXSCIPUS 0x0150 +#define MACSEC_RXSCIPDS 0x0154 +#define MACSEC_RXSCIPLS 0x0158 +#define MACSEC_RXAN0INUSS 0x0160 +#define MACSEC_RXAN0IPUSS 0x0170 +#define MACSEC_RXSA_A_IPOS 0x0194 +#define MACSEC_RXSA_A_IPIS 0x01B0 +#define MACSEC_RXSA_A_IPNVS 0x01B4 +#define MACSEC_RXSA_B_IPOS 0x01D4 +#define MACSEC_RXSA_B_IPIS 0x01F0 +#define MACSEC_RXSA_B_IPNVS 0x01F4 +#define MACSEC_OPUS 0x021C +#define MACSEC_OPTLS 0x022C +#define MACSEC_OOP1HS 0x0240 +#define MACSEC_OOP2HS 0x0244 +#define MACSEC_OOE1HS 0x0248 +#define MACSEC_OOE2HS 0x024C +#define MACSEC_TXSA_A_OPPS 0x028C +#define MACSEC_TXSA_A_OPES 0x0290 +#define MACSEC_TXSA_B_OPPS 0x02CC +#define MACSEC_TXSA_B_OPES 0x02D0 +#define MACSEC_INPWTS 0x0630 +#define MACSEC_INPBTS 0x0638 +#define MACSEC_IPSNFS 0x063C + +#define TJA11XX_TLV_TX_NEEDED_HEADROOM (32) +#define TJA11XX_TLV_NEEDED_TAILROOM (0) + +#define ETH_P_TJA11XX_TLV (0x4e58) + +enum nxp_c45_sa_type { + TX_SA, + RX_SA, +}; + +struct nxp_c45_sa { + void *sa; + const struct nxp_c45_sa_regs *regs; + enum nxp_c45_sa_type type; + bool is_key_a; + u8 an; + struct list_head list; +}; + +struct nxp_c45_secy { + struct macsec_secy *secy; + struct macsec_rx_sc *rx_sc; + struct list_head sa_list; + int secy_id; + bool rx_sc0_impl; + struct list_head list; +}; + +struct nxp_c45_macsec { + struct list_head secy_list; + DECLARE_BITMAP(secy_bitmap, TX_SC_MAX); + DECLARE_BITMAP(tx_sc_bitmap, TX_SC_MAX); +}; + +struct nxp_c45_sa_regs { + u16 cs; + u16 npn; + u16 xnpn; + u16 lnpn; + u16 lxnpn; + u16 ka; + u16 ssci; + u16 salt; + u16 ipis; + u16 ipnvs; + u16 ipos; + u16 opps; + u16 opes; +}; + +static const struct nxp_c45_sa_regs rx_sa_a_regs = { + .cs = MACSEC_RXSA_A_CS, + .npn = MACSEC_RXSA_A_NPN, + .xnpn = MACSEC_RXSA_A_XNPN, + .lnpn = MACSEC_RXSA_A_LNPN, + .lxnpn = MACSEC_RXSA_A_LXNPN, + .ka = MACSEC_RXSA_A_KA, + .ssci = MACSEC_RXSA_A_SSCI, + .salt = MACSEC_RXSA_A_SALT, + .ipis = MACSEC_RXSA_A_IPIS, + .ipnvs = MACSEC_RXSA_A_IPNVS, + .ipos = MACSEC_RXSA_A_IPOS, +}; + +static const struct nxp_c45_sa_regs rx_sa_b_regs = { + .cs = MACSEC_RXSA_B_CS, + .npn = MACSEC_RXSA_B_NPN, + .xnpn = MACSEC_RXSA_B_XNPN, + .lnpn = MACSEC_RXSA_B_LNPN, + .lxnpn = MACSEC_RXSA_B_LXNPN, + .ka = MACSEC_RXSA_B_KA, + .ssci = MACSEC_RXSA_B_SSCI, + .salt = MACSEC_RXSA_B_SALT, + .ipis = MACSEC_RXSA_B_IPIS, + .ipnvs = MACSEC_RXSA_B_IPNVS, + .ipos = MACSEC_RXSA_B_IPOS, +}; + +static const struct nxp_c45_sa_regs tx_sa_a_regs = { + .cs = MACSEC_TXSA_A_CS, + .npn = MACSEC_TXSA_A_NPN, + .xnpn = MACSEC_TXSA_A_XNPN, + .ka = MACSEC_TXSA_A_KA, + .ssci = MACSEC_TXSA_A_SSCI, + .salt = MACSEC_TXSA_A_SALT, + .opps = MACSEC_TXSA_A_OPPS, + .opes = MACSEC_TXSA_A_OPES, +}; + +static const struct nxp_c45_sa_regs tx_sa_b_regs = { + .cs = MACSEC_TXSA_B_CS, + .npn = MACSEC_TXSA_B_NPN, + .xnpn = MACSEC_TXSA_B_XNPN, + .ka = MACSEC_TXSA_B_KA, + .ssci = MACSEC_TXSA_B_SSCI, + .salt = MACSEC_TXSA_B_SALT, + .opps = MACSEC_TXSA_B_OPPS, + .opes = MACSEC_TXSA_B_OPES, +}; + +static const +struct nxp_c45_sa_regs *nxp_c45_sa_regs_get(enum nxp_c45_sa_type sa_type, + bool key_a) +{ + if (sa_type == RX_SA) + if (key_a) + return &rx_sa_a_regs; + else + return &rx_sa_b_regs; + else if (sa_type == TX_SA) + if (key_a) + return &tx_sa_a_regs; + else + return &tx_sa_b_regs; + else + return NULL; +} + +static int nxp_c45_macsec_write(struct phy_device *phydev, u16 addr, u32 value) +{ + u32 lvalue = value; + u16 laddr; + int ret; + + WARN_ON_ONCE(addr % 4); + + phydev_dbg(phydev, "write addr 0x%x value 0x%x\n", addr, value); + + laddr = VEND1_MACSEC_BASE + addr / 2; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, laddr, lvalue); + if (ret) + return ret; + + laddr += 1; + lvalue >>= 16; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, laddr, lvalue); + + return ret; +} + +static int nxp_c45_macsec_read(struct phy_device *phydev, u16 addr, u32 *value) +{ + u32 lvalue; + u16 laddr; + int ret; + + WARN_ON_ONCE(addr % 4); + + laddr = VEND1_MACSEC_BASE + addr / 2; + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, laddr); + if (ret < 0) + return ret; + + laddr += 1; + lvalue = (u32)ret & 0xffff; + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, laddr); + if (ret < 0) + return ret; + + lvalue |= (u32)ret << 16; + *value = lvalue; + + phydev_dbg(phydev, "read addr 0x%x value 0x%x\n", addr, *value); + + return 0; +} + +static void nxp_c45_macsec_read32_64(struct phy_device *phydev, u16 addr, + u64 *value) +{ + u32 lvalue; + + nxp_c45_macsec_read(phydev, addr, &lvalue); + *value = lvalue; +} + +static void nxp_c45_macsec_read64(struct phy_device *phydev, u16 addr, + u64 *value) +{ + u32 lvalue; + + nxp_c45_macsec_read(phydev, addr, &lvalue); + *value = (u64)lvalue << 32; + nxp_c45_macsec_read(phydev, addr + 4, &lvalue); + *value |= lvalue; +} + +static void nxp_c45_secy_irq_en(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy, bool en) +{ + u32 reg; + + nxp_c45_macsec_read(phydev, MACSEC_EVER, ®); + if (en) + reg |= TX_SC_BIT(phy_secy->secy_id); + else + reg &= ~TX_SC_BIT(phy_secy->secy_id); + nxp_c45_macsec_write(phydev, MACSEC_EVER, reg); +} + +static struct nxp_c45_secy *nxp_c45_find_secy(struct list_head *secy_list, + sci_t sci) +{ + struct nxp_c45_secy *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, secy_list, list) + if (pos->secy->sci == sci) + return pos; + + return ERR_PTR(-EINVAL); +} + +static struct +nxp_c45_secy *nxp_c45_find_secy_by_id(struct list_head *secy_list, + int id) +{ + struct nxp_c45_secy *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, secy_list, list) + if (pos->secy_id == id) + return pos; + + return ERR_PTR(-EINVAL); +} + +static void nxp_c45_secy_free(struct nxp_c45_secy *phy_secy) +{ + list_del(&phy_secy->list); + kfree(phy_secy); +} + +static struct nxp_c45_sa *nxp_c45_find_sa(struct list_head *sa_list, + enum nxp_c45_sa_type sa_type, u8 an) +{ + struct nxp_c45_sa *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, sa_list, list) + if (pos->an == an && pos->type == sa_type) + return pos; + + return ERR_PTR(-EINVAL); +} + +static struct nxp_c45_sa *nxp_c45_sa_alloc(struct list_head *sa_list, void *sa, + enum nxp_c45_sa_type sa_type, u8 an) +{ + struct nxp_c45_sa *first = NULL, *pos, *tmp; + int occurrences = 0; + + list_for_each_entry_safe(pos, tmp, sa_list, list) { + if (pos->type != sa_type) + continue; + + if (pos->an == an) + return ERR_PTR(-EINVAL); + + first = pos; + occurrences++; + if (occurrences >= 2) + return ERR_PTR(-ENOSPC); + } + + tmp = kzalloc_obj(*tmp); + if (!tmp) + return ERR_PTR(-ENOMEM); + + if (first) + tmp->is_key_a = !first->is_key_a; + else + tmp->is_key_a = true; + + tmp->sa = sa; + tmp->type = sa_type; + tmp->an = an; + tmp->regs = nxp_c45_sa_regs_get(tmp->type, tmp->is_key_a); + list_add_tail(&tmp->list, sa_list); + + return tmp; +} + +static void nxp_c45_sa_free(struct nxp_c45_sa *sa) +{ + list_del(&sa->list); + kfree(sa); +} + +static void nxp_c45_sa_list_free(struct list_head *sa_list) +{ + struct nxp_c45_sa *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, sa_list, list) + nxp_c45_sa_free(pos); +} + +static void nxp_c45_sa_set_pn(struct phy_device *phydev, + struct nxp_c45_sa *sa, u64 pn, + u32 replay_window) +{ + const struct nxp_c45_sa_regs *sa_regs = sa->regs; + pn_t npn = {.full64 = pn}; + pn_t lnpn; + + nxp_c45_macsec_write(phydev, sa_regs->npn, npn.lower); + nxp_c45_macsec_write(phydev, sa_regs->xnpn, npn.upper); + if (sa->type != RX_SA) + return; + + if (pn > replay_window) + lnpn.full64 = pn - replay_window; + else + lnpn.full64 = 1; + + nxp_c45_macsec_write(phydev, sa_regs->lnpn, lnpn.lower); + nxp_c45_macsec_write(phydev, sa_regs->lxnpn, lnpn.upper); +} + +static void nxp_c45_sa_set_key(struct macsec_context *ctx, + const struct nxp_c45_sa_regs *sa_regs, + u8 *salt, ssci_t ssci) +{ + struct phy_device *phydev = ctx->phydev; + u32 key_size = ctx->secy->key_len / 4; + u32 salt_size = MACSEC_SALT_LEN / 4; + u32 *key_u32 = (u32 *)ctx->sa.key; + u32 *salt_u32 = (u32 *)salt; + u32 reg, value; + int i; + + for (i = 0; i < key_size; i++) { + reg = sa_regs->ka + i * 4; + value = (__force u32)cpu_to_be32(key_u32[i]); + nxp_c45_macsec_write(phydev, reg, value); + } + + if (ctx->secy->xpn) { + for (i = 0; i < salt_size; i++) { + reg = sa_regs->salt + (2 - i) * 4; + value = (__force u32)cpu_to_be32(salt_u32[i]); + nxp_c45_macsec_write(phydev, reg, value); + } + + value = (__force u32)cpu_to_be32((__force u32)ssci); + nxp_c45_macsec_write(phydev, sa_regs->ssci, value); + } + + nxp_c45_macsec_write(phydev, sa_regs->cs, MACSEC_SA_CS_A); +} + +static void nxp_c45_rx_sa_clear_stats(struct phy_device *phydev, + struct nxp_c45_sa *sa) +{ + nxp_c45_macsec_write(phydev, sa->regs->ipis, 0); + nxp_c45_macsec_write(phydev, sa->regs->ipnvs, 0); + nxp_c45_macsec_write(phydev, sa->regs->ipos, 0); + + nxp_c45_macsec_write(phydev, MACSEC_RXAN0INUSS + sa->an * 4, 0); + nxp_c45_macsec_write(phydev, MACSEC_RXAN0IPUSS + sa->an * 4, 0); +} + +static void nxp_c45_rx_sa_read_stats(struct phy_device *phydev, + struct nxp_c45_sa *sa, + struct macsec_rx_sa_stats *stats) +{ + nxp_c45_macsec_read(phydev, sa->regs->ipis, &stats->InPktsInvalid); + nxp_c45_macsec_read(phydev, sa->regs->ipnvs, &stats->InPktsNotValid); + nxp_c45_macsec_read(phydev, sa->regs->ipos, &stats->InPktsOK); +} + +static void nxp_c45_tx_sa_clear_stats(struct phy_device *phydev, + struct nxp_c45_sa *sa) +{ + nxp_c45_macsec_write(phydev, sa->regs->opps, 0); + nxp_c45_macsec_write(phydev, sa->regs->opes, 0); +} + +static void nxp_c45_tx_sa_read_stats(struct phy_device *phydev, + struct nxp_c45_sa *sa, + struct macsec_tx_sa_stats *stats) +{ + nxp_c45_macsec_read(phydev, sa->regs->opps, &stats->OutPktsProtected); + nxp_c45_macsec_read(phydev, sa->regs->opes, &stats->OutPktsEncrypted); +} + +static void nxp_c45_rx_sa_update(struct phy_device *phydev, + struct nxp_c45_sa *sa, bool en) +{ + const struct nxp_c45_sa_regs *sa_regs = sa->regs; + u32 cfg; + + cfg = sa->an << MACSEC_RXSA_CS_AN_OFF; + cfg |= en ? MACSEC_RXSA_CS_EN : 0; + nxp_c45_macsec_write(phydev, sa_regs->cs, cfg); +} + +static void nxp_c45_tx_sa_update(struct phy_device *phydev, + struct nxp_c45_sa *sa, bool en) +{ + u32 cfg = 0; + + nxp_c45_macsec_read(phydev, MACSEC_TXSC_CFG, &cfg); + + cfg &= ~MACSEC_TXSC_CFG_AN_MASK; + cfg |= sa->an << MACSEC_TXSC_CFG_AN_OFF; + + if (sa->is_key_a) + cfg &= ~MACSEC_TXSC_CFG_ASA; + else + cfg |= MACSEC_TXSC_CFG_ASA; + + if (en) + cfg |= MACSEC_TXSC_CFG_SCE; + else + cfg &= ~MACSEC_TXSC_CFG_SCE; + + nxp_c45_macsec_write(phydev, MACSEC_TXSC_CFG, cfg); +} + +static void nxp_c45_set_sci(struct phy_device *phydev, u16 sci_base_addr, + sci_t sci) +{ + u64 lsci = sci_to_cpu(sci); + + nxp_c45_macsec_write(phydev, sci_base_addr, lsci >> 32); + nxp_c45_macsec_write(phydev, sci_base_addr + 4, lsci); +} + +static bool nxp_c45_port_is_1(sci_t sci) +{ + u16 port = sci_to_cpu(sci); + + return port == 1; +} + +static void nxp_c45_select_secy(struct phy_device *phydev, u8 id) +{ + nxp_c45_macsec_write(phydev, MACSEC_RXSCA, id); + nxp_c45_macsec_write(phydev, MACSEC_RXSCKA, id); + nxp_c45_macsec_write(phydev, MACSEC_TXSCA, id); + nxp_c45_macsec_write(phydev, MACSEC_TXSCKA, id); +} + +static bool nxp_c45_secy_valid(struct nxp_c45_secy *phy_secy, + bool can_rx_sc0_impl) +{ + bool end_station = phy_secy->secy->tx_sc.end_station; + bool scb = phy_secy->secy->tx_sc.scb; + + phy_secy->rx_sc0_impl = false; + + if (end_station) { + if (!nxp_c45_port_is_1(phy_secy->secy->sci)) + return false; + if (!phy_secy->rx_sc) + return true; + return nxp_c45_port_is_1(phy_secy->rx_sc->sci); + } + + if (scb) + return false; + + if (!can_rx_sc0_impl) + return false; + + if (phy_secy->secy_id != 0) + return false; + + phy_secy->rx_sc0_impl = true; + + return true; +} + +static bool nxp_c45_rx_sc0_impl(struct nxp_c45_secy *phy_secy) +{ + bool end_station = phy_secy->secy->tx_sc.end_station; + bool send_sci = phy_secy->secy->tx_sc.send_sci; + bool scb = phy_secy->secy->tx_sc.scb; + + return !end_station && !send_sci && !scb; +} + +static bool nxp_c45_mac_addr_free(struct macsec_context *ctx) +{ + struct nxp_c45_phy *priv = ctx->phydev->priv; + struct nxp_c45_secy *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &priv->macsec->secy_list, list) { + if (pos->secy == ctx->secy) + continue; + + if (memcmp(pos->secy->netdev->dev_addr, + ctx->secy->netdev->dev_addr, ETH_ALEN) == 0) + return false; + } + + return true; +} + +static void nxp_c45_tx_sc_en_flt(struct phy_device *phydev, int secy_id, + bool en) +{ + u32 tx_flt_base = TX_FLT_BASE(secy_id); + u32 reg = 0; + + nxp_c45_macsec_read(phydev, TX_SC_FLT_MAC_CFG(tx_flt_base), ®); + if (en) + reg |= TX_SC_FLT_EN; + else + reg &= ~TX_SC_FLT_EN; + nxp_c45_macsec_write(phydev, TX_SC_FLT_MAC_CFG(tx_flt_base), reg); +} + +static void nxp_c45_tx_sc_set_flt(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy) +{ + const u8 *dev_addr = phy_secy->secy->netdev->dev_addr; + u32 tx_flt_base = TX_FLT_BASE(phy_secy->secy_id); + u32 reg; + + reg = dev_addr[0] << 8 | dev_addr[1]; + nxp_c45_macsec_write(phydev, TX_SC_FLT_MAC_DA_SA(tx_flt_base), reg); + reg = dev_addr[5] | dev_addr[4] << 8 | dev_addr[3] << 16 | + dev_addr[2] << 24; + + nxp_c45_macsec_write(phydev, TX_SC_FLT_MAC_SA(tx_flt_base), reg); + nxp_c45_macsec_read(phydev, TX_SC_FLT_MAC_CFG(tx_flt_base), ®); + reg &= TX_SC_FLT_EN; + reg |= TX_SC_FLT_BY_SA | phy_secy->secy_id; + nxp_c45_macsec_write(phydev, TX_SC_FLT_MAC_CFG(tx_flt_base), reg); +} + +static void nxp_c45_tx_sc_update(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy) +{ + u32 cfg = 0; + + nxp_c45_macsec_read(phydev, MACSEC_TXSC_CFG, &cfg); + + phydev_dbg(phydev, "XPN %s\n", phy_secy->secy->xpn ? "on" : "off"); + if (phy_secy->secy->xpn) + cfg |= MACSEC_TXSC_CFG_XPN; + else + cfg &= ~MACSEC_TXSC_CFG_XPN; + + phydev_dbg(phydev, "key len %u\n", phy_secy->secy->key_len); + if (phy_secy->secy->key_len == 32) + cfg |= MACSEC_TXSC_CFG_AES_256; + else + cfg &= ~MACSEC_TXSC_CFG_AES_256; + + phydev_dbg(phydev, "encryption %s\n", + phy_secy->secy->tx_sc.encrypt ? "on" : "off"); + if (phy_secy->secy->tx_sc.encrypt) + cfg |= MACSEC_TXSC_CFG_ENCRYPT; + else + cfg &= ~MACSEC_TXSC_CFG_ENCRYPT; + + phydev_dbg(phydev, "protect frames %s\n", + phy_secy->secy->protect_frames ? "on" : "off"); + if (phy_secy->secy->protect_frames) + cfg |= MACSEC_TXSC_CFG_PROTECT; + else + cfg &= ~MACSEC_TXSC_CFG_PROTECT; + + phydev_dbg(phydev, "send sci %s\n", + phy_secy->secy->tx_sc.send_sci ? "on" : "off"); + if (phy_secy->secy->tx_sc.send_sci) + cfg |= MACSEC_TXSC_CFG_SEND_SCI; + else + cfg &= ~MACSEC_TXSC_CFG_SEND_SCI; + + phydev_dbg(phydev, "end station %s\n", + phy_secy->secy->tx_sc.end_station ? "on" : "off"); + if (phy_secy->secy->tx_sc.end_station) + cfg |= MACSEC_TXSC_CFG_END_STATION; + else + cfg &= ~MACSEC_TXSC_CFG_END_STATION; + + phydev_dbg(phydev, "scb %s\n", + phy_secy->secy->tx_sc.scb ? "on" : "off"); + if (phy_secy->secy->tx_sc.scb) + cfg |= MACSEC_TXSC_CFG_SCB; + else + cfg &= ~MACSEC_TXSC_CFG_SCB; + + nxp_c45_macsec_write(phydev, MACSEC_TXSC_CFG, cfg); +} + +static void nxp_c45_tx_sc_clear_stats(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy) +{ + struct nxp_c45_sa *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &phy_secy->sa_list, list) + if (pos->type == TX_SA) + nxp_c45_tx_sa_clear_stats(phydev, pos); + + nxp_c45_macsec_write(phydev, MACSEC_OPUS, 0); + nxp_c45_macsec_write(phydev, MACSEC_OPTLS, 0); + nxp_c45_macsec_write(phydev, MACSEC_OOP1HS, 0); + nxp_c45_macsec_write(phydev, MACSEC_OOP2HS, 0); + nxp_c45_macsec_write(phydev, MACSEC_OOE1HS, 0); + nxp_c45_macsec_write(phydev, MACSEC_OOE2HS, 0); +} + +static void nxp_c45_set_rx_sc0_impl(struct phy_device *phydev, + bool enable) +{ + u32 reg = 0; + + nxp_c45_macsec_read(phydev, MACSEC_CFG, ®); + if (enable) + reg |= MACSEC_CFG_S0I; + else + reg &= ~MACSEC_CFG_S0I; + nxp_c45_macsec_write(phydev, MACSEC_CFG, reg); +} + +static bool nxp_c45_is_rx_sc0_impl(struct list_head *secy_list) +{ + struct nxp_c45_secy *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, secy_list, list) + if (pos->rx_sc0_impl) + return pos->rx_sc0_impl; + + return false; +} + +static void nxp_c45_rx_sc_en(struct phy_device *phydev, + struct macsec_rx_sc *rx_sc, bool en) +{ + u32 reg = 0; + + nxp_c45_macsec_read(phydev, MACSEC_RXSC_CFG, ®); + if (rx_sc->active && en) + reg |= MACSEC_RXSC_CFG_SCI_EN; + else + reg &= ~MACSEC_RXSC_CFG_SCI_EN; + nxp_c45_macsec_write(phydev, MACSEC_RXSC_CFG, reg); +} + +static void nxp_c45_rx_sc_update(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy) +{ + struct macsec_rx_sc *rx_sc = phy_secy->rx_sc; + struct nxp_c45_phy *priv = phydev->priv; + u32 cfg = 0; + + nxp_c45_macsec_read(phydev, MACSEC_RXSC_CFG, &cfg); + cfg &= ~MACSEC_RXSC_CFG_VF_MASK; + cfg = phy_secy->secy->validate_frames << MACSEC_RXSC_CFG_VF_OFF; + + phydev_dbg(phydev, "validate frames %u\n", + phy_secy->secy->validate_frames); + phydev_dbg(phydev, "replay_protect %s window %u\n", + phy_secy->secy->replay_protect ? "on" : "off", + phy_secy->secy->replay_window); + if (phy_secy->secy->replay_protect) { + cfg |= MACSEC_RXSC_CFG_RP; + nxp_c45_macsec_write(phydev, MACSEC_RPW, + phy_secy->secy->replay_window); + } else { + cfg &= ~MACSEC_RXSC_CFG_RP; + } + + phydev_dbg(phydev, "rx_sc->active %s\n", + rx_sc->active ? "on" : "off"); + if (rx_sc->active && + test_bit(phy_secy->secy_id, priv->macsec->secy_bitmap)) + cfg |= MACSEC_RXSC_CFG_SCI_EN; + else + cfg &= ~MACSEC_RXSC_CFG_SCI_EN; + + phydev_dbg(phydev, "key len %u\n", phy_secy->secy->key_len); + if (phy_secy->secy->key_len == 32) + cfg |= MACSEC_RXSC_CFG_AES_256; + else + cfg &= ~MACSEC_RXSC_CFG_AES_256; + + phydev_dbg(phydev, "XPN %s\n", phy_secy->secy->xpn ? "on" : "off"); + if (phy_secy->secy->xpn) + cfg |= MACSEC_RXSC_CFG_XPN; + else + cfg &= ~MACSEC_RXSC_CFG_XPN; + + nxp_c45_macsec_write(phydev, MACSEC_RXSC_CFG, cfg); +} + +static void nxp_c45_rx_sc_clear_stats(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy) +{ + struct nxp_c45_sa *pos, *tmp; + int i; + + list_for_each_entry_safe(pos, tmp, &phy_secy->sa_list, list) + if (pos->type == RX_SA) + nxp_c45_rx_sa_clear_stats(phydev, pos); + + nxp_c45_macsec_write(phydev, MACSEC_INOD1HS, 0); + nxp_c45_macsec_write(phydev, MACSEC_INOD2HS, 0); + + nxp_c45_macsec_write(phydev, MACSEC_INOV1HS, 0); + nxp_c45_macsec_write(phydev, MACSEC_INOV2HS, 0); + + nxp_c45_macsec_write(phydev, MACSEC_RXSCIPDS, 0); + nxp_c45_macsec_write(phydev, MACSEC_RXSCIPLS, 0); + nxp_c45_macsec_write(phydev, MACSEC_RXSCIPUS, 0); + + for (i = 0; i < MACSEC_NUM_AN; i++) { + nxp_c45_macsec_write(phydev, MACSEC_RXAN0INUSS + i * 4, 0); + nxp_c45_macsec_write(phydev, MACSEC_RXAN0IPUSS + i * 4, 0); + } +} + +static void nxp_c45_rx_sc_del(struct phy_device *phydev, + struct nxp_c45_secy *phy_secy) +{ + struct nxp_c45_sa *pos, *tmp; + + nxp_c45_macsec_write(phydev, MACSEC_RXSC_CFG, 0); + nxp_c45_macsec_write(phydev, MACSEC_RPW, 0); + nxp_c45_set_sci(phydev, MACSEC_RXSC_SCI_1H, 0); + + nxp_c45_rx_sc_clear_stats(phydev, phy_secy); + + list_for_each_entry_safe(pos, tmp, &phy_secy->sa_list, list) { + if (pos->type == RX_SA) { + nxp_c45_rx_sa_update(phydev, pos, false); + nxp_c45_sa_free(pos); + } + } +} + +static void nxp_c45_clear_global_stats(struct phy_device *phydev) +{ + nxp_c45_macsec_write(phydev, MACSEC_INPBTS, 0); + nxp_c45_macsec_write(phydev, MACSEC_INPWTS, 0); + nxp_c45_macsec_write(phydev, MACSEC_IPSNFS, 0); +} + +static void nxp_c45_macsec_en(struct phy_device *phydev, bool en) +{ + u32 reg; + + nxp_c45_macsec_read(phydev, MACSEC_CFG, ®); + if (en) + reg |= MACSEC_CFG_BYPASS; + else + reg &= ~MACSEC_CFG_BYPASS; + nxp_c45_macsec_write(phydev, MACSEC_CFG, reg); +} + +static int nxp_c45_mdo_dev_open(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + nxp_c45_tx_sc_en_flt(phydev, phy_secy->secy_id, true); + nxp_c45_set_rx_sc0_impl(phydev, phy_secy->rx_sc0_impl); + if (phy_secy->rx_sc) + nxp_c45_rx_sc_en(phydev, phy_secy->rx_sc, true); + + if (bitmap_empty(priv->macsec->secy_bitmap, TX_SC_MAX)) + nxp_c45_macsec_en(phydev, true); + + set_bit(phy_secy->secy_id, priv->macsec->secy_bitmap); + + return 0; +} + +static int nxp_c45_mdo_dev_stop(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + nxp_c45_tx_sc_en_flt(phydev, phy_secy->secy_id, false); + if (phy_secy->rx_sc) + nxp_c45_rx_sc_en(phydev, phy_secy->rx_sc, false); + nxp_c45_set_rx_sc0_impl(phydev, false); + + clear_bit(phy_secy->secy_id, priv->macsec->secy_bitmap); + if (bitmap_empty(priv->macsec->secy_bitmap, TX_SC_MAX)) + nxp_c45_macsec_en(phydev, false); + + return 0; +} + +static int nxp_c45_mdo_add_secy(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + bool can_rx_sc0_impl; + int idx; + + phydev_dbg(phydev, "add SecY SCI %016llx\n", + sci_to_cpu(ctx->secy->sci)); + + if (!nxp_c45_mac_addr_free(ctx)) + return -EBUSY; + + if (nxp_c45_is_rx_sc0_impl(&priv->macsec->secy_list)) + return -EBUSY; + + idx = find_first_zero_bit(priv->macsec->tx_sc_bitmap, TX_SC_MAX); + if (idx == TX_SC_MAX) + return -ENOSPC; + + phy_secy = kzalloc_obj(*phy_secy); + if (!phy_secy) + return -ENOMEM; + + INIT_LIST_HEAD(&phy_secy->sa_list); + phy_secy->secy = ctx->secy; + phy_secy->secy_id = idx; + + /* If the point to point mode should be enabled, we should have no + * SecY added yet. + */ + can_rx_sc0_impl = list_count_nodes(&priv->macsec->secy_list) == 0; + if (!nxp_c45_secy_valid(phy_secy, can_rx_sc0_impl)) { + kfree(phy_secy); + return -EINVAL; + } + + phy_secy->rx_sc0_impl = nxp_c45_rx_sc0_impl(phy_secy); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_set_sci(phydev, MACSEC_TXSC_SCI_1H, ctx->secy->sci); + nxp_c45_tx_sc_set_flt(phydev, phy_secy); + nxp_c45_tx_sc_update(phydev, phy_secy); + if (phy_interrupt_is_valid(phydev)) + nxp_c45_secy_irq_en(phydev, phy_secy, true); + + set_bit(idx, priv->macsec->tx_sc_bitmap); + list_add_tail(&phy_secy->list, &priv->macsec->secy_list); + + return 0; +} + +static void nxp_c45_tx_sa_next(struct nxp_c45_secy *phy_secy, + struct nxp_c45_sa *next_sa, u8 encoding_sa) +{ + struct nxp_c45_sa *sa; + + sa = nxp_c45_find_sa(&phy_secy->sa_list, TX_SA, encoding_sa); + if (!IS_ERR(sa)) { + memcpy(next_sa, sa, sizeof(*sa)); + } else { + next_sa->is_key_a = true; + next_sa->an = encoding_sa; + } +} + +static int nxp_c45_mdo_upd_secy(struct macsec_context *ctx) +{ + u8 encoding_sa = ctx->secy->tx_sc.encoding_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + struct nxp_c45_sa next_sa; + bool can_rx_sc0_impl; + + phydev_dbg(phydev, "update SecY SCI %016llx\n", + sci_to_cpu(ctx->secy->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + if (!nxp_c45_mac_addr_free(ctx)) + return -EBUSY; + + /* If the point to point mode should be enabled, we should have only + * one SecY added, respectively the updated one. + */ + can_rx_sc0_impl = list_count_nodes(&priv->macsec->secy_list) == 1; + if (!nxp_c45_secy_valid(phy_secy, can_rx_sc0_impl)) + return -EINVAL; + phy_secy->rx_sc0_impl = nxp_c45_rx_sc0_impl(phy_secy); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_tx_sc_set_flt(phydev, phy_secy); + nxp_c45_tx_sc_update(phydev, phy_secy); + nxp_c45_tx_sa_next(phy_secy, &next_sa, encoding_sa); + nxp_c45_tx_sa_update(phydev, &next_sa, ctx->secy->operational); + + nxp_c45_set_rx_sc0_impl(phydev, phy_secy->rx_sc0_impl); + if (phy_secy->rx_sc) + nxp_c45_rx_sc_update(phydev, phy_secy); + + return 0; +} + +static int nxp_c45_mdo_del_secy(struct macsec_context *ctx) +{ + u8 encoding_sa = ctx->secy->tx_sc.encoding_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + struct nxp_c45_sa next_sa; + + phydev_dbg(phydev, "delete SecY SCI %016llx\n", + sci_to_cpu(ctx->secy->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + nxp_c45_mdo_dev_stop(ctx); + nxp_c45_tx_sa_next(phy_secy, &next_sa, encoding_sa); + nxp_c45_tx_sa_update(phydev, &next_sa, false); + nxp_c45_tx_sc_clear_stats(phydev, phy_secy); + if (phy_secy->rx_sc) + nxp_c45_rx_sc_del(phydev, phy_secy); + + nxp_c45_sa_list_free(&phy_secy->sa_list); + if (phy_interrupt_is_valid(phydev)) + nxp_c45_secy_irq_en(phydev, phy_secy, false); + + clear_bit(phy_secy->secy_id, priv->macsec->tx_sc_bitmap); + nxp_c45_secy_free(phy_secy); + + if (list_empty(&priv->macsec->secy_list)) + nxp_c45_clear_global_stats(phydev); + + return 0; +} + +static int nxp_c45_mdo_add_rxsc(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + + phydev_dbg(phydev, "add RX SC SCI %016llx %s\n", + sci_to_cpu(ctx->rx_sc->sci), + ctx->rx_sc->active ? "enabled" : "disabled"); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + if (phy_secy->rx_sc) + return -ENOSPC; + + if (phy_secy->secy->tx_sc.end_station && + !nxp_c45_port_is_1(ctx->rx_sc->sci)) + return -EINVAL; + + phy_secy->rx_sc = ctx->rx_sc; + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_set_sci(phydev, MACSEC_RXSC_SCI_1H, ctx->rx_sc->sci); + nxp_c45_rx_sc_update(phydev, phy_secy); + + return 0; +} + +static int nxp_c45_mdo_upd_rxsc(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + + phydev_dbg(phydev, "update RX SC SCI %016llx %s\n", + sci_to_cpu(ctx->rx_sc->sci), + ctx->rx_sc->active ? "enabled" : "disabled"); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_rx_sc_update(phydev, phy_secy); + + return 0; +} + +static int nxp_c45_mdo_del_rxsc(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + + phydev_dbg(phydev, "delete RX SC SCI %016llx %s\n", + sci_to_cpu(ctx->rx_sc->sci), + ctx->rx_sc->active ? "enabled" : "disabled"); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_rx_sc_del(phydev, phy_secy); + phy_secy->rx_sc = NULL; + + return 0; +} + +static int nxp_c45_mdo_add_rxsa(struct macsec_context *ctx) +{ + struct macsec_rx_sa *rx_sa = ctx->sa.rx_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phydev_dbg(phydev, "add RX SA %u %s to RX SC SCI %016llx\n", + an, rx_sa->active ? "enabled" : "disabled", + sci_to_cpu(rx_sa->sc->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_sa_alloc(&phy_secy->sa_list, rx_sa, RX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_sa_set_pn(phydev, sa, rx_sa->next_pn, + ctx->secy->replay_window); + nxp_c45_sa_set_key(ctx, sa->regs, rx_sa->key.salt.bytes, rx_sa->ssci); + nxp_c45_rx_sa_update(phydev, sa, rx_sa->active); + + return 0; +} + +static int nxp_c45_mdo_upd_rxsa(struct macsec_context *ctx) +{ + struct macsec_rx_sa *rx_sa = ctx->sa.rx_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phydev_dbg(phydev, "update RX SA %u %s to RX SC SCI %016llx\n", + an, rx_sa->active ? "enabled" : "disabled", + sci_to_cpu(rx_sa->sc->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_find_sa(&phy_secy->sa_list, RX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + if (ctx->sa.update_pn) + nxp_c45_sa_set_pn(phydev, sa, rx_sa->next_pn, + ctx->secy->replay_window); + nxp_c45_rx_sa_update(phydev, sa, rx_sa->active); + + return 0; +} + +static int nxp_c45_mdo_del_rxsa(struct macsec_context *ctx) +{ + struct macsec_rx_sa *rx_sa = ctx->sa.rx_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phydev_dbg(phydev, "delete RX SA %u %s to RX SC SCI %016llx\n", + an, rx_sa->active ? "enabled" : "disabled", + sci_to_cpu(rx_sa->sc->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_find_sa(&phy_secy->sa_list, RX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_rx_sa_update(phydev, sa, false); + nxp_c45_rx_sa_clear_stats(phydev, sa); + + nxp_c45_sa_free(sa); + + return 0; +} + +static int nxp_c45_mdo_add_txsa(struct macsec_context *ctx) +{ + struct macsec_tx_sa *tx_sa = ctx->sa.tx_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phydev_dbg(phydev, "add TX SA %u %s to TX SC %016llx\n", + an, ctx->sa.tx_sa->active ? "enabled" : "disabled", + sci_to_cpu(ctx->secy->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_sa_alloc(&phy_secy->sa_list, tx_sa, TX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_sa_set_pn(phydev, sa, tx_sa->next_pn, 0); + nxp_c45_sa_set_key(ctx, sa->regs, tx_sa->key.salt.bytes, tx_sa->ssci); + if (ctx->secy->tx_sc.encoding_sa == sa->an) + nxp_c45_tx_sa_update(phydev, sa, tx_sa->active); + + return 0; +} + +static int nxp_c45_mdo_upd_txsa(struct macsec_context *ctx) +{ + struct macsec_tx_sa *tx_sa = ctx->sa.tx_sa; + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phydev_dbg(phydev, "update TX SA %u %s to TX SC %016llx\n", + an, ctx->sa.tx_sa->active ? "enabled" : "disabled", + sci_to_cpu(ctx->secy->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_find_sa(&phy_secy->sa_list, TX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + if (ctx->sa.update_pn) + nxp_c45_sa_set_pn(phydev, sa, tx_sa->next_pn, 0); + if (ctx->secy->tx_sc.encoding_sa == sa->an) + nxp_c45_tx_sa_update(phydev, sa, tx_sa->active); + + return 0; +} + +static int nxp_c45_mdo_del_txsa(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phydev_dbg(phydev, "delete TX SA %u %s to TX SC %016llx\n", + an, ctx->sa.tx_sa->active ? "enabled" : "disabled", + sci_to_cpu(ctx->secy->sci)); + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_find_sa(&phy_secy->sa_list, TX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + nxp_c45_select_secy(phydev, phy_secy->secy_id); + if (ctx->secy->tx_sc.encoding_sa == sa->an) + nxp_c45_tx_sa_update(phydev, sa, false); + nxp_c45_tx_sa_clear_stats(phydev, sa); + + nxp_c45_sa_free(sa); + + return 0; +} + +static int nxp_c45_mdo_get_dev_stats(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct macsec_dev_stats *dev_stats; + struct nxp_c45_secy *phy_secy; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + dev_stats = ctx->stats.dev_stats; + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + nxp_c45_macsec_read32_64(phydev, MACSEC_OPUS, + &dev_stats->OutPktsUntagged); + nxp_c45_macsec_read32_64(phydev, MACSEC_OPTLS, + &dev_stats->OutPktsTooLong); + nxp_c45_macsec_read32_64(phydev, MACSEC_INPBTS, + &dev_stats->InPktsBadTag); + + if (phy_secy->secy->validate_frames == MACSEC_VALIDATE_STRICT) + nxp_c45_macsec_read32_64(phydev, MACSEC_INPWTS, + &dev_stats->InPktsNoTag); + else + nxp_c45_macsec_read32_64(phydev, MACSEC_INPWTS, + &dev_stats->InPktsUntagged); + + if (phy_secy->secy->validate_frames == MACSEC_VALIDATE_STRICT) + nxp_c45_macsec_read32_64(phydev, MACSEC_IPSNFS, + &dev_stats->InPktsNoSCI); + else + nxp_c45_macsec_read32_64(phydev, MACSEC_IPSNFS, + &dev_stats->InPktsUnknownSCI); + + /* Always 0. */ + dev_stats->InPktsOverrun = 0; + + return 0; +} + +static int nxp_c45_mdo_get_tx_sc_stats(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct macsec_tx_sa_stats tx_sa_stats; + struct macsec_tx_sc_stats *stats; + struct nxp_c45_secy *phy_secy; + struct nxp_c45_sa *pos, *tmp; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + stats = ctx->stats.tx_sc_stats; + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + nxp_c45_macsec_read64(phydev, MACSEC_OOE1HS, + &stats->OutOctetsEncrypted); + nxp_c45_macsec_read64(phydev, MACSEC_OOP1HS, + &stats->OutOctetsProtected); + list_for_each_entry_safe(pos, tmp, &phy_secy->sa_list, list) { + if (pos->type != TX_SA) + continue; + + memset(&tx_sa_stats, 0, sizeof(tx_sa_stats)); + nxp_c45_tx_sa_read_stats(phydev, pos, &tx_sa_stats); + + stats->OutPktsEncrypted += tx_sa_stats.OutPktsEncrypted; + stats->OutPktsProtected += tx_sa_stats.OutPktsProtected; + } + + return 0; +} + +static int nxp_c45_mdo_get_tx_sa_stats(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct macsec_tx_sa_stats *stats; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_find_sa(&phy_secy->sa_list, TX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + stats = ctx->stats.tx_sa_stats; + nxp_c45_select_secy(phydev, phy_secy->secy_id); + nxp_c45_tx_sa_read_stats(phydev, sa, stats); + + return 0; +} + +static int nxp_c45_mdo_get_rx_sc_stats(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct macsec_rx_sa_stats rx_sa_stats; + struct macsec_rx_sc_stats *stats; + struct nxp_c45_secy *phy_secy; + struct nxp_c45_sa *pos, *tmp; + u32 reg = 0; + int i; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + if (phy_secy->rx_sc != ctx->rx_sc) + return -EINVAL; + + stats = ctx->stats.rx_sc_stats; + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + list_for_each_entry_safe(pos, tmp, &phy_secy->sa_list, list) { + if (pos->type != RX_SA) + continue; + + memset(&rx_sa_stats, 0, sizeof(rx_sa_stats)); + nxp_c45_rx_sa_read_stats(phydev, pos, &rx_sa_stats); + + stats->InPktsInvalid += rx_sa_stats.InPktsInvalid; + stats->InPktsNotValid += rx_sa_stats.InPktsNotValid; + stats->InPktsOK += rx_sa_stats.InPktsOK; + } + + for (i = 0; i < MACSEC_NUM_AN; i++) { + nxp_c45_macsec_read(phydev, MACSEC_RXAN0INUSS + i * 4, ®); + stats->InPktsNotUsingSA += reg; + nxp_c45_macsec_read(phydev, MACSEC_RXAN0IPUSS + i * 4, ®); + stats->InPktsUnusedSA += reg; + } + + nxp_c45_macsec_read64(phydev, MACSEC_INOD1HS, + &stats->InOctetsDecrypted); + nxp_c45_macsec_read64(phydev, MACSEC_INOV1HS, + &stats->InOctetsValidated); + + nxp_c45_macsec_read32_64(phydev, MACSEC_RXSCIPDS, + &stats->InPktsDelayed); + nxp_c45_macsec_read32_64(phydev, MACSEC_RXSCIPLS, + &stats->InPktsLate); + nxp_c45_macsec_read32_64(phydev, MACSEC_RXSCIPUS, + &stats->InPktsUnchecked); + + return 0; +} + +static int nxp_c45_mdo_get_rx_sa_stats(struct macsec_context *ctx) +{ + struct phy_device *phydev = ctx->phydev; + struct nxp_c45_phy *priv = phydev->priv; + struct macsec_rx_sa_stats *stats; + struct nxp_c45_secy *phy_secy; + u8 an = ctx->sa.assoc_num; + struct nxp_c45_sa *sa; + + phy_secy = nxp_c45_find_secy(&priv->macsec->secy_list, ctx->secy->sci); + if (IS_ERR(phy_secy)) + return PTR_ERR(phy_secy); + + sa = nxp_c45_find_sa(&phy_secy->sa_list, RX_SA, an); + if (IS_ERR(sa)) + return PTR_ERR(sa); + + stats = ctx->stats.rx_sa_stats; + nxp_c45_select_secy(phydev, phy_secy->secy_id); + + nxp_c45_rx_sa_read_stats(phydev, sa, stats); + nxp_c45_macsec_read(phydev, MACSEC_RXAN0INUSS + an * 4, + &stats->InPktsNotUsingSA); + nxp_c45_macsec_read(phydev, MACSEC_RXAN0IPUSS + an * 4, + &stats->InPktsUnusedSA); + + return 0; +} + +struct tja11xx_tlv_header { + struct ethhdr eth; + u8 subtype; + u8 len; + u8 payload[28]; +}; + +static int nxp_c45_mdo_insert_tx_tag(struct phy_device *phydev, + struct sk_buff *skb) +{ + struct tja11xx_tlv_header *tlv; + struct ethhdr *eth; + + eth = eth_hdr(skb); + tlv = skb_push(skb, TJA11XX_TLV_TX_NEEDED_HEADROOM); + memmove(tlv, eth, sizeof(*eth)); + skb_reset_mac_header(skb); + tlv->eth.h_proto = htons(ETH_P_TJA11XX_TLV); + tlv->subtype = 1; + tlv->len = sizeof(tlv->payload); + memset(tlv->payload, 0, sizeof(tlv->payload)); + + return 0; +} + +static const struct macsec_ops nxp_c45_macsec_ops = { + .mdo_dev_open = nxp_c45_mdo_dev_open, + .mdo_dev_stop = nxp_c45_mdo_dev_stop, + .mdo_add_secy = nxp_c45_mdo_add_secy, + .mdo_upd_secy = nxp_c45_mdo_upd_secy, + .mdo_del_secy = nxp_c45_mdo_del_secy, + .mdo_add_rxsc = nxp_c45_mdo_add_rxsc, + .mdo_upd_rxsc = nxp_c45_mdo_upd_rxsc, + .mdo_del_rxsc = nxp_c45_mdo_del_rxsc, + .mdo_add_rxsa = nxp_c45_mdo_add_rxsa, + .mdo_upd_rxsa = nxp_c45_mdo_upd_rxsa, + .mdo_del_rxsa = nxp_c45_mdo_del_rxsa, + .mdo_add_txsa = nxp_c45_mdo_add_txsa, + .mdo_upd_txsa = nxp_c45_mdo_upd_txsa, + .mdo_del_txsa = nxp_c45_mdo_del_txsa, + .mdo_get_dev_stats = nxp_c45_mdo_get_dev_stats, + .mdo_get_tx_sc_stats = nxp_c45_mdo_get_tx_sc_stats, + .mdo_get_tx_sa_stats = nxp_c45_mdo_get_tx_sa_stats, + .mdo_get_rx_sc_stats = nxp_c45_mdo_get_rx_sc_stats, + .mdo_get_rx_sa_stats = nxp_c45_mdo_get_rx_sa_stats, + .mdo_insert_tx_tag = nxp_c45_mdo_insert_tx_tag, + .needed_headroom = TJA11XX_TLV_TX_NEEDED_HEADROOM, + .needed_tailroom = TJA11XX_TLV_NEEDED_TAILROOM, +}; + +int nxp_c45_macsec_config_init(struct phy_device *phydev) +{ + struct nxp_c45_phy *priv = phydev->priv; + int ret; + + if (!priv->macsec) + return 0; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PORT_FUNC_ENABLES, + MACSEC_EN | ADAPTER_EN); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, ADPTR_CNTRL, ADPTR_CNTRL_CONFIG_EN | + ADPTR_CNTRL_ADPTR_EN); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, ADPTR_TX_TAG_CNTRL, + ADPTR_TX_TAG_CNTRL_ENA); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, ADPTR_CNTRL, ADPTR_CNTRL_ADPTR_EN); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, MACSEC_TPNET, PN_WRAP_THRESHOLD); + if (ret) + return ret; + + /* Set MKA filter. */ + ret = nxp_c45_macsec_write(phydev, MACSEC_UPFR0D2, ETH_P_PAE); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, MACSEC_UPFR0M1, MACSEC_OVP); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, MACSEC_UPFR0M2, ETYPE_MASK); + if (ret) + return ret; + + ret = nxp_c45_macsec_write(phydev, MACSEC_UPFR0R, MACSEC_UPFR_EN); + + return ret; +} + +int nxp_c45_macsec_probe(struct phy_device *phydev) +{ + struct nxp_c45_phy *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + + priv->macsec = devm_kzalloc(dev, sizeof(*priv->macsec), GFP_KERNEL); + if (!priv->macsec) + return -ENOMEM; + + INIT_LIST_HEAD(&priv->macsec->secy_list); + phydev->macsec_ops = &nxp_c45_macsec_ops; + + return 0; +} + +void nxp_c45_macsec_remove(struct phy_device *phydev) +{ + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *secy_p, *secy_t; + struct nxp_c45_sa *sa_p, *sa_t; + struct list_head *secy_list; + + if (!priv->macsec) + return; + + secy_list = &priv->macsec->secy_list; + nxp_c45_macsec_en(phydev, false); + + list_for_each_entry_safe(secy_p, secy_t, secy_list, list) { + list_for_each_entry_safe(sa_p, sa_t, &secy_p->sa_list, list) + nxp_c45_sa_free(sa_p); + nxp_c45_secy_free(secy_p); + } +} + +void nxp_c45_handle_macsec_interrupt(struct phy_device *phydev, + irqreturn_t *ret) +{ + struct nxp_c45_phy *priv = phydev->priv; + struct nxp_c45_secy *secy; + struct nxp_c45_sa *sa; + u8 encoding_sa; + int secy_id; + u32 reg = 0; + + if (!priv->macsec) + return; + + do { + nxp_c45_macsec_read(phydev, MACSEC_EVR, ®); + if (!reg) + return; + + secy_id = MACSEC_REG_SIZE - ffs(reg); + secy = nxp_c45_find_secy_by_id(&priv->macsec->secy_list, + secy_id); + if (IS_ERR(secy)) { + WARN_ON(1); + goto macsec_ack_irq; + } + + encoding_sa = secy->secy->tx_sc.encoding_sa; + phydev_dbg(phydev, "pn_wrapped: TX SC %d, encoding_sa %u\n", + secy->secy_id, encoding_sa); + + sa = nxp_c45_find_sa(&secy->sa_list, TX_SA, encoding_sa); + if (!IS_ERR(sa)) + macsec_pn_wrapped(secy->secy, sa->sa); + else + WARN_ON(1); + +macsec_ack_irq: + nxp_c45_macsec_write(phydev, MACSEC_EVR, + TX_SC_BIT(secy_id)); + *ret = IRQ_HANDLED; + } while (reg); +} diff --git a/drivers/net/phy/nxp-c45-tja11xx.c b/drivers/net/phy/nxp-c45-tja11xx.c new file mode 100644 index 000000000..f526528d2 --- /dev/null +++ b/drivers/net/phy/nxp-c45-tja11xx.c @@ -0,0 +1,2190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* NXP C45 PHY driver + * Copyright 2021-2025 NXP + * Author: Radu Pirea + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nxp-c45-tja11xx.h" + +/* Same id: TJA1103, TJA1104 */ +#define PHY_ID_TJA_1103 0x001BB010 +/* Same id: TJA1120, TJA1121 */ +#define PHY_ID_TJA_1120 0x001BB031 + +#define VEND1_DEVICE_ID3 0x0004 +#define TJA1120_DEV_ID3_SILICON_VERSION GENMASK(15, 12) +#define TJA1120_DEV_ID3_SAMPLE_TYPE GENMASK(11, 8) +#define DEVICE_ID3_SAMPLE_TYPE_R 0x9 + +#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 MACSEC_ABILITY BIT(5) +#define PTP_ABILITY BIT(3) + +#define VEND1_PORT_FUNC_IRQ_EN 0x807A +#define MACSEC_IRQS BIT(5) +#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_SGMII_BASIC_CONTROL 0xB000 +#define SGMII_LPM BIT(11) + +#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 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) + +#define TJA11XX_REVERSE_MODE BIT(0) + +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); +}; + +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, ®map->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, ®map->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, ®map->domain_number); + hwts->msg_type = + nxp_c45_read_reg_field(phydev, ®map->msg_type); + hwts->sequence_id = + nxp_c45_read_reg_field(phydev, ®map->sequence_id); + hwts->nsec = + nxp_c45_read_reg_field(phydev, ®map->nsec_15_0); + hwts->nsec |= + nxp_c45_read_reg_field(phydev, ®map->nsec_29_16) << 16; + hwts->sec = nxp_c45_read_reg_field(phydev, ®map->sec_1_0); + hwts->sec |= nxp_c45_read_reg_field(phydev, ®map->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; + + 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, + ®map->pps_enable); + nxp_c45_clear_reg_field(priv->phydev, + ®map->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, + ®map->pps_polarity); + else + nxp_c45_set_reg_field(priv->phydev, + ®map->pps_polarity); + } + + nxp_c45_gpio_config(priv, pin, GPIO_PPS_OUT_CFG); + + nxp_c45_set_reg_field(priv->phydev, ®map->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; + + /* 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, + .supported_extts_flags = PTP_RISING_EDGE | + PTP_FALLING_EDGE | + PTP_STRICT_FLAGS, + .supported_perout_flags = PTP_PEROUT_PHASE, + }; + + 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_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy, + mii_ts); + + cfg->tx_type = priv->hwts_tx; + cfg->rx_filter = priv->hwts_rx ? HWTSTAMP_FILTER_PTP_V2_L2_EVENT + : HWTSTAMP_FILTER_NONE; + + return 0; +} + +static int nxp_c45_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) +{ + 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; + + 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 0; +} + +static int nxp_c45_ts_info(struct mii_timestamper *mii_ts, + struct kernel_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)) { + ethtool_puts(&data, common_hw_stats[i].name); + continue; + } + idx = i - ARRAY_SIZE(common_hw_stats); + ethtool_puts(&data, phy_data->stats[idx].name); + } +} + +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) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + VEND1_PORT_FUNC_IRQ_EN, MACSEC_IRQS); + if (ret) + return ret; + + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + VEND1_PHY_IRQ_EN, PHY_IRQ_LINK_EVENT); + } + + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + VEND1_PORT_FUNC_IRQ_EN, MACSEC_IRQS); + if (ret) + return ret; + + 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); + nxp_c45_handle_macsec_interrupt(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; + + usleep_range(2000, 2050); + + 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, ®map->cable_test_valid); + if (!ret) { + *finished = false; + return 0; + } + + *finished = true; + cable_test_result = nxp_c45_read_reg_field(phydev, + ®map->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) +{ + struct nxp_c45_phy *priv = phydev->priv; + u16 basic_config; + 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; + } + + basic_config = MII_BASIC_CONFIG_RMII; + + /* This is not PHY_INTERFACE_MODE_REVRMII */ + if (priv->flags & TJA11XX_REVERSE_MODE) + basic_config |= MII_BASIC_CONFIG_REV; + + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG, + basic_config); + 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; +} + +/* Errata: ES_TJA1120 and ES_TJA1121 Rev. 1.0 — 28 November 2024 Section 3.1 & 3.2 */ +static void nxp_c45_tja1120_errata(struct phy_device *phydev) +{ + bool macsec_ability, sgmii_ability; + int silicon_version, sample_type; + int phy_abilities; + int ret = 0; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_DEVICE_ID3); + if (ret < 0) + return; + + sample_type = FIELD_GET(TJA1120_DEV_ID3_SAMPLE_TYPE, ret); + if (sample_type != DEVICE_ID3_SAMPLE_TYPE_R) + return; + + silicon_version = FIELD_GET(TJA1120_DEV_ID3_SILICON_VERSION, ret); + + phy_abilities = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_PORT_ABILITIES); + macsec_ability = !!(phy_abilities & MACSEC_ABILITY); + sgmii_ability = !!(phy_abilities & SGMII_ABILITY); + if ((!macsec_ability && silicon_version == 2) || + (macsec_ability && silicon_version == 1)) { + /* TJA1120/TJA1121 PHY configuration errata workaround. + * Apply PHY writes sequence before link up. + */ + if (!macsec_ability) { + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F8, 0x4b95); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F9, 0xf3cd); + } else { + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F8, 0x89c7); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F9, 0x0893); + } + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0476, 0x58a0); + + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 0x8921, 0xa3a); + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 0x89F1, 0x16c1); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F8, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01F9, 0x0); + + if (sgmii_ability) { + /* TJA1120B/TJA1121B SGMII PCS restart errata workaround. + * Put SGMII PCS into power down mode and back up. + */ + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + VEND1_SGMII_BASIC_CONTROL, + SGMII_LPM); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + VEND1_SGMII_BASIC_CONTROL, + SGMII_LPM); + } + } +} + +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); + + if (phy_id_compare(phydev->phy_id, PHY_ID_TJA_1120, GENMASK(31, 4))) + nxp_c45_tja1120_errata(phydev); + + 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); + ret = nxp_c45_macsec_config_init(phydev); + if (ret) + return ret; + + 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_parse_dt(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct nxp_c45_phy *priv = phydev->priv; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return 0; + + if (of_property_read_bool(node, "nxp,rmii-refclk-out")) + priv->flags |= TJA11XX_REVERSE_MODE; + + return 0; +} + +static int nxp_c45_probe(struct phy_device *phydev) +{ + struct nxp_c45_phy *priv; + bool macsec_ability; + int phy_abilities; + bool 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; + + nxp_c45_parse_dt(phydev); + + mutex_init(&priv->ptp_lock); + + phy_abilities = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_PORT_ABILITIES); + ptp_ability = !!(phy_abilities & 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_set = nxp_c45_hwtstamp_set; + priv->mii_ts.hwtstamp_get = nxp_c45_hwtstamp_get; + priv->mii_ts.ts_info = nxp_c45_ts_info; + phydev->mii_ts = &priv->mii_ts; + ret = nxp_c45_init_ptp_clock(priv); + + /* Timestamp selected by default to keep legacy API */ + phydev->default_timestamp = true; + } else { + phydev_dbg(phydev, "PTP support not enabled even if the phy supports it"); + } + +no_ptp_support: + macsec_ability = !!(phy_abilities & MACSEC_ABILITY); + if (!macsec_ability) { + phydev_info(phydev, "the phy does not support MACsec\n"); + goto no_macsec_support; + } + + if (IS_ENABLED(CONFIG_MACSEC)) { + ret = nxp_c45_macsec_probe(phydev); + phydev_dbg(phydev, "MACsec support enabled."); + } else { + phydev_dbg(phydev, "MACsec support not enabled even if the phy supports it"); + } + +no_macsec_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); + nxp_c45_macsec_remove(phydev); +} + +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 int nxp_c45_macsec_ability(struct phy_device *phydev) +{ + bool macsec_ability; + int phy_abilities; + + phy_abilities = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_PORT_ABILITIES); + macsec_ability = !!(phy_abilities & MACSEC_ABILITY); + + return macsec_ability; +} + +static bool tja11xx_phy_id_compare(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + u32 id = phydev->is_c45 ? phydev->c45_ids.device_ids[MDIO_MMD_PMAPMD] : + phydev->phy_id; + + return phy_id_compare(id, phydrv->phy_id, phydrv->phy_id_mask); +} + +static int tja11xx_no_macsec_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return tja11xx_phy_id_compare(phydev, phydrv) && + !nxp_c45_macsec_ability(phydev); +} + +static int tja11xx_macsec_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return tja11xx_phy_id_compare(phydev, phydrv) && + nxp_c45_macsec_ability(phydev); +} + +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, + .match_phy_device = tja11xx_no_macsec_match_phy_device, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_TJA_1103), + .name = "NXP C45 TJA1104", + .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, + .match_phy_device = tja11xx_macsec_match_phy_device, + }, + { + 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, + .match_phy_device = tja11xx_no_macsec_match_phy_device, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_TJA_1120), + .name = "NXP C45 TJA1121", + .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, + .match_phy_device = tja11xx_macsec_match_phy_device, + }, +}; + +module_phy_driver(nxp_c45_driver); + +static const 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 "); +MODULE_DESCRIPTION("NXP C45 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/nxp-c45-tja11xx.h b/drivers/net/phy/nxp-c45-tja11xx.h new file mode 100644 index 000000000..8b5fc3837 --- /dev/null +++ b/drivers/net/phy/nxp-c45-tja11xx.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* NXP C45 PHY driver header file + * Copyright 2023 NXP + * Author: Radu Pirea + */ + +#include + +#define VEND1_PORT_FUNC_ENABLES 0x8048 + +struct nxp_c45_macsec; + +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; + struct nxp_c45_macsec *macsec; + u32 flags; +}; + +#if IS_ENABLED(CONFIG_MACSEC) +int nxp_c45_macsec_config_init(struct phy_device *phydev); +int nxp_c45_macsec_probe(struct phy_device *phydev); +void nxp_c45_macsec_remove(struct phy_device *phydev); +void nxp_c45_handle_macsec_interrupt(struct phy_device *phydev, + irqreturn_t *ret); +#else +static inline +int nxp_c45_macsec_config_init(struct phy_device *phydev) +{ + return 0; +} + +static inline +int nxp_c45_macsec_probe(struct phy_device *phydev) +{ + return 0; +} + +static inline +void nxp_c45_macsec_remove(struct phy_device *phydev) +{ +} + +static inline +void nxp_c45_handle_macsec_interrupt(struct phy_device *phydev, + irqreturn_t *ret) +{ +} +#endif diff --git a/drivers/net/phy/nxp-cbtx.c b/drivers/net/phy/nxp-cbtx.c new file mode 100644 index 000000000..3286fcb4f --- /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 +#include +#include +#include + +#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) +{ + ethtool_puts(&data, "100btx_rx_err"); +} + +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 const 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 "); +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..079d912a1 --- /dev/null +++ b/drivers/net/phy/nxp-tja11xx.c @@ -0,0 +1,942 @@ +// SPDX-License-Identifier: GPL-2.0 +/* NXP TJA1100 BroadRReach PHY driver + * + * Copyright (C) 2018 Marek Vasut + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_ID_MASK 0xfffffff0 +#define PHY_ID_TJA1100 0x0180dc40 +#define PHY_ID_TJA1101 0x0180dd00 +#define PHY_ID_TJA1102 0x0180dc80 +#define PHY_ID_TJA1102S 0x0180dc90 + +#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_SLEEP (0xa << 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) + +#define MII_CFG3 28 +#define MII_CFG3_PHY_EN BIT(0) + +/* Configure REF_CLK as input in RMII mode */ +#define TJA110X_RMII_MODE_REFCLK_IN BIT(0) + +struct tja11xx_priv { + 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); + case MII_ECTRL_POWER_MODE_SLEEP: + switch (phydev->phy_id & PHY_ID_MASK) { + case PHY_ID_TJA1102S: + /* Enable PHY, maybe it is disabled due to pin strapping */ + return phy_set_bits(phydev, MII_CFG3, MII_CFG3_PHY_EN); + default: + return 0; + } + 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_TJA1102S: + 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++) + ethtool_puts(&data, tja11xx_hw_stats[i].string); +} + +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 *hdev, *dev = &phydev->mdio.dev; + + hdev = devm_hwmon_device_register_with_info(dev, NULL, phydev, + &tja11xx_hwmon_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hdev); +} + +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; + phydev->priv = priv; + 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 void tja1102_p0_remove(struct phy_device *phydev) +{ + struct tja11xx_priv *priv = phydev->priv; + + cancel_work_sync(&priv->phy_register_work); +} + +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, + const struct phy_driver *phydrv) +{ + return tja1102_match_phy_device(phydev, true); +} + +static int tja1102_p1_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + 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, + .remove = tja1102_p0_remove, + .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, + }, { + PHY_ID_MATCH_MODEL(PHY_ID_TJA1102S), + .name = "NXP TJA1102S", + .features = PHY_BASIC_T1_FEATURES, + .flags = PHY_POLL_CABLE_TEST, + .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, + .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 const 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) }, + { PHY_ID_MATCH_MODEL(PHY_ID_TJA1102S) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, tja11xx_tbl); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("NXP TJA11xx BoardR-Reach PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/open_alliance_helpers.c b/drivers/net/phy/open_alliance_helpers.c new file mode 100644 index 000000000..36a70451d --- /dev/null +++ b/drivers/net/phy/open_alliance_helpers.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * open_alliance_helpers.c - OPEN Alliance specific PHY diagnostic helpers + * + * This file contains helper functions for implementing advanced diagnostic + * features as specified by the OPEN Alliance for automotive Ethernet PHYs. + * These helpers include functionality for Time Delay Reflection (TDR), dynamic + * channel quality assessment, and other PHY diagnostics. + * + * For more information on the specifications, refer to the OPEN Alliance + * documentation: https://opensig.org/automotive-ethernet-specifications/ + * Currently following specifications are partially or fully implemented: + * - Advanced diagnostic features for 1000BASE-T1 automotive Ethernet PHYs. + * TC12 - advanced PHY features. + * https://opensig.org/wp-content/uploads/2024/03/Advanced_PHY_features_for_automotive_Ethernet_v2.0_fin.pdf + */ + +#include +#include + +#include "open_alliance_helpers.h" + +/** + * oa_1000bt1_get_ethtool_cable_result_code - Convert TDR status to ethtool + * result code + * @reg_value: Value read from the TDR register + * + * This function takes a register value from the HDD.TDR register and converts + * the TDR status to the corresponding ethtool cable test result code. + * + * Return: The appropriate ethtool result code based on the TDR status + */ +int oa_1000bt1_get_ethtool_cable_result_code(u16 reg_value) +{ + u8 tdr_status = FIELD_GET(OA_1000BT1_HDD_TDR_STATUS_MASK, reg_value); + u8 dist_val = FIELD_GET(OA_1000BT1_HDD_TDR_DISTANCE_MASK, reg_value); + + switch (tdr_status) { + case OA_1000BT1_HDD_TDR_STATUS_CABLE_OK: + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + case OA_1000BT1_HDD_TDR_STATUS_OPEN: + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + case OA_1000BT1_HDD_TDR_STATUS_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + case OA_1000BT1_HDD_TDR_STATUS_NOISE: + return ETHTOOL_A_CABLE_RESULT_CODE_NOISE; + default: + if (dist_val == OA_1000BT1_HDD_TDR_DISTANCE_RESOLUTION_NOT_POSSIBLE) + return ETHTOOL_A_CABLE_RESULT_CODE_RESOLUTION_NOT_POSSIBLE; + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } +} +EXPORT_SYMBOL_GPL(oa_1000bt1_get_ethtool_cable_result_code); + +/** + * oa_1000bt1_get_tdr_distance - Get distance to the main fault from TDR + * register value + * @reg_value: Value read from the TDR register + * + * This function takes a register value from the HDD.TDR register and extracts + * the distance to the main fault detected by the TDR feature. The distance is + * measured in centimeters and ranges from 0 to 3100 centimeters. If the + * distance is not available (0x3f), the function returns -ERANGE. + * + * Return: The distance to the main fault in centimeters, or -ERANGE if the + * resolution is not possible. + */ +int oa_1000bt1_get_tdr_distance(u16 reg_value) +{ + u8 dist_val = FIELD_GET(OA_1000BT1_HDD_TDR_DISTANCE_MASK, reg_value); + + if (dist_val == OA_1000BT1_HDD_TDR_DISTANCE_RESOLUTION_NOT_POSSIBLE) + return -ERANGE; + + return dist_val * 100; +} +EXPORT_SYMBOL_GPL(oa_1000bt1_get_tdr_distance); diff --git a/drivers/net/phy/open_alliance_helpers.h b/drivers/net/phy/open_alliance_helpers.h new file mode 100644 index 000000000..8b7d97bc6 --- /dev/null +++ b/drivers/net/phy/open_alliance_helpers.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef OPEN_ALLIANCE_HELPERS_H +#define OPEN_ALLIANCE_HELPERS_H + +/* + * These defines reflect the TDR (Time Delay Reflection) diagnostic feature + * for 1000BASE-T1 automotive Ethernet PHYs as specified by the OPEN Alliance. + * + * The register values are part of the HDD.TDR register, which provides + * information about the cable status and faults. The exact register offset + * is device-specific and should be provided by the driver. + */ +#define OA_1000BT1_HDD_TDR_ACTIVATION_MASK GENMASK(1, 0) +#define OA_1000BT1_HDD_TDR_ACTIVATION_OFF 1 +#define OA_1000BT1_HDD_TDR_ACTIVATION_ON 2 + +#define OA_1000BT1_HDD_TDR_STATUS_MASK GENMASK(7, 4) +#define OA_1000BT1_HDD_TDR_STATUS_SHORT 3 +#define OA_1000BT1_HDD_TDR_STATUS_OPEN 6 +#define OA_1000BT1_HDD_TDR_STATUS_NOISE 5 +#define OA_1000BT1_HDD_TDR_STATUS_CABLE_OK 7 +#define OA_1000BT1_HDD_TDR_STATUS_TEST_IN_PROGRESS 8 +#define OA_1000BT1_HDD_TDR_STATUS_TEST_NOT_POSSIBLE 13 + +/* + * OA_1000BT1_HDD_TDR_DISTANCE_MASK: + * This mask is used to extract the distance to the first/main fault + * detected by the TDR feature. Each bit represents an approximate distance + * of 1 meter, ranging from 0 to 31 meters. The exact interpretation of the + * bits may vary, but generally: + * 000000 = no error + * 000001 = error about 0-1m away + * 000010 = error between 1-2m away + * ... + * 011111 = error about 30-31m away + * 111111 = resolution not possible / out of distance + */ +#define OA_1000BT1_HDD_TDR_DISTANCE_MASK GENMASK(13, 8) +#define OA_1000BT1_HDD_TDR_DISTANCE_NO_ERROR 0 +#define OA_1000BT1_HDD_TDR_DISTANCE_RESOLUTION_NOT_POSSIBLE 0x3f + +int oa_1000bt1_get_ethtool_cable_result_code(u16 reg_value); +int oa_1000bt1_get_tdr_distance(u16 reg_value); + +#endif /* OPEN_ALLIANCE_HELPERS_H */ + diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c new file mode 100644 index 000000000..870920311 --- /dev/null +++ b/drivers/net/phy/phy-c45.c @@ -0,0 +1,1967 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Clause 45 PHY support + */ +#include +#include +#include +#include +#include +#include + +#include "mdio-open-alliance.h" +#include "phylib-internal.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_PMA_CTRL1_SPEED2_5G; + /* Assume 2.5Gbase-T */ + ctrl2 |= MDIO_PMA_CTRL2_2_5GBT; + break; + case SPEED_5000: + ctrl1 |= MDIO_PMA_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_1000BT1 | MDIO_AN_T1_ADV_M_100BT1 | + 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_pma_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register + * @phydev: target phy_device struct + * + * Return: 0 on success, negative errno on failure. + */ +int genphy_c45_pma_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, + MDIO_CTRL1, val, + !(val & MDIO_CTRL1_RESET), + 5000, 600000, true); +} +EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset); + +/** + * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs + * @phydev: target phy_device struct + * + * Description: Configure the forced or preferred Master/Slave role + * 10GBASE-T control register (MMD 7, Register 0x0020) according to + * IEEE 802.3 standards. + * + * Return: negative errno code on failure, 0 if Master/Slave didn't change, + * or 1 if Master/Slave modes changed. + */ +static int genphy_c45_an_setup_master_slave(struct phy_device *phydev) +{ + u16 ctl = 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE; + break; + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + break; + case MASTER_SLAVE_CFG_MASTER_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE; + break; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE; + 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_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_MS_ENABLE | + MDIO_AN_10GBT_CTRL_MS_VALUE | + MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl); +} + +/** + * genphy_c45_read_master_slave - read master/slave status + * @phydev: target phy_device struct + * + * Description: Read the Master/Slave configuration and status + * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021). + * + * Return: 0 on success, or a negative error code on failure. + */ +static int genphy_c45_read_master_slave(struct phy_device *phydev) +{ + int val; + + phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) { + if (val & MDIO_AN_10GBT_CTRL_MS_VALUE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE; + } else { + if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED; + } + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_STAT_MS_FAULT) { + phydev->master_slave_state = MASTER_SLAVE_STATE_ERR; + } else if (phydev->link) { + if (val & MDIO_AN_10GBT_STAT_MS_RES) + phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; + else + phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; + } else { + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + } + + return 0; +} + +/** + * 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 = false; + phydev->asym_pause = false; + + 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; + phydev->asym_pause = val & MDIO_AN_T1_ADV_L_PAUSE_ASYM; + + 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 = false; + phydev->asym_pause = false; + + 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; + phydev->asym_pause = val & LPA_PAUSE_ASYM; + + /* 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_PMA_CTRL1_SPEED2_5G: + phydev->speed = SPEED_2500; + break; + case MDIO_PMA_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 + */ +static 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); + + /* 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_intersects(phydev->supported_eee, PHY_EEE_CAP2_FEATURES)) { + val = linkmode_to_mii_eee_cap2_t(adv); + + /* IEEE 802.3-2022 45.2.7.16 EEE advertisement 2 + * (Register 7.62) + */ + val = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, + MDIO_AN_EEE_ADV2, + MDIO_EEE_2_5GT | MDIO_EEE_5GT, + 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_intersects(phydev->supported_eee, PHY_EEE_CAP2_FEATURES)) { + /* IEEE 802.3-2022 45.2.7.16 EEE advertisement 2 + * (Register 7.62) + */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV2); + if (val < 0) + return val; + + mii_eee_cap2_mod_linkmode_adv_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_intersects(phydev->supported_eee, PHY_EEE_CAP2_FEATURES)) { + /* IEEE 802.3-2022 45.2.7.17 EEE link partner ability 2 + * (Register 7.63) + */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE2); + if (val < 0) + return val; + + mii_eee_cap2_mod_linkmode_adv_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_cap2 - read supported EEE link modes from register 3.21 + * @phydev: target phy_device struct + */ +static int genphy_c45_read_eee_cap2(struct phy_device *phydev) +{ + int val; + + /* IEEE 802.3-2022 45.2.3.11 EEE control and capability 2 + * (Register 3.21) + */ + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE2); + if (val < 0) + return val; + + /* IEEE 802.3-2022 45.2.3.11 says 9 bits are reserved. */ + if (val == 0xffff) + return 0; + + mii_eee_cap2_mod_linkmode_sup_t(phydev->supported_eee, val); + + 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; + } + + /* Same for cap2 (3.21) */ + if (linkmode_intersects(phydev->supported, PHY_EEE_CAP2_FEATURES)) { + val = genphy_c45_read_eee_cap2(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) +{ + /* Writing MMD AN advertisements while autoneg is disabled has no + * effect on link-partner negotiation, but on some PHYs (e.g. the + * Broadcom BCM54213PE) the write itself disturbs the receive + * datapath. Skip it. + */ + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + + if (!phydev->eee_cfg.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); +} +EXPORT_SYMBOL_GPL(genphy_c45_an_config_eee_aneg); + +/** + * 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_ext_abilities - read supported link modes from PMA + * @phydev: target phy_device struct + * + * Read the supported link modes from the PMA/PMD extended ability register + * (Register 1.11). + */ +int genphy_c45_pma_read_ext_abilities(struct phy_device *phydev) +{ + int val; + + 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; + } + + return 0; +} +EXPORT_SYMBOL_GPL(genphy_c45_pma_read_ext_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 = genphy_c45_pma_read_ext_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 = false; + phydev->asym_pause = false; + + 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; + } else { + ret = genphy_c45_read_master_slave(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; + + if (!genphy_c45_baset1_able(phydev)) { + ret = genphy_c45_an_setup_master_slave(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, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + 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 + * @lp: variable to store LP advertised linkmodes + * + * Description: this function will read link partner PHY advertisement + * and compare it to local advertisement to return current EEE state. + */ +int genphy_c45_eee_is_active(struct phy_device *phydev, unsigned long *lp) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp_lp) = {}; + __ETHTOOL_DECLARE_LINK_MODE_MASK(common); + int ret; + + if (!phydev->eee_cfg.eee_enabled) + return 0; + + ret = genphy_c45_read_eee_lpa(phydev, tmp_lp); + if (ret) + return ret; + + if (lp) + linkmode_copy(lp, tmp_lp); + + linkmode_and(common, phydev->advertising_eee, tmp_lp); + if (linkmode_empty(common)) + return 0; + + return phy_check_valid(phydev->speed, phydev->duplex, common); +} +EXPORT_SYMBOL(genphy_c45_eee_is_active); + +/** + * genphy_c45_ethtool_get_eee - get EEE supported and status + * @phydev: target phy_device struct + * @data: ethtool_keee data + * + * Description: it reports the Supported/Advertisement/LP Advertisement + * capabilities. + */ +int genphy_c45_ethtool_get_eee(struct phy_device *phydev, + struct ethtool_keee *data) +{ + int ret; + + ret = genphy_c45_eee_is_active(phydev, data->lp_advertised); + if (ret < 0) + return ret; + + data->eee_active = phydev->eee_active; + linkmode_andnot(data->supported, phydev->supported_eee, + phydev->eee_disabled_modes); + linkmode_copy(data->advertised, phydev->advertising_eee); + 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_keee 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. + * Returns either error code, 0 if there was no change, or positive + * value if there was a change which triggered auto-neg. + */ +int genphy_c45_ethtool_set_eee(struct phy_device *phydev, + struct ethtool_keee *data) +{ + int ret; + + if (data->eee_enabled) { + unsigned long *adv = data->advertised; + + if (!linkmode_empty(adv)) { + __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp); + + if (linkmode_andnot(tmp, adv, phydev->supported_eee)) { + phydev_warn(phydev, "At least some EEE link modes are not supported.\n"); + return -EINVAL; + } + + linkmode_andnot(phydev->advertising_eee, adv, + phydev->eee_disabled_modes); + } else if (linkmode_empty(phydev->advertising_eee)) { + phy_advertise_eee_all(phydev); + } + } + + ret = genphy_c45_an_config_eee_aneg(phydev); + if (ret > 0) { + ret = phy_restart_aneg(phydev); + if (ret < 0) + return ret; + + /* explicitly return 1, otherwise (ret > 0) value will be + * overwritten by phy_restart_aneg(). + */ + return 1; + } + + return ret; +} +EXPORT_SYMBOL(genphy_c45_ethtool_set_eee); + +/** + * oatc14_cable_test_get_result_code - Convert hardware cable test status to + * ethtool result code. + * @status: The hardware-reported cable test status + * + * This helper function maps the OATC14 HDD cable test status to the + * corresponding ethtool cable test result code. It provides a translation + * between the device-specific status values and the standardized ethtool + * result codes. + * + * Return: + * * ETHTOOL_A_CABLE_RESULT_CODE_OK - Cable is OK + * * ETHTOOL_A_CABLE_RESULT_CODE_OPEN - Open circuit detected + * * ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT - Short circuit detected + * * ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC - Status not detectable or invalid + */ +static int oatc14_cable_test_get_result_code(enum oatc14_hdd_status status) +{ + switch (status) { + case OATC14_HDD_STATUS_CABLE_OK: + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + case OATC14_HDD_STATUS_OPEN: + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + case OATC14_HDD_STATUS_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + case OATC14_HDD_STATUS_NOT_DETECTABLE: + default: + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } +} + +/** + * genphy_c45_oatc14_cable_test_get_status - Get status of OATC14 10Base-T1S + * PHY cable test. + * @phydev: pointer to the PHY device structure + * @finished: pointer to a boolean set true if the test is complete + * + * Retrieves the current status of the OATC14 10Base-T1S PHY cable test. + * This function reads the OATC14 HDD register to determine whether the test + * results are valid and whether the test has finished. + * + * If the test is complete, the function reports the cable test result via + * the ethtool cable test interface using ethnl_cable_test_result(), and then + * clears the test control bit in the PHY register to reset the test state. + * + * Return: 0 on success, or a negative error code on failure (e.g. register + * read/write error). + */ +int genphy_c45_oatc14_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + int ret; + u8 sts; + + *finished = false; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD); + if (ret < 0) + return ret; + + if (!(ret & OATC14_HDD_VALID)) + return 0; + + *finished = true; + + sts = FIELD_GET(OATC14_HDD_SHORT_OPEN_STATUS, ret); + + ret = ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + oatc14_cable_test_get_result_code(sts)); + if (ret) + return ret; + + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, + MDIO_OATC14_HDD, OATC14_HDD_CONTROL); +} +EXPORT_SYMBOL(genphy_c45_oatc14_cable_test_get_status); + +/** + * genphy_c45_oatc14_cable_test_start - Start a cable test on an OATC14 + * 10Base-T1S PHY. + * @phydev: Pointer to the PHY device structure + * + * This function initiates a cable diagnostic test on a Clause 45 OATC14 + * 10Base-T1S capable PHY device. It first reads the PHY’s advanced diagnostic + * capability register to check if High Definition Diagnostics (HDD) mode is + * supported. If the PHY does not report HDD capability, cable testing is not + * supported and the function returns -EOPNOTSUPP. + * + * For PHYs that support HDD, the function sets the appropriate control bits in + * the OATC14_HDD register to enable and start the cable diagnostic test. + * + * Return: + * * 0 on success + * * -EOPNOTSUPP if the PHY does not support HDD capability + * * A negative error code on I/O or register access failures + */ +int genphy_c45_oatc14_cable_test_start(struct phy_device *phydev) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_ADFCAP); + if (ret < 0) + return ret; + + if (!(ret & OATC14_ADFCAP_HDD_CAPABILITY)) + return -EOPNOTSUPP; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD, + OATC14_HDD_CONTROL); + if (ret) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD); + if (ret < 0) + return ret; + + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD, + OATC14_HDD_START_CONTROL); +} +EXPORT_SYMBOL(genphy_c45_oatc14_cable_test_start); + +/** + * oatc14_update_sqi_capability - Read and update OATC14 10Base-T1S PHY SQI/SQI+ + * capability + * @phydev: Pointer to the PHY device structure + * + * This helper reads the OATC14 ADFCAP capability register to determine whether + * the PHY supports SQI or SQI+ reporting. + * + * SQI+ capability is detected first. The SQI+ field indicates the number of + * valid MSBs (3–8), corresponding to 8–256 SQI+ levels. When present, the + * function stores the number of SQI+ bits and computes the maximum SQI+ value + * as (2^bits - 1). + * + * If SQI+ is not supported, the function checks for basic SQI capability, + * which provides 0–7 SQI levels. + * + * On success, the capability information is stored in + * @phydev->oatc14_sqi_capability and marked as updated. + * + * Return: + * * 0 - capability successfully read and stored + * * -EOPNOTSUPP - SQI/SQI+ not supported by this PHY + * * Negative errno on read failure + */ +static int oatc14_update_sqi_capability(struct phy_device *phydev) +{ + u8 bits; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_ADFCAP); + if (ret < 0) + return ret; + + /* Check for SQI+ capability + * 0 - SQI+ is not supported + * (3-8) bits for (8-256) SQI+ levels supported + */ + bits = FIELD_GET(OATC14_ADFCAP_SQIPLUS_CAPABILITY, ret); + if (bits) { + phydev->oatc14_sqi_capability.sqiplus_bits = bits; + /* Max sqi+ level supported: (2 ^ bits) - 1 */ + phydev->oatc14_sqi_capability.sqi_max = BIT(bits) - 1; + goto update_done; + } + + /* Check for SQI capability + * 0 - SQI is not supported + * 1 - SQI is supported (0-7 levels) + */ + if (ret & OATC14_ADFCAP_SQI_CAPABILITY) { + phydev->oatc14_sqi_capability.sqi_max = OATC14_SQI_MAX_LEVEL; + goto update_done; + } + + return -EOPNOTSUPP; + +update_done: + phydev->oatc14_sqi_capability.updated = true; + return 0; +} + +/** + * genphy_c45_oatc14_get_sqi_max - Get maximum supported SQI or SQI+ level of + * OATC14 10Base-T1S PHY + * @phydev: pointer to the PHY device structure + * + * This function returns the maximum supported Signal Quality Indicator (SQI) or + * SQI+ level. The SQI capability is updated on first invocation if it has not + * already been updated. + * + * Return: + * * Maximum SQI/SQI+ level supported + * * Negative errno on capability read failure + */ +int genphy_c45_oatc14_get_sqi_max(struct phy_device *phydev) +{ + int ret; + + if (!phydev->oatc14_sqi_capability.updated) { + ret = oatc14_update_sqi_capability(phydev); + if (ret) + return ret; + } + + return phydev->oatc14_sqi_capability.sqi_max; +} +EXPORT_SYMBOL(genphy_c45_oatc14_get_sqi_max); + +/** + * genphy_c45_oatc14_get_sqi - Get Signal Quality Indicator (SQI) from an OATC14 + * 10Base-T1S PHY + * @phydev: pointer to the PHY device structure + * + * This function reads the SQI+ or SQI value from an OATC14-compatible + * 10Base-T1S PHY. If SQI+ capability is supported, the function returns the + * extended SQI+ value; otherwise, it returns the basic SQI value. The SQI + * capability is updated on first invocation if it has not already been updated. + * + * Return: + * * SQI/SQI+ value on success + * * Negative errno on read failure + */ +int genphy_c45_oatc14_get_sqi(struct phy_device *phydev) +{ + u8 shift; + int ret; + + if (!phydev->oatc14_sqi_capability.updated) { + ret = oatc14_update_sqi_capability(phydev); + if (ret) + return ret; + } + + /* Calculate and return SQI+ value if supported */ + if (phydev->oatc14_sqi_capability.sqiplus_bits) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + MDIO_OATC14_DCQ_SQIPLUS); + if (ret < 0) + return ret; + + /* SQI+ uses N MSBs out of 8 bits, left-aligned with padding 1's + * Calculate the right-shift needed to isolate the N bits. + */ + shift = 8 - phydev->oatc14_sqi_capability.sqiplus_bits; + + return (ret & OATC14_DCQ_SQIPLUS_VALUE) >> shift; + } + + /* Read and return SQI value if SQI+ capability is not supported */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_DCQ_SQI); + if (ret < 0) + return ret; + + return ret & OATC14_DCQ_SQI_VALUE; +} +EXPORT_SYMBOL(genphy_c45_oatc14_get_sqi); diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h new file mode 100644 index 000000000..421088e6f --- /dev/null +++ b/drivers/net/phy/phy-caps.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * link caps internal header, for link modes <-> capabilities <-> interfaces + * conversions. + */ + +#ifndef __PHY_CAPS_H +#define __PHY_CAPS_H + +#include +#include + +enum { + LINK_CAPA_10HD = 0, + LINK_CAPA_10FD, + LINK_CAPA_100HD, + LINK_CAPA_100FD, + LINK_CAPA_1000HD, + LINK_CAPA_1000FD, + LINK_CAPA_2500FD, + LINK_CAPA_5000FD, + LINK_CAPA_10000FD, + LINK_CAPA_20000FD, + LINK_CAPA_25000FD, + LINK_CAPA_40000FD, + LINK_CAPA_50000FD, + LINK_CAPA_56000FD, + LINK_CAPA_80000FD, + LINK_CAPA_100000FD, + LINK_CAPA_200000FD, + LINK_CAPA_400000FD, + LINK_CAPA_800000FD, + LINK_CAPA_1600000FD, + + __LINK_CAPA_MAX, +}; + +#define LINK_CAPA_ALL GENMASK((__LINK_CAPA_MAX - 1), 0) + +struct link_capabilities { + int speed; + unsigned int duplex; + __ETHTOOL_DECLARE_LINK_MODE_MASK(linkmodes); +}; + +int __init phy_caps_init(void); + +size_t phy_caps_speeds(unsigned int *speeds, size_t size, + unsigned long *linkmodes); +void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes); +bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes); +void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes); +unsigned long phy_caps_from_interface(phy_interface_t interface); + +const struct link_capabilities * +phy_caps_lookup_by_linkmode(const unsigned long *linkmodes); + +const struct link_capabilities * +phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only); + +const struct link_capabilities * +phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported, + bool exact); + +void phy_caps_medium_get_supported(unsigned long *supported, + enum ethtool_link_medium medium, + int lanes); +u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes); + +#endif /* __PHY_CAPS_H */ diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c new file mode 100644 index 000000000..d7a4a977f --- /dev/null +++ b/drivers/net/phy/phy-core.c @@ -0,0 +1,903 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Core PHY library, taken from phy.c + */ +#include +#include +#include +#include + +#include "phylib.h" +#include "phylib-internal.h" +#include "phy-caps.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 != 125, + "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_80000: + return "80Gbps"; + case SPEED_100000: + return "100Gbps"; + case SPEED_200000: + return "200Gbps"; + case SPEED_400000: + return "400Gbps"; + case SPEED_800000: + return "800Gbps"; + case SPEED_1600000: + return "1600Gbps"; + 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_fix_phy_mode_for_mac_delays - Convenience function for fixing PHY + * mode based on whether mac adds internal delay + * + * @interface: The current interface mode of the port + * @mac_txid: True if the mac adds internal tx delay + * @mac_rxid: True if the mac adds internal rx delay + * + * Return: fixed PHY mode, or PHY_INTERFACE_MODE_NA if the interface can + * not apply the internal delay + */ +phy_interface_t phy_fix_phy_mode_for_mac_delays(phy_interface_t interface, + bool mac_txid, bool mac_rxid) +{ + if (!phy_interface_mode_is_rgmii(interface)) + return interface; + + if (mac_txid && mac_rxid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + if (mac_txid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII_RXID; + if (interface == PHY_INTERFACE_MODE_RGMII_TXID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + if (mac_rxid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII_TXID; + if (interface == PHY_INTERFACE_MODE_RGMII_RXID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + return interface; +} +EXPORT_SYMBOL_GPL(phy_fix_phy_mode_for_mac_delays); + +/** + * 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_MIILITE: + 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: + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + case PHY_INTERFACE_MODE_100GBASEP: + return 1; + case PHY_INTERFACE_MODE_QSGMII: + case PHY_INTERFACE_MODE_QUSGMII: + case PHY_INTERFACE_MODE_10G_QXGMII: + 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); + +static void __set_phy_supported(struct phy_device *phydev, u32 max_speed) +{ + struct phy_port *port; + + phy_caps_linkmode_max_speed(max_speed, phydev->supported); + + phy_for_each_port(phydev, port) + phy_caps_linkmode_max_speed(max_speed, port->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; + unsigned long *modes = phydev->eee_disabled_modes; + + if (!IS_ENABLED(CONFIG_OF_MDIO) || !node) + return; + + linkmode_zero(modes); + + if (of_property_read_bool(node, "eee-broken-100tx")) + linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, modes); + if (of_property_read_bool(node, "eee-broken-1000t")) + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, modes); + if (of_property_read_bool(node, "eee-broken-10gt")) + linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, modes); + if (of_property_read_bool(node, "eee-broken-1000kx")) + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, modes); + if (of_property_read_bool(node, "eee-broken-10gkx4")) + linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, modes); + if (of_property_read_bool(node, "eee-broken-10gkr")) + linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, modes); +} + +/** + * of_set_phy_timing_role - Set the master/slave mode of the PHY + * + * @phydev: The phy_device struct + * + * Set master/slave configuration of the PHY based on the device tree. + */ +void of_set_phy_timing_role(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + const char *master; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return; + + if (!node) + return; + + if (of_property_read_string(node, "timing-role", &master)) + return; + + if (strcmp(master, "forced-master") == 0) + phydev->master_slave_set = MASTER_SLAVE_CFG_MASTER_FORCE; + else if (strcmp(master, "forced-slave") == 0) + phydev->master_slave_set = MASTER_SLAVE_CFG_SLAVE_FORCE; + else if (strcmp(master, "preferred-master") == 0) + phydev->master_slave_set = MASTER_SLAVE_CFG_MASTER_PREFERRED; + else if (strcmp(master, "preferred-slave") == 0) + phydev->master_slave_set = MASTER_SLAVE_CFG_SLAVE_PREFERRED; + else + phydev_warn(phydev, "Unknown master-slave mode %s\n", master); +} + +/** + * 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); + const struct link_capabilities *c; + + linkmode_and(common, phydev->lp_advertising, phydev->advertising); + + c = phy_caps_lookup_by_linkmode(common); + if (c) { + phydev->speed = c->speed; + phydev->duplex = c->duplex; + } + + 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); + const struct link_capabilities *c; + int 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); + + c = phy_caps_lookup_by_linkmode(common); + if (c) + speed = c->speed; + + 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; +} + +static int phy_resolve_min_speed(struct phy_device *phydev, bool fdx_only) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(common); + const struct link_capabilities *c; + + linkmode_and(common, phydev->lp_advertising, phydev->advertising); + + c = phy_caps_lookup_by_linkmode_rev(common, fdx_only); + if (c) + return c->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; + + phy_caps_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); +} + +int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum) +{ + if (is_c45) + return __mdiobus_c45_read(bus, phy_addr, devad, regnum); + + mmd_phy_indirect(bus, phy_addr, devad, regnum); + /* Read the content of the MMD's selected register */ + return __mdiobus_read(bus, phy_addr, MII_MMD_DATA); +} +EXPORT_SYMBOL_GPL(mmd_phy_read); + +int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum, u16 val) +{ + if (is_c45) + return __mdiobus_c45_write(bus, phy_addr, devad, regnum, val); + + mmd_phy_indirect(bus, phy_addr, devad, regnum); + /* Write the data into MMD's selected register */ + return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val); +} +EXPORT_SYMBOL_GPL(mmd_phy_write); + +/** + * __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) +{ + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; + + if (phydev->drv && phydev->drv->read_mmd) + return phydev->drv->read_mmd(phydev, devad, regnum); + + return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr, + phydev->is_c45, devad, regnum); +} +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) +{ + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; + + if (phydev->drv && phydev->drv->write_mmd) + return phydev->drv->write_mmd(phydev, devad, regnum, val); + + return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr, + phydev->is_c45, devad, regnum, val); +} +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..fce9bc7be --- /dev/null +++ b/drivers/net/phy/phy.c @@ -0,0 +1,2106 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phylib-internal.h" +#include "phy-caps.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_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_caps_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_caps_valid(speed, duplex, features); +} +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 link_capabilities *c; + + c = phy_caps_lookup(phydev->speed, phydev->duplex, phydev->supported, + false); + + if (c) { + phydev->speed = c->speed; + phydev->duplex = c->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 = phydev->is_internal ? + 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); + struct kernel_hwtstamp_config kernel_cfg; + struct netlink_ext_ack extack = {}; + u16 val = mii_data->val_in; + bool change_autoneg = false; + struct hwtstamp_config cfg; + int prtad, devad; + int ret; + + 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); + ret = mdiobus_c45_read(phydev->mdio.bus, prtad, devad, + mii_data->reg_num); + + } else { + ret = mdiobus_read(phydev->mdio.bus, mii_data->phy_id, + mii_data->reg_num); + } + + if (ret < 0) + return ret; + + mii_data->val_out = ret; + + 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_set) { + if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) + return -EFAULT; + + hwtstamp_config_to_kernel(&kernel_cfg, &cfg); + ret = phydev->mii_ts->hwtstamp_set(phydev->mii_ts, + &kernel_cfg, + &extack); + if (ret) + return ret; + + hwtstamp_config_from_kernel(&cfg, &kernel_cfg); + if (copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg))) + return -EFAULT; + + return 0; + } + 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; + + if (phydev->mii_ts && phydev->mii_ts->hwtstamp_get) + return phydev->mii_ts->hwtstamp_get(phydev->mii_ts, config); + + return -EOPNOTSUPP; +} + +/** + * __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; + + if (phydev->mii_ts && phydev->mii_ts->hwtstamp_set) + return phydev->mii_ts->hwtstamp_set(phydev->mii_ts, config, + extack); + + return -EOPNOTSUPP; +} + +/** + * phy_queue_state_machine - Trigger the state machine to run soon + * + * @phydev: the phy_device struct + * @jiffies: Run the state machine after these jiffies + */ +static void phy_queue_state_machine(struct phy_device *phydev, + unsigned long jiffies) +{ + mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, + jiffies); +} + +/** + * 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_phy_stats - Retrieve standardized PHY statistics + * @phydev: Pointer to the PHY device + * @phy_stats: Pointer to ethtool_eth_phy_stats structure + * @phydev_stats: Pointer to ethtool_phy_stats structure + * + * Fetches PHY statistics using a kernel-defined interface for consistent + * diagnostics. Unlike phy_ethtool_get_stats(), which allows custom stats, + * this function enforces a standardized format for better interoperability. + */ +void __phy_ethtool_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *phy_stats, + struct ethtool_phy_stats *phydev_stats) +{ + if (!phydev->drv || !phydev->drv->get_phy_stats) + return; + + mutex_lock(&phydev->lock); + phydev->drv->get_phy_stats(phydev, phy_stats, phydev_stats); + mutex_unlock(&phydev->lock); +} + +/** + * __phy_ethtool_get_link_ext_stats - Retrieve extended link statistics for a PHY + * @phydev: Pointer to the PHY device + * @link_stats: Pointer to the structure to store extended link statistics + * + * Populates the ethtool_link_ext_stats structure with link down event counts + * and additional driver-specific link statistics, if available. + */ +void __phy_ethtool_get_link_ext_stats(struct phy_device *phydev, + struct ethtool_link_ext_stats *link_stats) +{ + link_stats->link_down_events = READ_ONCE(phydev->link_down_events); + + if (!phydev->drv || !phydev->drv->get_link_stats) + return; + + mutex_lock(&phydev->lock); + phydev->drv->get_link_stats(phydev, link_stats); + mutex_unlock(&phydev->lock); +} + +/** + * 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_obj(*curr_plca_cfg); + 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; + err = genphy_c45_eee_is_active(phydev, NULL); + phydev->eee_active = err > 0; + phydev->enable_tx_lpi = phydev->eee_cfg.tx_lpi_enabled && + phydev->eee_active; + + phy_link_up(phydev); + } else if (!phydev->link && phydev->state != PHY_NOLINK) { + phydev->state = PHY_NOLINK; + phydev->eee_active = false; + phydev->enable_tx_lpi = false; + phy_link_down(phydev); + } + + return 0; +} + +/** + * phy_inband_caps - query which in-band signalling modes are supported + * @phydev: a pointer to a &struct phy_device + * @interface: the interface mode for the PHY + * + * Returns zero if it is unknown what in-band signalling is supported by the + * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise, + * returns a bit mask of the LINK_INBAND_* values from + * &enum link_inband_signalling to describe which inband modes are supported + * by the PHY for this interface mode. + */ +unsigned int phy_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + if (phydev->drv && phydev->drv->inband_caps) + return phydev->drv->inband_caps(phydev, interface); + + return 0; +} +EXPORT_SYMBOL_GPL(phy_inband_caps); + +/** + * phy_config_inband - configure the desired PHY in-band mode + * @phydev: the phy_device struct + * @modes: in-band modes to configure + * + * Description: disables, enables or enables-with-bypass in-band signalling + * between the PHY and host system. + * + * Returns: zero on success, or negative errno value. + */ +int phy_config_inband(struct phy_device *phydev, unsigned int modes) +{ + lockdep_assert_held(&phydev->lock); + + if (!!(modes & LINK_INBAND_DISABLE) + + !!(modes & LINK_INBAND_ENABLE) + + !!(modes & LINK_INBAND_BYPASS) != 1) + return -EINVAL; + + if (!phydev->drv) + return -EIO; + else if (!phydev->drv->config_inband) + return -EOPNOTSUPP; + + return phydev->drv->config_inband(phydev, modes); +} +EXPORT_SYMBOL(phy_config_inband); + +/** + * _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; + + 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; +} +EXPORT_SYMBOL(_phy_start_aneg); + +/** + * 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) || + !linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + phydev->supported))) + 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); + phy_process_error(phydev); +} + +/** + * 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_write_barrier - ensure the last write completed for this PHY device + * @phydev: target phy_device struct + * + * MDIO bus controllers are not required to wait for write transactions to + * complete before returning. Calling this function ensures that the previous + * write has completed. + */ +static void phy_write_barrier(struct phy_device *phydev) +{ + if (mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, MII_PHYSID1) == + -EOPNOTSUPP) + mdiobus_c45_read(phydev->mdio.bus, phydev->mdio.addr, + MDIO_MMD_PMAPMD, MII_PHYSID1); +} + +/** + * phy_disable_interrupts - Disable the PHY interrupts from the PHY side + * @phydev: target phy_device struct + */ +int phy_disable_interrupts(struct phy_device *phydev) +{ + int err; + + /* Disable PHY interrupts */ + err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + if (err) + return err; + + phy_write_barrier(phydev); + return 0; +} + +/** + * 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; + 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->ethtool->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 = phydev->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_update_stats - Update PHY device statistics if supported. + * @phydev: Pointer to the PHY device structure. + * + * If the PHY driver provides an update_stats callback, this function + * invokes it to update the PHY statistics. If not, it returns 0. + * + * Return: 0 on success, or a negative error code if the callback fails. + */ +static int phy_update_stats(struct phy_device *phydev) +{ + if (!phydev->drv->update_stats) + return 0; + + return phydev->drv->update_stats(phydev); +} + +/** + * 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_get_next_update_time - Determine the next PHY update time + * @phydev: Pointer to the phy_device structure + * + * This function queries the PHY driver to get the time for the next polling + * event. If the driver does not implement the callback, a default value is + * used. + * + * Return: The time for the next polling event in jiffies + */ +static unsigned int phy_get_next_update_time(struct phy_device *phydev) +{ + if (phydev->drv && phydev->drv->get_next_update_time) + return phydev->drv->get_next_update_time(phydev); + + return PHY_STATE_TIME; +} + +enum phy_state_work { + PHY_STATE_WORK_NONE, + PHY_STATE_WORK_ANEG, + PHY_STATE_WORK_SUSPEND, +}; + +static enum phy_state_work _phy_state_machine(struct phy_device *phydev) +{ + enum phy_state_work state_work = PHY_STATE_WORK_NONE; + struct net_device *dev = phydev->attached_dev; + enum phy_state old_state = phydev->state; + const void *func = NULL; + bool finished = false; + int err = 0; + + switch (phydev->state) { + case PHY_DOWN: + case PHY_READY: + break; + case PHY_UP: + state_work = PHY_STATE_WORK_ANEG; + break; + case PHY_NOLINK: + case PHY_RUNNING: + err = phy_check_link_status(phydev); + func = &phy_check_link_status; + + if (!err) + err = phy_update_stats(phydev); + break; + case PHY_CABLETEST: + err = phydev->drv->cable_test_get_status(phydev, &finished); + if (err) { + phy_abort_cable_test(phydev); + netif_testing_off(dev); + state_work = PHY_STATE_WORK_ANEG; + phydev->state = PHY_UP; + break; + } + + if (finished) { + ethnl_cable_test_finished(phydev); + netif_testing_off(dev); + state_work = PHY_STATE_WORK_ANEG; + phydev->state = PHY_UP; + } + break; + case PHY_HALTED: + if (phydev->link) { + if (phydev->autoneg == AUTONEG_ENABLE) { + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + } + if (phydev->master_slave_state != + MASTER_SLAVE_STATE_UNSUPPORTED) + phydev->master_slave_state = + MASTER_SLAVE_STATE_UNKNOWN; + phydev->mdix = ETH_TP_MDI_INVALID; + linkmode_zero(phydev->lp_advertising); + } + fallthrough; + case PHY_ERROR: + if (phydev->link) { + phydev->link = 0; + phydev->eee_active = false; + phydev->enable_tx_lpi = false; + phy_link_down(phydev); + } + state_work = PHY_STATE_WORK_SUSPEND; + break; + } + + if (state_work == PHY_STATE_WORK_ANEG) { + err = _phy_start_aneg(phydev); + func = &_phy_start_aneg; + } + + if (err == -ENODEV) + return state_work; + + 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. + */ + if (phy_polling_mode(phydev) && phy_is_started(phydev)) + phy_queue_state_machine(phydev, + phy_get_next_update_time(phydev)); + + return state_work; +} + +/* unlocked part of the PHY state machine */ +static void _phy_state_machine_post_work(struct phy_device *phydev, + enum phy_state_work state_work) +{ + if (state_work == PHY_STATE_WORK_SUSPEND) + phy_suspend(phydev); +} + +/** + * phy_state_machine - Handle the state machine + * @work: work_struct that describes the work to be done + */ +void phy_state_machine(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct phy_device *phydev = + container_of(dwork, struct phy_device, state_queue); + enum phy_state_work state_work; + + mutex_lock(&phydev->lock); + state_work = _phy_state_machine(phydev); + mutex_unlock(&phydev->lock); + + _phy_state_machine_post_work(phydev, state_work); +} + +/** + * 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_work state_work; + 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); + + state_work = _phy_state_machine(phydev); + mutex_unlock(&phydev->lock); + + _phy_state_machine_post_work(phydev, state_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_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_loopback - Configure loopback mode of PHY + * @phydev: target phy_device struct + * @enable: enable or disable loopback mode + * @speed: enable loopback mode with speed + * + * Configure loopback mode of PHY and signal link down and link up if speed is + * changing. + * + * Return: 0 on success, negative error code on failure. + */ +int phy_loopback(struct phy_device *phydev, bool enable, int speed) +{ + bool link_up = false; + 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 (enable) { + /* + * Link up is signaled with a defined speed. If speed changes, + * then first link down and after that link up needs to be + * signaled. + */ + if (phydev->link && phydev->state == PHY_RUNNING) { + /* link is up and signaled */ + if (speed && phydev->speed != speed) { + /* signal link down and up for new speed */ + phydev->link = false; + phydev->state = PHY_NOLINK; + phy_link_down(phydev); + + link_up = true; + } + } else { + /* link is not signaled */ + if (speed) { + /* signal link up for new speed */ + link_up = true; + } + } + } + + if (phydev->drv->set_loopback) + ret = phydev->drv->set_loopback(phydev, enable, speed); + else + ret = genphy_loopback(phydev, enable, speed); + + if (ret) { + if (enable) { + /* try to restore link if enabling loopback fails */ + if (phydev->drv->set_loopback) + phydev->drv->set_loopback(phydev, false, 0); + else + genphy_loopback(phydev, false, 0); + } + + goto out; + } + + if (link_up) { + phydev->link = true; + phydev->state = PHY_RUNNING; + phy_link_up(phydev); + } + + phydev->loopback_enabled = enable; + +out: + mutex_unlock(&phydev->lock); + return ret; +} +EXPORT_SYMBOL(phy_loopback); + +/** + * phy_eee_tx_clock_stop_capable() - indicate whether the MAC can stop tx clock + * @phydev: target phy_device struct + * + * Indicate whether the MAC can disable the transmit xMII clock while in LPI + * state. Returns 1 if the MAC may stop the transmit clock, 0 if the MAC must + * not stop the transmit clock, or negative error. + */ +int phy_eee_tx_clock_stop_capable(struct phy_device *phydev) +{ + int stat1; + + stat1 = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); + if (stat1 < 0) + return stat1; + + return !!(stat1 & MDIO_PCS_STAT1_CLKSTOP_CAP); +} +EXPORT_SYMBOL_GPL(phy_eee_tx_clock_stop_capable); + +/** + * phy_eee_rx_clock_stop() - configure PHY receive clock in LPI + * @phydev: target phy_device struct + * @clk_stop_enable: flag to indicate whether the clock can be stopped + * + * Configure whether the PHY can disable its receive clock during LPI mode, + * See IEEE 802.3 sections 22.2.2.2, 35.2.2.10, and 45.2.3.1.4. + * + * Returns: 0 or negative error. + */ +int phy_eee_rx_clock_stop(struct phy_device *phydev, bool clk_stop_enable) +{ + /* Configure the PHY to stop receiving xMII + * clock while it is signaling LPI. + */ + return phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, + MDIO_PCS_CTRL1_CLKSTOP_EN, + clk_stop_enable ? MDIO_PCS_CTRL1_CLKSTOP_EN : 0); +} +EXPORT_SYMBOL_GPL(phy_eee_rx_clock_stop); + +/** + * 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); + if (ret < 0) + return ret; + if (!ret) + return -EPROTONOSUPPORT; + + if (clk_stop_enable) + ret = phy_eee_rx_clock_stop(phydev, true); + + 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_keee data + * + * Description: get the current EEE settings, filling in all members of + * @data. + */ +int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_keee *data) +{ + int ret; + + if (!phydev->drv) + return -EIO; + + mutex_lock(&phydev->lock); + ret = genphy_c45_ethtool_get_eee(phydev, data); + eeecfg_to_eee(data, &phydev->eee_cfg); + mutex_unlock(&phydev->lock); + + return ret; +} +EXPORT_SYMBOL(phy_ethtool_get_eee); + +/** + * phy_ethtool_set_eee_noneg - Adjusts MAC LPI configuration without PHY + * renegotiation + * @phydev: pointer to the target PHY device structure + * @old_cfg: pointer to the eee_config structure containing the old EEE settings + * + * This function updates the Energy Efficient Ethernet (EEE) configuration + * for cases where only the MAC's Low Power Idle (LPI) configuration changes, + * without triggering PHY renegotiation. It ensures that the MAC is properly + * informed of the new LPI settings by cycling the link down and up, which + * is necessary for the MAC to adopt the new configuration. This adjustment + * is done only if there is a change in the tx_lpi_enabled or tx_lpi_timer + * configuration. + */ +static void phy_ethtool_set_eee_noneg(struct phy_device *phydev, + const struct eee_config *old_cfg) +{ + bool enable_tx_lpi; + + if (!phydev->link) + return; + + enable_tx_lpi = phydev->eee_cfg.tx_lpi_enabled && phydev->eee_active; + + if (phydev->enable_tx_lpi != enable_tx_lpi || + phydev->eee_cfg.tx_lpi_timer != old_cfg->tx_lpi_timer) { + phydev->enable_tx_lpi = false; + phydev->link = false; + phy_link_down(phydev); + phydev->enable_tx_lpi = enable_tx_lpi; + phydev->link = true; + phy_link_up(phydev); + } +} + +/** + * phy_ethtool_set_eee - set EEE supported and status + * @phydev: target phy_device struct + * @data: ethtool_keee data + * + * Description: it is to program the Advertisement EEE register. + */ +int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data) +{ + struct eee_config old_cfg; + int ret; + + if (!phydev->drv) + return -EIO; + + mutex_lock(&phydev->lock); + + old_cfg = phydev->eee_cfg; + eee_to_eeecfg(&phydev->eee_cfg, data); + + ret = genphy_c45_ethtool_set_eee(phydev, data); + if (ret == 0) + phy_ethtool_set_eee_noneg(phydev, &old_cfg); + else if (ret < 0) + phydev->eee_cfg = old_cfg; + + mutex_unlock(&phydev->lock); + + return ret < 0 ? ret : 0; +} +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_caps.c b/drivers/net/phy/phy_caps.c new file mode 100644 index 000000000..942d43191 --- /dev/null +++ b/drivers/net/phy/phy_caps.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "phy-caps.h" + +static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = { + { SPEED_10, DUPLEX_HALF, {0} }, /* LINK_CAPA_10HD */ + { SPEED_10, DUPLEX_FULL, {0} }, /* LINK_CAPA_10FD */ + { SPEED_100, DUPLEX_HALF, {0} }, /* LINK_CAPA_100HD */ + { SPEED_100, DUPLEX_FULL, {0} }, /* LINK_CAPA_100FD */ + { SPEED_1000, DUPLEX_HALF, {0} }, /* LINK_CAPA_1000HD */ + { SPEED_1000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1000FD */ + { SPEED_2500, DUPLEX_FULL, {0} }, /* LINK_CAPA_2500FD */ + { SPEED_5000, DUPLEX_FULL, {0} }, /* LINK_CAPA_5000FD */ + { SPEED_10000, DUPLEX_FULL, {0} }, /* LINK_CAPA_10000FD */ + { SPEED_20000, DUPLEX_FULL, {0} }, /* LINK_CAPA_20000FD */ + { SPEED_25000, DUPLEX_FULL, {0} }, /* LINK_CAPA_25000FD */ + { SPEED_40000, DUPLEX_FULL, {0} }, /* LINK_CAPA_40000FD */ + { SPEED_50000, DUPLEX_FULL, {0} }, /* LINK_CAPA_50000FD */ + { SPEED_56000, DUPLEX_FULL, {0} }, /* LINK_CAPA_56000FD */ + { SPEED_80000, DUPLEX_FULL, {0} }, /* LINK_CAPA_80000FD */ + { SPEED_100000, DUPLEX_FULL, {0} }, /* LINK_CAPA_100000FD */ + { SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */ + { SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */ + { SPEED_800000, DUPLEX_FULL, {0} }, /* LINK_CAPA_800000FD */ + { SPEED_1600000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1600000FD */ +}; + +static int speed_duplex_to_capa(int speed, unsigned int duplex) +{ + if (duplex == DUPLEX_UNKNOWN || + (speed > SPEED_1000 && duplex != DUPLEX_FULL)) + return -EINVAL; + + switch (speed) { + case SPEED_10: return duplex == DUPLEX_FULL ? + LINK_CAPA_10FD : LINK_CAPA_10HD; + case SPEED_100: return duplex == DUPLEX_FULL ? + LINK_CAPA_100FD : LINK_CAPA_100HD; + case SPEED_1000: return duplex == DUPLEX_FULL ? + LINK_CAPA_1000FD : LINK_CAPA_1000HD; + case SPEED_2500: return LINK_CAPA_2500FD; + case SPEED_5000: return LINK_CAPA_5000FD; + case SPEED_10000: return LINK_CAPA_10000FD; + case SPEED_20000: return LINK_CAPA_20000FD; + case SPEED_25000: return LINK_CAPA_25000FD; + case SPEED_40000: return LINK_CAPA_40000FD; + case SPEED_50000: return LINK_CAPA_50000FD; + case SPEED_56000: return LINK_CAPA_56000FD; + case SPEED_80000: return LINK_CAPA_80000FD; + case SPEED_100000: return LINK_CAPA_100000FD; + case SPEED_200000: return LINK_CAPA_200000FD; + case SPEED_400000: return LINK_CAPA_400000FD; + case SPEED_800000: return LINK_CAPA_800000FD; + case SPEED_1600000: return LINK_CAPA_1600000FD; + } + + return -EINVAL; +} + +#define for_each_link_caps_asc_speed(cap) \ + for (cap = link_caps; cap < &link_caps[__LINK_CAPA_MAX]; cap++) + +#define for_each_link_caps_desc_speed(cap) \ + for (cap = &link_caps[__LINK_CAPA_MAX - 1]; cap >= link_caps; cap--) + +/** + * phy_caps_init() - Initializes the link_caps array from the link_mode_params. + * + * Returns: 0 if phy caps init was successful, -EINVAL if we found an + * unexpected linkmode setting that requires LINK_CAPS update. + * + */ +int __init phy_caps_init(void) +{ + const struct link_mode_info *linkmode; + int i, capa; + + /* Fill the caps array from net/ethtool/common.c */ + for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) { + linkmode = &link_mode_params[i]; + + /* Sanity check the linkmodes array for number of pairs */ + if (linkmode->pairs < linkmode->min_pairs) { + pr_err("Pairs count must not be under min_pairs for linkmode %d\n", + i); + return -EINVAL; + } + + capa = speed_duplex_to_capa(linkmode->speed, linkmode->duplex); + + if (capa < 0) { + if (linkmode->speed != SPEED_UNKNOWN) { + pr_err("Unknown speed %d, please update LINK_CAPS\n", + linkmode->speed); + return -EINVAL; + } + continue; + } + + __set_bit(i, link_caps[capa].linkmodes); + } + + return 0; +} + +/** + * phy_caps_speeds() - Fill an array of supported SPEED_* values for given modes + * @speeds: Output array to store the speeds list into + * @size: Size of the output array + * @linkmodes: Linkmodes to get the speeds from + * + * Fills the speeds array with all possible speeds that can be achieved with + * the specified linkmodes. + * + * Returns: The number of speeds filled into the array. If the input array isn't + * big enough to store all speeds, fill it as much as possible. + */ +size_t phy_caps_speeds(unsigned int *speeds, size_t size, + unsigned long *linkmodes) +{ + struct link_capabilities *lcap; + size_t count = 0; + + for_each_link_caps_asc_speed(lcap) { + if (linkmode_intersects(lcap->linkmodes, linkmodes) && + (count == 0 || speeds[count - 1] != lcap->speed)) { + speeds[count++] = lcap->speed; + if (count >= size) + break; + } + } + + return count; +} + +/** + * phy_caps_lookup_by_linkmode() - Lookup the fastest matching link_capabilities + * @linkmodes: Linkmodes to match against + * + * Returns: The highest-speed link_capabilities that intersects the given + * linkmodes. In case several DUPLEX_ options exist at that speed, + * DUPLEX_FULL is matched first. NULL is returned if no match. + */ +const struct link_capabilities * +phy_caps_lookup_by_linkmode(const unsigned long *linkmodes) +{ + struct link_capabilities *lcap; + + for_each_link_caps_desc_speed(lcap) + if (linkmode_intersects(lcap->linkmodes, linkmodes)) + return lcap; + + return NULL; +} + +/** + * phy_caps_lookup_by_linkmode_rev() - Lookup the slowest matching link_capabilities + * @linkmodes: Linkmodes to match against + * @fdx_only: Full duplex match only when set + * + * Returns: The lowest-speed link_capabilities that intersects the given + * linkmodes. When set, fdx_only will ignore half-duplex matches. + * NULL is returned if no match. + */ +const struct link_capabilities * +phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only) +{ + struct link_capabilities *lcap; + + for_each_link_caps_asc_speed(lcap) { + if (fdx_only && lcap->duplex != DUPLEX_FULL) + continue; + + if (linkmode_intersects(lcap->linkmodes, linkmodes)) + return lcap; + } + + return NULL; +} + +/** + * phy_caps_lookup() - Lookup capabilities by speed/duplex that matches a mask + * @speed: Speed to match + * @duplex: Duplex to match + * @supported: Mask of linkmodes to match + * @exact: Perform an exact match or not. + * + * Lookup a link_capabilities entry that intersect the supported linkmodes mask, + * and that matches the passed speed and duplex. + * + * When @exact is set, an exact match is performed on speed and duplex, meaning + * that if the linkmodes for the given speed and duplex intersect the supported + * mask, this capability is returned, otherwise we don't have a match and return + * NULL. + * + * When @exact is not set, we return either an exact match, or matching capabilities + * at lower speed, or the lowest matching speed, or NULL. + * + * Non-exact matches will try to return an exact speed and duplex match, but may + * return matching capabilities with same speed but a different duplex. + * + * Returns: a matched link_capabilities according to the above process, NULL + * otherwise. + */ +const struct link_capabilities * +phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported, + bool exact) +{ + const struct link_capabilities *lcap, *match = NULL, *last = NULL; + + for_each_link_caps_desc_speed(lcap) { + if (linkmode_intersects(lcap->linkmodes, supported)) { + last = lcap; + /* exact match on speed and duplex*/ + if (lcap->speed == speed && lcap->duplex == duplex) { + return lcap; + } else if (!exact) { + if (!match && lcap->speed <= speed) + match = lcap; + + if (lcap->speed < speed) + break; + } + } + } + + if (!match && !exact) + match = last; + + return match; +} +EXPORT_SYMBOL_GPL(phy_caps_lookup); + +/** + * phy_caps_linkmode_max_speed() - Clamp a linkmodes set to a max speed + * @max_speed: Speed limit for the linkmode set + * @linkmodes: Linkmodes to limit + */ +void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes) +{ + struct link_capabilities *lcap; + + for_each_link_caps_desc_speed(lcap) + if (lcap->speed > max_speed) + linkmode_andnot(linkmodes, linkmodes, lcap->linkmodes); + else + break; +} + +/** + * phy_caps_valid() - Validate a linkmodes set agains given speed and duplex + * @speed: input speed to validate + * @duplex: input duplex to validate. Passing DUPLEX_UNKNOWN is always not valid + * @linkmodes: The linkmodes to validate + * + * Returns: True if at least one of the linkmodes in @linkmodes can function at + * the given speed and duplex, false otherwise. + */ +bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes) +{ + int capa = speed_duplex_to_capa(speed, duplex); + + if (capa < 0) + return false; + + return linkmode_intersects(link_caps[capa].linkmodes, linkmodes); +} + +/** + * phy_caps_linkmodes() - Convert a bitfield of capabilities into linkmodes + * @caps: The list of caps, each bit corresponding to a LINK_CAPA value + * @linkmodes: The set of linkmodes to fill. Must be previously initialized. + */ +void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes) +{ + unsigned long capa; + + for_each_set_bit(capa, &caps, __LINK_CAPA_MAX) + linkmode_or(linkmodes, linkmodes, link_caps[capa].linkmodes); +} +EXPORT_SYMBOL_GPL(phy_caps_linkmodes); + +/** + * phy_caps_from_interface() - Get the link capa from a given PHY interface + * @interface: The PHY interface we want to get the possible Speed/Duplex from + * + * Returns: A bitmask of LINK_CAPA_xxx values that can be achieved with the + * provided interface. + */ +unsigned long phy_caps_from_interface(phy_interface_t interface) +{ + unsigned long link_caps = 0; + + switch (interface) { + case PHY_INTERFACE_MODE_USXGMII: + link_caps |= BIT(LINK_CAPA_10000FD) | BIT(LINK_CAPA_5000FD); + fallthrough; + + case PHY_INTERFACE_MODE_10G_QXGMII: + link_caps |= BIT(LINK_CAPA_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: + link_caps |= BIT(LINK_CAPA_1000HD) | BIT(LINK_CAPA_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: + link_caps |= BIT(LINK_CAPA_10HD) | BIT(LINK_CAPA_10FD); + fallthrough; + + case PHY_INTERFACE_MODE_100BASEX: + link_caps |= BIT(LINK_CAPA_100HD) | BIT(LINK_CAPA_100FD); + break; + + case PHY_INTERFACE_MODE_MIILITE: + link_caps |= BIT(LINK_CAPA_10FD) | BIT(LINK_CAPA_100FD); + break; + + case PHY_INTERFACE_MODE_TBI: + case PHY_INTERFACE_MODE_MOCA: + case PHY_INTERFACE_MODE_RTBI: + case PHY_INTERFACE_MODE_1000BASEX: + link_caps |= BIT(LINK_CAPA_1000HD); + fallthrough; + case PHY_INTERFACE_MODE_1000BASEKX: + case PHY_INTERFACE_MODE_TRGMII: + link_caps |= BIT(LINK_CAPA_1000FD); + break; + + case PHY_INTERFACE_MODE_2500BASEX: + link_caps |= BIT(LINK_CAPA_2500FD); + break; + + case PHY_INTERFACE_MODE_5GBASER: + link_caps |= BIT(LINK_CAPA_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: + link_caps |= BIT(LINK_CAPA_10000FD); + break; + + case PHY_INTERFACE_MODE_25GBASER: + link_caps |= BIT(LINK_CAPA_25000FD); + break; + + case PHY_INTERFACE_MODE_XLGMII: + link_caps |= BIT(LINK_CAPA_40000FD); + break; + + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + link_caps |= BIT(LINK_CAPA_50000FD); + break; + + case PHY_INTERFACE_MODE_100GBASEP: + link_caps |= BIT(LINK_CAPA_100000FD); + break; + + case PHY_INTERFACE_MODE_INTERNAL: + link_caps |= LINK_CAPA_ALL; + break; + + case PHY_INTERFACE_MODE_NA: + case PHY_INTERFACE_MODE_MAX: + break; + } + + return link_caps; +} +EXPORT_SYMBOL_GPL(phy_caps_from_interface); + +/** + * phy_caps_medium_get_supported() - Returns linkmodes supported on a given medium + * @supported: After this call, contains all possible linkmodes on a given medium, + * and with the given number of pairs, or less. + * @medium: The medium to get the support from + * @pairs: The number of pairs used on the given medium. Only relevant for modes + * that support this notion, such as BaseT. Pass 0 if not applicable. + * + * If no match exists, the supported field is left untouched. + */ +void phy_caps_medium_get_supported(unsigned long *supported, + enum ethtool_link_medium medium, + int pairs) +{ + int i; + + for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) { + /* Special bits such as Autoneg, Pause, Asym_pause, etc. are + * set and will be masked away by the port parent. + */ + if (link_mode_params[i].mediums == BIT(ETHTOOL_LINK_MEDIUM_NONE)) { + linkmode_set_bit(i, supported); + continue; + } + + /* If this medium matches, and had a non-zero min-pairs */ + if (link_mode_params[i].mediums & BIT(medium) && + (!link_mode_params[i].min_pairs || + (link_mode_params[i].min_pairs <= pairs && + link_mode_params[i].pairs >= pairs))) + linkmode_set_bit(i, supported); + } +} +EXPORT_SYMBOL_GPL(phy_caps_medium_get_supported); + +/** + * phy_caps_mediums_from_linkmodes() - Get all mediums from a linkmodes list + * @linkmodes: A bitset of linkmodes to get the mediums from + * + * Returns: A bitset of ETHTOOL_MEDIUM_XXX values corresponding to all medium + * types in the linkmodes list + */ +u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes) +{ + const struct link_mode_info *linkmode; + u32 mediums = 0; + int i; + + for_each_set_bit(i, linkmodes, __ETHTOOL_LINK_MODE_MASK_NBITS) { + linkmode = &link_mode_params[i]; + mediums |= linkmode->mediums; + } + + return mediums; +} +EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c new file mode 100644 index 000000000..94b2e85e0 --- /dev/null +++ b/drivers/net/phy/phy_device.c @@ -0,0 +1,4038 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phylib-internal.h" +#include "phy-caps.h" + +MODULE_DESCRIPTION("PHY library"); +MODULE_AUTHOR("Andy Fleming"); +MODULE_LICENSE("GPL"); + +struct phy_fixup { + struct list_head list; + char bus_id[MII_BUS_ID_SIZE + 3]; + u32 phy_uid; + u32 phy_uid_mask; + int (*run)(struct phy_device *phydev); +}; + +static struct phy_driver genphy_c45_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic Clause 45 PHY", + .read_status = genphy_c45_read_status, +}; + +__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_10gbit_features) __ro_after_init; +EXPORT_SYMBOL_GPL(phy_10gbit_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); + +static const int phy_all_ports_features_array[7] __initconst = { + 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, +}; + +static const int phy_10_100_features_array[4] __initconst = { + ETHTOOL_LINK_MODE_10baseT_Half_BIT, + ETHTOOL_LINK_MODE_10baseT_Full_BIT, + ETHTOOL_LINK_MODE_100baseT_Half_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, +}; + +static const int phy_basic_t1_features_array[3] __initconst = { + ETHTOOL_LINK_MODE_TP_BIT, + ETHTOOL_LINK_MODE_10baseT1L_Full_BIT, + ETHTOOL_LINK_MODE_100baseT1_Full_BIT, +}; + +static const int phy_basic_t1s_p2mp_features_array[2] __initconst = { + ETHTOOL_LINK_MODE_TP_BIT, + ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT, +}; + +static const int phy_gbit_features_array[2] __initconst = { + ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, +}; + +static const int phy_eee_cap1_features_array[] __initconst = { + 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 const int phy_eee_cap2_features_array[] __initconst = { + ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + ETHTOOL_LINK_MODE_5000baseT_Full_BIT, +}; + +__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_eee_cap2_features) __ro_after_init; +EXPORT_SYMBOL_GPL(phy_eee_cap2_features); + +static void __init 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(ETHTOOL_LINK_MODE_FIBRE_BIT, phy_gbit_fibre_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(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + phy_10gbit_features); + + linkmode_set_bit_array(phy_eee_cap1_features_array, + ARRAY_SIZE(phy_eee_cap1_features_array), + phy_eee_cap1_features); + linkmode_set_bit_array(phy_eee_cap2_features_array, + ARRAY_SIZE(phy_eee_cap2_features_array), + phy_eee_cap2_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 phy_drv_wol_enabled(struct phy_device *phydev) +{ + struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; + + phy_ethtool_get_wol(phydev, &wol); + + return wol.wolopts != 0; +} + +bool phy_may_wakeup(struct phy_device *phydev) +{ + /* If the PHY is using driver-model based wakeup, use that state. */ + if (phy_can_wakeup(phydev)) + return device_may_wakeup(&phydev->mdio.dev); + + return phy_drv_wol_enabled(phydev); +} +EXPORT_SYMBOL_GPL(phy_may_wakeup); + +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_uses_state_machine - test whether consumer driver uses PAL state machine + * @phydev: the target PHY device structure + * + * Ultimately, this aims to indirectly determine whether the PHY is attached + * to a consumer which uses the state machine by calling phy_start() and + * phy_stop(). + * + * When the PHY driver consumer uses phylib, it must have previously called + * phy_connect_direct() or one of its derivatives, so that phy_prepare_link() + * has set up a hook for monitoring state changes. + * + * When the PHY driver is used by the MAC driver consumer through phylink (the + * only other provider of a phy_link_change() method), using the PHY state + * machine is not optional. + * + * Return: true if consumer calls phy_start() and phy_stop(), false otherwise. + */ +static bool phy_uses_state_machine(struct phy_device *phydev) +{ + if (phydev->phy_link_change == phy_link_change) + return phydev->attached_dev && phydev->adjust_link; + + return !!phydev->phy_link_change; +} + +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; + + /* If the PHY on the mido bus is not attached but has WOL enabled + * we cannot suspend the PHY. + */ + if (!netdev && phy_may_wakeup(phydev)) + 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->ethtool->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 (phy_uses_state_machine(phydev)) + 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 (phy_uses_state_machine(phydev)) + 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 NULL) + * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY) + * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before + * comparison (or 0 to disable id-based matching) + * @run: The actual code to be run when a matching PHY is found + */ +static 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_obj(*fixup); + + if (!fixup) + return -ENOMEM; + + if (bus_id) + 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; +} + +/* 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(NULL, 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, 0, 0, run); +} +EXPORT_SYMBOL(phy_register_fixup_for_id); + +static bool phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) +{ + if (!strcmp(fixup->bus_id, phydev_name(phydev))) + return true; + + if (fixup->phy_uid_mask && + phy_id_compare(phydev->phy_id, fixup->phy_uid, fixup->phy_uid_mask)) + return true; + + return false; +} + +/* 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; +} + +/** + * genphy_match_phy_device - match a PHY device with a PHY driver + * @phydev: target phy_device struct + * @phydrv: target phy_driver struct + * + * Description: Checks whether the given PHY device matches the specified + * PHY driver. For Clause 45 PHYs, iterates over the available device + * identifiers and compares them against the driver's expected PHY ID, + * applying the provided mask. For Clause 22 PHYs, a direct ID comparison + * is performed. + * + * Return: 1 if the PHY device matches the driver, 0 otherwise. + */ +int genphy_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + if (phydev->is_c45) { + const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids); + int i; + + 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; + } + + return phy_id_compare(phydev->phy_id, phydrv->phy_id, + phydrv->phy_id_mask); +} +EXPORT_SYMBOL_GPL(genphy_match_phy_device); + +static int phy_bus_match(struct device *dev, const struct device_driver *drv) +{ + struct phy_device *phydev = to_phy_device(dev); + const struct phy_driver *phydrv = to_phy_driver(drv); + + if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY)) + return 0; + + if (phydrv->match_phy_device) + return phydrv->match_phy_device(phydev, phydrv); + + return genphy_match_phy_device(phydev, phydrv); +} + +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 (phydev->is_internal) + 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, +}; + +static const struct attribute_group phy_dev_group = { + .attrs = phy_dev_attrs, +}; + +#define MMD_DEVICE_ID_ATTR(n) \ +static ssize_t mmd##n##_device_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->c45_ids.device_ids[n]); \ +} \ +static DEVICE_ATTR_RO(mmd##n##_device_id) + +MMD_DEVICE_ID_ATTR(1); +MMD_DEVICE_ID_ATTR(2); +MMD_DEVICE_ID_ATTR(3); +MMD_DEVICE_ID_ATTR(4); +MMD_DEVICE_ID_ATTR(5); +MMD_DEVICE_ID_ATTR(6); +MMD_DEVICE_ID_ATTR(7); +MMD_DEVICE_ID_ATTR(8); +MMD_DEVICE_ID_ATTR(9); +MMD_DEVICE_ID_ATTR(10); +MMD_DEVICE_ID_ATTR(11); +MMD_DEVICE_ID_ATTR(12); +MMD_DEVICE_ID_ATTR(13); +MMD_DEVICE_ID_ATTR(14); +MMD_DEVICE_ID_ATTR(15); +MMD_DEVICE_ID_ATTR(16); +MMD_DEVICE_ID_ATTR(17); +MMD_DEVICE_ID_ATTR(18); +MMD_DEVICE_ID_ATTR(19); +MMD_DEVICE_ID_ATTR(20); +MMD_DEVICE_ID_ATTR(21); +MMD_DEVICE_ID_ATTR(22); +MMD_DEVICE_ID_ATTR(23); +MMD_DEVICE_ID_ATTR(24); +MMD_DEVICE_ID_ATTR(25); +MMD_DEVICE_ID_ATTR(26); +MMD_DEVICE_ID_ATTR(27); +MMD_DEVICE_ID_ATTR(28); +MMD_DEVICE_ID_ATTR(29); +MMD_DEVICE_ID_ATTR(30); +MMD_DEVICE_ID_ATTR(31); + +static struct attribute *phy_mmd_attrs[] = { + &dev_attr_mmd1_device_id.attr, + &dev_attr_mmd2_device_id.attr, + &dev_attr_mmd3_device_id.attr, + &dev_attr_mmd4_device_id.attr, + &dev_attr_mmd5_device_id.attr, + &dev_attr_mmd6_device_id.attr, + &dev_attr_mmd7_device_id.attr, + &dev_attr_mmd8_device_id.attr, + &dev_attr_mmd9_device_id.attr, + &dev_attr_mmd10_device_id.attr, + &dev_attr_mmd11_device_id.attr, + &dev_attr_mmd12_device_id.attr, + &dev_attr_mmd13_device_id.attr, + &dev_attr_mmd14_device_id.attr, + &dev_attr_mmd15_device_id.attr, + &dev_attr_mmd16_device_id.attr, + &dev_attr_mmd17_device_id.attr, + &dev_attr_mmd18_device_id.attr, + &dev_attr_mmd19_device_id.attr, + &dev_attr_mmd20_device_id.attr, + &dev_attr_mmd21_device_id.attr, + &dev_attr_mmd22_device_id.attr, + &dev_attr_mmd23_device_id.attr, + &dev_attr_mmd24_device_id.attr, + &dev_attr_mmd25_device_id.attr, + &dev_attr_mmd26_device_id.attr, + &dev_attr_mmd27_device_id.attr, + &dev_attr_mmd28_device_id.attr, + &dev_attr_mmd29_device_id.attr, + &dev_attr_mmd30_device_id.attr, + &dev_attr_mmd31_device_id.attr, + NULL +}; + +static umode_t phy_mmd_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct phy_device *phydev = to_phy_device(dev); + const int i = index + 1; + + if (!phydev->is_c45) + return 0; + if (i >= ARRAY_SIZE(phydev->c45_ids.device_ids) || + phydev->c45_ids.device_ids[i] == 0xffffffff) + return 0; + + return attr->mode; +} + +static const struct attribute_group phy_mmd_group = { + .name = "c45_phy_ids", + .attrs = phy_mmd_attrs, + .is_visible = phy_mmd_is_visible, +}; + +static const struct attribute_group *phy_device_groups[] = { + &phy_dev_group, + &phy_mmd_group, + NULL, +}; + +static const struct device_type mdio_bus_phy_type = { + .name = "PHY", + .groups = phy_device_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_obj(*dev); + 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; + mdiodev->reset_state = -1; + + dev->speed = SPEED_UNKNOWN; + dev->duplex = DUPLEX_UNKNOWN; + dev->pause = false; + dev->asym_pause = false; + 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); + INIT_LIST_HEAD(&dev->ports); + + /* The driver's probe function must change that to the real number + * of ports possible on the PHY. We assume by default we are dealing + * with a single-port PHY + */ + dev->max_n_ports = 1; + + 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 or no device responds. + */ +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) + /* returning -ENODEV doesn't stop bus + * scanning + */ + return (ret == -EIO || + ret == -ENODEV) ? -ENODEV : -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_next - finds the next PHY device on the bus + * @bus: the target MII bus + * @pos: cursor + * + * Return: next phy_device on the bus, or NULL + */ +struct phy_device *phy_find_next(struct mii_bus *bus, struct phy_device *pos) +{ + for (int addr = pos ? pos->mdio.addr + 1 : 0; + addr < PHY_MAX_ADDR; addr++) { + struct phy_device *phydev = mdiobus_get_phy(bus, addr); + + if (phydev) + return phydev; + } + return NULL; +} +EXPORT_SYMBOL_GPL(phy_find_next); + +/** + * 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); + if (ret < 0) + return ret; + + /* see comment in genphy_soft_reset for an explanation */ + phydev->suspended = 0; + } + + ret = phy_scan_fixups(phydev); + if (ret < 0) + return ret; + + phy_interface_zero(phydev->possible_interfaces); + + 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; + } + + /* Re-apply autonomous EEE disable after soft reset */ + if (phydev->autonomous_eee_disabled && + phydev->drv->disable_autonomous_eee) { + ret = phydev->drv->disable_autonomous_eee(phydev); + if (ret) + 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_connect_phy - Connect the SFP module's PHY to the upstream PHY + * @upstream: pointer to the upstream phy device + * @phy: pointer to the SFP module's phy device + * + * This helper allows keeping track of PHY devices on the link. It adds the + * SFP module's phy to the phy namespace of the upstream phy + * + * Return: 0 on success, otherwise a negative error code. + */ +static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) +{ + struct phy_device *phydev = upstream; + struct net_device *dev = phydev->attached_dev; + + if (dev) + return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev); + + return 0; +} + +/** + * phy_sfp_disconnect_phy - Disconnect the SFP module's PHY from the upstream PHY + * @upstream: pointer to the upstream phy device + * @phy: pointer to the SFP module's phy device + * + * This helper allows keeping track of PHY devices on the link. It removes the + * SFP module's phy to the phy namespace of the upstream phy. As the module phy + * will be destroyed, re-inserting the same module will add a new phy with a + * new index. + */ +static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) +{ + struct phy_device *phydev = upstream; + struct net_device *dev = phydev->attached_dev; + + if (dev) + phy_link_topo_del_phy(dev, phy); +} + +/** + * 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. + */ +static 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; +} + +/** + * 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. + */ +static 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; +} + +static int phy_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support); + struct phy_device *phydev = upstream; + const struct sfp_module_caps *caps; + struct phy_port *port; + + phy_interface_t iface; + + linkmode_zero(sfp_support); + + port = phy_get_sfp_port(phydev); + if (!port) + return -EINVAL; + + caps = sfp_get_module_caps(phydev->sfp_bus); + + linkmode_and(sfp_support, port->supported, caps->link_modes); + if (linkmode_empty(sfp_support)) { + dev_err(&phydev->mdio.dev, "incompatible SFP module inserted, no common linkmode\n"); + return -EINVAL; + } + + iface = sfp_select_interface(phydev->sfp_bus, sfp_support); + if (iface == PHY_INTERFACE_MODE_NA) { + dev_err(&phydev->mdio.dev, "PHY %s does not support the SFP module's requested MII interfaces\n", + phydev_name(phydev)); + return -EINVAL; + } + + if (phydev->n_ports == 1) + phydev->port = caps->port; + + if (port->ops && port->ops->configure_mii) + return port->ops->configure_mii(port, true, iface); + + return 0; +} + +static void phy_sfp_module_remove(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->configure_mii) + port->ops->configure_mii(port, false, PHY_INTERFACE_MODE_NA); + + if (phydev->n_ports == 1) + phydev->port = PORT_NONE; +} + +static void phy_sfp_link_up(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->link_up) + port->ops->link_up(port); +} + +static void phy_sfp_link_down(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->link_down) + port->ops->link_down(port); +} + +static const struct sfp_upstream_ops sfp_phydev_ops = { + .attach = phy_sfp_attach, + .detach = phy_sfp_detach, + .module_insert = phy_sfp_module_insert, + .module_remove = phy_sfp_module_remove, + .link_up = phy_sfp_link_up, + .link_down = phy_sfp_link_down, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, +}; + +static int phy_add_port(struct phy_device *phydev, struct phy_port *port) +{ + int ret = 0; + + if (phydev->n_ports == phydev->max_n_ports) + return -EBUSY; + + /* We set all ports as active by default, PHY drivers may deactivate + * them (when unused) + */ + port->active = true; + + if (port->is_mii) { + if (phydev->drv && phydev->drv->attach_mii_port) + ret = phydev->drv->attach_mii_port(phydev, port); + } else { + if (phydev->drv && phydev->drv->attach_mdi_port) + ret = phydev->drv->attach_mdi_port(phydev, port); + } + + if (ret) + return ret; + + /* The PHY driver might have added, removed or set medium/pairs info, + * so update the port supported accordingly. + */ + phy_port_update_supported(port); + + list_add(&port->head, &phydev->ports); + + phydev->n_ports++; + + return 0; +} + +static void phy_del_port(struct phy_device *phydev, struct phy_port *port) +{ + if (!phydev->n_ports) + return; + + list_del(&port->head); + + phydev->n_ports--; +} + +static int phy_setup_sfp_port(struct phy_device *phydev) +{ + struct phy_port *port = phy_port_alloc(); + int ret; + + if (!port) + return -ENOMEM; + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + /* The PHY is a media converter, the port connected to the SFP cage + * is a MII port. + */ + port->is_mii = true; + port->is_sfp = true; + + /* The port->supported and port->interfaces list will be populated + * when attaching the port to the phydev. + */ + ret = phy_add_port(phydev, port); + if (ret) + phy_port_destroy(port); + + return ret; +} + +/** + * phy_sfp_probe - probe for a SFP cage attached to this PHY device + * @phydev: Pointer to phy_device + */ +static int phy_sfp_probe(struct phy_device *phydev) +{ + 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, &sfp_phydev_ops); + sfp_bus_put(bus); + + if (ret) + phydev->sfp_bus = NULL; + } + + if (!ret && phydev->sfp_bus) + ret = phy_setup_sfp_port(phydev); + + return ret; +} + +static bool phy_drv_supports_irq(const struct phy_driver *phydrv) +{ + return phydrv->config_intr && phydrv->handle_interrupt; +} + +/** + * 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; + 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; + + phydev->is_genphy_driven = 1; + } + + 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 (phydev->is_genphy_driven) { + 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; + + err = phy_link_topo_add_phy(dev, phydev, PHY_UPSTREAM_MAC, dev); + if (err) + goto error; + } + + /* 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; + + if (!phy_drv_supports_irq(phydev->drv) && phy_interrupt_is_valid(phydev)) + 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 (phydev->is_genphy_driven) + 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 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); + phydev->is_genphy_driven = 0; + 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_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); + phydev->devlink = NULL; + } + + 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) { + struct hwtstamp_provider *hwprov; + + /* hwprov may technically be protected by ops lock but + * not for devices with a phydev, see phy_link_topo_add_phy() + */ + hwprov = rtnl_dereference(dev->hwprov); + /* Disable timestamp if it is the one selected */ + if (hwprov && hwprov->phydev == phydev) { + rcu_assign_pointer(dev->hwprov, NULL); + kfree_rcu(hwprov, rcu_head); + } + + phydev->attached_dev->phydev = NULL; + phydev->attached_dev = NULL; + phy_link_topo_del_phy(dev, phydev); + } + + phydev->phy_link_change = NULL; + phydev->phylink = NULL; + + if (phydev->mdio.dev.driver) + module_put(phydev->mdio.dev.driver->owner); + + /* If the device had no specific driver before (i.e. - it + * was using the generic driver), we unbind the device + * from the generic driver so that there's a chance a + * real driver could be loaded + */ + if (phydev->is_genphy_driven) { + device_release_driver(&phydev->mdio.dev); + phydev->is_genphy_driven = 0; + } + + /* 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 net_device *netdev = phydev->attached_dev; + const struct phy_driver *phydrv = phydev->drv; + int ret; + + if (phydev->suspended || !phydrv) + return 0; + + phydev->wol_enabled = phy_may_wakeup(phydev) || + (netdev && netdev->ethtool->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->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) +{ + const 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); + +/** + * 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 + * @advert: auto-negotiation parameters to advertise + * + * 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, + const unsigned long *advert) +{ + int err, bmsr, changed = 0; + u32 adv; + + adv = linkmode_adv_to_mii_adv_t(advert); + + /* 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(advert); + + 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_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 = false; + phydev->asym_pause = false; + + 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) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(fixed_advert); + const struct link_capabilities *c; + unsigned long *advert; + 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 (phydev->autoneg == AUTONEG_ENABLE) { + /* Only allow advertising what this PHY supports */ + linkmode_and(phydev->advertising, phydev->advertising, + phydev->supported); + advert = phydev->advertising; + } else if (phydev->speed < SPEED_1000) { + return genphy_setup_forced(phydev); + } else { + linkmode_zero(fixed_advert); + + c = phy_caps_lookup(phydev->speed, phydev->duplex, + phydev->supported, true); + if (c) + linkmode_and(fixed_advert, phydev->supported, + c->linkmodes); + + advert = fixed_advert; + } + + err = genphy_config_advert(phydev, advert); + 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 + * if 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 = false; + phydev->asym_pause = false; + + 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 + * @changed: pointer where to store if link changed + * + * 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. + * + * If link has changed, @changed is set to true, false otherwise. + */ +int genphy_c37_read_status(struct phy_device *phydev, bool *changed) +{ + 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) { + *changed = false; + return 0; + } + + /* Signal link has changed */ + *changed = true; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = false; + phydev->asym_pause = false; + + 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); + +/* Some PHYs support direct C45 register access but not C22 indirect + * MMD access (registers 13 and 14). When discovered via C22, phylib + * routes MMD access through the indirect path, which won't work on + * these devices. These helpers bypass indirect access and use the bus + * C45 accessors directly. + */ +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_read(bus, addr, devnum, regnum); +} +EXPORT_SYMBOL(genphy_read_mmd_c45); + +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_write(bus, addr, devnum, regnum, val); +} +EXPORT_SYMBOL(genphy_write_mmd_c45); + +/* 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, int speed) +{ + if (enable) { + u16 ctl = BMCR_LOOPBACK; + int ret, val; + + if (speed == SPEED_10 || speed == SPEED_100 || + speed == SPEED_1000) + phydev->speed = speed; + else if (speed) + return -EINVAL; + + 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_advertise_eee_all - Advertise all supported EEE modes + * @phydev: target phy_device struct + * + * Description: Per default phylib preserves the EEE advertising at the time of + * phy probing, which might be a subset of the supported EEE modes. Use this + * function when all supported EEE modes should be advertised. This does not + * trigger auto-negotiation, so must be called before phy_start()/ + * phylink_start() which will start auto-negotiation. + */ +void phy_advertise_eee_all(struct phy_device *phydev) +{ + linkmode_andnot(phydev->advertising_eee, phydev->supported_eee, + phydev->eee_disabled_modes); +} +EXPORT_SYMBOL_GPL(phy_advertise_eee_all); + +/** + * phy_support_eee - Set initial EEE policy configuration + * @phydev: Target phy_device struct + * + * This function configures the initial policy for Energy Efficient Ethernet + * (EEE) on the specified PHY device, influencing that EEE capabilities are + * advertised before the link is established. It should be called during PHY + * registration by the MAC driver and/or the PHY driver (for SmartEEE PHYs) + * if MAC supports LPI or PHY is capable to compensate missing LPI functionality + * of the MAC. + * + * The function sets default EEE policy parameters, including preparing the PHY + * to advertise EEE capabilities based on hardware support. + * + * It also sets the expected configuration for Low Power Idle (LPI) in the MAC + * driver. If the PHY framework determines that both local and remote + * advertisements support EEE, and the negotiated link mode is compatible with + * EEE, it will set enable_tx_lpi = true. The MAC driver is expected to act on + * this setting by enabling the LPI timer if enable_tx_lpi is set. + */ +void phy_support_eee(struct phy_device *phydev) +{ + linkmode_andnot(phydev->advertising_eee, phydev->supported_eee, + phydev->eee_disabled_modes); + phydev->eee_cfg.tx_lpi_enabled = true; + phydev->eee_cfg.eee_enabled = true; + + /* If the PHY supports autonomous EEE, disable it so the MAC can + * manage LPI signaling instead. The flag is stored so it can be + * re-applied after a PHY soft reset (e.g. suspend/resume). + */ + if (phydev->drv && phydev->drv->disable_autonomous_eee) { + int ret = phydev->drv->disable_autonomous_eee(phydev); + + if (ret) + phydev_warn(phydev, "Failed to disable autonomous EEE: %pe\n", + ERR_PTR(ret)); + else + phydev->autonomous_eee_disabled = true; + } +} +EXPORT_SYMBOL(phy_support_eee); + +/** + * phy_disable_eee - Disable EEE for the PHY + * @phydev: Target phy_device struct + * + * This function is used by MAC drivers for MAC's which don't support EEE. + * It disables EEE on the PHY layer. + */ +void phy_disable_eee(struct phy_device *phydev) +{ + linkmode_zero(phydev->advertising_eee); + phydev->eee_cfg.tx_lpi_enabled = false; + phydev->eee_cfg.eee_enabled = false; + /* don't let userspace re-enable EEE advertisement */ + linkmode_fill(phydev->eee_disabled_modes); +} +EXPORT_SYMBOL_GPL(phy_disable_eee); + +/** + * 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_u32_property(struct device *dev, const char *name, u32 *val) +{ + return device_property_read_u32(dev, name, val); +} +#else +static int phy_get_u32_property(struct device *dev, const char *name, u32 *val) +{ + return -EINVAL; +} +#endif + +/** + * phy_get_internal_delay - returns the index of the internal delay + * @phydev: phy_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, const int *delay_values, + int size, bool is_rx) +{ + struct device *dev = &phydev->mdio.dev; + int i, ret; + u32 delay; + + if (is_rx) { + ret = phy_get_u32_property(dev, "rx-internal-delay-ps", &delay); + if (ret < 0 && size == 0) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) + return 1; + else + return 0; + } + + } else { + ret = phy_get_u32_property(dev, "tx-internal-delay-ps", &delay); + if (ret < 0 && size == 0) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) + return 1; + else + return 0; + } + } + + if (ret < 0) + return ret; + + if (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); + +/** + * phy_get_tx_amplitude_gain - stores tx amplitude gain in @val + * @phydev: phy_device struct + * @dev: pointer to the devices device struct + * @linkmode: linkmode for which the tx amplitude gain should be retrieved + * @val: tx amplitude gain + * + * Returns: 0 on success, < 0 on failure + */ +int phy_get_tx_amplitude_gain(struct phy_device *phydev, struct device *dev, + enum ethtool_link_mode_bit_indices linkmode, + u32 *val) +{ + switch (linkmode) { + case ETHTOOL_LINK_MODE_100baseT_Full_BIT: + return phy_get_u32_property(dev, + "tx-amplitude-100base-tx-percent", + val); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(phy_get_tx_amplitude_gain); + +/** + * phy_get_mac_termination - stores MAC termination in @val + * @phydev: phy_device struct + * @dev: pointer to the devices device struct + * @val: MAC termination + * + * Returns: 0 on success, < 0 on failure + */ +int phy_get_mac_termination(struct phy_device *phydev, struct device *dev, + u32 *val) +{ + return phy_get_u32_property(dev, "mac-termination-ohms", val); +} +EXPORT_SYMBOL_GPL(phy_get_mac_termination); + +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, *tmp; + + list_for_each_entry_safe(phyled, tmp, &phydev->leds, list) { + led_classdev_unregister(&phyled->led_cdev); + list_del(&phyled->list); + } +} + +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; + unsigned long modes = 0; + 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; + + if (of_property_read_bool(led, "active-high")) + set_bit(PHY_LED_ACTIVE_HIGH, &modes); + if (of_property_read_bool(led, "active-low")) + set_bit(PHY_LED_ACTIVE_LOW, &modes); + if (of_property_read_bool(led, "inactive-high-impedance")) + set_bit(PHY_LED_INACTIVE_HIGH_IMPEDANCE, &modes); + + if (WARN_ON(modes & BIT(PHY_LED_ACTIVE_LOW) && + modes & BIT(PHY_LED_ACTIVE_HIGH))) + return -EINVAL; + + if (modes) { + /* Return error if asked to set polarity modes but not supported */ + if (!phydev->drv->led_polarity_set) + return -EINVAL; + + err = phydev->drv->led_polarity_set(phydev, index, modes); + if (err) + return err; + } + + 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; + 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; + + /* Check if the PHY driver have at least an OP to + * set the LEDs. + */ + if (!(phydev->drv->led_brightness_set || + phydev->drv->led_blink_set || + phydev->drv->led_hw_control_set)) { + phydev_dbg(phydev, "ignoring leds node defined with no PHY driver support\n"); + goto exit; + } + + for_each_available_child_of_node_scoped(leds, led) { + err = of_phy_led(phydev, led); + if (err) { + of_node_put(leds); + phy_leds_unregister(phydev); + return err; + } + } + +exit: + of_node_put(leds); + return 0; +} + +static void phy_cleanup_ports(struct phy_device *phydev) +{ + struct phy_port *tmp, *port; + + list_for_each_entry_safe(port, tmp, &phydev->ports, head) { + phy_del_port(phydev, port); + phy_port_destroy(port); + } +} + +static int phy_default_setup_single_port(struct phy_device *phydev) +{ + struct phy_port *port = phy_port_alloc(); + unsigned long mode; + + if (!port) + return -ENOMEM; + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + /* Let the PHY driver know that this port was never described anywhere. + * This is the usual case, where we assume single-port PHY devices with + * no SFP. In that case, the port supports exactly the same thing as + * the PHY itself. + * + * However, this can also be because we have a combo-port PHY, with + * only one port described in DT, through SFP for example. + * + * In that case, the PHY driver will be in charge of saying what we can + * do on that non-represented port. + */ + port->not_described = true; + linkmode_copy(port->supported, phydev->supported); + port->mediums = phy_caps_mediums_from_linkmodes(port->supported); + + for_each_set_bit(mode, port->supported, __ETHTOOL_LINK_MODE_MASK_NBITS) + port->pairs = max_t(int, port->pairs, + ethtool_linkmode_n_pairs(mode)); + + phy_add_port(phydev, port); + + return 0; +} + +static int of_phy_ports(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct device_node *mdi; + struct phy_port *port; + int err; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return 0; + + if (!node) + return 0; + + mdi = of_get_child_by_name(node, "mdi"); + if (!mdi) + return 0; + + for_each_available_child_of_node_scoped(mdi, port_node) { + port = phy_of_parse_port(port_node); + if (IS_ERR(port)) { + err = PTR_ERR(port); + goto out_err; + } + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + linkmode_copy(port->supported, phydev->supported); + + err = phy_add_port(phydev, port); + if (err) { + phy_port_destroy(port); + goto out_err; + } + } + of_node_put(mdi); + + return 0; + +out_err: + phy_cleanup_ports(phydev); + of_node_put(mdi); + return err; +} + +static int phy_setup_ports(struct phy_device *phydev) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(ports_supported); + struct phy_port *port; + int ret; + + ret = of_phy_ports(phydev); + if (ret) + return ret; + + /* We don't support SFP with genphy drivers. Also, genphy driver + * binding occurs with RTNL help, which will deadlock the call to + * sfp_bus_add_upstream(). + */ + if (!phydev->is_genphy_driven) { + ret = phy_sfp_probe(phydev); + if (ret) + goto out; + } + + if (phydev->n_ports < phydev->max_n_ports) { + ret = phy_default_setup_single_port(phydev); + if (ret) + goto out; + } + + linkmode_zero(ports_supported); + + /* Aggregate the supported modes, which are made-up of : + * - What the PHY itself supports + * - What the sum of all ports support + */ + list_for_each_entry(port, &phydev->ports, head) + if (port->active) + linkmode_or(ports_supported, ports_supported, + port->supported); + + if (!linkmode_empty(ports_supported)) + linkmode_and(phydev->supported, phydev->supported, + ports_supported); + + /* For now, the phy->port field is set as the first active port's type */ + list_for_each_entry(port, &phydev->ports, head) + if (port->active) { + phydev->port = phy_port_get_type(port); + break; + } + + return 0; + +out: + phy_cleanup_ports(phydev); + return ret; +} + +/** + * phy_get_sfp_port() - Returns the first valid SFP port of a PHY + * @phydev: pointer to the PHY device to get the SFP port from + * + * Returns: The first active SFP (serdes) port of a PHY device, NULL if none + * exist. + */ +struct phy_port *phy_get_sfp_port(struct phy_device *phydev) +{ + struct phy_port *port; + + list_for_each_entry(port, &phydev->ports, head) + if (port->active && port->is_sfp) + return port; + + return NULL; +} +EXPORT_SYMBOL_GPL(phy_get_sfp_port); + +/** + * 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); + +/** + * 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_ERR(phy_node) || is_acpi_node(fwnode)) + return phy_node; + phy_node = fwnode_find_reference(fwnode, "phy", 0); + if (!IS_ERR(phy_node)) + return phy_node; + return fwnode_find_reference(fwnode, "phy-device", 0); +} +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); + + err = phy_setup_ports(phydev); + if (err) + goto out; + + 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; + + /* Get the EEE modes we want to prohibit. */ + of_set_phy_eee_broken(phydev); + + /* Some PHYs may advertise, by default, not support EEE modes. So, + * we need to clean them. In addition remove all disabled EEE modes. + */ + linkmode_and(phydev->advertising_eee, phydev->supported_eee, + phydev->advertising_eee); + linkmode_andnot(phydev->advertising_eee, phydev->advertising_eee, + phydev->eee_disabled_modes); + + /* There is no "enabled" flag. If PHY is advertising, assume it is + * kind of enabled. + */ + phydev->eee_cfg.eee_enabled = !linkmode_empty(phydev->advertising_eee); + + /* Get master/slave strap overrides */ + of_set_phy_timing_role(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; + + /* Register the PHY LED triggers */ + if (!phydev->is_on_sfp_module) + phy_led_triggers_register(phydev); + + /* Get the LEDs from the device tree, and instantiate standard + * LEDs for them. + */ + if (IS_ENABLED(CONFIG_PHYLIB_LEDS) && !phy_driver_is_genphy(phydev)) { + err = of_phy_leds(phydev); + if (err) + goto out; + } + + return 0; + +out: + sfp_bus_del_upstream(phydev->sfp_bus); + phydev->sfp_bus = NULL; + + phy_cleanup_ports(phydev); + + if (!phydev->is_on_sfp_module) + phy_led_triggers_unregister(phydev); + + /* Re-assert the reset signal on error */ + 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_driver_is_genphy(phydev)) + phy_leds_unregister(phydev); + + if (!phydev->is_on_sfp_module) + phy_led_triggers_unregister(phydev); + + phydev->state = PHY_DOWN; + + sfp_bus_del_upstream(phydev->sfp_bus); + phydev->sfp_bus = NULL; + + phy_cleanup_ports(phydev); + + 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 + */ +static 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; +} + +static void phy_driver_unregister(struct phy_driver *drv) +{ + driver_unregister(&drv->mdiodrv.driver); +} + +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_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, + .get_phy_stats = __phy_ethtool_get_phy_stats, + .get_link_ext_stats = __phy_ethtool_get_link_ext_stats, +}; + +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; + + rc = class_register(&mdio_bus_class); + if (rc) + return rc; + + rc = bus_register(&mdio_bus_type); + if (rc) + goto err_class; + + rtnl_lock(); + ethtool_set_ethtool_phy_ops(&phy_ethtool_phy_ops); + phylib_register_stubs(); + rtnl_unlock(); + + rc = phy_caps_init(); + if (rc) + goto err_ethtool_phy_ops; + + features_init(); + + rc = phy_driver_register(&genphy_c45_driver, THIS_MODULE); + if (rc) + goto err_ethtool_phy_ops; + + rc = phy_driver_register(&genphy_driver, THIS_MODULE); + if (rc) + goto err_c45; + + return 0; + +err_c45: + phy_driver_unregister(&genphy_c45_driver); +err_ethtool_phy_ops: + rtnl_lock(); + phylib_unregister_stubs(); + ethtool_set_ethtool_phy_ops(NULL); + rtnl_unlock(); + bus_unregister(&mdio_bus_type); +err_class: + class_unregister(&mdio_bus_class); + + return rc; +} + +static void __exit phy_exit(void) +{ + phy_driver_unregister(&genphy_c45_driver); + phy_driver_unregister(&genphy_driver); + rtnl_lock(); + phylib_unregister_stubs(); + ethtool_set_ethtool_phy_ops(NULL); + rtnl_unlock(); + bus_unregister(&mdio_bus_type); + class_unregister(&mdio_bus_class); +} + +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..4eb7716bb --- /dev/null +++ b/drivers/net/phy/phy_led_triggers.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2016 National Instruments Corp. */ +#include +#include +#include +#include + +#include "phylib-internal.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 = kzalloc_obj(*phy->led_link_trigger); + 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 = kzalloc_objs(struct phy_led_trigger, + phy->phy_num_led_triggers); + 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]); + kfree(phy->phy_led_triggers); +out_unreg_link: + phy_led_trigger_unregister(phy->led_link_trigger); +out_free_link: + kfree(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]); + kfree(phy->phy_led_triggers); + phy->phy_led_triggers = NULL; + + if (phy->led_link_trigger) { + phy_led_trigger_unregister(phy->led_link_trigger); + kfree(phy->led_link_trigger); + phy->led_link_trigger = NULL; + } +} +EXPORT_SYMBOL_GPL(phy_led_triggers_unregister); diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c new file mode 100644 index 000000000..4134de7ae --- /dev/null +++ b/drivers/net/phy/phy_link_topology.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Infrastructure to handle all PHY devices connected to a given netdev, + * either directly or indirectly attached. + * + * Copyright (c) 2023 Maxime Chevallier + */ + +#include +#include +#include +#include +#include + +static int netdev_alloc_phy_link_topology(struct net_device *dev) +{ + struct phy_link_topology *topo; + + topo = kzalloc_obj(*topo); + if (!topo) + return -ENOMEM; + + xa_init_flags(&topo->phys, XA_FLAGS_ALLOC1); + topo->next_phy_index = 1; + + dev->link_topo = topo; + + return 0; +} + +int phy_link_topo_add_phy(struct net_device *dev, + struct phy_device *phy, + enum phy_upstream upt, void *upstream) +{ + struct phy_link_topology *topo = dev->link_topo; + struct phy_device_node *pdn; + int ret; + + /* ethtool ops may run without rtnl_lock, and rtnl_lock is what + * currently protects the PHY topology. No driver currently mixes + * the two, flag if someone tries. See also: + * - ethnl_req_get_phydev() + * - phy_detach() + */ + if (WARN_ON_ONCE(netdev_need_ops_lock(dev))) + return -EOPNOTSUPP; + + if (!topo) { + ret = netdev_alloc_phy_link_topology(dev); + if (ret) + return ret; + + topo = dev->link_topo; + } + + pdn = kzalloc_obj(*pdn); + if (!pdn) + return -ENOMEM; + + pdn->phy = phy; + switch (upt) { + case PHY_UPSTREAM_MAC: + pdn->upstream.netdev = (struct net_device *)upstream; + if (phy_on_sfp(phy)) + pdn->parent_sfp_bus = pdn->upstream.netdev->sfp_bus; + break; + case PHY_UPSTREAM_PHY: + pdn->upstream.phydev = (struct phy_device *)upstream; + if (phy_on_sfp(phy)) + pdn->parent_sfp_bus = pdn->upstream.phydev->sfp_bus; + break; + default: + ret = -EINVAL; + goto err; + } + pdn->upstream_type = upt; + + /* Attempt to re-use a previously allocated phy_index */ + if (phy->phyindex) + ret = xa_insert(&topo->phys, phy->phyindex, pdn, GFP_KERNEL); + else + ret = xa_alloc_cyclic(&topo->phys, &phy->phyindex, pdn, + xa_limit_32b, &topo->next_phy_index, + GFP_KERNEL); + + if (ret < 0) + goto err; + + return 0; + +err: + kfree(pdn); + return ret; +} +EXPORT_SYMBOL_GPL(phy_link_topo_add_phy); + +void phy_link_topo_del_phy(struct net_device *dev, + struct phy_device *phy) +{ + struct phy_link_topology *topo = dev->link_topo; + struct phy_device_node *pdn; + + if (!topo) + return; + + pdn = xa_erase(&topo->phys, phy->phyindex); + + /* We delete the PHY from the topology, however we don't re-set the + * phy->phyindex field. If the PHY isn't gone, we can re-assign it the + * same index next time it's added back to the topology + */ + + kfree(pdn); +} +EXPORT_SYMBOL_GPL(phy_link_topo_del_phy); diff --git a/drivers/net/phy/phy_package.c b/drivers/net/phy/phy_package.c new file mode 100644 index 000000000..735806c5b --- /dev/null +++ b/drivers/net/phy/phy_package.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PHY package support + */ + +#include +#include + +#include "phylib.h" +#include "phylib-internal.h" + +/** + * struct phy_package_shared - Shared information in PHY packages + * @base_addr: Base PHY address of PHY package used to combine PHYs + * in one package and for offset calculation of phy_package_read/write + * @np: Pointer to the Device Node if PHY package defined in DT + * @refcnt: Number of PHYs connected to this shared data + * @flags: Initialization of PHY package + * @priv_size: Size of the shared private data @priv + * @priv: Driver private data shared across a PHY package + * + * Represents a shared structure between different phydev's in the same + * package, for example a quad PHY. See phy_package_join() and + * phy_package_leave(). + */ +struct phy_package_shared { + u8 base_addr; + /* With PHY package defined in DT this points to the PHY package node */ + struct device_node *np; + refcount_t refcnt; + unsigned long flags; + size_t priv_size; + + /* private data pointer */ + /* note that this pointer is shared between different phydevs and + * the user has to take care of appropriate locking. It is allocated + * and freed automatically by phy_package_join() and + * phy_package_leave(). + */ + void *priv; +}; + +struct device_node *phy_package_get_node(struct phy_device *phydev) +{ + return phydev->shared->np; +} +EXPORT_SYMBOL_GPL(phy_package_get_node); + +void *phy_package_get_priv(struct phy_device *phydev) +{ + return phydev->shared->priv; +} +EXPORT_SYMBOL_GPL(phy_package_get_priv); + +/** + * phy_package_lock - acquire the PHY package lock + * @phydev: PHY device that has joined the package + * + * Use this to serialize access to package-private data. Release the lock + * with phy_package_unlock(). + */ +void phy_package_lock(struct phy_device *phydev) +{ + mutex_lock(&phydev->mdio.bus->shared_lock); +} +EXPORT_SYMBOL_GPL(phy_package_lock); + +/** + * phy_package_unlock - release the PHY package lock + * @phydev: PHY device that has joined the package + */ +void phy_package_unlock(struct phy_device *phydev) +{ + mutex_unlock(&phydev->mdio.bus->shared_lock); +} +EXPORT_SYMBOL_GPL(phy_package_unlock); + +static int phy_package_address(struct phy_device *phydev, + unsigned int addr_offset) +{ + struct phy_package_shared *shared = phydev->shared; + u8 base_addr = shared->base_addr; + + if (addr_offset >= PHY_MAX_ADDR - base_addr) + return -EIO; + + /* we know that addr will be in the range 0..31 and thus the + * implicit cast to a signed int is not a problem. + */ + return base_addr + addr_offset; +} + +int __phy_package_read(struct phy_device *phydev, unsigned int addr_offset, + u32 regnum) +{ + int addr = phy_package_address(phydev, addr_offset); + + if (addr < 0) + return addr; + + return __mdiobus_read(phydev->mdio.bus, addr, regnum); +} +EXPORT_SYMBOL_GPL(__phy_package_read); + +int __phy_package_write(struct phy_device *phydev, unsigned int addr_offset, + u32 regnum, u16 val) +{ + int addr = phy_package_address(phydev, addr_offset); + + if (addr < 0) + return addr; + + return __mdiobus_write(phydev->mdio.bus, addr, regnum, val); +} +EXPORT_SYMBOL_GPL(__phy_package_write); + +/** + * __phy_package_read_mmd - read MMD reg relative to PHY package base addr + * @phydev: The phy_device struct + * @addr_offset: The offset to be added to PHY package base_addr + * @devad: The MMD to read from + * @regnum: The register on the MMD to read + * + * Convenience helper for reading a register of an MMD on a given PHY + * using the PHY package base address. The base address is added to + * the addr_offset value. + * + * Same calling rules as for __phy_read(); + * + * NOTE: It's assumed that the entire PHY package is either C22 or C45. + */ +int __phy_package_read_mmd(struct phy_device *phydev, + unsigned int addr_offset, int devad, + u32 regnum) +{ + int addr = phy_package_address(phydev, addr_offset); + + if (addr < 0) + return addr; + + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; + + return mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad, + regnum); +} +EXPORT_SYMBOL(__phy_package_read_mmd); + +/** + * __phy_package_write_mmd - write MMD reg relative to PHY package base addr + * @phydev: The phy_device struct + * @addr_offset: The offset to be added to PHY package base_addr + * @devad: The MMD to write to + * @regnum: The register on the MMD to write + * @val: value to write to @regnum + * + * Convenience helper for writing a register of an MMD on a given PHY + * using the PHY package base address. The base address is added to + * the addr_offset value. + * + * Same calling rules as for __phy_write(); + * + * NOTE: It's assumed that the entire PHY package is either C22 or C45. + */ +int __phy_package_write_mmd(struct phy_device *phydev, + unsigned int addr_offset, int devad, + u32 regnum, u16 val) +{ + int addr = phy_package_address(phydev, addr_offset); + + if (addr < 0) + return addr; + + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; + + return mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad, + regnum, val); +} +EXPORT_SYMBOL(__phy_package_write_mmd); + +static bool __phy_package_set_once(struct phy_device *phydev, unsigned int b) +{ + struct phy_package_shared *shared = phydev->shared; + + if (!shared) + return false; + + return !test_and_set_bit(b, &shared->flags); +} + +bool phy_package_init_once(struct phy_device *phydev) +{ + return __phy_package_set_once(phydev, 0); +} +EXPORT_SYMBOL_GPL(phy_package_init_once); + +bool phy_package_probe_once(struct phy_device *phydev) +{ + return __phy_package_set_once(phydev, 1); +} +EXPORT_SYMBOL_GPL(phy_package_probe_once); + +/** + * phy_package_join - join a common PHY group + * @phydev: target phy_device struct + * @base_addr: cookie and base PHY address of PHY package for offset + * calculation of 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 base_addr parameter serves as cookie which has to have the same values + * for all members of one group and as the base PHY address of the PHY package + * for offset calculation 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 as base and added to the + * passed offset in those functions. + * + * 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 base_addr, size_t priv_size) +{ + struct mii_bus *bus = phydev->mdio.bus; + struct phy_package_shared *shared; + int ret; + + if (base_addr < 0 || base_addr >= PHY_MAX_ADDR) + return -EINVAL; + + mutex_lock(&bus->shared_lock); + shared = bus->shared[base_addr]; + if (!shared) { + ret = -ENOMEM; + shared = kzalloc_obj(*shared); + 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->base_addr = base_addr; + shared->np = NULL; + refcount_set(&shared->refcnt, 1); + bus->shared[base_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); + +/** + * of_phy_package_join - join a common PHY group in PHY package + * @phydev: target phy_device struct + * @priv_size: if non-zero allocate this amount of bytes for private data + * + * This is a variant of phy_package_join for PHY package defined in DT. + * + * The parent node of the @phydev is checked as a valid PHY package node + * structure (by matching the node name "ethernet-phy-package") and the + * base_addr for the PHY package is passed to phy_package_join. + * + * With this configuration the shared struct will also have the np value + * filled to use additional DT defined properties in PHY specific + * probe_once and config_init_once PHY package OPs. + * + * Returns < 0 on error, 0 on success. Esp. calling phy_package_join() + * with the same cookie but a different priv_size is an error. Or a parent + * node is not detected or is not valid or doesn't match the expected node + * name for PHY package. + */ +int of_phy_package_join(struct phy_device *phydev, size_t priv_size) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct device_node *package_node; + u32 base_addr; + int ret; + + if (!node) + return -EINVAL; + + package_node = of_get_parent(node); + if (!package_node) + return -EINVAL; + + if (!of_node_name_eq(package_node, "ethernet-phy-package")) { + ret = -EINVAL; + goto exit; + } + + if (of_property_read_u32(package_node, "reg", &base_addr)) { + ret = -EINVAL; + goto exit; + } + + ret = phy_package_join(phydev, base_addr, priv_size); + if (ret) + goto exit; + + phydev->shared->np = package_node; + + return 0; +exit: + of_node_put(package_node); + return ret; +} +EXPORT_SYMBOL_GPL(of_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; + + /* Decrease the node refcount on leave if present */ + if (shared->np) + of_node_put(shared->np); + + if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) { + bus->shared[shared->base_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 + * @base_addr: cookie and base PHY address of PHY package for offset + * calculation of 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 base_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, base_addr, priv_size); + + if (!ret) { + *ptr = phydev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_phy_package_join); + +/** + * devm_of_phy_package_join - resource managed of_phy_package_join() + * @dev: device that is registering this PHY package + * @phydev: target phy_device struct + * @priv_size: if non-zero allocate this amount of bytes for private data + * + * Managed of_phy_package_join(). Shared storage fetched by this function, + * phy_package_leave() is automatically called on driver detach. See + * of_phy_package_join() for more information. + */ +int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev, + 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 = of_phy_package_join(phydev, priv_size); + + if (!ret) { + *ptr = phydev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_of_phy_package_join); + +MODULE_DESCRIPTION("PHY package support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/phy_port.c b/drivers/net/phy/phy_port.c new file mode 100644 index 000000000..e56e29d0e --- /dev/null +++ b/drivers/net/phy/phy_port.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Framework to drive Ethernet ports + * + * Copyright (c) 2024 Maxime Chevallier + */ + +#include +#include +#include + +#include "phy-caps.h" + +/** + * phy_port_alloc() - Allocate a new phy_port + * + * Returns: a newly allocated struct phy_port, or NULL. + */ +struct phy_port *phy_port_alloc(void) +{ + struct phy_port *port; + + port = kzalloc_obj(*port); + if (!port) + return NULL; + + linkmode_zero(port->supported); + INIT_LIST_HEAD(&port->head); + + return port; +} +EXPORT_SYMBOL_GPL(phy_port_alloc); + +/** + * phy_port_destroy() - Free a struct phy_port + * @port: The port to destroy + */ +void phy_port_destroy(struct phy_port *port) +{ + kfree(port); +} +EXPORT_SYMBOL_GPL(phy_port_destroy); + +/** + * phy_of_parse_port() - Create a phy_port from a firmware representation + * @dn: device_node representation of the port, following the + * ethernet-connector.yaml binding + * + * Returns: a newly allocated and initialized phy_port pointer, or an ERR_PTR. + */ +struct phy_port *phy_of_parse_port(struct device_node *dn) +{ + struct fwnode_handle *fwnode = of_fwnode_handle(dn); + enum ethtool_link_medium medium; + struct phy_port *port; + const char *med_str; + u32 pairs = 0; + int ret; + + ret = fwnode_property_read_string(fwnode, "media", &med_str); + if (ret) + return ERR_PTR(ret); + + medium = ethtool_str_to_medium(med_str); + if (medium == ETHTOOL_LINK_MEDIUM_NONE) + return ERR_PTR(-EINVAL); + + if (medium == ETHTOOL_LINK_MEDIUM_BASET) { + ret = fwnode_property_read_u32(fwnode, "pairs", &pairs); + if (ret) + return ERR_PTR(ret); + + switch (pairs) { + case 1: /* BaseT1 */ + case 2: /* 100BaseTX */ + case 4: + break; + default: + pr_err("%u is not a valid number of pairs\n", pairs); + return ERR_PTR(-EINVAL); + } + } + + if (pairs && medium != ETHTOOL_LINK_MEDIUM_BASET) { + pr_err("pairs property is only compatible with BaseT medium\n"); + return ERR_PTR(-EINVAL); + } + + port = phy_port_alloc(); + if (!port) + return ERR_PTR(-ENOMEM); + + port->pairs = pairs; + port->mediums = BIT(medium); + + return port; +} +EXPORT_SYMBOL_GPL(phy_of_parse_port); + +/** + * phy_port_update_supported() - Setup the port->supported field + * @port: the port to update + * + * Once the port's medium list and number of pairs has been configured based + * on firmware, straps and vendor-specific properties, this function may be + * called to update the port's supported linkmodes list. + * + * Any mode that was manually set in the port's supported list remains set. + */ +void phy_port_update_supported(struct phy_port *port) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = {0}; + unsigned long mode; + int i; + + /* If there's no pairs specified, we grab the default number of + * pairs as the max of the default pairs for each linkmode + */ + if (!port->pairs) + for_each_set_bit(mode, port->supported, + __ETHTOOL_LINK_MODE_MASK_NBITS) + port->pairs = max_t(int, port->pairs, + ethtool_linkmode_n_pairs(mode)); + + for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) { + __ETHTOOL_DECLARE_LINK_MODE_MASK(med_supported) = {0}; + + phy_caps_medium_get_supported(med_supported, i, port->pairs); + linkmode_or(supported, supported, med_supported); + } + + /* If port->supported is already populated, filter it out with the + * medium/pair support. Otherwise, let's just use this medium-based + * support as the port's supported list. + */ + if (linkmode_empty(port->supported)) + linkmode_copy(port->supported, supported); + else + linkmode_and(port->supported, supported, port->supported); + + /* Serdes ports supported through SFP may not have any medium set, + * as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive + * the supported list based on these interfaces + */ + if (port->is_mii && !port->mediums) { + unsigned long interface, link_caps = 0; + + /* Get each interface's caps */ + for_each_set_bit(interface, port->interfaces, + PHY_INTERFACE_MODE_MAX) + link_caps |= phy_caps_from_interface(interface); + + phy_caps_linkmodes(link_caps, port->supported); + } +} +EXPORT_SYMBOL_GPL(phy_port_update_supported); + +/** + * phy_port_filter_supported() - Make sure that port->supported match port->mediums + * @port: The port to filter + * + * After updating a port's mediums to a more restricted subset, this helper will + * make sure that port->supported only contains linkmodes that are compatible + * with port->mediums. + */ +static void phy_port_filter_supported(struct phy_port *port) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 }; + int i; + + for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) + phy_caps_medium_get_supported(supported, i, port->pairs); + + linkmode_and(port->supported, port->supported, supported); +} + +/** + * phy_port_restrict_mediums - Mask away some of the port's supported mediums + * @port: The port to act upon + * @mediums: A mask of mediums to support on the port + * + * This helper allows removing some mediums from a port's list of supported + * mediums, which occurs once we have enough information about the port to + * know its nature. + * + * Returns: 0 if the change was donne correctly, a negative value otherwise. + */ +int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums) +{ + /* We forbid ending-up with a port with empty mediums */ + if (!(port->mediums & mediums)) + return -EINVAL; + + port->mediums &= mediums; + + phy_port_filter_supported(port); + + return 0; +} +EXPORT_SYMBOL_GPL(phy_port_restrict_mediums); + +/** + * phy_port_get_type() - get the PORT_* attribute for that port. + * @port: The port we want the information from + * + * Returns: A PORT_XXX value. + */ +int phy_port_get_type(struct phy_port *port) +{ + if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET)) + return PORT_TP; + + if (phy_port_is_fiber(port)) + return PORT_FIBRE; + + return PORT_OTHER; +} +EXPORT_SYMBOL_GPL(phy_port_get_type); diff --git a/drivers/net/phy/phylib-internal.h b/drivers/net/phy/phylib-internal.h new file mode 100644 index 000000000..664ed7faa --- /dev/null +++ b/drivers/net/phy/phylib-internal.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * phylib-internal header + */ + +#ifndef __PHYLIB_INTERNAL_H +#define __PHYLIB_INTERNAL_H + +struct mdio_device; +struct phy_device; + +extern const struct bus_type mdio_bus_type; +extern const struct class mdio_bus_class; + +/* + * phy_supported_speeds - return all speeds currently supported by a PHY device + */ +unsigned int phy_supported_speeds(struct phy_device *phy, + unsigned int *speeds, + unsigned int size); +void of_set_phy_supported(struct phy_device *phydev); +void of_set_phy_eee_broken(struct phy_device *phydev); +void of_set_phy_timing_role(struct phy_device *phydev); +int phy_speed_down_core(struct phy_device *phydev); +void phy_check_downshift(struct phy_device *phydev); + +int mdiobus_register_device(struct mdio_device *mdiodev); +int mdiobus_unregister_device(struct mdio_device *mdiodev); + +int genphy_c45_read_eee_adv(struct phy_device *phydev, unsigned long *adv); + +#endif /* __PHYLIB_INTERNAL_H */ diff --git a/drivers/net/phy/phylib.h b/drivers/net/phy/phylib.h new file mode 100644 index 000000000..c6e26ac6b --- /dev/null +++ b/drivers/net/phy/phylib.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * phylib header + */ + +#ifndef __PHYLIB_H +#define __PHYLIB_H + +struct device_node; +struct phy_device; +struct mii_bus; + +struct device_node *phy_package_get_node(struct phy_device *phydev); +void *phy_package_get_priv(struct phy_device *phydev); +void phy_package_lock(struct phy_device *phydev); +void phy_package_unlock(struct phy_device *phydev); +int __phy_package_read(struct phy_device *phydev, unsigned int addr_offset, + u32 regnum); +int __phy_package_write(struct phy_device *phydev, unsigned int addr_offset, + u32 regnum, u16 val); +int __phy_package_read_mmd(struct phy_device *phydev, + unsigned int addr_offset, int devad, + u32 regnum); +int __phy_package_write_mmd(struct phy_device *phydev, + unsigned int addr_offset, int devad, + u32 regnum, u16 val); +bool phy_package_init_once(struct phy_device *phydev); +bool phy_package_probe_once(struct phy_device *phydev); +int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size); +int of_phy_package_join(struct phy_device *phydev, size_t priv_size); +void phy_package_leave(struct phy_device *phydev); +int devm_phy_package_join(struct device *dev, struct phy_device *phydev, + int base_addr, size_t priv_size); +int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev, + size_t priv_size); +int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum); +int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum, u16 val); + +#endif /* __PHYLIB_H */ diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c new file mode 100644 index 000000000..a1458da81 --- /dev/null +++ b/drivers/net/phy/phylink.c @@ -0,0 +1,4434 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-caps.h" +#include "sfp.h" +#include "swphy.h" + +enum { + PHYLINK_DISABLE_STOPPED, + PHYLINK_DISABLE_LINK, + PHYLINK_DISABLE_MAC_WOL, + PHYLINK_DISABLE_REPLAY, + + 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 req_link_an_mode; /* Requested MLO_AN_xxx mode */ + u8 act_link_an_mode; /* Active MLO_AN_xxx mode */ + u8 link_port; /* The current non-phy ethtool port */ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_lpi); + + /* 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; + + struct mutex state_mutex; + /* Serialize updates to pl->phydev with phylink_resolve() */ + struct mutex phydev_mutex; + struct phylink_link_state phy_state; + unsigned int phy_ib_mode; + struct work_struct resolve; + unsigned int pcs_neg_mode; + unsigned int pcs_state; + + bool link_failed; + bool suspend_link_up; + bool force_major_config; + bool major_config_failed; + bool mac_supports_eee_ops; + bool mac_supports_eee; + bool phy_enable_tx_lpi; + bool mac_enable_tx_lpi; + bool mac_tx_clk_stop; + u32 mac_tx_lpi_timer; + u8 mac_rx_clk_stop_blocked; + + 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; + + struct eee_config eee_cfg; + + u32 wolopts_mac; + u8 wol_sopass[SOPASS_MAX]; +}; + +#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 + +static const phy_interface_t phylink_sfp_interface_preference[] = { + PHY_INTERFACE_MODE_100GBASEP, + PHY_INTERFACE_MODE_50GBASER, + PHY_INTERFACE_MODE_LAUI, + 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); + +/** + * 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. + */ +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 const char *phylink_pcs_mode_str(unsigned int mode) +{ + if (!mode) + return "none"; + + if (mode & PHYLINK_PCS_NEG_OUTBAND) + return "outband"; + + if (mode & PHYLINK_PCS_NEG_INBAND) { + if (mode & PHYLINK_PCS_NEG_ENABLED) + return "inband,an-enabled"; + else + return "inband,an-disabled"; + } + + return "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: + case PHY_INTERFACE_MODE_MIILITE: + 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: + case PHY_INTERFACE_MODE_10G_QXGMII: + 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_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + return SPEED_50000; + + case PHY_INTERFACE_MODE_100GBASEP: + return SPEED_100000; + + 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; +} + +static struct { + unsigned long mask; + int speed; + unsigned int duplex; + unsigned int caps_bit; +} phylink_caps_params[] = { + { MAC_400000FD, SPEED_400000, DUPLEX_FULL, BIT(LINK_CAPA_400000FD) }, + { MAC_200000FD, SPEED_200000, DUPLEX_FULL, BIT(LINK_CAPA_200000FD) }, + { MAC_100000FD, SPEED_100000, DUPLEX_FULL, BIT(LINK_CAPA_100000FD) }, + { MAC_80000FD, SPEED_80000, DUPLEX_FULL, BIT(LINK_CAPA_80000FD) }, + { MAC_56000FD, SPEED_56000, DUPLEX_FULL, BIT(LINK_CAPA_56000FD) }, + { MAC_50000FD, SPEED_50000, DUPLEX_FULL, BIT(LINK_CAPA_50000FD) }, + { MAC_40000FD, SPEED_40000, DUPLEX_FULL, BIT(LINK_CAPA_40000FD) }, + { MAC_25000FD, SPEED_25000, DUPLEX_FULL, BIT(LINK_CAPA_25000FD) }, + { MAC_20000FD, SPEED_20000, DUPLEX_FULL, BIT(LINK_CAPA_20000FD) }, + { MAC_10000FD, SPEED_10000, DUPLEX_FULL, BIT(LINK_CAPA_10000FD) }, + { MAC_5000FD, SPEED_5000, DUPLEX_FULL, BIT(LINK_CAPA_5000FD) }, + { MAC_2500FD, SPEED_2500, DUPLEX_FULL, BIT(LINK_CAPA_2500FD) }, + { MAC_1000FD, SPEED_1000, DUPLEX_FULL, BIT(LINK_CAPA_1000FD) }, + { MAC_1000HD, SPEED_1000, DUPLEX_HALF, BIT(LINK_CAPA_1000HD) }, + { MAC_100FD, SPEED_100, DUPLEX_FULL, BIT(LINK_CAPA_100FD) }, + { MAC_100HD, SPEED_100, DUPLEX_HALF, BIT(LINK_CAPA_100HD) }, + { MAC_10FD, SPEED_10, DUPLEX_FULL, BIT(LINK_CAPA_10FD) }, + { MAC_10HD, SPEED_10, DUPLEX_HALF, BIT(LINK_CAPA_10HD) }, +}; + +/** + * phylink_caps_to_link_caps() - Convert a set of MAC capabilities LINK caps + * @caps: A set of MAC capabilities + * + * Returns: The corresponding set of LINK_CAPA as defined in phy-caps.h + */ +static unsigned long phylink_caps_to_link_caps(unsigned long caps) +{ + unsigned long link_caps = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(phylink_caps_params); i++) + if (caps & phylink_caps_params[i].mask) + link_caps |= phylink_caps_params[i].caps_bit; + + return link_caps; +} + +static unsigned long phylink_link_caps_to_mac_caps(unsigned long link_caps) +{ + unsigned long caps = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(phylink_caps_params); i++) + if (link_caps & phylink_caps_params[i].caps_bit) + caps |= phylink_caps_params[i].mask; + + return caps; +} + +/** + * 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. + */ +static void phylink_caps_to_linkmodes(unsigned long *linkmodes, + unsigned long caps) +{ + unsigned long link_caps = phylink_caps_to_link_caps(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); + + phy_caps_linkmodes(link_caps, linkmodes); +} + +/** + * 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. + */ +static unsigned long phylink_get_capabilities(phy_interface_t interface, + unsigned long mac_capabilities, + int rate_matching) +{ + unsigned long link_caps = phy_caps_from_interface(interface); + int max_speed = phylink_interface_max_speed(interface); + unsigned long caps = MAC_SYM_PAUSE | MAC_ASYM_PAUSE; + unsigned long matched_caps = 0; + + caps |= phylink_link_caps_to_mac_caps(link_caps); + + 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)) + matched_caps = GENMASK(__fls(caps), __fls(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; +} + +/** + * 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. + */ +static 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); +} + +static int phylink_validate_mac_and_pcs(struct phylink *pl, + unsigned long *supported, + struct phylink_link_state *state) +{ + struct phylink_pcs *pcs = NULL; + unsigned long capabilities; + int ret; + + /* Get the PCS for this interface mode */ + if (pl->mac_ops->mac_select_pcs) { + pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface); + if (IS_ERR(pcs)) + return PTR_ERR(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; + } + + /* Ensure that this PCS supports the interface which the MAC + * returned it for. It is an error for the MAC to return a PCS + * that does not support the interface mode. + */ + if (!phy_interface_empty(pcs->supported_interfaces) && + !test_bit(state->interface, pcs->supported_interfaces)) { + phylink_err(pl, "MAC returned PCS which does not support %s\n", + phy_modes(state->interface)); + 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->mac_get_caps) + capabilities = pl->mac_ops->mac_get_caps(pl->config, + state->interface); + else + capabilities = pl->config->mac_capabilities; + + phylink_validate_mask_caps(supported, state, capabilities); + + return phylink_is_empty_linkmode(supported) ? -EINVAL : 0; +} + +static void phylink_validate_one(struct phylink *pl, struct phy_device *phy, + const unsigned long *supported, + const struct phylink_link_state *state, + phy_interface_t interface, + unsigned long *accum_supported, + unsigned long *accum_advertising) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp_supported); + struct phylink_link_state tmp_state; + + linkmode_copy(tmp_supported, supported); + + tmp_state = *state; + tmp_state.interface = interface; + + if (phy) + tmp_state.rate_matching = phy_get_rate_matching(phy, interface); + + if (!phylink_validate_mac_and_pcs(pl, tmp_supported, &tmp_state)) { + phylink_dbg(pl, " interface %u (%s) rate match %s supports %*pbl\n", + interface, phy_modes(interface), + phy_rate_matching_to_str(tmp_state.rate_matching), + __ETHTOOL_LINK_MODE_MASK_NBITS, tmp_supported); + + linkmode_or(accum_supported, accum_supported, tmp_supported); + linkmode_or(accum_advertising, accum_advertising, + tmp_state.advertising); + } +} + +static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy, + 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, }; + int interface; + + for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX) + phylink_validate_one(pl, phy, supported, state, interface, + all_s, all_adv); + + 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, NULL, supported, state, + interfaces); + + if (!test_bit(state->interface, interfaces)) + return -EINVAL; + + return phylink_validate_mac_and_pcs(pl, supported, state); +} + +static void phylink_fill_fixedlink_supported(unsigned long *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_Autoneg_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_2500baseT_Full_BIT, supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, supported); +} + +static int phylink_parse_fixedlink(struct phylink *pl, + const struct fwnode_handle *fwnode) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(match) = { 0, }; + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + const struct link_capabilities *c; + struct fwnode_handle *fixed_node; + 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; + } + + phylink_warn(pl, "%pfw uses deprecated array-style fixed-link binding!\n", + fwnode); + + 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); + + linkmode_zero(pl->supported); + phylink_fill_fixedlink_supported(pl->supported); + + linkmode_copy(pl->link_config.advertising, pl->supported); + phylink_validate(pl, pl->supported, &pl->link_config); + + c = phy_caps_lookup(pl->link_config.speed, pl->link_config.duplex, + pl->supported, true); + if (c) + linkmode_and(match, pl->supported, c->linkmodes); + + linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, mask); + linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask); + linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, mask); + linkmode_and(pl->supported, pl->supported, mask); + + phylink_set(pl->supported, MII); + + if (c) { + linkmode_or(pl->supported, pl->supported, match); + linkmode_or(pl->link_config.lp_advertising, + pl->link_config.lp_advertising, match); + } 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; + unsigned long caps; + + if (pl->config->default_an_inband) + pl->cfg_link_an_mode = MLO_AN_INBAND; + + dn = fwnode_get_named_child_node(fwnode, "fixed-link"); + if (dn || fwnode_property_present(fwnode, "fixed-link")) + pl->cfg_link_an_mode = MLO_AN_FIXED; + fwnode_handle_put(dn); + + if ((fwnode_property_read_string(fwnode, "managed", &managed) == 0 && + strcmp(managed, "in-band-status") == 0)) { + if (pl->cfg_link_an_mode == MLO_AN_FIXED) { + phylink_err(pl, + "can't use both fixed-link and in-band-status\n"); + return -EINVAL; + } + + pl->cfg_link_an_mode = MLO_AN_INBAND; + } + + if (pl->cfg_link_an_mode == MLO_AN_INBAND) { + 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); + + 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: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + case PHY_INTERFACE_MODE_5GBASER: + case PHY_INTERFACE_MODE_25GBASER: + case PHY_INTERFACE_MODE_USXGMII: + case PHY_INTERFACE_MODE_10G_QXGMII: + case PHY_INTERFACE_MODE_10GKR: + case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_XLGMII: + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + case PHY_INTERFACE_MODE_100GBASEP: + caps = ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE); + caps = phylink_get_capabilities(pl->link_config.interface, caps, + RATE_MATCH_NONE); + phylink_caps_to_linkmodes(pl->supported, caps); + 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 unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + if (pcs && pcs->ops->pcs_inband_caps) + return pcs->ops->pcs_inband_caps(pcs, interface); + + return 0; +} + +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_disable_eee(struct phylink_pcs *pcs) +{ + if (pcs && pcs->ops->pcs_disable_eee) + pcs->ops->pcs_disable_eee(pcs); +} + +static void phylink_pcs_enable_eee(struct phylink_pcs *pcs) +{ + if (pcs && pcs->ops->pcs_enable_eee) + pcs->ops->pcs_enable_eee(pcs); +} + +/* Query inband for a specific interface mode, asking the MAC for the + * PCS which will be used to handle the interface mode. + */ +static unsigned int phylink_inband_caps(struct phylink *pl, + phy_interface_t interface) +{ + struct phylink_pcs *pcs; + + if (!pl->mac_ops->mac_select_pcs) + return 0; + + pcs = pl->mac_ops->mac_select_pcs(pl->config, interface); + if (IS_ERR_OR_NULL(pcs)) + return 0; + + return phylink_pcs_inband_caps(pcs, interface); +} + +static void phylink_pcs_poll_stop(struct phylink *pl) +{ + if (pl->cfg_link_an_mode == MLO_AN_INBAND) + timer_delete(&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); +} + +int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs) +{ + int ret = 0; + + /* Signal to PCS driver that MAC requires RX clock for init */ + if (pl->config->mac_requires_rxc) + pcs->rxc_always_on = true; + + if (pcs->ops->pcs_pre_init) + ret = pcs->ops->pcs_pre_init(pcs); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_pcs_pre_init); + +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->act_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->act_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->act_link_an_mode)) + pl->pcs->ops->pcs_an_restart(pl->pcs); +} + +enum inband_type { + INBAND_NONE, + INBAND_CISCO_SGMII, + INBAND_BASEX, +}; + +static enum inband_type phylink_get_inband_type(phy_interface_t interface) +{ + switch (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_USXGMII: + case PHY_INTERFACE_MODE_10G_QXGMII: + /* These protocols are designed for use with a PHY which + * communicates its negotiation result back to the MAC via + * inband communication. Note: there exist PHYs that run + * with SGMII but do not send the inband data. + */ + return INBAND_CISCO_SGMII; + + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + /* 1000base-X is designed for use media-side for Fibre + * connections, and thus the Autoneg bit needs to be + * taken into account. We also do this for 2500base-X + * as well, but drivers may not support this, so may + * need to override this. + */ + return INBAND_BASEX; + + default: + return INBAND_NONE; + } +} + +/** + * phylink_pcs_neg_mode() - helper to determine PCS inband mode + * @pl: a pointer to a &struct phylink returned from phylink_create() + * @pcs: a pointer to &struct phylink_pcs + * @interface: interface mode to be used + * @advertising: adertisement ethtool link mode mask + * + * Determines the negotiation mode to be used by the PCS, and returns + * one of: + * + * - %PHYLINK_PCS_NEG_NONE: interface mode does not support inband + * - %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY) + * will be used. + * - %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg + * disabled + * - %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled + * + * Note: this is for cases where the PCS itself is involved in negotiation + * (e.g. Clause 37, SGMII and similar) not Clause 73. + */ +static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs, + phy_interface_t interface, + const unsigned long *advertising) +{ + unsigned int pcs_ib_caps = 0; + unsigned int phy_ib_caps = 0; + unsigned int neg_mode, mode; + enum inband_type type; + + type = phylink_get_inband_type(interface); + if (type == INBAND_NONE) { + pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE; + pl->act_link_an_mode = pl->req_link_an_mode; + return; + } + + mode = pl->req_link_an_mode; + + pl->phy_ib_mode = 0; + + if (pcs) + pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface); + + if (pl->phydev) + phy_ib_caps = phy_inband_caps(pl->phydev, interface); + + phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n", + phy_modes(interface), pcs_ib_caps, phy_ib_caps); + + if (!phylink_autoneg_inband(mode)) { + bool pcs_ib_only = false; + bool phy_ib_only = false; + + if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) { + /* PCS supports reporting in-band capabilities, and + * supports more than disable mode. + */ + if (pcs_ib_caps & LINK_INBAND_DISABLE) + neg_mode = PHYLINK_PCS_NEG_OUTBAND; + else if (pcs_ib_caps & LINK_INBAND_ENABLE) + pcs_ib_only = true; + } + + if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) { + /* PHY supports in-band capabilities, and supports + * more than disable mode. + */ + if (phy_ib_caps & LINK_INBAND_DISABLE) + pl->phy_ib_mode = LINK_INBAND_DISABLE; + else if (phy_ib_caps & LINK_INBAND_BYPASS) + pl->phy_ib_mode = LINK_INBAND_BYPASS; + else if (phy_ib_caps & LINK_INBAND_ENABLE) + phy_ib_only = true; + } + + /* If either the PCS or PHY requires inband to be enabled, + * this is an invalid configuration. Provide a diagnostic + * message for this case, but don't try to force the issue. + */ + if (pcs_ib_only || phy_ib_only) + phylink_warn(pl, + "firmware wants %s mode, but %s%s%s requires inband\n", + phylink_an_mode_str(mode), + pcs_ib_only ? "PCS" : "", + pcs_ib_only && phy_ib_only ? " and " : "", + phy_ib_only ? "PHY" : ""); + + neg_mode = PHYLINK_PCS_NEG_OUTBAND; + } else if (type == INBAND_CISCO_SGMII || pl->phydev) { + /* For SGMII modes which are designed to be used with PHYs, or + * Base-X with a PHY, we try to use in-band mode where-ever + * possible. However, there are some PHYs e.g. BCM84881 which + * do not support in-band. + */ + const unsigned int inband_ok = LINK_INBAND_ENABLE | + LINK_INBAND_BYPASS; + const unsigned int outband_ok = LINK_INBAND_DISABLE | + LINK_INBAND_BYPASS; + /* PCS PHY + * D E D E + * 0 0 0 0 no information inband enabled + * 1 0 0 0 pcs doesn't support outband + * 0 1 0 0 pcs required inband enabled + * 1 1 0 0 pcs optional inband enabled + * 0 0 1 0 phy doesn't support outband + * 1 0 1 0 pcs+phy doesn't support outband + * 0 1 1 0 pcs required, phy doesn't support, invalid + * 1 1 1 0 pcs optional, phy doesn't support, outband + * 0 0 0 1 phy required inband enabled + * 1 0 0 1 pcs doesn't support, phy required, invalid + * 0 1 0 1 pcs+phy required inband enabled + * 1 1 0 1 pcs optional, phy required inband enabled + * 0 0 1 1 phy optional inband enabled + * 1 0 1 1 pcs doesn't support, phy optional, outband + * 0 1 1 1 pcs required, phy optional inband enabled + * 1 1 1 1 pcs+phy optional inband enabled + */ + if ((!pcs_ib_caps || pcs_ib_caps & inband_ok) && + (!phy_ib_caps || phy_ib_caps & inband_ok)) { + /* In-band supported or unknown at both ends. Enable + * in-band mode with or without bypass at the PHY. + */ + if (phy_ib_caps & LINK_INBAND_ENABLE) + pl->phy_ib_mode = LINK_INBAND_ENABLE; + else if (phy_ib_caps & LINK_INBAND_BYPASS) + pl->phy_ib_mode = LINK_INBAND_BYPASS; + + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) && + (!phy_ib_caps || phy_ib_caps & outband_ok)) { + /* Either in-band not supported at at least one end. + * In-band bypass at the other end is possible. + */ + if (phy_ib_caps & LINK_INBAND_DISABLE) + pl->phy_ib_mode = LINK_INBAND_DISABLE; + else if (phy_ib_caps & LINK_INBAND_BYPASS) + pl->phy_ib_mode = LINK_INBAND_BYPASS; + + neg_mode = PHYLINK_PCS_NEG_OUTBAND; + if (pl->phydev) + mode = MLO_AN_PHY; + } else { + /* invalid */ + phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band", + phy_modes(interface)); + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + } + } else { + /* For Base-X without a PHY */ + if (pcs_ib_caps == LINK_INBAND_DISABLE) + /* If the PCS doesn't support inband, then inband must + * be disabled. + */ + neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED; + else if (pcs_ib_caps == LINK_INBAND_ENABLE) + /* If the PCS requires inband, then inband must always + * be enabled. + */ + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + advertising)) + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + else + neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED; + } + + pl->pcs_neg_mode = neg_mode; + pl->act_link_an_mode = mode; +} + +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; + int err; + + phylink_dbg(pl, "major config, requested %s/%s\n", + phylink_an_mode_str(pl->req_link_an_mode), + phy_modes(state->interface)); + + pl->major_config_failed = false; + + if (pl->mac_ops->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); + + pl->major_config_failed = true; + return; + } + + pcs_changed = pl->pcs != pcs; + } + + phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising); + + phylink_dbg(pl, "major config, active %s/%s/%s\n", + phylink_an_mode_str(pl->act_link_an_mode), + phylink_pcs_mode_str(pl->pcs_neg_mode), + phy_modes(state->interface)); + + phylink_pcs_poll_stop(pl); + + if (pl->mac_ops->mac_prepare) { + err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode, + state->interface); + if (err < 0) { + phylink_err(pl, "mac_prepare failed: %pe\n", + ERR_PTR(err)); + pl->major_config_failed = true; + 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; + + if (pcs) + 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) { + err = phylink_pcs_post_config(pl->pcs, state->interface); + if (err < 0) { + phylink_err(pl, "pcs_post_config failed: %pe\n", + ERR_PTR(err)); + + pl->major_config_failed = true; + } + } + + if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed) + phylink_pcs_enable(pl->pcs); + + err = phylink_pcs_config(pl->pcs, 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)); + pl->major_config_failed = true; + } 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->act_link_an_mode, + state->interface); + if (err < 0) { + phylink_err(pl, "mac_finish failed: %pe\n", + ERR_PTR(err)); + + pl->major_config_failed = true; + } + } + + if (pl->phydev && pl->phy_ib_mode) { + phylink_dbg(pl, "configuring PHY for inband%s%s%s\n", + pl->phy_ib_mode & LINK_INBAND_DISABLE ? + " disable" : "", + pl->phy_ib_mode & LINK_INBAND_ENABLE ? + " enable" : "", + pl->phy_ib_mode & LINK_INBAND_BYPASS ? + " bypass" : ""); + err = phy_config_inband(pl->phydev, pl->phy_ib_mode); + if (err < 0) { + phylink_err(pl, "phy_config_inband: %pe\n", + ERR_PTR(err)); + + pl->major_config_failed = true; + } + } + + 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) +{ + 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->req_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 */ + phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface, + pl->link_config.advertising); + + /* 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, 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) +{ + struct phylink_pcs *pcs; + bool autoneg; + + 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; + state->an_complete = 0; + state->link = 1; + + autoneg = pl->pcs_neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; + if (autoneg) { + 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; + } + + pcs = pl->pcs; + if (pcs) + pcs->ops->pcs_get_state(pcs, pl->pcs_neg_mode, 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; + struct phy_device *phy = pl->phydev; + + switch (pl->req_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); + if (phy) + mutex_lock(&phy->lock); + phylink_major_config(pl, force_restart, &link_state); + if (phy) + mutex_unlock(&phy->lock); +} + +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_deactivate_lpi(struct phylink *pl) +{ + if (pl->mac_enable_tx_lpi) { + pl->mac_enable_tx_lpi = false; + + phylink_dbg(pl, "disabling LPI\n"); + + pl->mac_ops->mac_disable_tx_lpi(pl->config); + + phylink_pcs_disable_eee(pl->pcs); + } +} + +static void phylink_activate_lpi(struct phylink *pl) +{ + int err; + + if (!test_bit(pl->cur_interface, pl->config->lpi_interfaces)) { + phylink_dbg(pl, "MAC does not support LPI with %s\n", + phy_modes(pl->cur_interface)); + return; + } + + phylink_dbg(pl, "LPI timer %uus, tx clock stop %u\n", + pl->mac_tx_lpi_timer, pl->mac_tx_clk_stop); + + phylink_pcs_enable_eee(pl->pcs); + + err = pl->mac_ops->mac_enable_tx_lpi(pl->config, pl->mac_tx_lpi_timer, + pl->mac_tx_clk_stop); + if (err) { + phylink_pcs_disable_eee(pl->pcs); + phylink_err(pl, "%ps() failed: %pe\n", + pl->mac_ops->mac_enable_tx_lpi, ERR_PTR(err)); + return; + } + + pl->mac_enable_tx_lpi = true; +} + +static void phylink_link_up(struct phylink *pl, + struct phylink_link_state link_state) +{ + struct net_device *ndev = pl->netdev; + 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; + + phylink_pcs_link_up(pl->pcs, pl->pcs_neg_mode, pl->cur_interface, speed, + duplex); + + pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode, + pl->cur_interface, speed, duplex, + !!(link_state.pause & MLO_PAUSE_TX), rx_pause); + + if (pl->mac_supports_eee && pl->phy_enable_tx_lpi) + phylink_activate_lpi(pl); + + 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); + + phylink_deactivate_lpi(pl); + + pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode, + pl->cur_interface); + phylink_info(pl, "Link is Down\n"); +} + +static bool phylink_link_is_up(struct phylink *pl) +{ + return pl->netdev ? netif_carrier_ok(pl->netdev) : pl->old_link_state; +} + +static void phylink_resolve(struct work_struct *w) +{ + struct phylink *pl = container_of(w, struct phylink, resolve); + struct phylink_link_state link_state; + bool mac_config = false; + bool retrigger = false; + struct phy_device *phy; + bool cur_link_state; + + mutex_lock(&pl->phydev_mutex); + phy = pl->phydev; + if (phy) + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + cur_link_state = phylink_link_is_up(pl); + + if (pl->phylink_disable_state) { + pl->link_failed = false; + link_state = pl->link_config; + link_state.link = false; + } else if (pl->link_failed) { + link_state = pl->link_config; + link_state.link = false; + retrigger = true; + } else if (pl->act_link_an_mode == MLO_AN_FIXED) { + phylink_get_fixed_state(pl, &link_state); + mac_config = link_state.link; + } else if (pl->act_link_an_mode == MLO_AN_PHY) { + link_state = pl->phy_state; + mac_config = link_state.link; + } else { + 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 (phy) + link_state.link &= pl->phy_state.link; + + /* Only update if the PHY link is up */ + if (phy && 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; + } + } + + if (pl->act_link_an_mode != MLO_AN_FIXED) + phylink_apply_manual_flow(pl, &link_state); + + if ((mac_config && link_state.interface != pl->link_config.interface) || + pl->force_major_config) { + /* The interface has changed or a forced major configuration + * was requested, so 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; + pl->force_major_config = false; + } + + /* If configuration of the interface failed, force the link down + * until we get a successful configuration. + */ + if (pl->major_config_failed) + link_state.link = false; + + 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->link_failed = false; + queue_work(system_power_efficient_wq, &pl->resolve); + } + mutex_unlock(&pl->state_mutex); + if (phy) + mutex_unlock(&phy->lock); + mutex_unlock(&pl->phydev_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_set_fixed_link() - set the fixed link + * @pl: a pointer to a &struct phylink returned from phylink_create() + * @state: a pointer to a struct phylink_link_state. + * + * This function is used when the link parameters are known and do not change, + * making it suitable for certain types of network connections. + * + * Returns: zero on success or negative error code. + */ +int phylink_set_fixed_link(struct phylink *pl, + const struct phylink_link_state *state) +{ + const struct link_capabilities *c; + unsigned long *adv; + + if (pl->cfg_link_an_mode != MLO_AN_PHY || !state || + !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) + return -EINVAL; + + c = phy_caps_lookup(state->speed, state->duplex, + pl->supported, true); + if (!c) + return -EINVAL; + + adv = pl->link_config.advertising; + linkmode_and(adv, pl->supported, c->linkmodes); + linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv); + + pl->link_config.speed = state->speed; + pl->link_config.duplex = state->duplex; + pl->link_config.link = 1; + pl->link_config.an_complete = 1; + + pl->cfg_link_an_mode = MLO_AN_FIXED; + pl->req_link_an_mode = pl->cfg_link_an_mode; + + return 0; +} +EXPORT_SYMBOL_GPL(phylink_set_fixed_link); + +/** + * 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) +{ + 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); + } + + pl = kzalloc_obj(*pl); + if (!pl) + return ERR_PTR(-ENOMEM); + + mutex_init(&pl->phydev_mutex); + 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 { + ret = -EINVAL; + goto free_pl; + } + + pl->mac_supports_eee_ops = phylink_mac_implements_lpi(mac_ops); + pl->mac_supports_eee = pl->mac_supports_eee_ops && + pl->config->lpi_capabilities && + !phy_interface_empty(pl->config->lpi_interfaces); + + /* Set the default EEE configuration */ + pl->eee_cfg.eee_enabled = pl->config->eee_enabled_default; + pl->eee_cfg.tx_lpi_enabled = pl->eee_cfg.eee_enabled; + pl->eee_cfg.tx_lpi_timer = pl->config->lpi_timer_default; + + 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); + + linkmode_fill(pl->supported); + 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) + goto free_pl; + + if (pl->cfg_link_an_mode == MLO_AN_FIXED) { + ret = phylink_parse_fixedlink(pl, fwnode); + if (ret < 0) + goto release_link_gpio; + } + + pl->req_link_an_mode = pl->cfg_link_an_mode; + + ret = phylink_register_sfp(pl, fwnode); + if (ret < 0) + goto release_link_gpio; + + return pl; + +release_link_gpio: + if (pl->link_gpio) + gpiod_put(pl->link_gpio); +free_pl: + kfree(pl); + return ERR_PTR(ret); +} +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) + 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; + if (!up) + pl->link_failed = true; + + /* Get the LPI state from phylib */ + pl->phy_enable_tx_lpi = phydev->enable_tx_lpi; + pl->mac_tx_lpi_timer = phydev->eee_cfg.tx_lpi_timer; + mutex_unlock(&pl->state_mutex); + + phylink_run_resolve(pl); + + phylink_dbg(pl, "phy link %s %s/%s/%s/%s/%s/%slpi\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), + phydev->enable_tx_lpi ? "" : "no"); +} + +static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy, + unsigned long *supported, + struct phylink_link_state *state) +{ + DECLARE_PHY_INTERFACE_MASK(interfaces); + + /* If the PHY provides a bitmap of the interfaces it will be using + * depending on the negotiated media speeds, use this to validate + * which ethtool link modes can be used. + */ + if (!phy_interface_empty(phy->possible_interfaces)) { + /* We only care about the union of the PHY's interfaces and + * those which the host supports. + */ + phy_interface_and(interfaces, phy->possible_interfaces, + pl->config->supported_interfaces); + + if (phy_interface_empty(interfaces)) { + phylink_err(pl, "PHY has no common interfaces\n"); + return -EINVAL; + } + + if (phy_on_sfp(phy)) { + /* If the PHY is on a SFP, limit the interfaces to + * those that can be used with a SFP module. + */ + phy_interface_and(interfaces, interfaces, + phylink_sfp_interfaces); + + if (phy_interface_empty(interfaces)) { + phylink_err(pl, "SFP PHY's possible interfaces becomes empty\n"); + return -EINVAL; + } + } + + phylink_dbg(pl, "PHY %s uses interfaces %*pbl, validating %*pbl\n", + phydev_name(phy), + (int)PHY_INTERFACE_MODE_MAX, + phy->possible_interfaces, + (int)PHY_INTERFACE_MODE_MAX, interfaces); + + return phylink_validate_mask(pl, phy, supported, state, + interfaces); + } + + phylink_dbg(pl, "PHY %s doesn't supply possible interfaces\n", + phydev_name(phy)); + + /* Check whether we would use rate matching for the proposed interface + * mode. + */ + state->rate_matching = phy_get_rate_matching(phy, state->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 && state->rate_matching == RATE_MATCH_NONE && + state->interface != PHY_INTERFACE_MODE_RXAUI && + state->interface != PHY_INTERFACE_MODE_XAUI && + state->interface != PHY_INTERFACE_MODE_USXGMII) + state->interface = PHY_INTERFACE_MODE_NA; + + return phylink_validate(pl, supported, state); +} + +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"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. + */ + phy_support_asym_pause(phy); + + memset(&config, 0, sizeof(config)); + linkmode_copy(supported, phy->supported); + linkmode_copy(config.advertising, phy->advertising); + config.interface = interface; + + ret = phylink_validate_phy(pl, phy, 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(&pl->phydev_mutex); + 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); + + /* If the MAC supports phylink managed EEE, restrict the EEE + * advertisement according to the MAC's LPI capabilities. + */ + if (pl->mac_supports_eee) { + /* If EEE is enabled, then we need to call phy_support_eee() + * to ensure that the advertising mask is appropriately set. + * This also enables EEE at the PHY. + */ + if (pl->eee_cfg.eee_enabled) + phy_support_eee(phy); + + phy->eee_cfg.tx_lpi_enabled = pl->eee_cfg.tx_lpi_enabled; + phy->eee_cfg.tx_lpi_timer = pl->eee_cfg.tx_lpi_timer; + + /* Convert the MAC's LPI capabilities to linkmodes */ + linkmode_zero(pl->supported_lpi); + phylink_caps_to_linkmodes(pl->supported_lpi, + pl->config->lpi_capabilities); + + /* Restrict the PHYs EEE support/advertisement to the modes + * that the MAC supports. + */ + linkmode_and(phy->advertising_eee, phy->advertising_eee, + pl->supported_lpi); + } else if (pl->mac_supports_eee_ops) { + /* MAC supports phylink EEE, but wants EEE always disabled. */ + phy_disable_eee(phy); + } + + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + mutex_unlock(&pl->phydev_mutex); + + 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 (pl->config->mac_managed_pm) + phy->mac_managed_pm = true; + + /* Allow the MAC to stop its clock if the PHY has the capability */ + pl->mac_tx_clk_stop = phy_eee_tx_clock_stop_capable(phy) > 0; + + if (pl->mac_supports_eee_ops) { + /* Explicitly configure whether the PHY is allowed to stop it's + * receive clock. + */ + ret = phy_eee_rx_clock_stop(phy, + pl->config->eee_rx_clk_stop_enable); + if (ret == -EOPNOTSUPP) + ret = 0; + } + + if (ret == 0 && phy_interrupt_is_valid(phy)) + phy_request_interrupt(phy); + + return ret; +} + +static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, + phy_interface_t interface) +{ + u32 flags = 0; + + if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED)) + return -EINVAL; + + if (pl->phydev) + return -EBUSY; + + if (pl->config->mac_requires_rxc) + flags |= PHY_F_RXC_ALWAYS_ON; + + return phy_attach_direct(pl->netdev, phy, flags, 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; + + if (!phylink_expects_phy(pl)) + return 0; + + phy_fwnode = fwnode_get_phy_node(fwnode); + if (IS_ERR(phy_fwnode)) { + /* PHY mode requires a PHY to be specified. */ + 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; + } + + if (pl->config->mac_requires_rxc) + flags |= PHY_F_RXC_ALWAYS_ON; + + 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(); + + mutex_lock(&pl->phydev_mutex); + phy = pl->phydev; + if (phy) { + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->phydev = NULL; + pl->phy_enable_tx_lpi = false; + pl->mac_tx_clk_stop = false; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + } + mutex_unlock(&pl->phydev_mutex); + + if (phy) { + 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->link_failed = 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->req_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); + timer_delete_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_rx_clk_stop_block() - block PHY ability to stop receive clock in LPI + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Disable the PHY's ability to stop the receive clock while the receive path + * is in EEE LPI state, until the number of calls to phylink_rx_clk_stop_block() + * are balanced by calls to phylink_rx_clk_stop_unblock(). + */ +void phylink_rx_clk_stop_block(struct phylink *pl) +{ + ASSERT_RTNL(); + + if (pl->mac_rx_clk_stop_blocked == U8_MAX) { + phylink_warn(pl, "%s called too many times - ignoring\n", + __func__); + dump_stack(); + return; + } + + /* Disable PHY receive clock stop if this is the first time this + * function has been called and clock-stop was previously enabled. + */ + if (pl->mac_rx_clk_stop_blocked++ == 0 && + pl->mac_supports_eee_ops && pl->phydev && + pl->config->eee_rx_clk_stop_enable) + phy_eee_rx_clock_stop(pl->phydev, false); +} +EXPORT_SYMBOL_GPL(phylink_rx_clk_stop_block); + +/** + * phylink_rx_clk_stop_unblock() - unblock PHY ability to stop receive clock + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * All calls to phylink_rx_clk_stop_block() must be balanced with a + * corresponding call to phylink_rx_clk_stop_unblock() to restore the PHYs + * ability to stop the receive clock when the receive path is in EEE LPI mode. + */ +void phylink_rx_clk_stop_unblock(struct phylink *pl) +{ + ASSERT_RTNL(); + + if (pl->mac_rx_clk_stop_blocked == 0) { + phylink_warn(pl, "%s called too many times - ignoring\n", + __func__); + dump_stack(); + return; + } + + /* Re-enable PHY receive clock stop if the number of unblocks matches + * the number of calls to the block function above. + */ + if (--pl->mac_rx_clk_stop_blocked == 0 && + pl->mac_supports_eee_ops && pl->phydev && + pl->config->eee_rx_clk_stop_enable) + phy_eee_rx_clock_stop(pl->phydev, true); +} +EXPORT_SYMBOL_GPL(phylink_rx_clk_stop_unblock); + +static bool phylink_mac_supports_wol(struct phylink *pl) +{ + return !!pl->mac_ops->mac_wol_set; +} + +static bool phylink_phy_supports_wol(struct phylink *pl, + struct phy_device *phydev) +{ + return phydev && (pl->config->wol_phy_legacy || phy_can_wakeup(phydev)); +} + +static bool phylink_phy_pm_speed_ctrl(struct phylink *pl) +{ + return pl->config->wol_phy_speed_ctrl && !pl->wolopts_mac && + pl->phydev && phy_may_wakeup(pl->phydev); +} + +/** + * 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. + * + * Note: when phylink managed Wake-on-Lan is in use, @mac_wol is ignored. + * (struct phylink_mac_ops.mac_set_wol populated.) + */ +void phylink_suspend(struct phylink *pl, bool mac_wol) +{ + ASSERT_RTNL(); + + if (phylink_mac_supports_wol(pl)) + mac_wol = !!pl->wolopts_mac; + + if (mac_wol && (!pl->netdev || pl->netdev->ethtool->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); + + pl->suspend_link_up = phylink_link_is_up(pl); + if (pl->suspend_link_up) { + /* 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); + 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); + } + + if (phylink_phy_pm_speed_ctrl(pl)) + phylink_speed_down(pl, false); +} +EXPORT_SYMBOL_GPL(phylink_suspend); + +/** + * phylink_prepare_resume() - prepare to resume a network device + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Optional, but if called must be called prior to phylink_resume(). + * + * Prepare to resume a network device, preparing the PHY as necessary. + */ +void phylink_prepare_resume(struct phylink *pl) +{ + struct phy_device *phydev = pl->phydev; + + ASSERT_RTNL(); + + /* IEEE 802.3 22.2.4.1.5 allows PHYs to stop their receive clock + * when PDOWN is set. However, some MACs require RXC to be running + * in order to resume. If the MAC requires RXC, and we have a PHY, + * then resume the PHY. Note that 802.3 allows PHYs 500ms before + * the clock meets requirements. We do not implement this delay. + */ + if (pl->config->mac_requires_rxc && phydev && phydev->suspended) + phy_resume(phydev); +} +EXPORT_SYMBOL_GPL(phylink_prepare_resume); + +/** + * 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 (phylink_phy_pm_speed_ctrl(pl)) + phylink_speed_up(pl); + + if (test_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state)) { + /* Wake-on-Lan enabled, MAC handling */ + + if (pl->suspend_link_up) { + /* 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 (phylink_mac_supports_wol(pl)) { + if (phylink_phy_supports_wol(pl, pl->phydev)) + phy_ethtool_get_wol(pl->phydev, wol); + + /* Where the MAC augments the WoL support, merge its support and + * current configuration. + */ + if (~wol->wolopts & pl->wolopts_mac & WAKE_MAGICSECURE) + memcpy(wol->sopass, pl->wol_sopass, + sizeof(wol->sopass)); + + wol->supported |= pl->config->wol_mac_support; + wol->wolopts |= pl->wolopts_mac; + } else { + /* Legacy */ + 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) +{ + struct ethtool_wolinfo w = { .cmd = ETHTOOL_GWOL }; + int ret = -EOPNOTSUPP; + bool changed; + u32 wolopts; + + ASSERT_RTNL(); + + if (phylink_mac_supports_wol(pl)) { + wolopts = wol->wolopts; + + if (phylink_phy_supports_wol(pl, pl->phydev)) { + ret = phy_ethtool_set_wol(pl->phydev, wol); + if (ret != 0 && ret != -EOPNOTSUPP) + return ret; + + phy_ethtool_get_wol(pl->phydev, &w); + + /* Any Wake-on-Lan modes which the PHY is handling + * should not be passed on to the MAC. + */ + wolopts &= ~w.wolopts; + } + + wolopts &= pl->config->wol_mac_support; + changed = pl->wolopts_mac != wolopts; + if (wolopts & WAKE_MAGICSECURE) + changed |= !!memcmp(wol->sopass, pl->wol_sopass, + sizeof(wol->sopass)); + memcpy(pl->wol_sopass, wol->sopass, sizeof(pl->wol_sopass)); + + if (changed) { + ret = pl->mac_ops->mac_wol_set(pl->config, wolopts, + wol->sopass); + if (!ret) + pl->wolopts_mac = wolopts; + } else { + ret = 0; + } + } else { + if (pl->phydev) + ret = phy_ethtool_set_wol(pl->phydev, wol); + } + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol); + +static phy_interface_t phylink_sfp_select_interface(struct phylink *pl, + const unsigned long *link_modes) +{ + phy_interface_t interface; + + interface = sfp_select_interface(pl->sfp_bus, link_modes); + if (interface == PHY_INTERFACE_MODE_NA) { + phylink_err(pl, + "selection of interface failed, advertisement %*pb\n", + __ETHTOOL_LINK_MODE_MASK_NBITS, + link_modes); + return interface; + } + + if (!test_bit(interface, pl->config->supported_interfaces)) { + phylink_err(pl, + "selection of interface failed, SFP selected %s (%u) but MAC supports %*pbl\n", + phy_modes(interface), interface, + (int)PHY_INTERFACE_MODE_MAX, + pl->config->supported_interfaces); + return PHY_INTERFACE_MODE_NA; + } + + return interface; +} + +static phy_interface_t phylink_sfp_select_interface_speed(struct phylink *pl, + u32 speed) +{ + phy_interface_t best_interface = PHY_INTERFACE_MODE_NA; + phy_interface_t interface; + u32 max_speed; + int i; + + for (i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); i++) { + interface = phylink_sfp_interface_preference[i]; + if (!test_bit(interface, pl->sfp_interfaces)) + continue; + + max_speed = phylink_interface_max_speed(interface); + + /* The logic here is: if speed == max_speed, then we've found + * the best interface. Otherwise we find the interface that + * can just support the requested speed. + */ + if (max_speed >= speed) + best_interface = interface; + + if (max_speed <= speed) + break; + } + + if (best_interface == PHY_INTERFACE_MODE_NA) + phylink_err(pl, "selection of interface failed, speed %u\n", + speed); + + return best_interface; +} + +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->act_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); + +static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl, + phy_interface_t interface, + unsigned long *adv) +{ + unsigned int inband = phylink_inband_caps(pl, interface); + unsigned int mask; + + /* If the PCS doesn't implement inband support, be permissive. */ + if (!inband) + return true; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv)) + mask = LINK_INBAND_ENABLE; + else + mask = LINK_INBAND_DISABLE; + + /* Check whether the PCS implements the required mode */ + return !!(inband & mask); +} + +/** + * 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); + const struct link_capabilities *c; + struct phylink_link_state config; + + 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. + */ + c = phy_caps_lookup(kset->base.speed, kset->base.duplex, + pl->supported, false); + if (!c) + 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->req_link_an_mode == MLO_AN_FIXED) { + if (c->speed != pl->link_config.speed || + c->duplex != pl->link_config.duplex) + return -EINVAL; + return 0; + } + + config.speed = c->speed; + config.duplex = c->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->req_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) { + if (kset->base.autoneg == AUTONEG_ENABLE) + config.interface = + phylink_sfp_select_interface(pl, + config.advertising); + else + config.interface = + phylink_sfp_select_interface_speed(pl, + config.speed); + if (config.interface == PHY_INTERFACE_MODE_NA) + 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->req_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; + + /* Validate the autonegotiation state. We don't have a PHY in this + * situation, so the PCS is the media-facing entity. + */ + if (!phylink_validate_pcs_inband_autoneg(pl, config.interface, + 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->req_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->link_failed = 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_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_keee for the read parameters + */ +int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_keee *eee) +{ + int ret = -EOPNOTSUPP; + + ASSERT_RTNL(); + + if (pl->mac_supports_eee_ops && !pl->mac_supports_eee) + return ret; + + if (pl->phydev) { + ret = phy_ethtool_get_eee(pl->phydev, eee); + /* Restrict supported linkmode mask */ + if (ret == 0 && pl->mac_supports_eee_ops) + linkmode_and(eee->supported, eee->supported, + pl->supported_lpi); + } + + 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_keee for the desired parameters + */ +int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee) +{ + bool mac_eee = pl->mac_supports_eee; + int ret = -EOPNOTSUPP; + + ASSERT_RTNL(); + + phylink_dbg(pl, "mac %s phylink EEE%s, adv %*pbl, LPI%s timer %uus\n", + mac_eee ? "supports" : "does not support", + eee->eee_enabled ? ", enabled" : "", + __ETHTOOL_LINK_MODE_MASK_NBITS, eee->advertised, + eee->tx_lpi_enabled ? " enabled" : "", eee->tx_lpi_timer); + + if (pl->mac_supports_eee_ops && !mac_eee) + return ret; + + if (pl->phydev) { + /* Restrict advertisement mask */ + if (pl->mac_supports_eee_ops) + linkmode_and(eee->advertised, eee->advertised, + pl->supported_lpi); + ret = phy_ethtool_set_eee(pl->phydev, eee); + if (ret == 0) + eee_to_eeecfg(&pl->eee_cfg, 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->act_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->act_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 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, unsigned long *supported, + struct phylink_link_state *state, + bool changed) +{ + u8 mode = MLO_AN_INBAND; + + 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->req_link_an_mode != mode || + pl->link_config.interface != state->interface) { + pl->req_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, struct phy_device *phy) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(support); + struct phylink_link_state config; + int ret; + + /* We're not using pl->sfp_interfaces, so clear it. */ + phy_interface_zero(pl->sfp_interfaces); + 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; + } + + config.interface = phylink_sfp_select_interface(pl, config.advertising); + if (config.interface == PHY_INTERFACE_MODE_NA) + return -EINVAL; + + /* Attach the PHY so that the PHY is present when we do the major + * configuration step. + */ + ret = phylink_attach_phy(pl, phy, config.interface); + if (ret < 0) + return ret; + + /* This will validate the configuration for us. */ + ret = phylink_bringup_phy(pl, phy, config.interface); + if (ret < 0) { + phy_detach(phy); + return ret; + } + + pl->link_port = pl->sfp_port; + + phylink_sfp_set_config(pl, support, &config, true); + + return 0; +} + +static int phylink_sfp_config_optical(struct phylink *pl) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(support); + struct phylink_link_state config; + enum inband_type inband_type; + 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(pl->sfp_interfaces, pl->config->supported_interfaces, + pl->sfp_interfaces); + if (phy_interface_empty(pl->sfp_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, NULL, pl->sfp_support, &config, + pl->sfp_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, pl->sfp_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)); + + inband_type = phylink_get_inband_type(interface); + if (inband_type == INBAND_NONE) { + /* If this is the sole interface, and there is no inband + * support, clear the advertising mask and Autoneg bit in + * the support mask. Otherwise, just clear the Autoneg bit + * in the advertising mask. + */ + if (phy_interface_weight(pl->sfp_interfaces) == 1) { + linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + pl->sfp_support); + linkmode_zero(config.advertising); + } else { + linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + config.advertising); + } + } + + if (!phylink_validate_pcs_inband_autoneg(pl, interface, + config.advertising)) { + phylink_err(pl, "autoneg setting not compatible with PCS"); + return -EINVAL; + } + + 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, pl->sfp_support, &config, false); + + return 0; +} + +static int phylink_sfp_module_insert(void *upstream, + const struct sfp_eeprom_id *id) +{ + const struct sfp_module_caps *caps; + struct phylink *pl = upstream; + + ASSERT_RTNL(); + + caps = sfp_get_module_caps(pl->sfp_bus); + phy_interface_copy(pl->sfp_interfaces, caps->interfaces); + linkmode_copy(pl->sfp_support, caps->link_modes); + pl->sfp_may_have_phy = caps->may_have_phy; + pl->sfp_port = caps->port; + + /* If this module may have a PHY connecting later, defer until later */ + if (pl->sfp_may_have_phy) + return 0; + + return phylink_sfp_config_optical(pl); +} + +static void phylink_sfp_module_remove(void *upstream) +{ + struct phylink *pl = upstream; + + phy_interface_zero(pl->sfp_interfaces); +} + +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); +} + +static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) +{ + struct phylink *pl = upstream; + + if (!phy->drv) { + phylink_err(pl, "PHY %s (id 0x%.8lx) has no driver loaded\n", + phydev_name(phy), (unsigned long)phy->phy_id); + phylink_err(pl, "Drivers which handle known common cases: CONFIG_BCM84881_PHY, CONFIG_MARVELL_PHY\n"); + return -EINVAL; + } + + /* + * 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"). MAC drivers + * set their support using the MAC_SYM_PAUSE and MAC_ASYM_PAUSE + * capabilities and must NOT change the phy's pause settings directly. + */ + phy_support_asym_pause(phy); + + /* Set the PHY's host supported interfaces */ + phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces, + pl->config->supported_interfaces); + + /* Do the initial configuration */ + return phylink_sfp_config_phy(pl, phy); +} + +static void phylink_sfp_disconnect_phy(void *upstream, + struct phy_device *phydev) +{ + 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_remove = phylink_sfp_module_remove, + .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. + * @neg_mode: link negotiation mode (PHYLINK_PCS_NEG_xxx) + * @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, + unsigned int neg_mode, u16 bmsr, u16 lpa) +{ + state->link = !!(bmsr & BMSR_LSTATUS); + state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE); + + /* If the link is down, the advertisement data is undefined. */ + if (!state->link) + return; + + switch (state->interface) { + case PHY_INTERFACE_MODE_1000BASEX: + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { + phylink_decode_c37_word(state, lpa, SPEED_1000); + } else { + state->speed = SPEED_1000; + state->duplex = DUPLEX_FULL; + state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX; + } + break; + + case PHY_INTERFACE_MODE_2500BASEX: + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { + phylink_decode_c37_word(state, lpa, SPEED_2500); + } else { + state->speed = SPEED_2500; + state->duplex = DUPLEX_FULL; + state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX; + } + break; + + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_PSGMII: + case PHY_INTERFACE_MODE_QSGMII: + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + phylink_decode_sgmii_word(state, lpa); + break; + + case PHY_INTERFACE_MODE_QUSGMII: + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + 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. + * @neg_mode: link negotiation mode (PHYLINK_PCS_NEG_xxx) + * @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, + unsigned int neg_mode, + 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, neg_mode, 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_PSGMII: + 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); + +/** + * phylink_replay_link_begin() - begin replay of link callbacks for driver + * which loses state + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Helper for MAC drivers which may perform a destructive reset at runtime. + * Both the own driver's mac_link_down() method is called, as well as the + * pcs_link_down() method of the split PCS (if any). + * + * This is similar to phylink_stop(), except it does not alter the state of + * the phylib PHY (it is assumed that it is not affected by the MAC destructive + * reset). + */ +void phylink_replay_link_begin(struct phylink *pl) +{ + ASSERT_RTNL(); + + phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_REPLAY); +} +EXPORT_SYMBOL_GPL(phylink_replay_link_begin); + +/** + * phylink_replay_link_end() - end replay of link callbacks for driver + * which lost state + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Helper for MAC drivers which may perform a destructive reset at runtime. + * Both the own driver's mac_config() and mac_link_up() methods, as well as the + * pcs_config() and pcs_link_up() method of the split PCS (if any), are called. + * + * This is similar to phylink_start(), except it does not alter the state of + * the phylib PHY. + * + * One must call this method only within the same rtnl_lock() critical section + * as a previous phylink_replay_link_start(). + */ +void phylink_replay_link_end(struct phylink *pl) +{ + ASSERT_RTNL(); + + if (WARN(!test_bit(PHYLINK_DISABLE_REPLAY, + &pl->phylink_disable_state), + "phylink_replay_link_end() called without a prior phylink_replay_link_begin()\n")) + return; + + pl->force_major_config = true; + phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_REPLAY); + flush_work(&pl->resolve); +} +EXPORT_SYMBOL_GPL(phylink_replay_link_end); + +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"); +MODULE_DESCRIPTION("phylink models the MAC to optional PHY connection"); diff --git a/drivers/net/phy/qcom/Kconfig b/drivers/net/phy/qcom/Kconfig new file mode 100644 index 000000000..06e8430c1 --- /dev/null +++ b/drivers/net/phy/qcom/Kconfig @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only +config QCOM_NET_PHYLIB + tristate + +config AT803X_PHY + tristate "Qualcomm Atheros AR803X PHYs" + select QCOM_NET_PHYLIB + depends on REGULATOR + help + Currently supports the AR8030, AR8031, AR8033, AR8035, IPQ5018 model + +config QCA83XX_PHY + tristate "Qualcomm Atheros QCA833x PHYs" + select QCOM_NET_PHYLIB + help + Currently supports the internal QCA8337(Internal qca8k PHY) model + +config QCA808X_PHY + tristate "Qualcomm QCA808x PHYs" + select QCOM_NET_PHYLIB + help + Currently supports the QCA8081 model + +config QCA807X_PHY + tristate "Qualcomm QCA807x PHYs" + select QCOM_NET_PHYLIB + select PHY_PACKAGE + depends on OF_MDIO + help + Currently supports the Qualcomm QCA8072, QCA8075 and the PSGMII + control PHY. diff --git a/drivers/net/phy/qcom/Makefile b/drivers/net/phy/qcom/Makefile new file mode 100644 index 000000000..f24fb550b --- /dev/null +++ b/drivers/net/phy/qcom/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-phy-lib.o +obj-$(CONFIG_AT803X_PHY) += at803x.o +obj-$(CONFIG_QCA83XX_PHY) += qca83xx.o +obj-$(CONFIG_QCA808X_PHY) += qca808x.o +obj-$(CONFIG_QCA807X_PHY) += qca807x.o diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c new file mode 100644 index 000000000..6872dbf77 --- /dev/null +++ b/drivers/net/phy/qcom/at803x.c @@ -0,0 +1,1249 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/net/phy/at803x.c + * + * Driver for Qualcomm Atheros AR803x PHY + * + * Author: Matus Ujhelyi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qcom.h" + +#define AT803X_LED_CONTROL 0x18 + +#define AT803X_REG_CHIP_CONFIG 0x1f +#define AT803X_BT_BX_REG_SEL 0x8000 + +#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_REG_1F 0x1F +#define AT803X_DEBUG_PLL_ON BIT(2) +#define AT803X_DEBUG_RGMII_1V8 BIT(3) + +/* 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_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 IPQ5018_PHY_ID 0x004dd0c0 + +#define QCA9561_PHY_ID 0x004dd042 + +#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) + +#define IPQ5018_PHY_FIFO_CONTROL 0x19 +#define IPQ5018_PHY_FIFO_RESET GENMASK(1, 0) + +#define IPQ5018_PHY_DEBUG_EDAC 0x4380 +#define IPQ5018_PHY_MMD1_MDAC 0x8100 +#define IPQ5018_PHY_DAC_MASK GENMASK(15, 8) + +/* MDAC and EDAC values for short cable length */ +#define IPQ5018_PHY_DEBUG_EDAC_VAL 0x10 +#define IPQ5018_PHY_MMD1_MDAC_VAL 0x10 + +#define IPQ5018_PHY_MMD1_MSE_THRESH1 0x1000 +#define IPQ5018_PHY_MMD1_MSE_THRESH2 0x1001 +#define IPQ5018_PHY_PCS_EEE_TX_TIMER 0x8008 +#define IPQ5018_PHY_PCS_EEE_RX_TIMER 0x8009 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL3 0x8074 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL4 0x8075 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL5 0x8076 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL6 0x8077 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL7 0x8078 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL9 0x807a +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL13 0x807e +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL14 0x807f + +#define IPQ5018_PHY_MMD1_MSE_THRESH1_VAL 0xf1 +#define IPQ5018_PHY_MMD1_MSE_THRESH2_VAL 0x1f6 +#define IPQ5018_PHY_PCS_EEE_TX_TIMER_VAL 0x7880 +#define IPQ5018_PHY_PCS_EEE_RX_TIMER_VAL 0xc8 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL3_VAL 0xc040 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL4_VAL 0xa060 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL5_VAL 0xc040 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL6_VAL 0xa060 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL7_VAL 0xc24c +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL9_VAL 0xc060 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL13_VAL 0xb060 +#define IPQ5018_PHY_PCS_NEAR_ECHO_THRESH_VAL 0x90b0 + +#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE 0x1 +#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK GENMASK(7, 4) +#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT 0x50 +#define IPQ5018_PHY_DEBUG_ANA_DAC_FILTER 0xa080 + +MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver"); +MODULE_AUTHOR("Matus Ujhelyi"); +MODULE_LICENSE("GPL"); + +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; +}; + +struct at803x_context { + u16 bmcr; + u16 advertise; + u16 control1000; + u16 int_enable; + u16 smart_speed; + u16 led_control; +}; + +struct ipq5018_priv { + struct reset_control *rst; + bool set_short_cable_dac; +}; + +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_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_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; + } + + 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; + } + } + + 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; + + 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) && + !(phydev->dev_flags & PHY_F_RXC_ALWAYS_ON)) + 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) +{ + int 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, ADVERTISE_XNP, 0); +} + +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 && phy_device_has_reset(phydev)) { + struct at803x_context context; + + at803x_context_save(phydev, &context); + + phy_device_reset(phydev, 1); + usleep_range(1000, 2000); + phy_device_reset(phydev, 0); + usleep_range(1000, 2000); + + at803x_context_restore(phydev, &context); + + phydev_dbg(phydev, "%s(): phy was reset\n", __func__); + } +} + +static int at803x_config_aneg(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + int ret; + + ret = at803x_prepare_config_aneg(phydev); + if (ret) + return ret; + + if (priv->is_1000basex) + return genphy_c37_config_aneg(phydev); + + return genphy_config_aneg(phydev); +} + +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_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; + + val = FIELD_PREP(AT803X_CDT_MDI_PAIR_MASK, pair) | + AT803X_CDT_ENABLE_TEST; + ret = at803x_cdt_start(phydev, val); + if (ret) + return ret; + + ret = at803x_cdt_wait_for_completion(phydev, AT803X_CDT_ENABLE_TEST); + 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)) { + val = FIELD_GET(AT803X_CDT_STATUS_DELTA_TIME_MASK, 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; + + *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 void at803x_cable_test_autoneg(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); +} + +static int at803x_cable_test_start(struct phy_device *phydev) +{ + at803x_cable_test_autoneg(phydev); + /* we do all the (time consuming) work later */ + return 0; +} + +static int at8031_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 at8031_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 = at8031_rgmii_reg_set_voltage_sel, + .get_voltage_sel = at8031_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_configure_mii(struct phy_port *port, bool enable, + phy_interface_t interface) +{ + struct phy_device *phydev = port_phydev(port); + + if (interface == PHY_INTERFACE_MODE_SGMII) + dev_warn(&phydev->mdio.dev, + "module may not function if 1000Base-X not supported\n"); + + return 0; +} + +static const struct phy_port_ops at803x_port_ops = { + .configure_mii = at803x_configure_mii, +}; + +static int at8031_attach_mii_port(struct phy_device *phydev, + struct phy_port *port) +{ + linkmode_zero(port->supported); + phylink_set(port->supported, 1000baseX_Full); + phylink_set(port->supported, 1000baseT_Full); + phylink_set(port->supported, Autoneg); + phylink_set(port->supported, Pause); + phylink_set(port->supported, Asym_Pause); + + /* This device doesn't really support SGMII. However, do our best + * to be compatible with copper modules (that usually require SGMII), + * in a degraded mode as we only allow 1000BaseT Full + */ + __set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + + port->ops = &at803x_port_ops; + + return 0; +} + +static int at8031_parse_dt(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct at803x_priv *priv = phydev->priv; + int ret; + + 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; + } + + return 0; +} + +static int at8031_probe(struct phy_device *phydev) +{ + struct at803x_priv *priv; + int mode_cfg; + int ccr; + int ret; + + ret = at803x_probe(phydev); + if (ret) + return ret; + + priv = phydev->priv; + + /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping + * options. + */ + ret = at8031_parse_dt(phydev); + if (ret) + return ret; + + ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG); + 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 + */ + return phy_modify_mmd(phydev, MDIO_MMD_PCS, + AT803X_PHY_MMD3_WOL_CTRL, + AT803X_WOL_EN, 0); +} + +static int at8031_config_init(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + int ret; + + /* 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; + + return at803x_config_init(phydev); +} + +static int at8031_config_intr(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + int err, value = 0; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED && + priv->is_fiber) { + /* Clear any pending interrupts */ + err = at803x_ack_interrupt(phydev); + if (err) + return err; + + value |= AT803X_INTR_ENABLE_LINK_FAIL_BX; + value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX; + + err = phy_set_bits(phydev, AT803X_INTR_ENABLE, value); + if (err) + return err; + } + + return at803x_config_intr(phydev); +} + +/* AR8031 and AR8033 share the same read status logic */ +static int at8031_read_status(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + bool changed; + + if (priv->is_1000basex) + return genphy_c37_read_status(phydev, &changed); + + return at803x_read_status(phydev); +} + +/* AR8031 and AR8035 share the same cable test get status reg */ +static int at8031_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + return at803x_cable_test_get_status(phydev, finished, 0xf); +} + +/* AR8031 and AR8035 share the same cable test start logic */ +static int at8031_cable_test_start(struct phy_device *phydev) +{ + at803x_cable_test_autoneg(phydev); + phy_write(phydev, MII_CTRL1000, 0); + /* we do all the (time consuming) work later */ + return 0; +} + +/* AR8032, AR9331 and QCA9561 share the same cable test get status reg */ +static int at8032_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + return at803x_cable_test_get_status(phydev, finished, 0x3); +} + +static int at8035_parse_dt(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + + /* Mask is set by the generic at803x_parse_dt + * if property is set. Assume property is set + * with the mask not zero. + */ + if (priv->clk_25m_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. + */ + priv->clk_25m_reg &= AT8035_CLK_OUT_MASK; + priv->clk_25m_mask &= AT8035_CLK_OUT_MASK; + } + + return 0; +} + +/* AR8030 and AR8035 shared the same special mask for clk_25m */ +static int at8035_probe(struct phy_device *phydev) +{ + int ret; + + ret = at803x_probe(phydev); + if (ret) + return ret; + + return at8035_parse_dt(phydev); +} + +static int ipq5018_cable_test_start(struct phy_device *phydev) +{ + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL3, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL3_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL4, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL4_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL5, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL5_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL6, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL6_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL7, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL7_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL9, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL9_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL13, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL13_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL3, + IPQ5018_PHY_PCS_NEAR_ECHO_THRESH_VAL); + + /* we do all the (time consuming) work later */ + return 0; +} + +static int ipq5018_config_init(struct phy_device *phydev) +{ + struct ipq5018_priv *priv = phydev->priv; + u16 val; + + /* + * set LDO efuse: first temporarily store ANA_DAC_FILTER value from + * debug register as it will be reset once the ANA_LDO_EFUSE register + * is written to + */ + val = at803x_debug_reg_read(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER); + at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE, + IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK, + IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT); + at803x_debug_reg_write(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER, val); + + /* set 8023AZ EEE TX and RX timer values */ + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_EEE_TX_TIMER, + IPQ5018_PHY_PCS_EEE_TX_TIMER_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_EEE_RX_TIMER, + IPQ5018_PHY_PCS_EEE_RX_TIMER_VAL); + + /* set MSE threshold values */ + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH1, + IPQ5018_PHY_MMD1_MSE_THRESH1_VAL); + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH2, + IPQ5018_PHY_MMD1_MSE_THRESH2_VAL); + + /* PHY DAC values are optional and only set in a PHY to PHY link architecture */ + if (priv->set_short_cable_dac) { + /* setting MDAC (Multi-level Digital-to-Analog Converter) in MMD1 */ + phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MDAC, + IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_MMD1_MDAC_VAL); + + /* setting EDAC (Error-detection and Correction) in debug register */ + at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_EDAC, + IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_DEBUG_EDAC_VAL); + } + + return 0; +} + +static void ipq5018_link_change_notify(struct phy_device *phydev) +{ + /* + * Reset the FIFO buffer upon link disconnects to clear any residual data + * which may cause issues with the FIFO which it cannot recover from. + */ + mdiobus_modify_changed(phydev->mdio.bus, phydev->mdio.addr, + IPQ5018_PHY_FIFO_CONTROL, IPQ5018_PHY_FIFO_RESET, + phydev->link ? IPQ5018_PHY_FIFO_RESET : 0); +} + +static int ipq5018_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct clk *rx_clk, *tx_clk; + struct ipq5018_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->set_short_cable_dac = of_property_read_bool(dev->of_node, + "qcom,dac-preset-short-cable"); + + rx_clk = devm_clk_get_enabled(dev, "rx"); + if (IS_ERR(rx_clk)) + return dev_err_probe(dev, PTR_ERR(rx_clk), + "failed to get and enable RX clock\n"); + + tx_clk = devm_clk_get_enabled(dev, "tx"); + if (IS_ERR(tx_clk)) + return dev_err_probe(dev, PTR_ERR(tx_clk), + "failed to get and enable TX clock\n"); + + priv->rst = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(priv->rst)) + return dev_err_probe(dev, PTR_ERR(priv->rst), + "failed to acquire reset\n"); + + ret = reset_control_reset(priv->rst); + if (ret) + return dev_err_probe(dev, ret, "failed to reset\n"); + + phydev->priv = priv; + + return 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 = at8035_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 = at8031_cable_test_start, + .cable_test_get_status = at8031_cable_test_get_status, +}, { + /* Qualcomm Atheros AR8030 */ + .phy_id = ATH8030_PHY_ID, + .name = "Qualcomm Atheros AR8030", + .phy_id_mask = AT8030_PHY_ID_MASK, + .probe = at8035_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 = at8031_probe, + .config_init = at8031_config_init, + .config_aneg = at803x_config_aneg, + .soft_reset = genphy_soft_reset, + .set_wol = at8031_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 = at8031_read_status, + .config_intr = at8031_config_intr, + .handle_interrupt = at803x_handle_interrupt, + .get_tunable = at803x_get_tunable, + .set_tunable = at803x_set_tunable, + .cable_test_start = at8031_cable_test_start, + .cable_test_get_status = at8031_cable_test_get_status, + .attach_mii_port = at8031_attach_mii_port, +}, { + /* 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 = at8032_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 = at8032_cable_test_get_status, + .read_status = at803x_read_status, + .soft_reset = genphy_soft_reset, + .config_aneg = at803x_config_aneg, +}, { + PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID), + .name = "Qualcomm Atheros IPQ5018 internal PHY", + .flags = PHY_IS_INTERNAL | PHY_POLL_CABLE_TEST, + .probe = ipq5018_probe, + .config_init = ipq5018_config_init, + .link_change_notify = ipq5018_link_change_notify, + .read_status = at803x_read_status, + .config_intr = at803x_config_intr, + .handle_interrupt = at803x_handle_interrupt, + .cable_test_start = ipq5018_cable_test_start, + .cable_test_get_status = qca808x_cable_test_get_status, + .soft_reset = genphy_soft_reset, +}, { + /* 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 = at8032_cable_test_get_status, + .read_status = at803x_read_status, + .soft_reset = genphy_soft_reset, + .config_aneg = at803x_config_aneg, +}, }; + +module_phy_driver(at803x_driver); + +static const 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(IPQ5018_PHY_ID) }, + { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, atheros_tbl); diff --git a/drivers/net/phy/qcom/qca807x.c b/drivers/net/phy/qcom/qca807x.c new file mode 100644 index 000000000..6004da574 --- /dev/null +++ b/drivers/net/phy/qcom/qca807x.c @@ -0,0 +1,856 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Sartura Ltd. + * + * Author: Robert Marko + * Christian Marangi + * + * Qualcomm QCA8072 and QCA8075 PHY driver + */ + +#include +#include +#include +#include +#include +#include + +#include "../phylib.h" +#include "qcom.h" + +#define QCA807X_CHIP_CONFIGURATION 0x1f +#define QCA807X_BT_BX_REG_SEL BIT(15) +#define QCA807X_BT_BX_REG_SEL_FIBER 0 +#define QCA807X_BT_BX_REG_SEL_COPPER 1 +#define QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK GENMASK(3, 0) +#define QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII 4 +#define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER 3 +#define QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER 0 + +#define QCA807X_MEDIA_SELECT_STATUS 0x1a +#define QCA807X_MEDIA_DETECTED_COPPER BIT(5) +#define QCA807X_MEDIA_DETECTED_1000_BASE_X BIT(4) +#define QCA807X_MEDIA_DETECTED_100_BASE_FX BIT(3) + +#define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION 0x807e +#define QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN BIT(0) + +#define QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH 0x801a +#define QCA807X_CONTROL_DAC_MASK GENMASK(2, 0) +/* List of tweaks enabled by this bit: + * - With both FULL amplitude and FULL bias current: bias current + * is set to half. + * - With only DSP amplitude: bias current is set to half and + * is set to 1/4 with cable < 10m. + * - With DSP bias current (included both DSP amplitude and + * DSP bias current): bias current is half the detected current + * with cable < 10m. + */ +#define QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK BIT(2) +#define QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT BIT(1) +#define QCA807X_CONTROL_DAC_DSP_AMPLITUDE BIT(0) + +#define QCA807X_MMD7_LED_100N_1 0x8074 +#define QCA807X_MMD7_LED_100N_2 0x8075 +#define QCA807X_MMD7_LED_1000N_1 0x8076 +#define QCA807X_MMD7_LED_1000N_2 0x8077 + +#define QCA807X_MMD7_LED_CTRL(x) (0x8074 + ((x) * 2)) +#define QCA807X_MMD7_LED_FORCE_CTRL(x) (0x8075 + ((x) * 2)) + +/* LED hw control pattern for fiber port */ +#define QCA807X_LED_FIBER_PATTERN_MASK GENMASK(11, 1) +#define QCA807X_LED_FIBER_TXACT_BLK_EN BIT(10) +#define QCA807X_LED_FIBER_RXACT_BLK_EN BIT(9) +#define QCA807X_LED_FIBER_FDX_ON_EN BIT(6) +#define QCA807X_LED_FIBER_HDX_ON_EN BIT(5) +#define QCA807X_LED_FIBER_1000BX_ON_EN BIT(2) +#define QCA807X_LED_FIBER_100FX_ON_EN BIT(1) + +/* Some device repurpose the LED as GPIO out */ +#define QCA807X_GPIO_FORCE_EN QCA808X_LED_FORCE_EN +#define QCA807X_GPIO_FORCE_MODE_MASK QCA808X_LED_FORCE_MODE_MASK + +#define QCA807X_FUNCTION_CONTROL 0x10 +#define QCA807X_FC_MDI_CROSSOVER_MODE_MASK GENMASK(6, 5) +#define QCA807X_FC_MDI_CROSSOVER_AUTO 3 +#define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDIX 1 +#define QCA807X_FC_MDI_CROSSOVER_MANUAL_MDI 0 + +/* PQSGMII Analog PHY specific */ +#define PQSGMII_CTRL_REG 0x0 +#define PQSGMII_ANALOG_SW_RESET BIT(6) +#define PQSGMII_DRIVE_CONTROL_1 0xb +#define PQSGMII_TX_DRIVER_MASK GENMASK(7, 4) +#define PQSGMII_TX_DRIVER_140MV 0x0 +#define PQSGMII_TX_DRIVER_160MV 0x1 +#define PQSGMII_TX_DRIVER_180MV 0x2 +#define PQSGMII_TX_DRIVER_200MV 0x3 +#define PQSGMII_TX_DRIVER_220MV 0x4 +#define PQSGMII_TX_DRIVER_240MV 0x5 +#define PQSGMII_TX_DRIVER_260MV 0x6 +#define PQSGMII_TX_DRIVER_280MV 0x7 +#define PQSGMII_TX_DRIVER_300MV 0x8 +#define PQSGMII_TX_DRIVER_320MV 0x9 +#define PQSGMII_TX_DRIVER_400MV 0xa +#define PQSGMII_TX_DRIVER_500MV 0xb +#define PQSGMII_TX_DRIVER_600MV 0xc +#define PQSGMII_MODE_CTRL 0x6d +#define PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK BIT(0) +#define PQSGMII_MMD3_SERDES_CONTROL 0x805a + +#define PHY_ID_QCA8072 0x004dd0b2 +#define PHY_ID_QCA8075 0x004dd0b1 + +#define QCA807X_COMBO_ADDR_OFFSET 4 +#define QCA807X_PQSGMII_ADDR_OFFSET 5 +#define SERDES_RESET_SLEEP 100 + +enum qca807x_global_phy { + QCA807X_COMBO_ADDR = 4, + QCA807X_PQSGMII_ADDR = 5, +}; + +struct qca807x_shared_priv { + unsigned int package_mode; + u32 tx_drive_strength; +}; + +struct qca807x_gpio_priv { + struct phy_device *phy; +}; + +struct qca807x_priv { + bool dac_full_amplitude; + bool dac_full_bias_current; + bool dac_disable_bias_current_tweak; + struct qcom_phy_hw_stats hw_stats; +}; + +static int qca807x_cable_test_start(struct phy_device *phydev) +{ + /* we do all the (time consuming) work later */ + return 0; +} + +static int qca807x_led_parse_netdev(struct phy_device *phydev, unsigned long rules, + u16 *offload_trigger) +{ + /* Parsing specific to netdev trigger */ + switch (phydev->port) { + case PORT_TP: + if (test_bit(TRIGGER_NETDEV_TX, &rules)) + *offload_trigger |= QCA808X_LED_TX_BLINK; + if (test_bit(TRIGGER_NETDEV_RX, &rules)) + *offload_trigger |= QCA808X_LED_RX_BLINK; + if (test_bit(TRIGGER_NETDEV_LINK_10, &rules)) + *offload_trigger |= QCA808X_LED_SPEED10_ON; + if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) + *offload_trigger |= QCA808X_LED_SPEED100_ON; + if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) + *offload_trigger |= QCA808X_LED_SPEED1000_ON; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) + *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON; + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) + *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON; + break; + case PORT_FIBRE: + if (test_bit(TRIGGER_NETDEV_TX, &rules)) + *offload_trigger |= QCA807X_LED_FIBER_TXACT_BLK_EN; + if (test_bit(TRIGGER_NETDEV_RX, &rules)) + *offload_trigger |= QCA807X_LED_FIBER_RXACT_BLK_EN; + if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) + *offload_trigger |= QCA807X_LED_FIBER_100FX_ON_EN; + if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) + *offload_trigger |= QCA807X_LED_FIBER_1000BX_ON_EN; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) + *offload_trigger |= QCA807X_LED_FIBER_HDX_ON_EN; + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) + *offload_trigger |= QCA807X_LED_FIBER_FDX_ON_EN; + break; + default: + return -EOPNOTSUPP; + } + + if (rules && !*offload_trigger) + return -EOPNOTSUPP; + + return 0; +} + +static int qca807x_led_hw_control_enable(struct phy_device *phydev, u8 index) +{ + u16 reg; + + if (index > 1) + return -EINVAL; + + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_hw_control_enable(phydev, reg); +} + +static int qca807x_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 offload_trigger = 0; + + if (index > 1) + return -EINVAL; + + return qca807x_led_parse_netdev(phydev, rules, &offload_trigger); +} + +static int qca807x_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 reg, mask, offload_trigger = 0; + int ret; + + if (index > 1) + return -EINVAL; + + ret = qca807x_led_parse_netdev(phydev, rules, &offload_trigger); + if (ret) + return ret; + + ret = qca807x_led_hw_control_enable(phydev, index); + if (ret) + return ret; + + switch (phydev->port) { + case PORT_TP: + reg = QCA807X_MMD7_LED_CTRL(index); + mask = QCA808X_LED_PATTERN_MASK; + break; + case PORT_FIBRE: + /* HW control pattern bits are in LED FORCE reg */ + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + mask = QCA807X_LED_FIBER_PATTERN_MASK; + break; + default: + return -EINVAL; + } + + return phy_modify_mmd(phydev, MDIO_MMD_AN, reg, mask, + offload_trigger); +} + +static bool qca807x_led_hw_control_status(struct phy_device *phydev, u8 index) +{ + u16 reg; + + if (index > 1) + return false; + + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_hw_control_status(phydev, reg); +} + +static int qca807x_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + u16 reg; + int val; + + if (index > 1) + return -EINVAL; + + /* Check if we have hw control enabled */ + if (qca807x_led_hw_control_status(phydev, index)) + return -EINVAL; + + /* Parsing specific to netdev trigger */ + switch (phydev->port) { + case PORT_TP: + reg = QCA807X_MMD7_LED_CTRL(index); + val = phy_read_mmd(phydev, MDIO_MMD_AN, reg); + if (val & QCA808X_LED_TX_BLINK) + set_bit(TRIGGER_NETDEV_TX, rules); + if (val & QCA808X_LED_RX_BLINK) + set_bit(TRIGGER_NETDEV_RX, rules); + if (val & QCA808X_LED_SPEED10_ON) + set_bit(TRIGGER_NETDEV_LINK_10, rules); + if (val & QCA808X_LED_SPEED100_ON) + set_bit(TRIGGER_NETDEV_LINK_100, rules); + if (val & QCA808X_LED_SPEED1000_ON) + set_bit(TRIGGER_NETDEV_LINK_1000, rules); + if (val & QCA808X_LED_HALF_DUPLEX_ON) + set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules); + if (val & QCA808X_LED_FULL_DUPLEX_ON) + set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules); + break; + case PORT_FIBRE: + /* HW control pattern bits are in LED FORCE reg */ + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + val = phy_read_mmd(phydev, MDIO_MMD_AN, reg); + if (val & QCA807X_LED_FIBER_TXACT_BLK_EN) + set_bit(TRIGGER_NETDEV_TX, rules); + if (val & QCA807X_LED_FIBER_RXACT_BLK_EN) + set_bit(TRIGGER_NETDEV_RX, rules); + if (val & QCA807X_LED_FIBER_100FX_ON_EN) + set_bit(TRIGGER_NETDEV_LINK_100, rules); + if (val & QCA807X_LED_FIBER_1000BX_ON_EN) + set_bit(TRIGGER_NETDEV_LINK_1000, rules); + if (val & QCA807X_LED_FIBER_HDX_ON_EN) + set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules); + if (val & QCA807X_LED_FIBER_FDX_ON_EN) + set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int qca807x_led_hw_control_reset(struct phy_device *phydev, u8 index) +{ + u16 reg, mask; + + if (index > 1) + return -EINVAL; + + switch (phydev->port) { + case PORT_TP: + reg = QCA807X_MMD7_LED_CTRL(index); + mask = QCA808X_LED_PATTERN_MASK; + break; + case PORT_FIBRE: + /* HW control pattern bits are in LED FORCE reg */ + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + mask = QCA807X_LED_FIBER_PATTERN_MASK; + break; + default: + return -EINVAL; + } + + return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg, mask); +} + +static int qca807x_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + u16 reg; + int ret; + + if (index > 1) + return -EINVAL; + + /* If we are setting off the LED reset any hw control rule */ + if (!value) { + ret = qca807x_led_hw_control_reset(phydev, index); + if (ret) + return ret; + } + + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_brightness_set(phydev, reg, value); +} + +static int qca807x_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + u16 reg; + + if (index > 1) + return -EINVAL; + + reg = QCA807X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off); +} + +#ifdef CONFIG_GPIOLIB +static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int qca807x_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct qca807x_gpio_priv *priv = gpiochip_get_data(gc); + u16 reg; + int val; + + reg = QCA807X_MMD7_LED_FORCE_CTRL(offset); + val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg); + + return !!FIELD_GET(QCA807X_GPIO_FORCE_MODE_MASK, val); +} + +static int qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct qca807x_gpio_priv *priv = gpiochip_get_data(gc); + u16 reg; + int val; + + reg = QCA807X_MMD7_LED_FORCE_CTRL(offset); + + val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg); + if (val < 0) + return val; + + val &= ~QCA807X_GPIO_FORCE_MODE_MASK; + val |= QCA807X_GPIO_FORCE_EN; + val |= FIELD_PREP(QCA807X_GPIO_FORCE_MODE_MASK, value); + + return phy_write_mmd(priv->phy, MDIO_MMD_AN, reg, val); +} + +static int qca807x_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int value) +{ + return qca807x_gpio_set(gc, offset, value); +} + +static int qca807x_gpio(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct qca807x_gpio_priv *priv; + struct gpio_chip *gc; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->phy = phydev; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + gc->label = dev_name(dev); + gc->base = -1; + gc->ngpio = 2; + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->can_sleep = true; + gc->get_direction = qca807x_gpio_get_direction; + gc->direction_output = qca807x_gpio_dir_out; + gc->get = qca807x_gpio_get; + gc->set = qca807x_gpio_set; + + return devm_gpiochip_add_data(dev, gc, priv); +} +#endif + +static int qca807x_read_fiber_status(struct phy_device *phydev) +{ + bool changed; + int ss, err; + + err = genphy_c37_read_status(phydev, &changed); + if (err || !changed) + return err; + + /* Read the QCA807x PHY-Specific Status register fiber page, + * 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; + + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) { + switch (FIELD_GET(AT803X_SS_SPEED_MASK, ss)) { + case AT803X_SS_SPEED_100: + phydev->speed = SPEED_100; + break; + case AT803X_SS_SPEED_1000: + phydev->speed = SPEED_1000; + break; + } + + if (ss & AT803X_SS_DUPLEX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + } + + return 0; +} + +static int qca807x_read_status(struct phy_device *phydev) +{ + if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported)) { + switch (phydev->port) { + case PORT_FIBRE: + return qca807x_read_fiber_status(phydev); + case PORT_TP: + return at803x_read_status(phydev); + default: + return -EINVAL; + } + } + + return at803x_read_status(phydev); +} + +static int qca807x_phy_package_probe_once(struct phy_device *phydev) +{ + struct qca807x_shared_priv *priv = phy_package_get_priv(phydev); + struct device_node *np = phy_package_get_node(phydev); + unsigned int tx_drive_strength; + const char *package_mode_name; + + /* Default to 600mw if not defined */ + if (of_property_read_u32(np, "qcom,tx-drive-strength-milliwatt", + &tx_drive_strength)) + tx_drive_strength = 600; + + switch (tx_drive_strength) { + case 140: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_140MV; + break; + case 160: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_160MV; + break; + case 180: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_180MV; + break; + case 200: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_200MV; + break; + case 220: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_220MV; + break; + case 240: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_240MV; + break; + case 260: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_260MV; + break; + case 280: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_280MV; + break; + case 300: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_300MV; + break; + case 320: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_320MV; + break; + case 400: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_400MV; + break; + case 500: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_500MV; + break; + case 600: + priv->tx_drive_strength = PQSGMII_TX_DRIVER_600MV; + break; + default: + return -EINVAL; + } + + priv->package_mode = PHY_INTERFACE_MODE_NA; + if (!of_property_read_string(np, "qcom,package-mode", + &package_mode_name)) { + if (!strcasecmp(package_mode_name, + phy_modes(PHY_INTERFACE_MODE_PSGMII))) + priv->package_mode = PHY_INTERFACE_MODE_PSGMII; + else if (!strcasecmp(package_mode_name, + phy_modes(PHY_INTERFACE_MODE_QSGMII))) + priv->package_mode = PHY_INTERFACE_MODE_QSGMII; + else + return -EINVAL; + } + + return 0; +} + +static int qca807x_phy_package_config_init_once(struct phy_device *phydev) +{ + struct qca807x_shared_priv *priv = phy_package_get_priv(phydev); + int val, ret; + + /* Make sure PHY follow PHY package mode if enforced */ + if (priv->package_mode != PHY_INTERFACE_MODE_NA && + phydev->interface != priv->package_mode) + return -EINVAL; + + phy_lock_mdio_bus(phydev); + + /* Set correct PHY package mode */ + val = __phy_package_read(phydev, QCA807X_COMBO_ADDR, + QCA807X_CHIP_CONFIGURATION); + val &= ~QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK; + /* package_mode can be QSGMII or PSGMII and we validate + * this in probe_once. + * With package_mode to NA, we default to PSGMII. + */ + switch (priv->package_mode) { + case PHY_INTERFACE_MODE_QSGMII: + val |= QCA807X_CHIP_CONFIGURATION_MODE_QSGMII_SGMII; + break; + case PHY_INTERFACE_MODE_PSGMII: + default: + val |= QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_ALL_COPPER; + } + ret = __phy_package_write(phydev, QCA807X_COMBO_ADDR, + QCA807X_CHIP_CONFIGURATION, val); + if (ret) + goto exit; + + /* After mode change Serdes reset is required */ + val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR, + PQSGMII_CTRL_REG); + val &= ~PQSGMII_ANALOG_SW_RESET; + ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR, + PQSGMII_CTRL_REG, val); + if (ret) + goto exit; + + msleep(SERDES_RESET_SLEEP); + + val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR, + PQSGMII_CTRL_REG); + val |= PQSGMII_ANALOG_SW_RESET; + ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR, + PQSGMII_CTRL_REG, val); + if (ret) + goto exit; + + /* Workaround to enable AZ transmitting ability */ + val = __phy_package_read_mmd(phydev, QCA807X_PQSGMII_ADDR, + MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL); + val &= ~PQSGMII_MODE_CTRL_AZ_WORKAROUND_MASK; + ret = __phy_package_write_mmd(phydev, QCA807X_PQSGMII_ADDR, + MDIO_MMD_PMAPMD, PQSGMII_MODE_CTRL, val); + if (ret) + goto exit; + + /* Set PQSGMII TX AMP strength */ + val = __phy_package_read(phydev, QCA807X_PQSGMII_ADDR, + PQSGMII_DRIVE_CONTROL_1); + val &= ~PQSGMII_TX_DRIVER_MASK; + val |= FIELD_PREP(PQSGMII_TX_DRIVER_MASK, priv->tx_drive_strength); + ret = __phy_package_write(phydev, QCA807X_PQSGMII_ADDR, + PQSGMII_DRIVE_CONTROL_1, val); + if (ret) + goto exit; + + /* Prevent PSGMII going into hibernation via PSGMII self test */ + val = __phy_package_read_mmd(phydev, QCA807X_COMBO_ADDR, + MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL); + val &= ~BIT(1); + ret = __phy_package_write_mmd(phydev, QCA807X_COMBO_ADDR, + MDIO_MMD_PCS, PQSGMII_MMD3_SERDES_CONTROL, val); + +exit: + phy_unlock_mdio_bus(phydev); + + return ret; +} + +static int qca807x_configure_serdes(struct phy_port *port, bool enable, + phy_interface_t interface) +{ + struct phy_device *phydev = port_phydev(port); + int ret; + + if (!phydev) + return -ENODEV; + + if (enable) { + /* Set PHY mode to PSGMII combo (1/4 copper + combo ports) mode */ + ret = phy_modify(phydev, + QCA807X_CHIP_CONFIGURATION, + QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK, + QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER); + if (ret) + return ret; + /* Enable fiber mode autodection (1000Base-X or 100Base-FX) */ + ret = phy_set_bits_mmd(phydev, + MDIO_MMD_AN, + QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION, + QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN); + if (ret) + return ret; + } + + phydev->port = enable ? PORT_FIBRE : PORT_TP; + + return phy_modify(phydev, QCA807X_CHIP_CONFIGURATION, + QCA807X_BT_BX_REG_SEL, + enable ? 0 : QCA807X_BT_BX_REG_SEL); +} + +static const struct phy_port_ops qca807x_serdes_port_ops = { + .configure_mii = qca807x_configure_serdes, +}; + +static int qca807x_attach_mii_port(struct phy_device *phydev, + struct phy_port *port) +{ + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_100BASEX, port->interfaces); + + port->ops = &qca807x_serdes_port_ops; + + return 0; +} + +static int qca807x_probe(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct qca807x_shared_priv *shared_priv; + struct device *dev = &phydev->mdio.dev; + struct qca807x_priv *priv; + int ret; + + ret = devm_of_phy_package_join(dev, phydev, sizeof(*shared_priv)); + if (ret) + return ret; + + if (phy_package_probe_once(phydev)) { + ret = qca807x_phy_package_probe_once(phydev); + if (ret) + return ret; + } + + shared_priv = phy_package_get_priv(phydev); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dac_full_amplitude = of_property_read_bool(node, "qcom,dac-full-amplitude"); + priv->dac_full_bias_current = of_property_read_bool(node, "qcom,dac-full-bias-current"); + priv->dac_disable_bias_current_tweak = of_property_read_bool(node, + "qcom,dac-disable-bias-current-tweak"); + +#if IS_ENABLED(CONFIG_GPIOLIB) + /* Do not register a GPIO controller unless flagged for it */ + if (of_property_read_bool(node, "gpio-controller")) { + ret = qca807x_gpio(phydev); + if (ret) + return ret; + } +#endif + + /* Attach SFP bus on combo port*/ + if (phy_read(phydev, QCA807X_CHIP_CONFIGURATION)) { + phydev->max_n_ports = 2; + + linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->advertising); + } + + phydev->priv = priv; + + return 0; +} + +static int qca807x_config_init(struct phy_device *phydev) +{ + struct qca807x_priv *priv = phydev->priv; + u16 control_dac; + int ret; + + if (phy_package_init_once(phydev)) { + ret = qca807x_phy_package_config_init_once(phydev); + if (ret) + return ret; + } + + ret = qcom_phy_counter_config(phydev); + if (ret) + return ret; + + control_dac = phy_read_mmd(phydev, MDIO_MMD_AN, + QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH); + control_dac &= ~QCA807X_CONTROL_DAC_MASK; + if (!priv->dac_full_amplitude) + control_dac |= QCA807X_CONTROL_DAC_DSP_AMPLITUDE; + if (!priv->dac_full_bias_current) + control_dac |= QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT; + if (!priv->dac_disable_bias_current_tweak) + control_dac |= QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK; + return phy_write_mmd(phydev, MDIO_MMD_AN, + QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH, + control_dac); +} + +static int qca807x_update_stats(struct phy_device *phydev) +{ + struct qca807x_priv *priv = phydev->priv; + + return qcom_phy_update_stats(phydev, &priv->hw_stats); +} + +static void qca807x_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct qca807x_priv *priv = phydev->priv; + + qcom_phy_get_stats(stats, priv->hw_stats); +} + +static struct phy_driver qca807x_drivers[] = { + { + PHY_ID_MATCH_EXACT(PHY_ID_QCA8072), + .name = "Qualcomm QCA8072", + .flags = PHY_POLL_CABLE_TEST, + /* PHY_GBIT_FEATURES */ + .probe = qca807x_probe, + .config_init = qca807x_config_init, + .read_status = qca807x_read_status, + .config_intr = at803x_config_intr, + .handle_interrupt = at803x_handle_interrupt, + .soft_reset = genphy_soft_reset, + .get_tunable = at803x_get_tunable, + .set_tunable = at803x_set_tunable, + .resume = genphy_resume, + .suspend = genphy_suspend, + .cable_test_start = qca807x_cable_test_start, + .cable_test_get_status = qca808x_cable_test_get_status, + .update_stats = qca807x_update_stats, + .get_phy_stats = qca807x_get_phy_stats, + .set_wol = at8031_set_wol, + .get_wol = at803x_get_wol, + .attach_mii_port = qca807x_attach_mii_port, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_QCA8075), + .name = "Qualcomm QCA8075", + .flags = PHY_POLL_CABLE_TEST, + /* PHY_GBIT_FEATURES */ + .probe = qca807x_probe, + .config_init = qca807x_config_init, + .read_status = qca807x_read_status, + .config_intr = at803x_config_intr, + .handle_interrupt = at803x_handle_interrupt, + .soft_reset = genphy_soft_reset, + .get_tunable = at803x_get_tunable, + .set_tunable = at803x_set_tunable, + .resume = genphy_resume, + .suspend = genphy_suspend, + .cable_test_start = qca807x_cable_test_start, + .cable_test_get_status = qca808x_cable_test_get_status, + .led_brightness_set = qca807x_led_brightness_set, + .led_blink_set = qca807x_led_blink_set, + .led_hw_is_supported = qca807x_led_hw_is_supported, + .led_hw_control_set = qca807x_led_hw_control_set, + .led_hw_control_get = qca807x_led_hw_control_get, + .update_stats = qca807x_update_stats, + .get_phy_stats = qca807x_get_phy_stats, + .set_wol = at8031_set_wol, + .get_wol = at803x_get_wol, + .attach_mii_port = qca807x_attach_mii_port, + }, +}; +module_phy_driver(qca807x_drivers); + +static const struct mdio_device_id __maybe_unused qca807x_tbl[] = { + { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072) }, + { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075) }, + { } +}; + +MODULE_AUTHOR("Robert Marko "); +MODULE_AUTHOR("Christian Marangi "); +MODULE_DESCRIPTION("Qualcomm QCA807x PHY driver"); +MODULE_DEVICE_TABLE(mdio, qca807x_tbl); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/qcom/qca808x.c b/drivers/net/phy/qcom/qca808x.c new file mode 100644 index 000000000..8eb51b1a0 --- /dev/null +++ b/drivers/net/phy/qcom/qca808x.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include + +#include "qcom.h" + +/* 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_MMD7_LED2_CTRL 0x8074 +#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075 +#define QCA808X_MMD7_LED1_CTRL 0x8076 +#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077 +#define QCA808X_MMD7_LED0_CTRL 0x8078 +#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2)) + +#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079 +#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2)) + +#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a +/* QSDK sets by default 0x46 to this reg that sets BIT 6 for + * LED to active high. It's not clear what BIT 3 and BIT 4 does. + */ +#define QCA808X_LED_ACTIVE_HIGH BIT(6) + +/* 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) + +#define QCA8081_PHY_ID 0x004dd101 + +MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver"); +MODULE_AUTHOR("Matus Ujhelyi"); +MODULE_LICENSE("GPL"); + +struct qca808x_priv { + int led_polarity_mode; + struct qcom_phy_hw_stats hw_stats; +}; + +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 bool qca808x_is_1g_only(struct phy_device *phydev) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE); + if (ret < 0) + return true; + + return !!(QCA808X_PHY_CHIP_TYPE_1G & ret); +} + +static void qca808x_fill_possible_interfaces(struct phy_device *phydev) +{ + unsigned long *possible = phydev->possible_interfaces; + + __set_bit(PHY_INTERFACE_MODE_SGMII, possible); + + if (!qca808x_is_1g_only(phydev)) + __set_bit(PHY_INTERFACE_MODE_2500BASEX, possible); +} + +static int qca808x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct qca808x_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Init LED polarity mode to -1 */ + priv->led_polarity_mode = -1; + + phydev->priv = priv; + + return 0; +} + +static int qca808x_config_init(struct phy_device *phydev) +{ + struct qca808x_priv *priv = phydev->priv; + int ret; + + /* Default to LED Active High if active-low not in DT */ + if (priv->led_polarity_mode == -1) { + ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, + QCA808X_MMD7_LED_POLARITY_CTRL, + QCA808X_LED_ACTIVE_HIGH); + if (ret) + return 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; + } + } + + qca808x_fill_possible_interfaces(phydev); + + ret = qcom_phy_counter_config(phydev); + 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) +{ + struct at803x_ss_mask ss_mask = { 0 }; + 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; + + /* qca8081 takes the different bits for speed value from at803x */ + ss_mask.speed_mask = QCA808X_SS_SPEED_MASK; + ss_mask.speed_shift = __bf_shf(QCA808X_SS_SPEED_MASK); + ret = at803x_read_specific_status(phydev, ss_mask); + 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 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_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. + */ + if (qca808x_is_1g_only(phydev)) + linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported); + + return 0; +} + +static int qca808x_config_aneg(struct phy_device *phydev) +{ + int phy_ctrl = 0; + int ret; + + ret = at803x_prepare_config_aneg(phydev); + if (ret) + return ret; + + /* 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 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 int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules, + u16 *offload_trigger) +{ + /* Parsing specific to netdev trigger */ + if (test_bit(TRIGGER_NETDEV_TX, &rules)) + *offload_trigger |= QCA808X_LED_TX_BLINK; + if (test_bit(TRIGGER_NETDEV_RX, &rules)) + *offload_trigger |= QCA808X_LED_RX_BLINK; + if (test_bit(TRIGGER_NETDEV_LINK_10, &rules)) + *offload_trigger |= QCA808X_LED_SPEED10_ON; + if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) + *offload_trigger |= QCA808X_LED_SPEED100_ON; + if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) + *offload_trigger |= QCA808X_LED_SPEED1000_ON; + if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules)) + *offload_trigger |= QCA808X_LED_SPEED2500_ON; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules)) + *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON; + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules)) + *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON; + + if (rules && !*offload_trigger) + return -EOPNOTSUPP; + + /* Enable BLINK_CHECK_BYPASS by default to make the LED + * blink even with duplex or speed mode not enabled. + */ + *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS; + + return 0; +} + +static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index) +{ + u16 reg; + + if (index > 2) + return -EINVAL; + + reg = QCA808X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_hw_control_enable(phydev, reg); +} + +static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 offload_trigger = 0; + + if (index > 2) + return -EINVAL; + + return qca808x_led_parse_netdev(phydev, rules, &offload_trigger); +} + +static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 reg, offload_trigger = 0; + int ret; + + if (index > 2) + return -EINVAL; + + reg = QCA808X_MMD7_LED_CTRL(index); + + ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger); + if (ret) + return ret; + + ret = qca808x_led_hw_control_enable(phydev, index); + if (ret) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_AN, reg, + QCA808X_LED_PATTERN_MASK, + offload_trigger); +} + +static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index) +{ + u16 reg; + + if (index > 2) + return false; + + reg = QCA808X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_hw_control_status(phydev, reg); +} + +static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + u16 reg; + int val; + + if (index > 2) + return -EINVAL; + + /* Check if we have hw control enabled */ + if (qca808x_led_hw_control_status(phydev, index)) + return -EINVAL; + + reg = QCA808X_MMD7_LED_CTRL(index); + + val = phy_read_mmd(phydev, MDIO_MMD_AN, reg); + if (val & QCA808X_LED_TX_BLINK) + set_bit(TRIGGER_NETDEV_TX, rules); + if (val & QCA808X_LED_RX_BLINK) + set_bit(TRIGGER_NETDEV_RX, rules); + if (val & QCA808X_LED_SPEED10_ON) + set_bit(TRIGGER_NETDEV_LINK_10, rules); + if (val & QCA808X_LED_SPEED100_ON) + set_bit(TRIGGER_NETDEV_LINK_100, rules); + if (val & QCA808X_LED_SPEED1000_ON) + set_bit(TRIGGER_NETDEV_LINK_1000, rules); + if (val & QCA808X_LED_SPEED2500_ON) + set_bit(TRIGGER_NETDEV_LINK_2500, rules); + if (val & QCA808X_LED_HALF_DUPLEX_ON) + set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules); + if (val & QCA808X_LED_FULL_DUPLEX_ON) + set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules); + + return 0; +} + +static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index) +{ + u16 reg; + + if (index > 2) + return -EINVAL; + + reg = QCA808X_MMD7_LED_CTRL(index); + + return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg, + QCA808X_LED_PATTERN_MASK); +} + +static int qca808x_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + u16 reg; + int ret; + + if (index > 2) + return -EINVAL; + + if (!value) { + ret = qca808x_led_hw_control_reset(phydev, index); + if (ret) + return ret; + } + + reg = QCA808X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_brightness_set(phydev, reg, value); +} + +static int qca808x_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + u16 reg; + + if (index > 2) + return -EINVAL; + + reg = QCA808X_MMD7_LED_FORCE_CTRL(index); + return qca808x_led_reg_blink_set(phydev, reg, delay_on, delay_off); +} + +static int qca808x_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + struct qca808x_priv *priv = phydev->priv; + bool active_low = false; + u32 mode; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + active_low = true; + break; + default: + return -EINVAL; + } + } + + /* PHY polarity is global and can't be set per LED. + * To detect this, check if last requested polarity mode + * match the new one. + */ + if (priv->led_polarity_mode >= 0 && + priv->led_polarity_mode != active_low) { + phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n"); + return -EINVAL; + } + + /* Save the last PHY polarity mode */ + priv->led_polarity_mode = active_low; + + return phy_modify_mmd(phydev, MDIO_MMD_AN, + QCA808X_MMD7_LED_POLARITY_CTRL, + QCA808X_LED_ACTIVE_HIGH, + active_low ? 0 : QCA808X_LED_ACTIVE_HIGH); +} + +static int qca808x_update_stats(struct phy_device *phydev) +{ + struct qca808x_priv *priv = phydev->priv; + + return qcom_phy_update_stats(phydev, &priv->hw_stats); +} + +static void qca808x_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct qca808x_priv *priv = phydev->priv; + + qcom_phy_get_stats(stats, priv->hw_stats); +} + +static struct phy_driver qca808x_driver[] = { +{ + /* Qualcomm QCA8081 */ + PHY_ID_MATCH_EXACT(QCA8081_PHY_ID), + .name = "Qualcomm QCA8081", + .flags = PHY_POLL_CABLE_TEST, + .probe = qca808x_probe, + .config_intr = at803x_config_intr, + .handle_interrupt = at803x_handle_interrupt, + .get_tunable = at803x_get_tunable, + .set_tunable = at803x_set_tunable, + .set_wol = at8031_set_wol, + .get_wol = at803x_get_wol, + .get_features = qca808x_get_features, + .config_aneg = qca808x_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, + .led_brightness_set = qca808x_led_brightness_set, + .led_blink_set = qca808x_led_blink_set, + .led_hw_is_supported = qca808x_led_hw_is_supported, + .led_hw_control_set = qca808x_led_hw_control_set, + .led_hw_control_get = qca808x_led_hw_control_get, + .led_polarity_set = qca808x_led_polarity_set, + .update_stats = qca808x_update_stats, + .get_phy_stats = qca808x_get_phy_stats, +}, }; + +module_phy_driver(qca808x_driver); + +static const struct mdio_device_id __maybe_unused qca808x_tbl[] = { + { PHY_ID_MATCH_EXACT(QCA8081_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, qca808x_tbl); diff --git a/drivers/net/phy/qcom/qca83xx.c b/drivers/net/phy/qcom/qca83xx.c new file mode 100644 index 000000000..bc70ed8ef --- /dev/null +++ b/drivers/net/phy/qcom/qca83xx.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include + +#include "qcom.h" + +#define AT803X_DEBUG_REG_3C 0x3C + +#define AT803X_DEBUG_REG_GREEN 0x3D +#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6) + +#define MDIO_AZ_DEBUG 0x800D + +#define QCA8327_A_PHY_ID 0x004dd033 +#define QCA8327_B_PHY_ID 0x004dd034 +#define QCA8337_PHY_ID 0x004dd036 + +#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0) + +static struct at803x_hw_stat qca83xx_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 qca83xx_priv { + u64 stats[ARRAY_SIZE(qca83xx_hw_stats)]; +}; + +MODULE_DESCRIPTION("Qualcomm Atheros QCA83XX PHY driver"); +MODULE_AUTHOR("Matus Ujhelyi"); +MODULE_AUTHOR("Christian Marangi "); +MODULE_LICENSE("GPL"); + +static int qca83xx_get_sset_count(struct phy_device *phydev) +{ + return ARRAY_SIZE(qca83xx_hw_stats); +} + +static void qca83xx_get_strings(struct phy_device *phydev, u8 *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) + ethtool_puts(&data, qca83xx_hw_stats[i].string); +} + +static u64 qca83xx_get_stat(struct phy_device *phydev, int i) +{ + struct at803x_hw_stat stat = qca83xx_hw_stats[i]; + struct qca83xx_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 qca83xx_get_stats(struct phy_device *phydev, + struct ethtool_stats *stats, u64 *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) + data[i] = qca83xx_get_stat(phydev, i); +} + +static int qca83xx_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct qca83xx_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + 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; + } + + /* Following original QCA sourcecode set port to prefer master */ + phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER); + + return 0; +} + +static int qca8327_config_init(struct phy_device *phydev) +{ + /* QCA8327 require DAC amplitude adjustment for 100m set to +6%. + * Disable on init and enable only with 100m speed following + * qca original source code. + */ + at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, + QCA8327_DEBUG_MANU_CTRL_EN, 0); + + return qca83xx_config_init(phydev); +} + +static void qca83xx_link_change_notify(struct phy_device *phydev) +{ + /* 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; + + usleep_range(1000, 2000); + + return 0; +} + +static int qca83xx_suspend(struct phy_device *phydev) +{ + 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 qca8337_suspend(struct phy_device *phydev) +{ + /* Only QCA8337 support actual suspend. */ + genphy_suspend(phydev); + + return qca83xx_suspend(phydev); +} + +static int qca8327_suspend(struct phy_device *phydev) +{ + u16 mask = 0; + + /* QCA8327 cause port unreliability when phy suspend + * is set. + */ + mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX); + phy_modify(phydev, MII_BMCR, mask, 0); + + return qca83xx_suspend(phydev); +} + +static struct phy_driver qca83xx_driver[] = { +{ + /* QCA8337 */ + PHY_ID_MATCH_EXACT(QCA8337_PHY_ID), + .name = "Qualcomm Atheros 8337 internal PHY", + /* PHY_GBIT_FEATURES */ + .probe = qca83xx_probe, + .flags = PHY_IS_INTERNAL, + .config_init = qca83xx_config_init, + .soft_reset = genphy_soft_reset, + .get_sset_count = qca83xx_get_sset_count, + .get_strings = qca83xx_get_strings, + .get_stats = qca83xx_get_stats, + .suspend = qca8337_suspend, + .resume = qca83xx_resume, +}, { + /* QCA8327-A from switch QCA8327-AL1A */ + PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID), + .name = "Qualcomm Atheros 8327-A internal PHY", + /* PHY_GBIT_FEATURES */ + .link_change_notify = qca83xx_link_change_notify, + .probe = qca83xx_probe, + .flags = PHY_IS_INTERNAL, + .config_init = qca8327_config_init, + .soft_reset = genphy_soft_reset, + .get_sset_count = qca83xx_get_sset_count, + .get_strings = qca83xx_get_strings, + .get_stats = qca83xx_get_stats, + .suspend = qca8327_suspend, + .resume = qca83xx_resume, +}, { + /* QCA8327-B from switch QCA8327-BL1A */ + PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID), + .name = "Qualcomm Atheros 8327-B internal PHY", + /* PHY_GBIT_FEATURES */ + .link_change_notify = qca83xx_link_change_notify, + .probe = qca83xx_probe, + .flags = PHY_IS_INTERNAL, + .config_init = qca8327_config_init, + .soft_reset = genphy_soft_reset, + .get_sset_count = qca83xx_get_sset_count, + .get_strings = qca83xx_get_strings, + .get_stats = qca83xx_get_stats, + .suspend = qca8327_suspend, + .resume = qca83xx_resume, +}, }; + +module_phy_driver(qca83xx_driver); + +static const struct mdio_device_id __maybe_unused qca83xx_tbl[] = { + { PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) }, + { PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) }, + { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, qca83xx_tbl); diff --git a/drivers/net/phy/qcom/qcom-phy-lib.c b/drivers/net/phy/qcom/qcom-phy-lib.c new file mode 100644 index 000000000..965c2bb99 --- /dev/null +++ b/drivers/net/phy/qcom/qcom-phy-lib.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include +#include +#include + +#include "qcom.h" + +MODULE_DESCRIPTION("Qualcomm PHY driver Common Functions"); +MODULE_AUTHOR("Matus Ujhelyi"); +MODULE_AUTHOR("Christian Marangi "); +MODULE_LICENSE("GPL"); + +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); +} +EXPORT_SYMBOL_GPL(at803x_debug_reg_read); + +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); +} +EXPORT_SYMBOL_GPL(at803x_debug_reg_mask); + +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); +} +EXPORT_SYMBOL_GPL(at803x_debug_reg_write); + +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 interrupt */ + ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL); + if (ret) + return ret; + } else { + /* 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; +} +EXPORT_SYMBOL_GPL(at803x_set_wol); + +int at8031_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + int ret; + + /* First setup MAC address and enable WOL interrupt */ + ret = at803x_set_wol(phydev, wol); + if (ret) + return ret; + + if (wol->wolopts & WAKE_MAGIC) + /* Enable WOL function for 1588 */ + ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, + AT803X_PHY_MMD3_WOL_CTRL, + 0, AT803X_WOL_EN); + else + /* Disable WoL function for 1588 */ + ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, + AT803X_PHY_MMD3_WOL_CTRL, + AT803X_WOL_EN, 0); + + return ret; +} +EXPORT_SYMBOL_GPL(at8031_set_wol); + +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; +} +EXPORT_SYMBOL_GPL(at803x_get_wol); + +int at803x_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, AT803X_INTR_STATUS); + + return (err < 0) ? err : 0; +} +EXPORT_SYMBOL_GPL(at803x_ack_interrupt); + +int at803x_config_intr(struct phy_device *phydev) +{ + int err; + int value; + + value = phy_read(phydev, AT803X_INTR_ENABLE); + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + /* 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; + + 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; +} +EXPORT_SYMBOL_GPL(at803x_config_intr); + +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; +} +EXPORT_SYMBOL_GPL(at803x_handle_interrupt); + +int at803x_read_specific_status(struct phy_device *phydev, + struct at803x_ss_mask ss_mask) +{ + 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; + + speed = ss & ss_mask.speed_mask; + speed >>= ss_mask.speed_shift; + + 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; +} +EXPORT_SYMBOL_GPL(at803x_read_specific_status); + +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)); +} +EXPORT_SYMBOL_GPL(at803x_config_mdix); + +int at803x_prepare_config_aneg(struct phy_device *phydev) +{ + 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; + } + + return 0; +} +EXPORT_SYMBOL_GPL(at803x_prepare_config_aneg); + +int at803x_read_status(struct phy_device *phydev) +{ + struct at803x_ss_mask ss_mask = { 0 }; + 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->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + + err = genphy_read_lpa(phydev); + if (err < 0) + return err; + + ss_mask.speed_mask = AT803X_SS_SPEED_MASK; + ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK); + err = at803x_read_specific_status(phydev, ss_mask); + if (err < 0) + return err; + + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) + phy_resolve_aneg_pause(phydev); + + return 0; +} +EXPORT_SYMBOL_GPL(at803x_read_status); + +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; +} + +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; + } +} +EXPORT_SYMBOL_GPL(at803x_get_tunable); + +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; + } +} +EXPORT_SYMBOL_GPL(at803x_set_tunable); + +int at803x_cdt_fault_length(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. + */ + return (dt * 824) / 10; +} +EXPORT_SYMBOL_GPL(at803x_cdt_fault_length); + +int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start) +{ + return phy_write(phydev, AT803X_CDT, cdt_start); +} +EXPORT_SYMBOL_GPL(at803x_cdt_start); + +int at803x_cdt_wait_for_completion(struct phy_device *phydev, + u32 cdt_en) +{ + int val, ret; + + /* 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; +} +EXPORT_SYMBOL_GPL(at803x_cdt_wait_for_completion); + +static bool qca808x_cdt_fault_length_valid(int cdt_code) +{ + switch (cdt_code) { + case QCA808X_CDT_STATUS_STAT_SAME_SHORT: + case QCA808X_CDT_STATUS_STAT_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT: + 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_SAME_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + case QCA808X_CDT_STATUS_STAT_SAME_OPEN: + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN: + case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT; + 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 result) +{ + 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; + + if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT) + val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val); + else + val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val); + + return at803x_cdt_fault_length(val); +} + +static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair, + u16 status) +{ + int length, result; + u16 pair_code; + + switch (pair) { + case ETHTOOL_A_CABLE_PAIR_A: + pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status); + break; + case ETHTOOL_A_CABLE_PAIR_B: + pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status); + break; + case ETHTOOL_A_CABLE_PAIR_C: + pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status); + break; + case ETHTOOL_A_CABLE_PAIR_D: + pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status); + break; + default: + return -EINVAL; + } + + result = qca808x_cable_test_result_trans(pair_code); + ethnl_cable_test_result(phydev, pair, result); + + if (qca808x_cdt_fault_length_valid(pair_code)) { + length = qca808x_cdt_fault_length(phydev, pair, result); + ethnl_cable_test_fault_length(phydev, pair, length); + } + + return 0; +} + +int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished) +{ + int ret, val; + + *finished = false; + + val = QCA808X_CDT_ENABLE_TEST | + QCA808X_CDT_LENGTH_UNIT; + ret = at803x_cdt_start(phydev, val); + if (ret) + return ret; + + ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST); + if (ret) + return ret; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS); + if (val < 0) + return val; + + ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val); + if (ret) + return ret; + + ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val); + if (ret) + return ret; + + ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val); + if (ret) + return ret; + + ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val); + if (ret) + return ret; + + *finished = true; + + return 0; +} +EXPORT_SYMBOL_GPL(qca808x_cable_test_get_status); + +int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg) +{ + return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg, + QCA808X_LED_FORCE_EN); +} +EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_enable); + +bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, reg); + return !(val & QCA808X_LED_FORCE_EN); +} +EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_status); + +int qca808x_led_reg_brightness_set(struct phy_device *phydev, + u16 reg, enum led_brightness value) +{ + return phy_modify_mmd(phydev, MDIO_MMD_AN, reg, + QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK, + QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON : + QCA808X_LED_FORCE_OFF)); +} +EXPORT_SYMBOL_GPL(qca808x_led_reg_brightness_set); + +int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int ret; + + /* Set blink to 50% off, 50% on at 4Hz by default */ + ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL, + QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK, + QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50); + if (ret) + return ret; + + /* We use BLINK_1 for normal blinking */ + ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg, + QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK, + QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1); + if (ret) + return ret; + + /* We set blink to 4Hz, aka 250ms */ + *delay_on = 250 / 2; + *delay_off = 250 / 2; + + return 0; +} +EXPORT_SYMBOL_GPL(qca808x_led_reg_blink_set); + +/* Enable CRC checking for both received and transmitted frames to ensure + * accurate counter recording. The hardware supports a 32-bit counter, + * configure the counter to clear after it is read to facilitate the + * implementation of a 64-bit software counter + */ +int qcom_phy_counter_config(struct phy_device *phydev) +{ + return phy_set_bits_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_CTRL, + QCA808X_MMD7_CNT_CTRL_CRC_CHECK_EN | + QCA808X_MMD7_CNT_CTRL_READ_CLEAR_EN); +} +EXPORT_SYMBOL_GPL(qcom_phy_counter_config); + +int qcom_phy_update_stats(struct phy_device *phydev, + struct qcom_phy_hw_stats *hw_stats) +{ + int ret; + u32 cnt; + + /* PHY 32-bit counter for RX packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_RX_PKT_15_0); + if (ret < 0) + return ret; + + cnt = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_RX_PKT_31_16); + if (ret < 0) + return ret; + + cnt |= ret << 16; + hw_stats->rx_pkts += cnt; + + /* PHY 16-bit counter for RX CRC error packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_RX_ERR_PKT); + if (ret < 0) + return ret; + + hw_stats->rx_err_pkts += ret; + + /* PHY 32-bit counter for TX packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_TX_PKT_15_0); + if (ret < 0) + return ret; + + cnt = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_TX_PKT_31_16); + if (ret < 0) + return ret; + + cnt |= ret << 16; + hw_stats->tx_pkts += cnt; + + /* PHY 16-bit counter for TX CRC error packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_TX_ERR_PKT); + if (ret < 0) + return ret; + + hw_stats->tx_err_pkts += ret; + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_phy_update_stats); + +void qcom_phy_get_stats(struct ethtool_phy_stats *stats, + struct qcom_phy_hw_stats hw_stats) +{ + stats->tx_packets = hw_stats.tx_pkts; + stats->tx_errors = hw_stats.tx_err_pkts; + stats->rx_packets = hw_stats.rx_pkts; + stats->rx_errors = hw_stats.rx_err_pkts; +} +EXPORT_SYMBOL_GPL(qcom_phy_get_stats); diff --git a/drivers/net/phy/qcom/qcom.h b/drivers/net/phy/qcom/qcom.h new file mode 100644 index 000000000..5071e7149 --- /dev/null +++ b/drivers/net/phy/qcom/qcom.h @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#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 QCA808X_CDT_ENABLE_TEST BIT(15) +#define QCA808X_CDT_INTER_CHECK_DIS BIT(13) +#define QCA808X_CDT_STATUS BIT(11) +#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_SAME_SHORT GENMASK(15, 8) +#define QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT 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_TYPE GENMASK(1, 0) +#define QCA808X_CDT_STATUS_STAT_FAIL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 0) +#define QCA808X_CDT_STATUS_STAT_NORMAL FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 1) +#define QCA808X_CDT_STATUS_STAT_SAME_OPEN FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 2) +#define QCA808X_CDT_STATUS_STAT_SAME_SHORT FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_TYPE, 3) + +#define QCA808X_CDT_STATUS_STAT_MDI GENMASK(3, 2) +#define QCA808X_CDT_STATUS_STAT_MDI1 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 1) +#define QCA808X_CDT_STATUS_STAT_MDI2 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 2) +#define QCA808X_CDT_STATUS_STAT_MDI3 FIELD_PREP_CONST(QCA808X_CDT_STATUS_STAT_MDI, 3) + +/* NORMAL are MDI with type set to 0 */ +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI1 +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\ + QCA808X_CDT_STATUS_STAT_MDI1) +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\ + QCA808X_CDT_STATUS_STAT_MDI1) +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI2 +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\ + QCA808X_CDT_STATUS_STAT_MDI2) +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\ + QCA808X_CDT_STATUS_STAT_MDI2) +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL QCA808X_CDT_STATUS_STAT_MDI3 +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN (QCA808X_CDT_STATUS_STAT_SAME_OPEN |\ + QCA808X_CDT_STATUS_STAT_MDI3) +#define QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT (QCA808X_CDT_STATUS_STAT_SAME_SHORT |\ + QCA808X_CDT_STATUS_STAT_MDI3) + +/* Added for reference of existence but should be handled by wait_for_completion already */ +#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3)) + +#define QCA808X_MMD7_LED_GLOBAL 0x8073 +#define QCA808X_LED_BLINK_1 GENMASK(11, 6) +#define QCA808X_LED_BLINK_2 GENMASK(5, 0) +/* Values are the same for both BLINK_1 and BLINK_2 */ +#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3) +#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0) +#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1) +#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2) +#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3) +#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4) +#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5) +#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6) +#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7) +#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0) +#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0) +#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1) +#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2) +#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3) +#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4) +#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5) +#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6) +#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7) + +/* LED hw control pattern is the same for every LED */ +#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0) +#define QCA808X_LED_SPEED2500_ON BIT(15) +#define QCA808X_LED_SPEED2500_BLINK BIT(14) +/* Follow blink trigger even if duplex or speed condition doesn't match */ +#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13) +#define QCA808X_LED_FULL_DUPLEX_ON BIT(12) +#define QCA808X_LED_HALF_DUPLEX_ON BIT(11) +#define QCA808X_LED_TX_BLINK BIT(10) +#define QCA808X_LED_RX_BLINK BIT(9) +#define QCA808X_LED_TX_ON_10MS BIT(8) +#define QCA808X_LED_RX_ON_10MS BIT(7) +#define QCA808X_LED_SPEED1000_ON BIT(6) +#define QCA808X_LED_SPEED100_ON BIT(5) +#define QCA808X_LED_SPEED10_ON BIT(4) +#define QCA808X_LED_COLLISION_BLINK BIT(3) +#define QCA808X_LED_SPEED1000_BLINK BIT(2) +#define QCA808X_LED_SPEED100_BLINK BIT(1) +#define QCA808X_LED_SPEED10_BLINK BIT(0) + +/* LED force ctrl is the same for every LED + * No documentation exist for this, not even internal one + * with NDA as QCOM gives only info about configuring + * hw control pattern rules and doesn't indicate any way + * to force the LED to specific mode. + * These define comes from reverse and testing and maybe + * lack of some info or some info are not entirely correct. + * For the basic LED control and hw control these finding + * are enough to support LED control in all the required APIs. + * + * On doing some comparison with implementation with qca807x, + * it was found that it's 1:1 equal to it and confirms all the + * reverse done. It was also found further specification with the + * force mode and the blink modes. + */ +#define QCA808X_LED_FORCE_EN BIT(15) +#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13) +#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3) +#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2) +#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1) +#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0) + +#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_PHY_MMD3_WOL_CTRL 0x8012 +#define AT803X_WOL_EN BIT(5) + +#define AT803X_DEBUG_ADDR 0x1D +#define AT803X_DEBUG_DATA 0x1E + +#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_DEFAULT_DOWNSHIFT 5 +#define AT803X_MIN_DOWNSHIFT 2 +#define AT803X_MAX_DOWNSHIFT 9 + +#define QCA808X_MMD7_CNT_CTRL 0x8029 +#define QCA808X_MMD7_CNT_CTRL_READ_CLEAR_EN BIT(1) +#define QCA808X_MMD7_CNT_CTRL_CRC_CHECK_EN BIT(0) + +#define QCA808X_MMD7_CNT_RX_PKT_31_16 0x802a +#define QCA808X_MMD7_CNT_RX_PKT_15_0 0x802b +#define QCA808X_MMD7_CNT_RX_ERR_PKT 0x802c +#define QCA808X_MMD7_CNT_TX_PKT_31_16 0x802d +#define QCA808X_MMD7_CNT_TX_PKT_15_0 0x802e +#define QCA808X_MMD7_CNT_TX_ERR_PKT 0x802f + +enum stat_access_type { + PHY, + MMD +}; + +struct at803x_hw_stat { + const char *string; + u8 reg; + u32 mask; + enum stat_access_type access_type; +}; + +struct at803x_ss_mask { + u16 speed_mask; + u8 speed_shift; +}; + +struct qcom_phy_hw_stats { + u64 rx_pkts; + u64 rx_err_pkts; + u64 tx_pkts; + u64 tx_err_pkts; +}; + +int at803x_debug_reg_read(struct phy_device *phydev, u16 reg); +int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg, + u16 clear, u16 set); +int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data); +int at803x_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol); +int at8031_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol); +void at803x_get_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol); +int at803x_ack_interrupt(struct phy_device *phydev); +int at803x_config_intr(struct phy_device *phydev); +irqreturn_t at803x_handle_interrupt(struct phy_device *phydev); +int at803x_read_specific_status(struct phy_device *phydev, + struct at803x_ss_mask ss_mask); +int at803x_config_mdix(struct phy_device *phydev, u8 ctrl); +int at803x_prepare_config_aneg(struct phy_device *phydev); +int at803x_read_status(struct phy_device *phydev); +int at803x_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data); +int at803x_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data); +int at803x_cdt_fault_length(int dt); +int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start); +int at803x_cdt_wait_for_completion(struct phy_device *phydev, + u32 cdt_en); +int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished); +int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg); +bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg); +int qca808x_led_reg_brightness_set(struct phy_device *phydev, + u16 reg, enum led_brightness value); +int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg, + unsigned long *delay_on, + unsigned long *delay_off); +int qcom_phy_counter_config(struct phy_device *phydev); +int qcom_phy_update_stats(struct phy_device *phydev, + struct qcom_phy_hw_stats *hw_stats); +void qcom_phy_get_stats(struct ethtool_phy_stats *stats, + struct qcom_phy_hw_stats hw_stats); diff --git a/drivers/net/phy/qsemi.c b/drivers/net/phy/qsemi.c new file mode 100644 index 000000000..7b70ba6ca --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* ------------------------------------------------------------------------- */ +/* 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 const struct mdio_device_id __maybe_unused qs6612_tbl[] = { + { 0x00181440, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, qs6612_tbl); diff --git a/drivers/net/phy/qt2025.rs b/drivers/net/phy/qt2025.rs new file mode 100644 index 000000000..470d89a0a --- /dev/null +++ b/drivers/net/phy/qt2025.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) Tehuti Networks Ltd. +// Copyright (C) 2024 FUJITA Tomonori + +//! Applied Micro Circuits Corporation QT2025 PHY driver +//! +//! This driver is based on the vendor driver `QT2025_phy.c`. This source +//! and firmware can be downloaded on the EN-9320SFP+ support site. +//! +//! The QT2025 PHY integrates an Intel 8051 micro-controller. + +use kernel::error::code; +use kernel::firmware::Firmware; +use kernel::io::poll::read_poll_timeout; +use kernel::net::phy::{ + self, + reg::{Mmd, C45}, + Driver, +}; +use kernel::prelude::*; +use kernel::sizes::{SZ_16K, SZ_8K}; +use kernel::time::Delta; + +kernel::module_phy_driver! { + drivers: [PhyQT2025], + device_table: [ + phy::DeviceId::new_with_driver::(), + ], + name: "qt2025_phy", + authors: ["FUJITA Tomonori "], + description: "AMCC QT2025 PHY driver", + license: "GPL", + firmware: ["qt2025-2.0.3.3.fw"], +} + +struct PhyQT2025; + +#[vtable] +impl Driver for PhyQT2025 { + const NAME: &'static CStr = c"QT2025 10Gpbs SFP+"; + const PHY_DEVICE_ID: phy::DeviceId = phy::DeviceId::new_with_exact_mask(0x0043a400); + + fn probe(dev: &mut phy::Device) -> Result<()> { + // Check the hardware revision code. + // Only 0xb3 works with this driver and firmware. + let hw_rev = dev.read(C45::new(Mmd::PMAPMD, 0xd001))?; + if (hw_rev >> 8) != 0xb3 { + return Err(code::ENODEV); + } + + // `MICRO_RESETN`: hold the micro-controller in reset while configuring. + dev.write(C45::new(Mmd::PMAPMD, 0xc300), 0x0000)?; + // `SREFCLK_FREQ`: configure clock frequency of the micro-controller. + dev.write(C45::new(Mmd::PMAPMD, 0xc302), 0x0004)?; + // Non loopback mode. + dev.write(C45::new(Mmd::PMAPMD, 0xc319), 0x0038)?; + // `CUS_LAN_WAN_CONFIG`: select between LAN and WAN (WIS) mode. + dev.write(C45::new(Mmd::PMAPMD, 0xc31a), 0x0098)?; + // The following writes use standardized registers (3.38 through + // 3.41 5/10/25GBASE-R PCS test pattern seed B) for something else. + // We don't know what. + dev.write(C45::new(Mmd::PCS, 0x0026), 0x0e00)?; + dev.write(C45::new(Mmd::PCS, 0x0027), 0x0893)?; + dev.write(C45::new(Mmd::PCS, 0x0028), 0xa528)?; + dev.write(C45::new(Mmd::PCS, 0x0029), 0x0003)?; + // Configure transmit and recovered clock. + dev.write(C45::new(Mmd::PMAPMD, 0xa30a), 0x06e1)?; + // `MICRO_RESETN`: release the micro-controller from the reset state. + dev.write(C45::new(Mmd::PMAPMD, 0xc300), 0x0002)?; + // The micro-controller will start running from the boot ROM. + dev.write(C45::new(Mmd::PCS, 0xe854), 0x00c0)?; + + let fw = Firmware::request(c"qt2025-2.0.3.3.fw", dev.as_ref())?; + if fw.data().len() > SZ_16K + SZ_8K { + return Err(code::EFBIG); + } + + // The 24kB of program memory space is accessible by MDIO. + // The first 16kB of memory is located in the address range 3.8000h - 3.BFFFh. + // The next 8kB of memory is located at 4.8000h - 4.9FFFh. + let mut dst_offset = 0; + let mut dst_mmd = Mmd::PCS; + for (src_idx, val) in fw.data().iter().enumerate() { + if src_idx == SZ_16K { + // Start writing to the next register with no offset + dst_offset = 0; + dst_mmd = Mmd::PHYXS; + } + + dev.write(C45::new(dst_mmd, 0x8000 + dst_offset), (*val).into())?; + + dst_offset += 1; + } + // The micro-controller will start running from SRAM. + dev.write(C45::new(Mmd::PCS, 0xe854), 0x0040)?; + + read_poll_timeout( + || dev.read(C45::new(Mmd::PCS, 0xd7fd)), + |val| *val != 0x00 && *val != 0x10, + Delta::from_millis(50), + Delta::from_secs(3), + )?; + + Ok(()) + } + + fn read_status(dev: &mut phy::Device) -> Result { + dev.genphy_read_status::() + } +} diff --git a/drivers/net/phy/realtek/Kconfig b/drivers/net/phy/realtek/Kconfig new file mode 100644 index 000000000..a741b34d1 --- /dev/null +++ b/drivers/net/phy/realtek/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config REALTEK_PHY + tristate "Realtek PHYs" + select PHY_PACKAGE + help + Currently supports RTL821x/RTL822x and fast ethernet PHYs + +if REALTEK_PHY + +config REALTEK_PHY_HWMON + bool "HWMON support for Realtek PHYs" + depends on HWMON && !(REALTEK_PHY=y && HWMON=m) + help + Optional hwmon support for the temperature sensor + +endif # REALTEK_PHY diff --git a/drivers/net/phy/realtek/Makefile b/drivers/net/phy/realtek/Makefile new file mode 100644 index 000000000..dd21cf87f --- /dev/null +++ b/drivers/net/phy/realtek/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +realtek-y += realtek_main.o +realtek-$(CONFIG_REALTEK_PHY_HWMON) += realtek_hwmon.o +obj-$(CONFIG_REALTEK_PHY) += realtek.o diff --git a/drivers/net/phy/realtek/realtek.h b/drivers/net/phy/realtek/realtek.h new file mode 100644 index 000000000..a39b44fa1 --- /dev/null +++ b/drivers/net/phy/realtek/realtek.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef REALTEK_H +#define REALTEK_H + +#include + +int rtl822x_hwmon_init(struct phy_device *phydev); + +#endif /* REALTEK_H */ diff --git a/drivers/net/phy/realtek/realtek_hwmon.c b/drivers/net/phy/realtek/realtek_hwmon.c new file mode 100644 index 000000000..ac96e2d1e --- /dev/null +++ b/drivers/net/phy/realtek/realtek_hwmon.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HWMON support for Realtek PHY's + * + * Author: Heiner Kallweit + */ + +#include +#include + +#include "realtek.h" + +#define RTL822X_VND2_TSALRM 0xa662 +#define RTL822X_VND2_TSRR 0xbd84 +#define RTL822X_VND2_TSSR 0xb54c + +static int rtl822x_hwmon_get_temp(int raw) +{ + if (raw >= 512) + raw -= 1024; + + return 1000 * raw / 2; +} + +static int rtl822x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct phy_device *phydev = dev_get_drvdata(dev); + int raw; + + switch (attr) { + case hwmon_temp_input: + raw = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_TSRR) & 0x3ff; + *val = rtl822x_hwmon_get_temp(raw); + break; + case hwmon_temp_max: + /* Chip reduces speed to 1G if threshold is exceeded */ + raw = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_TSSR) >> 6; + *val = rtl822x_hwmon_get_temp(raw); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct hwmon_ops rtl822x_hwmon_ops = { + .visible = 0444, + .read = rtl822x_hwmon_read, +}; + +static const struct hwmon_channel_info * const rtl822x_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX), + NULL +}; + +static const struct hwmon_chip_info rtl822x_hwmon_chip_info = { + .ops = &rtl822x_hwmon_ops, + .info = rtl822x_hwmon_info, +}; + +int rtl822x_hwmon_init(struct phy_device *phydev) +{ + struct device *hwdev, *dev = &phydev->mdio.dev; + + /* Ensure over-temp alarm is reset. */ + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_TSALRM, 3); + + hwdev = devm_hwmon_device_register_with_info(dev, NULL, phydev, + &rtl822x_hwmon_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwdev); +} diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c new file mode 100644 index 000000000..177b62a7b --- /dev/null +++ b/drivers/net/phy/realtek/realtek_main.c @@ -0,0 +1,3426 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* drivers/net/phy/realtek.c + * + * Driver for Realtek PHYs + * + * Author: Johnson Leung + * + * Copyright (c) 2004 Freescale Semiconductor, Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../phylib.h" +#include "realtek.h" + +#define RTL8201F_IER_PAGE 0x07 +#define RTL8201F_IER 0x13 +#define RTL8201F_IER_LINK BIT(13) +#define RTL8201F_IER_DUPLEX BIT(12) +#define RTL8201F_IER_ANERR BIT(11) +#define RTL8201F_IER_MASK (RTL8201F_IER_ANERR | \ + RTL8201F_IER_DUPLEX | \ + RTL8201F_IER_LINK) + +#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 RTL821x_INER 0x12 +#define RTL8211B_INER_SPEED BIT(14) +#define RTL8211B_INER_DUPLEX BIT(13) +#define RTL8211B_INER_LINK_STATUS BIT(10) +#define RTL8211B_INER_INIT (RTL8211B_INER_SPEED | \ + RTL8211B_INER_DUPLEX | \ + RTL8211B_INER_LINK_STATUS) +#define RTL8211E_INER_LINK_STATUS BIT(10) +#define RTL8211F_INER_PME BIT(7) +#define RTL8211F_INER_LINK_STATUS BIT(4) + +#define RTL821x_INSR 0x13 + +#define RTL821x_EXT_PAGE_SELECT 0x1e + +#define RTL821x_PAGE_SELECT 0x1f +#define RTL821x_SET_EXT_PAGE 0x07 + +/* RTL8211E extension page 44/0x2c */ +#define RTL8211E_LEDCR_EXT_PAGE 0x2c +#define RTL8211E_LEDCR1 0x1a +#define RTL8211E_LEDCR1_ACT_TXRX BIT(4) +#define RTL8211E_LEDCR1_MASK BIT(4) +#define RTL8211E_LEDCR1_SHIFT 1 + +#define RTL8211E_LEDCR2 0x1c +#define RTL8211E_LEDCR2_LINK_1000 BIT(2) +#define RTL8211E_LEDCR2_LINK_100 BIT(1) +#define RTL8211E_LEDCR2_LINK_10 BIT(0) +#define RTL8211E_LEDCR2_MASK GENMASK(2, 0) +#define RTL8211E_LEDCR2_SHIFT 4 + +/* RTL8211E extension page 164/0xa4 */ +#define RTL8211E_RGMII_EXT_PAGE 0xa4 +#define RTL8211E_RGMII_DELAY 0x1c +#define RTL8211E_CTRL_DELAY BIT(13) +#define RTL8211E_TX_DELAY BIT(12) +#define RTL8211E_RX_DELAY BIT(11) +#define RTL8211E_DELAY_MASK GENMASK(13, 11) + +/* RTL8211F PHY configuration */ +#define RTL8211F_PHYCR1 0x18 +#define RTL8211F_ALDPS_PLL_OFF BIT(1) +#define RTL8211F_ALDPS_ENABLE BIT(2) +#define RTL8211F_ALDPS_XTAL_OFF BIT(12) + +#define RTL8211F_PHYCR2 0x19 +#define RTL8211F_CLKOUT_EN BIT(0) +#define RTL8211F_SYSCLK_SSC_EN BIT(3) +#define RTL8211F_PHYCR2_PHY_EEE_ENABLE BIT(5) +#define RTL8211F_CLKOUT_SSC_EN BIT(7) +#define RTL8211F_CLKOUT_SSC_CAP GENMASK(13, 12) + +#define RTL8211F_INSR 0x1d + +/* RTL8211F SSC settings */ +#define RTL8211F_SSC_PAGE 0xc44 +#define RTL8211F_SSC_RXC 0x13 +#define RTL8211F_SSC_SYSCLK 0x17 + +/* RTL8211F LED configuration */ +#define RTL8211F_LEDCR_PAGE 0xd04 +#define RTL8211F_LEDCR 0x10 +#define RTL8211F_LEDCR_MODE BIT(15) +#define RTL8211F_LEDCR_ACT_TXRX BIT(4) +#define RTL8211F_LEDCR_LINK_1000 BIT(3) +#define RTL8211F_LEDCR_LINK_100 BIT(1) +#define RTL8211F_LEDCR_LINK_10 BIT(0) +#define RTL8211F_LEDCR_MASK GENMASK(4, 0) +#define RTL8211F_LEDCR_SHIFT 5 + +/* RTL8211F(D)(I)-VD-CG CLKOUT configuration is specified via magic values + * to undocumented register pages. The names here do not reflect the datasheet. + * Unlike other PHY models, CLKOUT configuration does not go through PHYCR2. + */ +#define RTL8211FVD_CLKOUT_PAGE 0xd05 +#define RTL8211FVD_CLKOUT_REG 0x11 +#define RTL8211FVD_CLKOUT_EN BIT(8) + +/* RTL8211F RGMII configuration */ +#define RTL8211F_RGMII_PAGE 0xd08 + +#define RTL8211F_TXCR 0x11 +#define RTL8211F_TX_DELAY BIT(8) + +#define RTL8211F_RXCR 0x15 +#define RTL8211F_RX_DELAY BIT(3) + +/* RTL8211F WOL settings */ +#define RTL8211F_WOL_PAGE 0xd8a +#define RTL8211F_WOL_SETTINGS_EVENTS 16 +#define RTL8211F_WOL_EVENT_MAGIC BIT(12) +#define RTL8211F_WOL_RST_RMSQ 17 +#define RTL8211F_WOL_RG_RSTB BIT(15) +#define RTL8211F_WOL_RMSQ 0x1fff + +/* RTL8211F Unique phyiscal and multicast address (WOL) */ +#define RTL8211F_PHYSICAL_ADDR_PAGE 0xd8c +#define RTL8211F_PHYSICAL_ADDR_WORD0 16 +#define RTL8211F_PHYSICAL_ADDR_WORD1 17 +#define RTL8211F_PHYSICAL_ADDR_WORD2 18 + +#define RTL8261X_EXT_ADDR_REG 0xa436 +#define RTL8261X_EXT_DATA_REG 0xa438 +#define RTL_8261X_SUB_PHY_ID_ADDR 0x801d + +#define RTL822X_VND1_SERDES_OPTION 0x697a +#define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0) +#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0 +#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX 2 + +#define RTL822X_VND1_SERDES_CTRL3 0x7580 +#define RTL822X_VND1_SERDES_CTRL3_MODE_MASK GENMASK(5, 0) +#define RTL822X_VND1_SERDES_CTRL3_MODE_SGMII 0x02 +#define RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX 0x16 + +#define RTL822X_VND1_SERDES_CMD 0x7587 +#define RTL822X_VND1_SERDES_CMD_WRITE BIT(1) +#define RTL822X_VND1_SERDES_CMD_BUSY BIT(0) +#define RTL822X_VND1_SERDES_ADDR 0x7588 +#define RTL822X_VND1_SERDES_ADDR_AUTONEG 0x2 +#define RTL822X_VND1_SERDES_INBAND_DISABLE 0x71d0 +#define RTL822X_VND1_SERDES_INBAND_ENABLE 0x70d0 +#define RTL822X_VND1_SERDES_DATA 0x7589 + +#define RTL822X_VND2_TO_PAGE(reg) ((reg) >> 4) +#define RTL822X_VND2_TO_PAGE_REG(reg) (16 + (((reg) & GENMASK(3, 0)) >> 1)) +#define RTL822X_VND2_TO_C22_REG(reg) (((reg) - 0xa400) / 2) +#define RTL822X_VND2_C22_REG(reg) (0xa400 + 2 * (reg)) + +#define RTL8221B_VND2_INER 0xa4d2 +#define RTL8221B_VND2_INER_LINK_STATUS BIT(4) + +#define RTL8221B_VND2_INSR 0xa4d4 + +#define RTL822X_VND2_LED(x) (0xd032 + ((x) * 2)) +#define RTL822X_VND2_LCR_LINK_10 BIT(0) +#define RTL822X_VND2_LCR_LINK_100 BIT(1) +#define RTL822X_VND2_LCR_LINK_1000 BIT(2) +#define RTL822X_VND2_LCR_LINK_2500 BIT(5) + +#define RTL822X_VND2_LCR6 0xd040 +#define RTL822X_VND2_LED_ACT(x) BIT(x) + +#define RTL822X_VND2_LCR7 0xd044 +#define RTL822X_VND2_LED_POLAR(x) BIT(x) + +#define RTL8224_MII_RTCT 0x11 +#define RTL8224_MII_RTCT_ENABLE BIT(0) +#define RTL8224_MII_RTCT_PAIR_A BIT(4) +#define RTL8224_MII_RTCT_PAIR_B BIT(5) +#define RTL8224_MII_RTCT_PAIR_C BIT(6) +#define RTL8224_MII_RTCT_PAIR_D BIT(7) +#define RTL8224_MII_RTCT_DONE BIT(15) + +#define RTL8224_MII_SRAM_ADDR 0x1b +#define RTL8224_MII_SRAM_DATA 0x1c + +#define RTL8224_SRAM_RTCT_FAULT(pair) (0x8026 + (pair) * 4) +#define RTL8224_SRAM_RTCT_FAULT_BUSY BIT(0) +#define RTL8224_SRAM_RTCT_FAULT_OPEN BIT(3) +#define RTL8224_SRAM_RTCT_FAULT_SAME_SHORT BIT(4) +#define RTL8224_SRAM_RTCT_FAULT_OK BIT(5) +#define RTL8224_SRAM_RTCT_FAULT_DONE BIT(6) +#define RTL8224_SRAM_RTCT_FAULT_CROSS_SHORT BIT(7) + +#define RTL8224_SRAM_RTCT_LEN(pair) (0x8028 + (pair) * 4) + +#define RTL8224_VND1_MDI_PAIR_SWAP 0xa90 +#define RTL8224_VND1_MDI_POLARITY_SWAP 0xa94 + +#define RTL8226_VND1_UNKNOWN_6A21 0x6a21 +#define RTL8226_VND1_UNKNOWN_6A21_MDI_SWAP_EN BIT(5) + +#define RTL8226_VND2_UNKNOWN_D068 0xd068 +#define RTL8226_VND2_UNKNOWN_D068_MDI_SWAP_FLAG BIT(1) +#define RTL8226_VND2_UNKNOWN_D068_PAIR_SEL GENMASK(4, 3) +#define RTL8226_VND2_ADCCAL_OFFSET 0xd06a + +#define RTL8226_VND2_RG_LPF_CAP_XG_P0_P1 0xbd5a +#define RTL8226_VND2_RG_LPF_CAP_XG_P2_P3 0xbd5c +#define RTL8226_VND2_RG_LPF_CAP_P0_P1 0xbc18 +#define RTL8226_VND2_RG_LPF_CAP_P2_P3 0xbc1a +#define RTL8226_RG_LPF_CAP_PAIR_A_MASK GENMASK(4, 0) +#define RTL8226_RG_LPF_CAP_PAIR_B_MASK GENMASK(12, 8) + +#define RTL8366RB_POWER_SAVE 0x15 +#define RTL8366RB_POWER_SAVE_ON BIT(12) + +#define RTL9000A_GINMR 0x14 +#define RTL9000A_GINMR_LINK_STATUS BIT(4) + +#define RTL_PHYSR MII_RESV2 +#define RTL_PHYSR_DUPLEX BIT(3) +#define RTL_PHYSR_SPEEDL GENMASK(5, 4) +#define RTL_PHYSR_SPEEDH GENMASK(10, 9) +#define RTL_PHYSR_MASTER BIT(11) +#define RTL_PHYSR_SPEED_MASK (RTL_PHYSR_SPEEDL | RTL_PHYSR_SPEEDH) + +#define RTL_MDIO_PCS_EEE_ABLE 0xa5c4 +#define RTL_MDIO_AN_EEE_ADV 0xa5d0 +#define RTL_MDIO_AN_EEE_LPABLE 0xa5d2 +#define RTL_MDIO_AN_10GBT_CTRL 0xa5d4 +#define RTL_MDIO_AN_10GBT_STAT 0xa5d6 +#define RTL_MDIO_PMA_SPEED 0xa616 +#define RTL_MDIO_AN_EEE_LPABLE2 0xa6d0 +#define RTL_MDIO_AN_EEE_ADV2 0xa6d4 +#define RTL_MDIO_PCS_EEE_ABLE2 0xa6ec + +#define RTL_GENERIC_PHYID 0x001cc800 +#define RTL_8211FVD_PHYID 0x001cc878 +#define RTL_8221B 0x001cc840 +#define RTL_8221B_VB_CG 0x001cc849 +#define RTL_8221B_VM_CG 0x001cc84a +#define RTL_8251B 0x001cc862 +#define RTL_8261C 0x001cc890 +#define RTL_8261C_CG 0x001cc898 + +#define RTL8261C_CE_MODEL 0x00 +#define RTL8261D_MODEL 0x81 +#define RTL8261X_INT_AUTONEG_ERROR BIT(0) +#define RTL8261X_INT_PAGE_RECV BIT(2) +#define RTL8261X_INT_AUTONEG_DONE BIT(3) +#define RTL8261X_INT_LINK_CHG BIT(4) +#define RTL8261X_INT_PHY_REG_ACCESS BIT(5) +#define RTL8261X_INT_PME BIT(7) +#define RTL8261X_INT_ALDPS_CHG BIT(9) +#define RTL8261X_INT_JABBER BIT(10) + +#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG | \ + RTL8261X_INT_AUTONEG_ERROR | \ + RTL8261X_INT_JABBER) + +#define RTL8261X_INT_MASK_ALL (RTL8261X_INT_AUTONEG_ERROR | \ + RTL8261X_INT_PAGE_RECV | \ + RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG | \ + RTL8261X_INT_PHY_REG_ACCESS | \ + RTL8261X_INT_PME | \ + RTL8261X_INT_ALDPS_CHG | \ + RTL8261X_INT_JABBER) + +#define FW_MAIN_MAGIC 0x52544C38 +#define FW_SUB_MAGIC_8261C 0x32363143 +#define RTL8261X_POLL_TIMEOUT_MS 100 +#define RTL8261X_MAX_MMD_DEV 31 + +#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin" +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME); + +enum rtl8261x_fw_op { + OP_WRITE = 0x00, /* Write */ + OP_POLL = 0x02, /* Polling */ +}; + +struct rtl8261x_fw_header { + __le32 main_magic; /* Main magic number */ + __le32 sub_magic; /* Sub magic number */ + __le16 version_major; /* Major version */ + __le16 version_minor; /* Minor version */ + __le16 num_entries; /* Number of entries */ + __le16 reserved; /* Reserved */ + __le32 crc32; /* CRC32 checksum */ +}; + +struct rtl8261x_fw_entry { + __u8 type; /* Operation type (OP_*) */ + __u8 dev; /* MMD device */ + __le16 addr; /* Register address */ + __u8 msb; /* MSB bit position */ + __u8 lsb; /* LSB bit position */ + __le16 value; /* Value to write/compare */ + __le16 timeout_ms; /* Poll timeout in milliseconds */ + __u8 poll_set; /* Poll until equal (1) or not equal (0) */ + __u8 reserved; /* Reserved */ +}; + +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header) +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry) + +/* RTL8211E and RTL8211F support up to three LEDs */ +#define RTL8211x_LED_COUNT 3 + +MODULE_DESCRIPTION("Realtek PHY driver"); +MODULE_AUTHOR("Johnson Leung"); +MODULE_LICENSE("GPL"); + +struct rtl821x_priv { + bool enable_aldps; + bool disable_clk_out; + bool enable_clkout_ssc; + bool enable_rxc_ssc; + bool enable_sysclk_ssc; + struct clk *clk; + /* rtl8211f */ + u16 iner; +}; + +struct rtl8261x_priv { + const char *fw_name; + bool fw_loaded; +}; + +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_read_ext_page(struct phy_device *phydev, u16 ext_page, + u32 regnum) +{ + int oldpage, ret = 0; + + oldpage = phy_select_page(phydev, RTL821x_SET_EXT_PAGE); + if (oldpage >= 0) { + ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, ext_page); + if (ret == 0) + ret = __phy_read(phydev, regnum); + } + + return phy_restore_page(phydev, oldpage, ret); +} + +static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page, + u32 regnum, u16 mask, u16 set) +{ + int oldpage, ret = 0; + + oldpage = phy_select_page(phydev, RTL821x_SET_EXT_PAGE); + if (oldpage >= 0) { + ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, ext_page); + if (ret == 0) + ret = __phy_modify(phydev, regnum, mask, set); + } + + return phy_restore_page(phydev, oldpage, ret); +} + +static int rtl8261x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct rtl8261x_priv *priv; + int sub_phy_id, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG, + RTL_8261X_SUB_PHY_ID_ADDR); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG); + if (ret < 0) + return ret; + + sub_phy_id = (ret >> 8) & 0xff; + + switch (sub_phy_id) { + case RTL8261C_CE_MODEL: + priv->fw_name = RTL8261C_CE_FW_NAME; + phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + case RTL8261D_MODEL: + phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + default: + phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id); + return -ENODEV; + } + + return 0; +} + +static int rtl8261x_get_features(struct phy_device *phydev) +{ + int ret; + + ret = genphy_c45_pma_read_abilities(phydev); + if (ret) + return ret; + /* + * Supplement Multi-Gig speeds that may not be automatically detected + * RTL8261X supports 2.5G/5G in addition to standard 10G + */ + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + phydev->supported); + + return 0; +} + +static int rtl8261x_read_status(struct phy_device *phydev) +{ + int ret, val = 0; + + if (phydev->autoneg == AUTONEG_ENABLE) { + ret = genphy_c45_aneg_done(phydev); + if (ret < 0) + return ret; + + if (ret) { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_STAT1000)); + if (val < 0) + return val; + } + } + + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); + + ret = genphy_c45_read_status(phydev); + if (ret < 0) + return ret; + + return 0; +} + +static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw) +{ + const struct rtl8261x_fw_header *hdr; + u32 main_magic, sub_magic; + u32 calc_crc, file_crc; + size_t data_len; + u16 num_entries; + + if (fw->size < FW_HEADER_SIZE) { + phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size); + return -EINVAL; + } + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + main_magic = le32_to_cpu(hdr->main_magic); + if (main_magic != FW_MAIN_MAGIC) { + phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic); + return -EINVAL; + } + + sub_magic = le32_to_cpu(hdr->sub_magic); + if (sub_magic != FW_SUB_MAGIC_8261C) { + phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic); + return -EINVAL; + } + + num_entries = le16_to_cpu(hdr->num_entries); + data_len = num_entries * FW_ENTRY_SIZE; + + if (fw->size != sizeof(*hdr) + data_len) { + phydev_err(phydev, "Firmware size mismatch\n"); + return -EINVAL; + } + + calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0; + file_crc = le32_to_cpu(hdr->crc32); + + if (calc_crc != file_crc) { + phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n", + calc_crc, file_crc); + return -EINVAL; + } + + return 0; +} + +static int rtl8261x_fw_execute_entry(struct phy_device *phydev, + const struct rtl8261x_fw_entry *entry) +{ + u16 addr, value, timeout_ms; + u8 dev, msb, lsb, poll_set; + u32 bits, expect_val; + int ret, val; + + dev = entry->dev; + addr = le16_to_cpu(entry->addr); + msb = entry->msb; + lsb = entry->lsb; + value = le16_to_cpu(entry->value); + timeout_ms = le16_to_cpu(entry->timeout_ms); + poll_set = entry->poll_set; + + if (timeout_ms == 0) + timeout_ms = RTL8261X_POLL_TIMEOUT_MS; + + if (dev > RTL8261X_MAX_MMD_DEV) { + phydev_err(phydev, "invalid firmware MMD device: dev=%u\n", dev); + return -EINVAL; + } + + if (msb > 15 || lsb > msb) { + phydev_err(phydev, "invalid firmware bits: msb=%u, lsb=%u\n", msb, lsb); + return -EINVAL; + } + + switch (entry->type) { + case OP_WRITE: + ret = phy_modify_mmd(phydev, dev, addr, + GENMASK(msb, lsb), (value << lsb) & GENMASK(msb, lsb)); + if (ret) + return ret; + break; + + case OP_POLL: + bits = GENMASK(msb, lsb); + expect_val = (value << lsb) & bits; + + if (poll_set) + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) == expect_val, + 1000, timeout_ms * 1000, false); + else + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) != expect_val, + 1000, timeout_ms * 1000, false); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int rtl8261x_fw_load(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + const struct rtl8261x_fw_entry *entry; + const struct rtl8261x_fw_header *hdr; + const struct firmware *fw; + int ret, i; + + if (!priv->fw_name) + return 0; + + ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev); + if (ret) { + phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, ret); + return ret; + } + + ret = rtl8261x_verify_firmware(phydev, fw); + if (ret) + goto release_fw; + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE); + for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) { + ret = rtl8261x_fw_execute_entry(phydev, entry); + if (ret) { + phydev_err(phydev, "Entry %d failed: %d\n", i, ret); + goto release_fw; + } + } + + priv->fw_loaded = true; + +release_fw: + release_firmware(fw); + return ret; +} + +static int rtl8261x_config_intr(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, + RTL8261X_INT_MASK_DEFAULT); + if (ret < 0) + return ret; + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (ret < 0) + return ret; + } + + return 0; +} + +static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev) +{ + int irq_status; + + irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (!(irq_status & RTL8261X_INT_MASK_ALL)) + return IRQ_NONE; + + if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE | + RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER)) + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int rtl8261x_config_aneg(struct phy_device *phydev) +{ + u16 adv_1g = 0; + int ret; + + ret = genphy_c45_config_aneg(phydev); + if (ret < 0) + return ret; + + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->advertising)) + adv_1g = ADVERTISE_1000FULL; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->advertising)) + adv_1g |= ADVERTISE_1000HALF; + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_CTRL1000), + ADVERTISE_1000FULL | ADVERTISE_1000HALF, + adv_1g); + if (ret < 0) + return ret; + if (ret > 0) + return genphy_c45_restart_aneg(phydev); + + return 0; +} + +static int rtl8261x_config_init(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + + /* The firmware parameters are preserved across IEEE soft resets and + * suspend/resume cycles. Reloading is only necessary after a power + * cycle or hard reset. + */ + if (priv->fw_name && !priv->fw_loaded) + return rtl8261x_fw_load(phydev); + + return 0; +} + +static int rtl821x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct rtl821x_priv *priv; + + 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"); + + priv->enable_aldps = of_property_read_bool(dev->of_node, + "realtek,aldps-enable"); + priv->disable_clk_out = of_property_read_bool(dev->of_node, + "realtek,clkout-disable"); + priv->enable_clkout_ssc = of_property_read_bool(dev->of_node, + "realtek,clkout-ssc-enable"); + priv->enable_rxc_ssc = of_property_read_bool(dev->of_node, + "realtek,rxc-ssc-enable"); + priv->enable_sysclk_ssc = of_property_read_bool(dev->of_node, + "realtek,sysclk-ssc-enable"); + + phydev->priv = priv; + + return 0; +} + +static int rtl8211f_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + int ret; + + ret = rtl821x_probe(phydev); + if (ret < 0) + return ret; + + /* Disable all PME events */ + ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE, + RTL8211F_WOL_SETTINGS_EVENTS, 0); + if (ret < 0) + return ret; + + /* Mark this PHY as wakeup capable and register the interrupt as a + * wakeup IRQ if the PHY is marked as a wakeup source in firmware, + * and the interrupt is valid. + */ + if (device_property_read_bool(dev, "wakeup-source") && + phy_interrupt_is_valid(phydev)) { + device_set_wakeup_capable(dev, true); + devm_pm_set_wake_irq(dev, phydev->irq); + } + + return ret; +} + +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(phydev, 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 = RTL8201F_IER_MASK; + err = phy_write_paged(phydev, RTL8201F_IER_PAGE, + RTL8201F_IER, val); + } else { + val = 0; + err = phy_write_paged(phydev, RTL8201F_IER_PAGE, + 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) +{ + struct rtl821x_priv *priv = phydev->priv; + 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); + if (err == 0) + priv->iner = val; + } else { + priv->iner = 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(phydev, RTL8211F_INSR); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (irq_status & RTL8211F_INER_LINK_STATUS) { + phy_trigger_machine(phydev); + return IRQ_HANDLED; + } + + if (irq_status & RTL8211F_INER_PME) { + pm_wakeup_event(&phydev->mdio.dev, 0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol) +{ + int wol_events; + + /* If the PHY is not capable of waking the system, then WoL can not + * be supported. + */ + if (!device_can_wakeup(&dev->mdio.dev)) { + wol->supported = 0; + return; + } + + wol->supported = WAKE_MAGIC; + + wol_events = phy_read_paged(dev, RTL8211F_WOL_PAGE, RTL8211F_WOL_SETTINGS_EVENTS); + if (wol_events < 0) + return; + + if (wol_events & RTL8211F_WOL_EVENT_MAGIC) + wol->wolopts = WAKE_MAGIC; +} + +static int rtl8211f_set_wol(struct phy_device *dev, struct ethtool_wolinfo *wol) +{ + const u8 *mac_addr = dev->attached_dev->dev_addr; + int oldpage; + + if (!device_can_wakeup(&dev->mdio.dev)) + return -EOPNOTSUPP; + + oldpage = phy_save_page(dev); + if (oldpage < 0) + goto err; + + if (wol->wolopts & WAKE_MAGIC) { + /* Store the device address for the magic packet */ + rtl821x_write_page(dev, RTL8211F_PHYSICAL_ADDR_PAGE); + __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD0, mac_addr[1] << 8 | (mac_addr[0])); + __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD1, mac_addr[3] << 8 | (mac_addr[2])); + __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD2, mac_addr[5] << 8 | (mac_addr[4])); + + /* Enable magic packet matching */ + rtl821x_write_page(dev, RTL8211F_WOL_PAGE); + __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, RTL8211F_WOL_EVENT_MAGIC); + /* Set the maximum packet size, and assert WoL reset */ + __phy_write(dev, RTL8211F_WOL_RST_RMSQ, RTL8211F_WOL_RMSQ); + } else { + /* Disable magic packet matching */ + rtl821x_write_page(dev, RTL8211F_WOL_PAGE); + __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, 0); + + /* Place WoL in reset */ + __phy_clear_bits(dev, RTL8211F_WOL_RST_RMSQ, + RTL8211F_WOL_RG_RSTB); + } + + device_set_wakeup_enable(&dev->mdio.dev, !!(wol->wolopts & WAKE_MAGIC)); + +err: + return phy_restore_page(dev, oldpage, 0); +} + +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_rgmii_delay(struct phy_device *phydev) +{ + u16 val_txdly, val_rxdly; + int 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, RTL8211F_RGMII_PAGE, + RTL8211F_TXCR, RTL8211F_TX_DELAY, + val_txdly); + if (ret < 0) { + phydev_err(phydev, "Failed to update the TX delay register: %pe\n", + ERR_PTR(ret)); + return ret; + } else if (ret) { + phydev_dbg(phydev, + "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n", + str_enable_disable(val_txdly)); + } else { + phydev_dbg(phydev, + "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", + str_enabled_disabled(val_txdly)); + } + + ret = phy_modify_paged_changed(phydev, RTL8211F_RGMII_PAGE, + RTL8211F_RXCR, RTL8211F_RX_DELAY, + val_rxdly); + if (ret < 0) { + phydev_err(phydev, "Failed to update the RX delay register: %pe\n", + ERR_PTR(ret)); + return ret; + } else if (ret) { + phydev_dbg(phydev, + "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n", + str_enable_disable(val_rxdly)); + } else { + phydev_dbg(phydev, + "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n", + str_enabled_disabled(val_rxdly)); + } + + return 0; +} + +static int rtl8211f_config_clk_out(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + int ret; + + /* The value is preserved if the device tree property is absent */ + if (!priv->disable_clk_out) + return 0; + + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + ret = phy_modify_paged(phydev, RTL8211FVD_CLKOUT_PAGE, + RTL8211FVD_CLKOUT_REG, + RTL8211FVD_CLKOUT_EN, 0); + else + ret = phy_modify(phydev, RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, + 0); + if (ret) + return ret; + + return genphy_soft_reset(phydev); +} + +/* Advance Link Down Power Saving (ALDPS) mode changes crystal/clock behaviour, + * which causes the RXC clock signal to stop for tens to hundreds of + * milliseconds. + * + * Some MACs need the RXC clock to support their internal RX logic, so ALDPS is + * only enabled based on an opt-in device tree property. + */ +static int rtl8211f_config_aldps(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + u16 mask = RTL8211F_ALDPS_PLL_OFF | + RTL8211F_ALDPS_ENABLE | + RTL8211F_ALDPS_XTAL_OFF; + + /* The value is preserved if the device tree property is absent */ + if (!priv->enable_aldps) + return 0; + + return phy_modify(phydev, RTL8211F_PHYCR1, mask, mask); +} + +static int rtl8211f_disable_autonomous_eee(struct phy_device *phydev) +{ + return phy_modify(phydev, RTL8211F_PHYCR2, + RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); +} + +static int rtl8211f_config_clkout_ssc(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + int ret; + + /* The value is preserved if the device tree property is absent */ + if (!priv->enable_clkout_ssc) + return 0; + + /* RTL8211FVD has PHYCR2 register, but configuration of CLKOUT SSC + * is not currently supported by this driver due to different bit + * layout. + */ + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + return 0; + + /* Unnamed registers from EMI improvement parameters application note 1.2 */ + ret = phy_write_paged(phydev, 0xd09, 0x10, 0xcf00); + if (ret < 0) { + dev_err(dev, "CLKOUT SSC initialization failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + /* Enable CLKOUT SSC and CLKOUT SSC Capability using PHYCR2 + * bits 7, 12, 13. This matches the register 25 write 0x38C3 + * from the EMI improvement parameters application note 1.2 + * section 2.3, without affecting unrelated bits. + */ + ret = phy_set_bits(phydev, RTL8211F_PHYCR2, + RTL8211F_CLKOUT_SSC_CAP | RTL8211F_CLKOUT_SSC_EN); + if (ret < 0) { + dev_err(dev, "CLKOUT SSC enable failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static int rtl8211f_config_rxc_ssc(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + int ret; + + /* The value is preserved if the device tree property is absent */ + if (!priv->enable_rxc_ssc) + return 0; + + /* RTL8211FVD has PHYCR2 register, but configuration of RXC SSC + * is not currently supported by this driver due to different bit + * layout. + */ + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + return 0; + + ret = phy_write_paged(phydev, RTL8211F_SSC_PAGE, RTL8211F_SSC_RXC, 0x5f00); + if (ret < 0) { + dev_err(dev, "RXC SSC configuration failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static int rtl8211f_config_sysclk_ssc(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + int ret; + + /* The value is preserved if the device tree property is absent */ + if (!priv->enable_sysclk_ssc) + return 0; + + /* RTL8211FVD has PHYCR2 register, but configuration of SYSCLK SSC + * is not currently supported by this driver due to different bit + * layout. + */ + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + return 0; + + ret = phy_write_paged(phydev, RTL8211F_SSC_PAGE, RTL8211F_SSC_SYSCLK, 0x4f00); + if (ret < 0) { + dev_err(dev, "SYSCLK SSC configuration failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + /* Enable SSC */ + ret = phy_set_bits(phydev, RTL8211F_PHYCR2, RTL8211F_SYSCLK_SSC_EN); + if (ret < 0) { + dev_err(dev, "SYSCLK SSC enable failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static int rtl8211f_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + int ret; + + ret = rtl8211f_config_aldps(phydev); + if (ret) { + dev_err(dev, "aldps mode configuration failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + ret = rtl8211f_config_rgmii_delay(phydev); + if (ret) + return ret; + + ret = rtl8211f_config_rxc_ssc(phydev); + if (ret) + return ret; + + ret = rtl8211f_config_sysclk_ssc(phydev); + if (ret) + return ret; + + ret = rtl8211f_config_clkout_ssc(phydev); + if (ret) + return ret; + + ret = rtl8211f_config_clk_out(phydev); + if (ret) { + dev_err(dev, "clkout configuration failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + return 0; +} + +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 rtl8211f_suspend(struct phy_device *phydev) +{ + u16 wol_rst; + int ret; + + ret = rtl821x_suspend(phydev); + if (ret < 0) + return ret; + + /* If a PME event is enabled, then configure the interrupt for + * PME events only, disabling link interrupt. We avoid switching + * to PMEB mode as we don't have a status bit for that. + */ + if (device_may_wakeup(&phydev->mdio.dev)) { + ret = phy_write_paged(phydev, 0xa42, RTL821x_INER, + RTL8211F_INER_PME); + if (ret < 0) + goto err; + + /* Read the INSR to clear any pending interrupt */ + phy_read(phydev, RTL8211F_INSR); + + /* Reset the WoL to ensure that an event is picked up. + * Unless we do this, even if we receive another packet, + * we may not have a PME interrupt raised. + */ + ret = phy_read_paged(phydev, RTL8211F_WOL_PAGE, + RTL8211F_WOL_RST_RMSQ); + if (ret < 0) + goto err; + + wol_rst = ret & ~RTL8211F_WOL_RG_RSTB; + ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE, + RTL8211F_WOL_RST_RMSQ, wol_rst); + if (ret < 0) + goto err; + + wol_rst |= RTL8211F_WOL_RG_RSTB; + ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE, + RTL8211F_WOL_RST_RMSQ, wol_rst); + } + +err: + 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 rtl8211f_resume(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + int ret; + + ret = rtl821x_resume(phydev); + if (ret < 0) + return ret; + + /* If the device was programmed for a PME event, restore the interrupt + * enable so phylib can receive link state interrupts. + */ + if (device_may_wakeup(&phydev->mdio.dev)) + ret = phy_write_paged(phydev, 0xa42, RTL821x_INER, priv->iner); + + return ret; +} + +static int rtl8211x_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + const unsigned long mask = 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); + + /* The RTL8211F PHY supports these LED settings on up to three LEDs: + * - Link: Configurable subset of 10/100/1000 link rates + * - Active: Blink on activity, RX or TX is not differentiated + * The Active option has two modes, A and B: + * - A: Link and Active indication at configurable, but matching, + * subset of 10/100/1000 link rates + * - B: Link indication at configurable subset of 10/100/1000 link + * rates and Active indication always at all three 10+100+1000 + * link rates. + * This code currently uses mode B only. + * + * RTL8211E PHY LED has one mode, which works like RTL8211F mode B. + */ + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + /* Filter out any other unsupported triggers. */ + if (rules & ~mask) + return -EOPNOTSUPP; + + /* RX and TX are not differentiated, either both are set or not set. */ + if (!(rules & BIT(TRIGGER_NETDEV_RX)) ^ !(rules & BIT(TRIGGER_NETDEV_TX))) + return -EOPNOTSUPP; + + return 0; +} + +static int rtl8211f_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + val = phy_read_paged(phydev, RTL8211F_LEDCR_PAGE, RTL8211F_LEDCR); + if (val < 0) + return val; + + val >>= RTL8211F_LEDCR_SHIFT * index; + val &= RTL8211F_LEDCR_MASK; + + if (val & RTL8211F_LEDCR_LINK_10) + __set_bit(TRIGGER_NETDEV_LINK_10, rules); + + if (val & RTL8211F_LEDCR_LINK_100) + __set_bit(TRIGGER_NETDEV_LINK_100, rules); + + if (val & RTL8211F_LEDCR_LINK_1000) + __set_bit(TRIGGER_NETDEV_LINK_1000, rules); + + if ((val & RTL8211F_LEDCR_LINK_10) && + (val & RTL8211F_LEDCR_LINK_100) && + (val & RTL8211F_LEDCR_LINK_1000)) { + __set_bit(TRIGGER_NETDEV_LINK, rules); + } + + if (val & RTL8211F_LEDCR_ACT_TXRX) { + __set_bit(TRIGGER_NETDEV_RX, rules); + __set_bit(TRIGGER_NETDEV_TX, rules); + } + + return 0; +} + +static int rtl8211f_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + const u16 mask = RTL8211F_LEDCR_MASK << (RTL8211F_LEDCR_SHIFT * index); + u16 reg = 0; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_10, &rules)) { + reg |= RTL8211F_LEDCR_LINK_10; + } + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_100, &rules)) { + reg |= RTL8211F_LEDCR_LINK_100; + } + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) { + reg |= RTL8211F_LEDCR_LINK_1000; + } + + if (test_bit(TRIGGER_NETDEV_RX, &rules) || + test_bit(TRIGGER_NETDEV_TX, &rules)) { + reg |= RTL8211F_LEDCR_ACT_TXRX; + } + + reg <<= RTL8211F_LEDCR_SHIFT * index; + reg |= RTL8211F_LEDCR_MODE; /* Mode B */ + + return phy_modify_paged(phydev, RTL8211F_LEDCR_PAGE, RTL8211F_LEDCR, + mask, reg); +} + +static int rtl8211e_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int ret; + u16 cr1, cr2; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + ret = rtl821x_read_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE, + RTL8211E_LEDCR1); + if (ret < 0) + return ret; + + cr1 = ret >> RTL8211E_LEDCR1_SHIFT * index; + if (cr1 & RTL8211E_LEDCR1_ACT_TXRX) { + __set_bit(TRIGGER_NETDEV_RX, rules); + __set_bit(TRIGGER_NETDEV_TX, rules); + } + + ret = rtl821x_read_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE, + RTL8211E_LEDCR2); + if (ret < 0) + return ret; + + cr2 = ret >> RTL8211E_LEDCR2_SHIFT * index; + if (cr2 & RTL8211E_LEDCR2_LINK_10) + __set_bit(TRIGGER_NETDEV_LINK_10, rules); + + if (cr2 & RTL8211E_LEDCR2_LINK_100) + __set_bit(TRIGGER_NETDEV_LINK_100, rules); + + if (cr2 & RTL8211E_LEDCR2_LINK_1000) + __set_bit(TRIGGER_NETDEV_LINK_1000, rules); + + if ((cr2 & RTL8211E_LEDCR2_LINK_10) && + (cr2 & RTL8211E_LEDCR2_LINK_100) && + (cr2 & RTL8211E_LEDCR2_LINK_1000)) { + __set_bit(TRIGGER_NETDEV_LINK, rules); + } + + return ret; +} + +static int rtl8211e_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + const u16 cr1mask = + RTL8211E_LEDCR1_MASK << (RTL8211E_LEDCR1_SHIFT * index); + const u16 cr2mask = + RTL8211E_LEDCR2_MASK << (RTL8211E_LEDCR2_SHIFT * index); + u16 cr1 = 0, cr2 = 0; + int ret; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + if (test_bit(TRIGGER_NETDEV_RX, &rules) || + test_bit(TRIGGER_NETDEV_TX, &rules)) { + cr1 |= RTL8211E_LEDCR1_ACT_TXRX; + } + + cr1 <<= RTL8211E_LEDCR1_SHIFT * index; + ret = rtl821x_modify_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE, + RTL8211E_LEDCR1, cr1mask, cr1); + if (ret < 0) + return ret; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_10, &rules)) { + cr2 |= RTL8211E_LEDCR2_LINK_10; + } + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_100, &rules)) { + cr2 |= RTL8211E_LEDCR2_LINK_100; + } + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) { + cr2 |= RTL8211E_LEDCR2_LINK_1000; + } + + cr2 <<= RTL8211E_LEDCR2_SHIFT * index; + ret = rtl821x_modify_ext_page(phydev, RTL8211E_LEDCR_EXT_PAGE, + RTL8211E_LEDCR2, cr2mask, cr2); + + return ret; +} + +static int rtl8211e_config_init(struct phy_device *phydev) +{ + 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 + */ + return rtl821x_modify_ext_page(phydev, RTL8211E_RGMII_EXT_PAGE, + RTL8211E_RGMII_DELAY, + RTL8211E_DELAY_MASK, val); +} + +static int rtl8211b_suspend(struct phy_device *phydev) +{ + phy_write(phydev, MII_MMD_DATA, BIT(9)); + + return genphy_suspend(phydev); +} + +static int rtl8211b_resume(struct phy_device *phydev) +{ + phy_write(phydev, MII_MMD_DATA, 0); + + return genphy_resume(phydev); +} + +static int rtl8366rb_config_init(struct phy_device *phydev) +{ + int ret; + + ret = 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 void rtlgen_decode_physr(struct phy_device *phydev, int val) +{ + /* bit 3 + * 0: Half Duplex + * 1: Full Duplex + */ + if (val & RTL_PHYSR_DUPLEX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + + switch (val & RTL_PHYSR_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; + } + + /* bit 11 + * 0: Slave Mode + * 1: Master Mode + */ + if (phydev->speed >= 1000) { + if (val & RTL_PHYSR_MASTER) + phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; + else + phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; + } else { + phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED; + } +} + +static int rtlgen_read_status(struct phy_device *phydev) +{ + int ret, val; + + ret = genphy_read_status(phydev); + if (ret < 0) + return ret; + + if (!phydev->link) + return 0; + + val = phy_read(phydev, RTL_PHYSR); + if (val < 0) + return val; + + rtlgen_decode_physr(phydev, val); + + return 0; +} + +static int rtlgen_read_vend2(struct phy_device *phydev, int regnum) +{ + return __mdiobus_c45_read(phydev->mdio.bus, 0, MDIO_MMD_VEND2, regnum); +} + +static int rtlgen_write_vend2(struct phy_device *phydev, int regnum, u16 val) +{ + return __mdiobus_c45_write(phydev->mdio.bus, 0, MDIO_MMD_VEND2, regnum, + val); +} + +static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum) +{ + int ret; + + if (devnum == MDIO_MMD_VEND2) + ret = rtlgen_read_vend2(phydev, regnum); + else if (devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) + ret = rtlgen_read_vend2(phydev, RTL_MDIO_PCS_EEE_ABLE); + else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) + ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_ADV); + else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) + ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_LPABLE); + 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_VEND2) + ret = rtlgen_write_vend2(phydev, regnum, val); + else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) + ret = rtlgen_write_vend2(phydev, RTL_MDIO_AN_EEE_ADV, val); + 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) + ret = rtlgen_read_vend2(phydev, RTL_MDIO_PCS_EEE_ABLE2); + else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV2) + ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_ADV2); + else if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE2) + ret = rtlgen_read_vend2(phydev, RTL_MDIO_AN_EEE_LPABLE2); + + 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) + ret = rtlgen_write_vend2(phydev, RTL_MDIO_AN_EEE_ADV2, val); + + return ret; +} + +static int rtl822x_probe(struct phy_device *phydev) +{ + if (IS_ENABLED(CONFIG_REALTEK_PHY_HWMON) && + phydev->phy_id != RTL_GENERIC_PHYID) + return rtl822x_hwmon_init(phydev); + + return 0; +} + +/* RTL822x cannot access MDIO_MMD_VEND2 via MII_MMD_CTRL/MII_MMD_DATA. + * A mapping to use paged access needs to be used instead. + * All other MMD devices can be accessed as usual. + */ +static int rtl822xb_read_mmd(struct phy_device *phydev, int devnum, u16 reg) +{ + int oldpage, ret, read_ret; + u16 page; + + /* Use default method for all MMDs except MDIO_MMD_VEND2 or in case + * Clause-45 access is available + */ + if (devnum != MDIO_MMD_VEND2 || phydev->is_c45) + return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr, + phydev->is_c45, devnum, reg); + + /* Simplify access to C22-registers addressed inside MDIO_MMD_VEND2 */ + if (reg >= RTL822X_VND2_C22_REG(0) && + reg <= RTL822X_VND2_C22_REG(30)) + return __phy_read(phydev, RTL822X_VND2_TO_C22_REG(reg)); + + /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */ + page = RTL822X_VND2_TO_PAGE(reg); + oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT); + if (oldpage < 0) + return oldpage; + + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, page); + if (ret < 0) + return ret; + } + + read_ret = __phy_read(phydev, RTL822X_VND2_TO_PAGE_REG(reg)); + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, oldpage); + if (ret < 0) + return ret; + } + + return read_ret; +} + +static int rtl822xb_write_mmd(struct phy_device *phydev, int devnum, u16 reg, + u16 val) +{ + int oldpage, ret, write_ret; + u16 page; + + /* Use default method for all MMDs except MDIO_MMD_VEND2 or in case + * Clause-45 access is available + */ + if (devnum != MDIO_MMD_VEND2 || phydev->is_c45) + return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr, + phydev->is_c45, devnum, reg, val); + + /* Simplify access to C22-registers addressed inside MDIO_MMD_VEND2 */ + if (reg >= RTL822X_VND2_C22_REG(0) && + reg <= RTL822X_VND2_C22_REG(30)) + return __phy_write(phydev, RTL822X_VND2_TO_C22_REG(reg), val); + + /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */ + page = RTL822X_VND2_TO_PAGE(reg); + oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT); + if (oldpage < 0) + return oldpage; + + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, page); + if (ret < 0) + return ret; + } + + write_ret = __phy_write(phydev, RTL822X_VND2_TO_PAGE_REG(reg), val); + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, oldpage); + if (ret < 0) + return ret; + } + + return write_ret; +} + +static int rtl8226_set_mdi_swap(struct phy_device *phydev, bool swap_enable) +{ + u16 val = swap_enable ? RTL8226_VND1_UNKNOWN_6A21_MDI_SWAP_EN : 0; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, RTL8226_VND1_UNKNOWN_6A21, + RTL8226_VND1_UNKNOWN_6A21_MDI_SWAP_EN, val); +} + +static int rtl8226_swap_rg_lpf_cap(struct phy_device *phydev, u32 reg_p0_p1, u32 reg_p2_p3) +{ + u16 val_p0, val_p1, val_p2, val_p3; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, reg_p0_p1); + if (ret < 0) + return ret; + + val_p0 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_A_MASK, ret); + val_p1 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_B_MASK, ret); + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, reg_p2_p3); + if (ret < 0) + return ret; + + val_p2 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_A_MASK, ret); + val_p3 = FIELD_GET(RTL8226_RG_LPF_CAP_PAIR_B_MASK, ret); + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, reg_p0_p1, + RTL8226_RG_LPF_CAP_PAIR_A_MASK | RTL8226_RG_LPF_CAP_PAIR_B_MASK, + FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_A_MASK, val_p3) | + FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_B_MASK, val_p2)); + if (ret < 0) + return ret; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, reg_p2_p3, + RTL8226_RG_LPF_CAP_PAIR_A_MASK | RTL8226_RG_LPF_CAP_PAIR_B_MASK, + FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_A_MASK, val_p1) | + FIELD_PREP(RTL8226_RG_LPF_CAP_PAIR_B_MASK, val_p0)); +} + +static int rtl8226_patch_mdi_swap(struct phy_device *phydev, bool swap_enable) +{ + u16 adccal_offset[4]; + bool is_patched; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068); + if (ret < 0) + return ret; + + is_patched = !(ret & RTL8226_VND2_UNKNOWN_D068_MDI_SWAP_FLAG); + + if (is_patched == swap_enable) { + /* Nothing to do */ + return 0; + } + + if (!swap_enable) { + /* Patching is only implemented one-way, see next comment. */ + phydev_err(phydev, "MDI swapping disabled, but PHY is already patched.\n"); + return -EINVAL; + } + + /* The exact meaning of these bits is unknown. We only know that bit 1 + * is used as a flag that swapping is already done. + */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068, 0x7, 0x1); + if (ret < 0) + return ret; + + for (int i = 0; i < 4; i++) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068, + RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, + FIELD_PREP(RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, i)); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_ADCCAL_OFFSET); + if (ret < 0) + return ret; + + adccal_offset[i] = ret; + } + + for (int i = 0; i < 4; i++) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_UNKNOWN_D068, + RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, + FIELD_PREP(RTL8226_VND2_UNKNOWN_D068_PAIR_SEL, i)); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8226_VND2_ADCCAL_OFFSET, + adccal_offset[3 - i]); + if (ret < 0) + return ret; + } + + ret = rtl8226_swap_rg_lpf_cap(phydev, RTL8226_VND2_RG_LPF_CAP_XG_P0_P1, + RTL8226_VND2_RG_LPF_CAP_XG_P2_P3); + if (ret < 0) + return ret; + + return rtl8226_swap_rg_lpf_cap(phydev, RTL8226_VND2_RG_LPF_CAP_P0_P1, + RTL8226_VND2_RG_LPF_CAP_P2_P3); +} + +static int rtl8226_config_mdi_order(struct phy_device *phydev) +{ + u32 order; + bool swap_enable; + int ret; + + ret = of_property_read_u32(phydev->mdio.dev.of_node, "enet-phy-pair-order", &order); + + /* Property not present, nothing to do */ + if (ret == -EINVAL || ret == -ENOSYS) + return 0; + + if (ret) + return ret; + + if (order & ~1) + return -EINVAL; + + swap_enable = !!(order & 1); + + ret = rtl8226_set_mdi_swap(phydev, swap_enable); + if (ret) + return ret; + + return rtl8226_patch_mdi_swap(phydev, swap_enable); +} + +static int rtl8226_probe(struct phy_device *phydev) +{ + return rtl8226_config_mdi_order(phydev); +} + +static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1) +{ + bool has_2500, has_sgmii; + u16 mode; + int ret; + + has_2500 = test_bit(PHY_INTERFACE_MODE_2500BASEX, + phydev->host_interfaces) || + phydev->interface == PHY_INTERFACE_MODE_2500BASEX; + + has_sgmii = test_bit(PHY_INTERFACE_MODE_SGMII, + phydev->host_interfaces) || + phydev->interface == PHY_INTERFACE_MODE_SGMII; + + /* fill in possible interfaces */ + __assign_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces, + has_2500); + __assign_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces, + has_sgmii); + + if (!has_2500 && !has_sgmii) + return 0; + + /* determine SerDes option mode */ + if (has_2500 && !has_sgmii) { + mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX; + phydev->rate_matching = RATE_MATCH_PAUSE; + } else { + mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII; + phydev->rate_matching = RATE_MATCH_NONE; + } + + /* the following sequence with magic numbers sets up the SerDes + * option mode + */ + + if (!gen1) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x75f3, 0); + if (ret < 0) + return ret; + } + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND1, + RTL822X_VND1_SERDES_OPTION, + RTL822X_VND1_SERDES_OPTION_MODE_MASK, + mode); + if (gen1 || ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6a04, 0x0503); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f10, 0xd455); + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020); +} + +static int rtl822x_config_init(struct phy_device *phydev) +{ + return rtl822x_set_serdes_option_mode(phydev, true); +} + +static int rtl822xb_config_init(struct phy_device *phydev) +{ + return rtl822x_set_serdes_option_mode(phydev, false); +} + +static int rtl822x_serdes_write(struct phy_device *phydev, u16 reg, u16 val) +{ + int ret, poll; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_ADDR, reg); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_DATA, val); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CMD, + RTL822X_VND1_SERDES_CMD_WRITE | + RTL822X_VND1_SERDES_CMD_BUSY); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + RTL822X_VND1_SERDES_CMD, poll, + !(poll & RTL822X_VND1_SERDES_CMD_BUSY), + 500, 100000, false); +} + +static int rtl822x_config_inband(struct phy_device *phydev, unsigned int modes) +{ + return rtl822x_serdes_write(phydev, RTL822X_VND1_SERDES_ADDR_AUTONEG, + (modes != LINK_INBAND_DISABLE) ? + RTL822X_VND1_SERDES_INBAND_ENABLE : + RTL822X_VND1_SERDES_INBAND_DISABLE); +} + +static unsigned int rtl822x_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_2500BASEX: + return LINK_INBAND_DISABLE; + case PHY_INTERFACE_MODE_SGMII: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + default: + return 0; + } +} + +static int rtl822xb_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + int val; + + /* Only rate matching at 2500base-x */ + if (iface != PHY_INTERFACE_MODE_2500BASEX) + return RATE_MATCH_NONE; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_OPTION); + if (val < 0) + return val; + + if ((val & RTL822X_VND1_SERDES_OPTION_MODE_MASK) == + RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX) + return RATE_MATCH_PAUSE; + + /* RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII */ + return RATE_MATCH_NONE; +} + +static int rtl822x_get_features(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_MDIO_PMA_SPEED); + if (val < 0) + return val; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->supported, val & MDIO_PMA_SPEED_2_5G); + linkmode_mod_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + phydev->supported, val & MDIO_PMA_SPEED_5G); + linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + phydev->supported, val & MDIO_SPEED_10G); + + return genphy_read_abilities(phydev); +} + +static int rtl822x_config_aneg(struct phy_device *phydev) +{ + int ret = 0; + + if (phydev->autoneg == AUTONEG_ENABLE) { + u16 adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising); + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL_MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_ADV2_5G | + MDIO_AN_10GBT_CTRL_ADV5G | + MDIO_AN_10GBT_CTRL_ADV10G, adv); + if (ret < 0) + return ret; + } + + return __genphy_config_aneg(phydev, ret); +} + +static void rtl822xb_update_interface(struct phy_device *phydev) +{ + int val; + + if (!phydev->link) + return; + + /* Change interface according to serdes mode */ + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CTRL3); + if (val < 0) + return; + + switch (val & RTL822X_VND1_SERDES_CTRL3_MODE_MASK) { + case RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX: + phydev->interface = PHY_INTERFACE_MODE_2500BASEX; + break; + case RTL822X_VND1_SERDES_CTRL3_MODE_SGMII: + phydev->interface = PHY_INTERFACE_MODE_SGMII; + break; + } +} + +static int rtl822x_read_status(struct phy_device *phydev) +{ + int lpadv, ret; + + mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0); + + ret = rtlgen_read_status(phydev); + if (ret < 0) + return ret; + + if (phydev->autoneg == AUTONEG_DISABLE || + !phydev->autoneg_complete) + return 0; + + lpadv = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_MDIO_AN_10GBT_STAT); + if (lpadv < 0) + return lpadv; + + mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpadv); + + return 0; +} + +static int rtl822xb_read_status(struct phy_device *phydev) +{ + int ret; + + ret = rtl822x_read_status(phydev); + if (ret < 0) + return ret; + + rtl822xb_update_interface(phydev); + + return 0; +} + +static int rtl822x_c45_get_features(struct phy_device *phydev) +{ + linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, + phydev->supported); + + return genphy_c45_pma_read_abilities(phydev); +} + +static int rtl822x_c45_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + int ret, val; + + 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; + + val = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); + + /* Vendor register as C45 has no standardized support for 1000BaseT */ + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_CTRL1000), + ADVERTISE_1000FULL, val); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return genphy_c45_check_and_restart_aneg(phydev, changed); +} + +static int rtl822x_c45_read_status(struct phy_device *phydev) +{ + int ret, val; + + /* Vendor register as C45 has no standardized support for 1000BaseT */ + if (phydev->autoneg == AUTONEG_ENABLE && genphy_c45_aneg_done(phydev)) { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_STAT1000)); + if (val < 0) + return val; + } else { + val = 0; + } + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); + + ret = genphy_c45_read_status(phydev); + if (ret < 0) + return ret; + + if (!phydev->link) { + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + return 0; + } + + /* Read actual speed from vendor register. */ + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL_PHYSR)); + if (val < 0) + return val; + + rtlgen_decode_physr(phydev, val); + + return 0; +} + +static int rtl822x_c45_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + ret = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_RESET, MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, + MDIO_CTRL1, val, + !(val & MDIO_CTRL1_RESET), + 5000, 100000, true); +} + +static int rtl822xb_c45_read_status(struct phy_device *phydev) +{ + int ret; + + ret = rtl822x_c45_read_status(phydev); + if (ret < 0) + return ret; + + rtl822xb_update_interface(phydev); + + return 0; +} + +static int rtl822xb_led_brightness_set(struct phy_device *phydev, u8 index, + enum led_brightness value) +{ + int ret; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + /* clear HW LED setup */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_LED(index), 0); + if (ret < 0) + return ret; + + /* clear HW LED blink */ + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6, + RTL822X_VND2_LED_ACT(index)); + if (ret < 0) + return ret; + + if (value != LED_OFF) + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_LCR7, + RTL822X_VND2_LED_POLAR(index)); + else + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_LCR7, + RTL822X_VND2_LED_POLAR(index)); +} + +static int rtl822xb_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + const unsigned long act_mask = BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + + const unsigned long link_mask = BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500); + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + /* Filter out any other unsupported triggers. */ + if (rules & ~(link_mask | act_mask)) + return -EOPNOTSUPP; + + /* RX and TX are not differentiated, they are not possible + * without combination with a link trigger. + */ + if ((rules & act_mask) && !(rules & link_mask)) + return -EOPNOTSUPP; + + return 0; +} + +static int rtl822xb_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int val; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LED(index)); + if (val < 0) + return val; + + if (val & RTL822X_VND2_LCR_LINK_10) + __set_bit(TRIGGER_NETDEV_LINK_10, rules); + + if (val & RTL822X_VND2_LCR_LINK_100) + __set_bit(TRIGGER_NETDEV_LINK_100, rules); + + if (val & RTL822X_VND2_LCR_LINK_1000) + __set_bit(TRIGGER_NETDEV_LINK_1000, rules); + + if (val & RTL822X_VND2_LCR_LINK_2500) + __set_bit(TRIGGER_NETDEV_LINK_2500, rules); + + if ((val & RTL822X_VND2_LCR_LINK_10) && + (val & RTL822X_VND2_LCR_LINK_100) && + (val & RTL822X_VND2_LCR_LINK_1000) && + (val & RTL822X_VND2_LCR_LINK_2500)) + __set_bit(TRIGGER_NETDEV_LINK, rules); + + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6); + if (val < 0) + return val; + + if (val & RTL822X_VND2_LED_ACT(index)) { + __set_bit(TRIGGER_NETDEV_RX, rules); + __set_bit(TRIGGER_NETDEV_TX, rules); + } + + return 0; +} + +static int rtl822xb_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + bool act; + int ret; + + if (index >= RTL8211x_LED_COUNT) + return -EINVAL; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_10, &rules)) + val |= RTL822X_VND2_LCR_LINK_10; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_100, &rules)) + val |= RTL822X_VND2_LCR_LINK_100; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) + val |= RTL822X_VND2_LCR_LINK_1000; + + if (test_bit(TRIGGER_NETDEV_LINK, &rules) || + test_bit(TRIGGER_NETDEV_LINK_2500, &rules)) + val |= RTL822X_VND2_LCR_LINK_2500; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_LED(index), val); + if (ret < 0) + return ret; + + act = test_bit(TRIGGER_NETDEV_RX, &rules) || + test_bit(TRIGGER_NETDEV_TX, &rules); + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6, + RTL822X_VND2_LED_ACT(index), act ? + RTL822X_VND2_LED_ACT(index) : 0); + if (ret < 0) + return ret; + + /* Reset polarity to default */ + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR7, + RTL822X_VND2_LED_POLAR(index)); +} + +static int rtl8224_cable_test_start(struct phy_device *phydev) +{ + u32 val; + int ret; + + /* disable auto-negotiation and force 1000/Full */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_BMCR), + BMCR_ANENABLE | BMCR_SPEED100 | BMCR_SPEED10, + BMCR_SPEED1000 | BMCR_FULLDPLX); + if (ret) + return ret; + + mdelay(500); + + /* trigger cable test */ + val = RTL8224_MII_RTCT_ENABLE; + val |= RTL8224_MII_RTCT_PAIR_A; + val |= RTL8224_MII_RTCT_PAIR_B; + val |= RTL8224_MII_RTCT_PAIR_C; + val |= RTL8224_MII_RTCT_PAIR_D; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_RTCT), + RTL8224_MII_RTCT_DONE, val); +} + +static int rtl8224_sram_read(struct phy_device *phydev, u32 reg) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_SRAM_ADDR), + reg); + if (ret) + return ret; + + return phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_SRAM_DATA)); +} + +static int rtl8224_pair_len_get(struct phy_device *phydev, u32 pair) +{ + int cable_len; + u32 reg_len; + int ret; + u32 cm; + + reg_len = RTL8224_SRAM_RTCT_LEN(pair); + + ret = rtl8224_sram_read(phydev, reg_len); + if (ret < 0) + return ret; + + cable_len = ret & 0xff00; + + ret = rtl8224_sram_read(phydev, reg_len + 1); + if (ret < 0) + return ret; + + cable_len |= (ret & 0xff00) >> 8; + + cable_len -= 620; + cable_len = max(cable_len, 0); + + cm = cable_len * 100 / 78; + + return cm; +} + +static int rtl8224_cable_test_result_trans(u32 result) +{ + if (!(result & RTL8224_SRAM_RTCT_FAULT_DONE)) + return -EBUSY; + + if (result & RTL8224_SRAM_RTCT_FAULT_OK) + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + + if (result & RTL8224_SRAM_RTCT_FAULT_OPEN) + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + + if (result & RTL8224_SRAM_RTCT_FAULT_SAME_SHORT) + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + + if (result & RTL8224_SRAM_RTCT_FAULT_BUSY) + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + + if (result & RTL8224_SRAM_RTCT_FAULT_CROSS_SHORT) + return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT; + + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; +} + +static int rtl8224_cable_test_report_pair(struct phy_device *phydev, unsigned int pair) +{ + int fault_rslt; + int ret; + + ret = rtl8224_sram_read(phydev, RTL8224_SRAM_RTCT_FAULT(pair)); + if (ret < 0) + return ret; + + fault_rslt = rtl8224_cable_test_result_trans(ret); + if (fault_rslt < 0) + return 0; + + ret = ethnl_cable_test_result(phydev, pair, fault_rslt); + 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: + ret = rtl8224_pair_len_get(phydev, pair); + if (ret < 0) + return ret; + + return ethnl_cable_test_fault_length(phydev, pair, ret); + default: + return 0; + } +} + +static int rtl8224_cable_test_report(struct phy_device *phydev, bool *finished) +{ + unsigned int pair; + int ret; + + for (pair = ETHTOOL_A_CABLE_PAIR_A; pair <= ETHTOOL_A_CABLE_PAIR_D; pair++) { + ret = rtl8224_cable_test_report_pair(phydev, pair); + if (ret == -EBUSY) { + *finished = false; + return 0; + } + + if (ret < 0) + return ret; + } + + return 0; +} + +static int rtl8224_cable_test_get_status(struct phy_device *phydev, bool *finished) +{ + int ret; + + *finished = false; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_RTCT)); + if (ret < 0) + return ret; + + if (!(ret & RTL8224_MII_RTCT_DONE)) + return 0; + + *finished = true; + + return rtl8224_cable_test_report(phydev, finished); +} + +static int rtl8224_package_modify_mmd(struct phy_device *phydev, int devad, + u32 regnum, u16 mask, u16 set) +{ + int val, ret; + + phy_lock_mdio_bus(phydev); + + val = __phy_package_read_mmd(phydev, 0, devad, regnum); + if (val < 0) { + ret = val; + goto exit; + } + + val &= ~mask; + val |= set; + + ret = __phy_package_write_mmd(phydev, 0, devad, regnum, val); + +exit: + phy_unlock_mdio_bus(phydev); + return ret; +} + +static int rtl8224_mdi_config_order(struct phy_device *phydev) +{ + struct device_node *np = phydev->mdio.dev.of_node; + u8 port_offset = phydev->mdio.addr & 3; + u32 order = 0; + int ret; + + ret = of_property_read_u32(np, "enet-phy-pair-order", &order); + + /* Do nothing in case the property is not present */ + if (ret == -EINVAL || ret == -ENOSYS) + return 0; + + if (ret) + return ret; + + if (order & ~1) + return -EINVAL; + + return rtl8224_package_modify_mmd(phydev, MDIO_MMD_VEND1, + RTL8224_VND1_MDI_PAIR_SWAP, + BIT(port_offset), + order ? BIT(port_offset) : 0); +} + +static int rtl8224_mdi_config_polarity(struct phy_device *phydev) +{ + struct device_node *np = phydev->mdio.dev.of_node; + u8 offset = (phydev->mdio.addr & 3) * 4; + u32 polarity = 0; + int ret; + + ret = of_property_read_u32(np, "enet-phy-pair-polarity", &polarity); + + /* Do nothing if the property is not present */ + if (ret == -EINVAL || ret == -ENOSYS) + return 0; + + if (ret) + return ret; + + if (polarity & ~0xf) + return -EINVAL; + + return rtl8224_package_modify_mmd(phydev, MDIO_MMD_VEND1, + RTL8224_VND1_MDI_POLARITY_SWAP, + 0xf << offset, + polarity << offset); +} + +static int rtl8224_config_init(struct phy_device *phydev) +{ + int ret; + + ret = rtl8224_mdi_config_order(phydev); + if (ret) + return ret; + + return rtl8224_mdi_config_polarity(phydev); +} + +static int rtl8224_probe(struct phy_device *phydev) +{ + /* Chip exposes 4 ports, join all of them in the same package */ + return devm_phy_package_join(&phydev->mdio.dev, phydev, + phydev->mdio.addr & ~3, 0); +} + +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 & MDIO_PMA_SPEED_2_5G; +} + +/* On internal PHY's MMD reads over C22 always return 0. + * Check a MMD register which is known to be non-zero. + */ +static bool rtlgen_supports_mmd(struct phy_device *phydev) +{ + int val; + + phy_lock_mdio_bus(phydev); + __phy_write(phydev, MII_MMD_CTRL, MDIO_MMD_PCS); + __phy_write(phydev, MII_MMD_DATA, MDIO_PCS_EEE_ABLE); + __phy_write(phydev, MII_MMD_CTRL, MDIO_MMD_PCS | MII_MMD_CTRL_NOINCR); + val = __phy_read(phydev, MII_MMD_DATA); + phy_unlock_mdio_bus(phydev); + + return val > 0; +} + +static int rtlgen_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return phydev->phy_id == RTL_GENERIC_PHYID && + !rtlgen_supports_2_5gbps(phydev); +} + +static int rtl8226_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return phydev->phy_id == RTL_GENERIC_PHYID && + rtlgen_supports_2_5gbps(phydev) && + rtlgen_supports_mmd(phydev); +} + +static int rtlgen_is_c45_match(struct phy_device *phydev, unsigned int id, + bool is_c45) +{ + if (phydev->is_c45) + return is_c45 && (id == phydev->c45_ids.device_ids[1]); + else + return !is_c45 && (id == phydev->phy_id); +} + +static int rtl8221b_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return phydev->phy_id == RTL_8221B && rtlgen_supports_mmd(phydev); +} + +static int rtl8221b_vb_cg_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true) || + rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false); +} + +static int rtl8221b_vm_cg_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, true) || + rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, false); +} + +static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + if (phydev->is_c45) + return false; + + switch (phydev->phy_id) { + case RTL_GENERIC_PHYID: + case RTL_8221B: + case RTL_8251B: + case RTL_8261C: + case 0x001cc841: + break; + default: + return false; + } + + return rtlgen_supports_2_5gbps(phydev) && !rtlgen_supports_mmd(phydev); +} + +static int rtl8251b_c45_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + return rtlgen_is_c45_match(phydev, RTL_8251B, true); +} + +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 rtlgen_c45_resume(struct phy_device *phydev) +{ + int ret = genphy_c45_pma_resume(phydev); + + 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 int rtl8221b_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + + return (err < 0) ? err : 0; +} + +static int rtl8221b_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + err = rtl8221b_ack_interrupt(phydev); + if (err) + return err; + + err = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, + RTL8221B_VND2_INER_LINK_STATUS); + } else { + err = phy_write_mmd(phydev, MDIO_MMD_VEND2, + RTL8221B_VND2_INER, 0); + if (err) + return err; + + err = rtl8221b_ack_interrupt(phydev); + } + + return err; +} + +static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev) +{ + int err; + + err = rtl8221b_ack_interrupt(phydev); + if (err) { + phy_error(phydev); + return IRQ_NONE; + } + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int rtlgen_sfp_get_features(struct phy_device *phydev) +{ + linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + phydev->supported); + + /* set default mode */ + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + + phydev->port = PORT_FIBRE; + + return 0; +} + +static int rtlgen_sfp_read_status(struct phy_device *phydev) +{ + int val, err; + + err = genphy_update_link(phydev); + if (err) + return err; + + if (!phydev->link) + return 0; + + val = phy_read(phydev, RTL_PHYSR); + if (val < 0) + return val; + + rtlgen_decode_physr(phydev, val); + + return 0; +} + +static int rtlgen_sfp_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +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, + .led_hw_is_supported = rtl8211x_led_hw_is_supported, + .led_hw_control_get = rtl8211e_led_hw_control_get, + .led_hw_control_set = rtl8211e_led_hw_control_set, + }, { + PHY_ID_MATCH_EXACT(0x001cc916), + .name = "RTL8211F Gigabit Ethernet", + .probe = rtl8211f_probe, + .config_init = &rtl8211f_config_init, + .read_status = rtlgen_read_status, + .config_intr = &rtl8211f_config_intr, + .handle_interrupt = rtl8211f_handle_interrupt, + .set_wol = rtl8211f_set_wol, + .get_wol = rtl8211f_get_wol, + .suspend = rtl8211f_suspend, + .resume = rtl8211f_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .flags = PHY_ALWAYS_CALL_SUSPEND, + .led_hw_is_supported = rtl8211x_led_hw_is_supported, + .led_hw_control_get = rtl8211f_led_hw_control_get, + .led_hw_control_set = rtl8211f_led_hw_control_set, + .disable_autonomous_eee = rtl8211f_disable_autonomous_eee, + }, { + 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, + .led_hw_is_supported = rtl8211x_led_hw_is_supported, + .led_hw_control_get = rtl8211f_led_hw_control_get, + .led_hw_control_set = rtl8211f_led_hw_control_set, + .disable_autonomous_eee = rtl8211f_disable_autonomous_eee, + }, { + .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 = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, + }, { + .match_phy_device = rtl8221b_match_phy_device, + .name = "RTL8226B_RTL8221B 2.5Gbps PHY", + .get_features = rtl822x_get_features, + .config_aneg = rtl822x_config_aneg, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, + .get_rate_matching = rtl822xb_get_rate_matching, + .read_status = rtl822xb_read_status, + .suspend = genphy_suspend, + .resume = rtlgen_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, + }, { + PHY_ID_MATCH_EXACT(0x001cc838), + .name = "RTL8226-CG 2.5Gbps PHY", + .soft_reset = rtl822x_c45_soft_reset, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .probe = rtl8226_probe, + .config_init = rtl822x_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, + .read_status = rtl822xb_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, + }, { + PHY_ID_MATCH_EXACT(0x001cc848), + .name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY", + .get_features = rtl822x_get_features, + .config_aneg = rtl822x_config_aneg, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, + .get_rate_matching = rtl822xb_get_rate_matching, + .read_status = rtl822xb_read_status, + .suspend = genphy_suspend, + .resume = rtlgen_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, + }, { + .match_phy_device = rtl8221b_vb_cg_match_phy_device, + .name = "RTL8221B-VB-CG 2.5Gbps PHY", + .config_intr = rtl8221b_config_intr, + .handle_interrupt = rtl8221b_handle_interrupt, + .probe = rtl822x_probe, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, + .get_rate_matching = rtl822xb_get_rate_matching, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .read_status = rtl822xb_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, + .led_brightness_set = rtl822xb_led_brightness_set, + .led_hw_is_supported = rtl822xb_led_hw_is_supported, + .led_hw_control_get = rtl822xb_led_hw_control_get, + .led_hw_control_set = rtl822xb_led_hw_control_set, + }, { + .match_phy_device = rtl8221b_vm_cg_match_phy_device, + .name = "RTL8221B-VM-CG 2.5Gbps PHY", + .config_intr = rtl8221b_config_intr, + .handle_interrupt = rtl8221b_handle_interrupt, + .probe = rtl822x_probe, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, + .get_rate_matching = rtl822xb_get_rate_matching, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .read_status = rtl822xb_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, + .led_brightness_set = rtl822xb_led_brightness_set, + .led_hw_is_supported = rtl822xb_led_hw_is_supported, + .led_hw_control_get = rtl822xb_led_hw_control_get, + .led_hw_control_set = rtl822xb_led_hw_control_set, + }, { + .match_phy_device = rtl8251b_c45_match_phy_device, + .name = "RTL8251B 5Gbps PHY", + .probe = rtl822x_probe, + .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, + }, { + .match_phy_device = rtl_internal_nbaset_match_phy_device, + .name = "Realtek Internal NBASE-T PHY", + .flags = PHY_IS_INTERNAL, + .probe = rtl822x_probe, + .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(PHY_ID_RTL_DUMMY_SFP), + .name = "Realtek SFP PHY Mode", + .flags = PHY_IS_INTERNAL, + .probe = rtl822x_probe, + .get_features = rtlgen_sfp_get_features, + .config_aneg = rtlgen_sfp_config_aneg, + .read_status = rtlgen_sfp_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(0x001ccad0), + .name = "RTL8224 2.5Gbps PHY", + .flags = PHY_POLL_CABLE_TEST, + .probe = rtl8224_probe, + .config_init = rtl8224_config_init, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .read_status = rtl822x_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .cable_test_start = rtl8224_cable_test_start, + .cable_test_get_status = rtl8224_cable_test_get_status, + }, { + 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, + }, { + PHY_ID_MATCH_EXACT(0x001cc960), + .name = "RTL8366S Gigabit Ethernet", + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_mmd = genphy_read_mmd_unsupported, + .write_mmd = genphy_write_mmd_unsupported, + }, { + PHY_ID_MATCH_EXACT(RTL_8261C_CG), + .name = "Realtek RTL8261C/D 10Gbps PHY", + .probe = rtl8261x_probe, + .config_init = rtl8261x_config_init, + .get_features = rtl8261x_get_features, + .config_aneg = rtl8261x_config_aneg, + .read_status = rtl8261x_read_status, + .config_intr = rtl8261x_config_intr, + .handle_interrupt = rtl8261x_handle_interrupt, + .soft_reset = genphy_c45_pma_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_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..b338f385e --- /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 + */ + +#include +#include +#include +#include +#include +#include + +#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 const struct mdio_device_id __maybe_unused rockchip_phy_tbl[] = { + { INTERNAL_EPHY_ID, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, rockchip_phy_tbl); + +MODULE_AUTHOR("David Wu "); +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..8679d27bf --- /dev/null +++ b/drivers/net/phy/sfp-bus.c @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include + +#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_upstream_ops *upstream_ops; + void *upstream; + struct phy_device *phydev; + + bool registered; + bool started; + + struct sfp_module_caps caps; +}; + +const struct sfp_module_caps *sfp_get_module_caps(struct sfp_bus *bus) +{ + return &bus->caps; +} +EXPORT_SYMBOL_GPL(sfp_get_module_caps); + +static void sfp_module_parse_port(struct sfp_bus *bus, + const struct sfp_eeprom_id *id) +{ + 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; + } + + switch (port) { + case PORT_FIBRE: + phylink_set(bus->caps.link_modes, FIBRE); + break; + + case PORT_TP: + phylink_set(bus->caps.link_modes, TP); + break; + } + + bus->caps.port = port; +} + +static void sfp_module_parse_may_have_phy(struct sfp_bus *bus, + const struct sfp_eeprom_id *id) +{ + if (id->base.e1000_base_t) { + bus->caps.may_have_phy = true; + return; + } + + 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: + bus->caps.may_have_phy = true; + return; + } + } + + bus->caps.may_have_phy = false; +} + +static void sfp_module_parse_support(struct sfp_bus *bus, + const struct sfp_eeprom_id *id) +{ + unsigned long *interfaces = bus->caps.interfaces; + unsigned long *modes = bus->caps.link_modes; + unsigned int br_min, br_nom, br_max; + + /* 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 (linkmode_empty(modes) && 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); + } + } + + phylink_set(modes, Autoneg); + phylink_set(modes, Pause); + phylink_set(modes, Asym_Pause); +} + +static void sfp_init_module(struct sfp_bus *bus, + const struct sfp_eeprom_id *id, + const struct sfp_quirk *quirk) +{ + memset(&bus->caps, 0, sizeof(bus->caps)); + + sfp_module_parse_support(bus, id); + sfp_module_parse_port(bus, id); + sfp_module_parse_may_have_phy(bus, id); + + if (quirk && quirk->support) + quirk->support(id, &bus->caps); +} + +/** + * 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, + const 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) || + phylink_test(link_modes, 2500baseT_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_obj(*new); + + 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->phydev); + } + 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); + +/** + * sfp_get_name() - Get the SFP device name + * @bus: a pointer to the &struct sfp_bus structure for the sfp module + * + * Gets the SFP device's name, if @bus has a registered socket. Callers must + * hold RTNL, and the returned name is only valid until RTNL is released. + * + * Returns: + * - The name of the SFP device registered with sfp_register_socket() + * - %NULL if no device was registered on @bus + */ +const char *sfp_get_name(struct sfp_bus *bus) +{ + ASSERT_RTNL(); + + if (bus->sfp_dev) + return dev_name(bus->sfp_dev); + + return NULL; +} +EXPORT_SYMBOL_GPL(sfp_get_name); + +/* 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); + 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; + + sfp_init_module(bus, id, 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); +} +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..2ec91466a --- /dev/null +++ b/drivers/net/phy/sfp.c @@ -0,0 +1,3503 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sfp.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 25 + +/* 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 + +/* Polling interval and consecutive-failure threshold for the I2C presence + * probe used on boards without a MOD_DEF0 GPIO (see sfp_i2c_get_state()). + * A single successful read asserts presence immediately; R_PROBE_ABSENT + * consecutive failures are required to declare a live module removed, to ride + * out a transient I2C error. Insertion is thus detected within + * T_PROBE_PRESENT and removal within T_PROBE_PRESENT * R_PROBE_ABSENT. + */ +#define T_PROBE_PRESENT msecs_to_jiffies(500) +#define R_PROBE_ABSENT 3 + +/* 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 + +#define SFP_POLL_INTERVAL msecs_to_jiffies(100) + +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_max_block_size; + 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; + + /* I2C-probed presence, for boards without a MOD_DEF0 GPIO. + * Access rules: st_mutex held (updated from the poll/state machine). + */ + bool i2c_present; + u8 i2c_present_nak; + unsigned long i2c_present_next; + + /* 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_ignore_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 phy_t_retry; + + unsigned int rate_kbd; + unsigned int rs_threshold_kbd; + unsigned int rs_state_mask; + + bool have_a2; + + 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 void sfp_schedule_poll(struct sfp *sfp) +{ + mod_delayed_work(system_percpu_wq, &sfp->poll, SFP_POLL_INTERVAL); +} + +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_los(struct sfp *sfp) +{ + /* This forces LOS to zero, so we ignore transitions */ + sfp->state_ignore_mask |= SFP_F_LOS; + /* Make sure that LOS options are clear */ + sfp->id.ext.options &= ~cpu_to_be16(SFP_OPTIONS_LOS_INVERTED | + SFP_OPTIONS_LOS_NORMAL); +} + +static void sfp_fixup_ignore_tx_fault(struct sfp *sfp) +{ + sfp->state_ignore_mask |= SFP_F_TX_FAULT; +} + +static void sfp_fixup_ignore_tx_fault_and_los(struct sfp *sfp) +{ + sfp_fixup_ignore_tx_fault(sfp); + sfp_fixup_ignore_los(sfp); +} + +static void sfp_fixup_ignore_hw(struct sfp *sfp, unsigned int mask) +{ + sfp->state_hw_mask &= ~mask; +} + +static void sfp_fixup_nokia(struct sfp *sfp) +{ + sfp_fixup_long_startup(sfp); + sfp_fixup_ignore_los(sfp); +} + +// 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(struct sfp *sfp) +{ + sfp->mdio_protocol = MDIO_I2C_ROLLBALL; + + /* RollBall modules may disallow access to PHY registers for up to 25 + * seconds, and the reads return 0xffff before that. Increase the time + * between PHY probe retries from 50ms to 1s so that we will wait for + * the PHY for a sufficient amount of time. + */ + sfp->phy_t_retry = msecs_to_jiffies(1000); +} + +static void sfp_fixup_rollball_wait4s(struct sfp *sfp) +{ + sfp_fixup_rollball(sfp); + + /* The RollBall fixup is not enough for FS modules, the PHY chip inside + * them does not return 0xffff for PHY ID registers in all MMDs for the + * while initializing. They need a 4 second wait before accessing PHY. + */ + sfp->module_t_wait = msecs_to_jiffies(4000); +} + +static void sfp_fixup_fs_10gt(struct sfp *sfp) +{ + sfp_fixup_10gbaset_30m(sfp); + sfp_fixup_rollball_wait4s(sfp); +} + +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_fixup_ignore_hw(sfp, SFP_F_TX_FAULT | SFP_F_LOS); +} + +static void sfp_fixup_potron(struct sfp *sfp) +{ + /* + * The TX_FAULT and LOS pins on this device are used for serial + * communication, so ignore them. Additionally, provide extra + * time for this device to fully start up. + */ + + sfp_fixup_long_startup(sfp); + sfp_fixup_ignore_hw(sfp, SFP_F_TX_FAULT | SFP_F_LOS); +} + +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, + struct sfp_module_caps *caps) +{ + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + caps->link_modes); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, caps->interfaces); +} + +static void sfp_quirk_disable_autoneg(const struct sfp_eeprom_id *id, + struct sfp_module_caps *caps) +{ + linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, caps->link_modes); +} + +static void sfp_quirk_oem_2_5g(const struct sfp_eeprom_id *id, + struct sfp_module_caps *caps) +{ + /* Copper 2.5G SFP */ + linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + caps->link_modes); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, caps->interfaces); + sfp_quirk_disable_autoneg(id, caps); +} + +static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id, + struct sfp_module_caps *caps) +{ + /* 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, + * along with the Autoneg and pause bits. + */ + linkmode_zero(caps->link_modes); + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + caps->link_modes); + linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, caps->link_modes); + linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, caps->link_modes); + linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, caps->link_modes); + + phy_interface_zero(caps->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, caps->interfaces); +} + +#define SFP_QUIRK(_v, _p, _s, _f) \ + { .vendor = _v, .part = _p, .support = _s, .fixup = _f, } +#define SFP_QUIRK_S(_v, _p, _s) SFP_QUIRK(_v, _p, _s, NULL) +#define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f) +/* Like SFP_QUIRK_F, but matches the part as a prefix; the vendor name + * is still matched exactly. Use for modules whose EEPROM vendor PN + * field reads back with garbage past the legitimate characters instead + * of the SFF-8472-mandated space padding, so sfp_strlen can't trim the + * field down to the legitimate length. + */ +#define SFP_QUIRK_F_PREFIX(_v, _p, _f) \ + { .vendor = _v, .part = _p, .support = NULL, .fixup = _f, \ + .part_prefix_match = true } + +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("ALCATELLUCENT", "G010SP", sfp_quirk_2500basex, + sfp_fixup_ignore_tx_fault), + + // 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_nokia), + + SFP_QUIRK_F("BIDB", "X-ONU-SFPP", sfp_fixup_potron), + + // FLYPRO SFP-10GT-CS-30M uses Rollball protocol to talk to the PHY. + SFP_QUIRK_F("FLYPRO", "SFP-10GT-CS-30M", sfp_fixup_rollball), + + // Fiberstore SFP-10G-T doesn't identify as copper, uses the Rollball + // protocol to talk to the PHY and needs 4 sec wait before probing the + // PHY. + SFP_QUIRK_F("FS", "SFP-10G-T", sfp_fixup_fs_10gt), + + // Fiberstore SFP-2.5G-T and SFP-10GM-T uses Rollball protocol to talk + // to the PHY and needs 4 sec wait before probing the PHY. + SFP_QUIRK_F("FS", "SFP-2.5G-T", sfp_fixup_rollball_wait4s), + SFP_QUIRK_F("FS", "SFP-10GM-T", sfp_fixup_rollball_wait4s), + + // 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), + + // Fiberstore XGS-SFP-ONT-MACI is a MAC-mode XGS-PON ONT stick with + // ONT-class serial-passthrough TX_FAULT/LOS wiring and slow startup; + // mask both signals and extend T_START_UP via the potron fixup. The + // PN is the product name (XGS-SFP-ONT-MAC-I) truncated at the 16-byte + // field width, so the field is fully occupied and matches exactly. + SFP_QUIRK_F("FS", "XGS-SFP-ONT-MACI", sfp_fixup_potron), + + SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp), + + SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron), + + // HG MXPD-483II-F 2.5G supports 2500Base-X, but incorrectly reports + // 2600MBd in their EERPOM + SFP_QUIRK_S("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_and_los), + + // Hisense LXT-010S-H is a GPON ONT SFP (sold as LEOX LXT-010S-H) that + // can operate at 2500base-X, but reports 1000BASE-LX / 1300MBd in its + // EEPROM + SFP_QUIRK("Hisense-Leox", "LXT-010S-H", sfp_quirk_2500basex, + sfp_fixup_ignore_tx_fault), + + // Hisense ZNID-GPON-2311NA can operate at 2500base-X, but reports + // 1000BASE-LX / 1300MBd in its EEPROM + SFP_QUIRK("Hisense", "ZNID-GPON-2311NA", sfp_quirk_2500basex, + sfp_fixup_ignore_tx_fault), + + // HSGQ HSGQ-XPON-Stick can operate at 2500base-X, but reports + // 1000BASE-LX / 1300MBd in its EEPROM + SFP_QUIRK("HSGQ", "HSGQ-XPON-Stick", sfp_quirk_2500basex, + sfp_fixup_ignore_tx_fault), + + // Lantech 8330-262D-E and 8330-265D can operate at 2500base-X, but + // incorrectly report 2500MBd NRZ in their EEPROM. + // Some 8330-265D modules have inverted LOS, while all of them report + // normal LOS in EEPROM. Therefore we need to ignore LOS entirely. + SFP_QUIRK_S("Lantech", "8330-262D-E", sfp_quirk_2500basex), + SFP_QUIRK("Lantech", "8330-265D", sfp_quirk_2500basex, + sfp_fixup_ignore_los), + + SFP_QUIRK_S("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("YV", "SFP+ONU-XGSPON", sfp_fixup_potron), + + // HORACO HC-10GE-113C uses Rollball protocol to talk to the PHY. + SFP_QUIRK_F("OEM", "HC-10GE-113C", sfp_fixup_rollball), + + // OEM SFP-GE-T is a 1000Base-T module with broken TX_FAULT indicator + SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault), + + SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc), + SFP_QUIRK_S("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g), + SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-D", sfp_quirk_2500basex), + SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex), + SFP_QUIRK_S("OEM", "SFP-2.5G-LH03-B", sfp_quirk_2500basex), + SFP_QUIRK_S("OEM", "SFP-2.5G-LH20-A", sfp_quirk_2500basex), + SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc), + SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc), + + // OEM XGSPONST2001 is an XGS-PON ONT stick with broken TX_FAULT and + // LOS indicators and slow startup, just like potron. On cold + // power-up the EEPROM vendor PN field reads back with non-printable + // garbage past the legitimate string instead of space padding, so + // match the part as a prefix. + SFP_QUIRK_F_PREFIX("OEM", "XGSPONST2001", sfp_fixup_potron), + + SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball), + SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball), + SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball), + + SFP_QUIRK_S("ZOERAX", "SFP-2.5G-T", sfp_quirk_oem_2_5g), +}; + +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, bool prefix) +{ + size_t qs_len; + + if (!qs) + return true; + qs_len = strlen(qs); + if (prefix ? qs_len > len : qs_len != len) + return false; + return !strncmp(qs, str, qs_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, false) && + sfp_match(q->part, id->base.vendor_pn, ps, + q->part_prefix_match)) + return q; + + return NULL; +} + +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_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf, + size_t len) +{ + union i2c_smbus_data smbus_data = {0}; + u8 bus_addr = a2 ? 0x51 : 0x50; + size_t this_len, transferred; + u32 functionality; + u8 *data = buf; + int ret; + + functionality = i2c_get_functionality(sfp->i2c); + + while (len) { + this_len = min(len, sfp->i2c_block_size); + + if (functionality & I2C_FUNC_SMBUS_READ_I2C_BLOCK) { + smbus_data.block[0] = this_len; + ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0, + I2C_SMBUS_READ, dev_addr, + I2C_SMBUS_I2C_BLOCK_DATA, &smbus_data); + if (ret < 0) + return ret; + + transferred = min_t(size_t, smbus_data.block[0], this_len); + if (!transferred) + return -EIO; + + memcpy(data, &smbus_data.block[1], transferred); + } else if (this_len >= 2 && + (functionality & I2C_FUNC_SMBUS_READ_WORD_DATA)) { + ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0, + I2C_SMBUS_READ, dev_addr, + I2C_SMBUS_WORD_DATA, &smbus_data); + if (ret < 0) + return ret; + + put_unaligned_le16(smbus_data.word, data); + transferred = 2; + } else { + ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0, + I2C_SMBUS_READ, dev_addr, + I2C_SMBUS_BYTE_DATA, &smbus_data); + if (ret < 0) + return ret; + + *data = smbus_data.byte; + transferred = 1; + } + + data += transferred; + len -= transferred; + dev_addr += transferred; + } + + return data - (u8 *)buf; +} + +static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf, + size_t len) +{ + union i2c_smbus_data smbus_data; + u8 bus_addr = a2 ? 0x51 : 0x50; + size_t this_len, transferred; + u32 functionality; + u8 *data = buf; + int ret; + + functionality = i2c_get_functionality(sfp->i2c); + + while (len) { + this_len = min(len, sfp->i2c_block_size); + + if (functionality & I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) { + smbus_data.block[0] = this_len; + memcpy(&smbus_data.block[1], data, this_len); + + ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0, + I2C_SMBUS_WRITE, dev_addr, + I2C_SMBUS_I2C_BLOCK_DATA, &smbus_data); + if (ret < 0) + return ret; + + transferred = this_len; + } else if (this_len >= 2 && + (functionality & I2C_FUNC_SMBUS_WRITE_WORD_DATA)) { + smbus_data.word = get_unaligned_le16(data); + ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0, + I2C_SMBUS_WRITE, dev_addr, + I2C_SMBUS_WORD_DATA, &smbus_data); + if (ret < 0) + return ret; + + transferred = 2; + } else { + smbus_data.byte = *data; + ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0, + I2C_SMBUS_WRITE, dev_addr, + I2C_SMBUS_BYTE_DATA, &smbus_data); + if (ret < 0) + return ret; + + transferred = 1; + } + + data += transferred; + len -= transferred; + dev_addr += transferred; + } + + return data - (u8 *)buf; +} + +static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) +{ + size_t max_block_size; + + sfp->i2c = i2c; + + if (i2c_check_functionality(i2c, I2C_FUNC_I2C)) { + sfp->read = sfp_i2c_read; + sfp->write = sfp_i2c_write; + max_block_size = SFP_EEPROM_BLOCK_SIZE; + } else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA) || + i2c_check_functionality(i2c, I2C_FUNC_SMBUS_I2C_BLOCK)) { + /* Either protocol alone covers any length: I2C-block carries + * 1..32 bytes per xfer, byte iterates one byte at a time. + */ + sfp->read = sfp_smbus_read; + sfp->write = sfp_smbus_write; + + if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_I2C_BLOCK)) + max_block_size = SFP_EEPROM_BLOCK_SIZE; + else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_WORD_DATA)) + max_block_size = 2; + else + max_block_size = 1; + } else if (WARN_ONCE(i2c_check_functionality(i2c, I2C_FUNC_SMBUS_WORD_DATA), + "SMBus word-only adapter; odd-length transfers will fail\n")) { + /* Word-only: even-length xfers work; odd-length xfers fall + * to BYTE, which the adapter does not advertise and will + * likely fail. + */ + sfp->read = sfp_smbus_read; + sfp->write = sfp_smbus_write; + max_block_size = 2; + } else { + sfp->i2c = NULL; + return -EINVAL; + } + + if (i2c->quirks && i2c->quirks->max_read_len) + max_block_size = min(max_block_size, i2c->quirks->max_read_len); + if (i2c->quirks && i2c->quirks->max_write_len) + max_block_size = min(max_block_size, i2c->quirks->max_write_len); + + sfp->i2c_max_block_size = max_block_size; + sfp->i2c_block_size = sfp->i2c_max_block_size; + 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); + mdiobus_free(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); +} + +/* Probe whether a module is physically present by attempting a single-byte + * I2C read of the EEPROM identifier (an empty cage NAKs). Used as the presence + * source on boards that do not wire MOD_DEF0 to a GPIO. + */ +static bool sfp_module_present_i2c(struct sfp *sfp) +{ + u8 id; + + return sfp_read(sfp, false, SFP_PHYS_ID, &id, sizeof(id)) == sizeof(id); +} + +/* get_state variant for boards without a MOD_DEF0 GPIO. Instead of assuming + * the module is always present, derive SFP_F_PRESENT from a throttled I2C + * probe so that hot-insertion and removal are detected. A single ACK asserts + * presence; R_PROBE_ABSENT consecutive failures clear it, to ride out a + * transient I2C error on a live module. + */ +static unsigned int sfp_i2c_get_state(struct sfp *sfp) +{ + unsigned int state = sfp_gpio_get_state(sfp); + + if (time_after_eq(jiffies, sfp->i2c_present_next)) { + if (sfp_module_present_i2c(sfp)) { + sfp->i2c_present = true; + sfp->i2c_present_nak = 0; + } else if (sfp->i2c_present && + ++sfp->i2c_present_nak >= R_PROBE_ABSENT) { + sfp->i2c_present = false; + sfp->i2c_present_nak = 0; + } + sfp->i2c_present_next = jiffies + T_PROBE_PRESENT; + } + + if (sfp->i2c_present) + state |= SFP_F_PRESENT; + + return state; +} + +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 & ~sfp->state_ignore_mask & + mask; + + if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) && + !sfp->need_poll) + sfp_schedule_poll(sfp); + 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\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_percpu_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_percpu_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; + } + + if (!IS_ERR_OR_NULL(sfp->hwmon_name)) { + kfree(sfp->hwmon_name); + sfp->hwmon_name = NULL; + } +} + +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->i2c_max_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->phy_t_retry = T_PHY_RETRY; + + sfp->state_ignore_mask = 0; + + 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); + + sfp->state_hw_mask &= ~sfp->state_ignore_mask; + 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) { + 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_percpu_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, + sfp->phy_t_retry); + dev_dbg(sfp->dev, + "no PHY detected, %u tries left\n", + sfp->sm_phy_retries); + 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; + 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) + sfp_schedule_poll(sfp); +} + +static struct sfp *sfp_alloc(struct device *dev) +{ + struct sfp *sfp; + + sfp = kzalloc_obj(*sfp); + if (!sfp) + return ERR_PTR(-ENOMEM); + + sfp->dev = dev; + + mutex_init(&sfp->sm_mutex); + mutex_init(&sfp->st_mutex); + INIT_DELAYED_WORK(&sfp->poll, sfp_poll); + INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout); + + sfp_hwmon_init(sfp); + + return sfp; +} + +static void sfp_cleanup(void *data) +{ + struct sfp *sfp = data; + + sfp_hwmon_exit(sfp); + + cancel_delayed_work_sync(&sfp->poll); + cancel_delayed_work_sync(&sfp->timeout); + if (sfp->i2c_mii) { + mdiobus_unregister(sfp->i2c_mii); + mdiobus_free(sfp->i2c_mii); + } + if (sfp->i2c) + i2c_put_adapter(sfp->i2c); + kfree(sfp); +} + +static int sfp_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; + + /* An SFP cage with no MOD_DEF0 GPIO has no hardware presence signal. + * Assuming the module is always present traps an empty cage in + * MOD_ERROR and never detects hot-insertion, so derive presence from a + * throttled I2C probe and poll for changes instead. sfp_i2c_configure() + * has already set i2c_max_block_size; seed i2c_block_size so the + * presence read does not issue a zero-length transfer before the first + * EEPROM read. Seed i2c_present_next to jiffies so the first probe + * happens immediately (a zero value would be in the past relative to + * the negative INITIAL_JIFFIES at boot and delay detection). + * + * A soldered-down module (sff,sff) has no presence signal and is + * genuinely always present, so it keeps the always-present behaviour; + * the I2C probe is gated on the cage type advertising SFP_F_PRESENT. + */ + if (!sfp->gpio[GPIO_MODDEF0]) { + if (sff->gpios & SFP_F_PRESENT) { + sfp->get_state = sfp_i2c_get_state; + sfp->i2c_block_size = sfp->i2c_max_block_size; + sfp->i2c_present_next = jiffies; + sfp->need_poll = true; + } else { + 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) + sfp_schedule_poll(sfp); + + /* 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; + + if (sfp->i2c_max_block_size < 2) + dev_warn(sfp->dev, + "Please note:\n" + "This SFP cage is accessed via an SMBus only capable of single byte\n" + "transactions. Some features are disabled, other may be unreliable or\n" + "sporadically fail. Use with caution. There is nothing that the kernel\n" + "or community can do to fix it, the kernel will try best efforts. Please\n" + "verify any problems on hardware that supports multi-byte I2C transactions.\n"); + + sfp_debugfs_init(sfp); + + return 0; +} + +static void 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(); +} + +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, + }, +}; + +module_platform_driver(sfp_driver); + +MODULE_ALIAS("platform:sfp"); +MODULE_AUTHOR("Russell King"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SFP cage support"); diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h new file mode 100644 index 000000000..19fe29d84 --- /dev/null +++ b/drivers/net/phy/sfp.h @@ -0,0 +1,45 @@ +#ifndef SFP_H +#define SFP_H + +#include +#include + +struct sfp; + +struct sfp_quirk { + const char *vendor; + const char *part; + void (*support)(const struct sfp_eeprom_id *id, + struct sfp_module_caps *caps); + void (*fixup)(struct sfp *sfp); + bool part_prefix_match; +}; + +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..48487149c --- /dev/null +++ b/drivers/net/phy/smsc.c @@ -0,0 +1,900 @@ +// 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 + * + * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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) +{ + u8 mdix_ctrl; + int val; + int rc; + + /* When auto-negotiation is disabled (forced mode), the PHY's + * Auto-MDIX will continue toggling the TX/RX pairs. + * + * To establish a stable link, we must select a fixed MDI mode. + * If the user has not specified a fixed MDI mode (i.e., mdix_ctrl is + * 'auto'), we default to ETH_TP_MDI. This choice of a ETH_TP_MDI mode + * mirrors the behavior the hardware would exhibit if the AUTOMDIX_EN + * strap were configured for a fixed MDI connection. + */ + if (phydev->autoneg == AUTONEG_DISABLE) { + if (phydev->mdix_ctrl == ETH_TP_MDI_AUTO) + mdix_ctrl = ETH_TP_MDI; + else + mdix_ctrl = phydev->mdix_ctrl; + } else { + mdix_ctrl = phydev->mdix_ctrl; + } + + switch (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_OVRRD_AMDIX_ | + 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 = 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 lan87xx_phy_config_init(struct phy_device *phydev) +{ + int rc; + + /* The LAN87xx PHY's initial MDI-X mode is determined by the AUTOMDIX_EN + * hardware strap, but the driver cannot read the strap's status. This + * creates an unpredictable initial state. + * + * To ensure consistent and reliable behavior across all boards, + * override the strap configuration on initialization and force the PHY + * into a known state with Auto-MDIX enabled, which is the expected + * default for modern hardware. + */ + rc = phy_modify(phydev, SPECIAL_CTRL_STS, + SPECIAL_CTRL_STS_OVRRD_AMDIX_ | + SPECIAL_CTRL_STS_AMDIX_ENABLE_ | + SPECIAL_CTRL_STS_AMDIX_STATE_, + SPECIAL_CTRL_STS_OVRRD_AMDIX_ | + SPECIAL_CTRL_STS_AMDIX_ENABLE_); + if (rc < 0) + return rc; + + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + + return smsc_phy_config_init(phydev); +} + +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++) + ethtool_puts(&data, smsc_hw_stats[i].string); +} + +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_with_rate(dev, NULL, + 50 * 1000 * 1000); + if (IS_ERR(refclk)) + return dev_err_probe(dev, PTR_ERR(refclk), + "Failed to request clock\n"); + + return 0; +} +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 = lan87xx_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 */ + + .flags = PHY_RST_AFTER_CLK_EN, + .probe = smsc_phy_probe, + + /* basic functions */ + .read_status = lan87xx_read_status, + .config_init = smsc_phy_config_init, + .soft_reset = smsc_phy_reset, + .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 const 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/ste10Xp.c b/drivers/net/phy/ste10Xp.c new file mode 100644 index 000000000..d4835d4c5 --- /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 + * + * Copyright (c) 2008 STMicroelectronics Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 const 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 "); +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 + +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 + * Anton Vorontsov + * + * Copyright (c) 2006-2007 MontaVista Software, Inc. + */ +#include +#include +#include +#include + +#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..46c5ff7d7 --- /dev/null +++ b/drivers/net/phy/teranetics.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Teranetics PHY + * + * Author: Shaohui Xie + * + * Copyright 2015 Freescale Semiconductor, Inc. + */ + +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Teranetics PHY driver"); +MODULE_AUTHOR("Shaohui Xie "); +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, + const struct phy_driver *phydrv) +{ + 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 const 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..900cb756c --- /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 +#include +#include + +#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 "); +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 const 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..1a430e832 --- /dev/null +++ b/drivers/net/phy/vitesse.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Vitesse PHYs + * + * Author: Kriston Carson + */ + +#include +#include +#include +#include +#include +#include + +/* Vitesse Extended Page Magic Register(s) */ +#define MII_VSC73XX_EXT_PAGE_1E 0x01 +#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 VSC8662 extended control register */ +#define VSC8662_EXT_CON1 0x17 +#define VSC8662_EXT_CON_MAC_AN BIT(13) + +#define VSC8662_MAC_AN 0x1b +#define VSC8662_MAC_AN_BYPASS BIT(13) + +/* Vitesse VSC73XX Extended Control Register */ +#define MII_VSC73XX_PHY_CTRL_EXT3 0x14 + +#define MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_EN BIT(4) +#define MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_CNT GENMASK(3, 2) +#define MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_STA BIT(1) +#define MII_VSC73XX_DOWNSHIFT_MAX 5 +#define MII_VSC73XX_DOWNSHIFT_INVAL 1 + +/* VSC73XX PHY_BYPASS_CTRL register*/ +#define MII_VSC73XX_PHY_BYPASS_CTRL MII_DCOUNTER +#define MII_VSC73XX_PBC_TX_DIS BIT(15) +#define MII_VSC73XX_PBC_FOR_SPD_AUTO_MDIX_DIS BIT(7) +#define MII_VSC73XX_PBC_PAIR_SWAP_DIS BIT(5) +#define MII_VSC73XX_PBC_POL_INV_DIS BIT(4) +#define MII_VSC73XX_PBC_PARALLEL_DET_DIS BIT(3) +#define MII_VSC73XX_PBC_AUTO_NP_EXCHANGE_DIS BIT(1) + +/* VSC73XX PHY_AUX_CTRL_STAT register */ +#define MII_VSC73XX_PHY_AUX_CTRL_STAT MII_NCONFIG +#define MII_VSC73XX_PACS_NO_MDI_X_IND BIT(13) + +/* 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; +} + +static unsigned int vsc8662_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_SGMII) + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE | + LINK_INBAND_BYPASS; + + return 0; +} + +static int vsc8662_config_inband(struct phy_device *phydev, unsigned int modes) +{ + u16 mask, set; + int ret; + + mask = VSC8662_MAC_AN_BYPASS; + set = modes & LINK_INBAND_BYPASS ? mask : 0; + ret = phy_modify(phydev, VSC8662_MAC_AN, mask, set); + if (ret < 0) + return ret; + + mask = VSC8662_EXT_CON_MAC_AN; + set = modes & (LINK_INBAND_ENABLE | LINK_INBAND_BYPASS) ? mask : 0; + + ret = phy_modify_changed(phydev, VSC8662_EXT_CON1, mask, set); + if (ret <= 0) + return ret; + + /* We need to soft-reset the PHY when changing VSC8662_EXT_CON_MAC_AN */ + return genphy_soft_reset(phydev); +} + +#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 int vsc73xx_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val, enable, cnt; + + val = phy_read_paged(phydev, MII_VSC73XX_EXT_PAGE_1E, + MII_VSC73XX_PHY_CTRL_EXT3); + if (val < 0) + return val; + + enable = FIELD_GET(MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_EN, val); + cnt = FIELD_GET(MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_CNT, val) + 2; + + *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int vsc73xx_set_downshift(struct phy_device *phydev, u8 cnt) +{ + u16 mask, val; + int ret; + + if (cnt > MII_VSC73XX_DOWNSHIFT_MAX) + return -E2BIG; + else if (cnt == MII_VSC73XX_DOWNSHIFT_INVAL) + return -EINVAL; + + mask = MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_EN; + + if (!cnt) { + val = 0; + } else { + mask |= MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_CNT; + val = MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_EN | + FIELD_PREP(MII_VSC73XX_PHY_CTRL_EXT3_DOWNSHIFT_CNT, + cnt - 2); + } + + ret = phy_modify_paged(phydev, MII_VSC73XX_EXT_PAGE_1E, + MII_VSC73XX_PHY_CTRL_EXT3, mask, val); + if (ret < 0) + return ret; + + return genphy_soft_reset(phydev); +} + +static int vsc73xx_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return vsc73xx_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int vsc73xx_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return vsc73xx_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + +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); + + /* Enable downshift by default */ + vsc73xx_set_downshift(phydev, MII_VSC73XX_DOWNSHIFT_MAX); + + /* Set Auto MDI-X by default */ + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; +} + +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_mdix_set(struct phy_device *phydev, u8 mdix) +{ + int ret; + u16 val; + + val = phy_read(phydev, MII_VSC73XX_PHY_BYPASS_CTRL); + + switch (mdix) { + case ETH_TP_MDI: + val |= MII_VSC73XX_PBC_FOR_SPD_AUTO_MDIX_DIS | + MII_VSC73XX_PBC_PAIR_SWAP_DIS | + MII_VSC73XX_PBC_POL_INV_DIS; + break; + case ETH_TP_MDI_X: + /* When MDI-X auto configuration is disabled, is possible + * to force only MDI mode. Let's use autoconfig for forced + * MDIX mode. + */ + case ETH_TP_MDI_AUTO: + val &= ~(MII_VSC73XX_PBC_FOR_SPD_AUTO_MDIX_DIS | + MII_VSC73XX_PBC_PAIR_SWAP_DIS | + MII_VSC73XX_PBC_POL_INV_DIS); + break; + default: + return -EINVAL; + } + + ret = phy_write(phydev, MII_VSC73XX_PHY_BYPASS_CTRL, val); + if (ret) + return ret; + + return genphy_restart_aneg(phydev); +} + +static int vsc73xx_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = vsc73xx_mdix_set(phydev, phydev->mdix_ctrl); + if (ret) + return ret; + + return genphy_config_aneg(phydev); +} + +static int vsc73xx_mdix_get(struct phy_device *phydev, u8 *mdix) +{ + u16 reg_val; + + reg_val = phy_read(phydev, MII_VSC73XX_PHY_AUX_CTRL_STAT); + if (reg_val & MII_VSC73XX_PACS_NO_MDI_X_IND) + *mdix = ETH_TP_MDI; + else + *mdix = ETH_TP_MDI_X; + + return 0; +} + +static int vsc73xx_read_status(struct phy_device *phydev) +{ + int ret; + + ret = vsc73xx_mdix_get(phydev, &phydev->mdix); + if (ret < 0) + return ret; + + return genphy_read_status(phydev); +} + +/* 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_status = vsc73xx_read_status, + .read_page = vsc73xx_read_page, + .write_page = vsc73xx_write_page, + .get_tunable = vsc73xx_get_tunable, + .set_tunable = vsc73xx_set_tunable, +}, { + .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_status = vsc73xx_read_status, + .read_page = vsc73xx_read_page, + .write_page = vsc73xx_write_page, + .get_tunable = vsc73xx_get_tunable, + .set_tunable = vsc73xx_set_tunable, +}, { + .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_status = vsc73xx_read_status, + .read_page = vsc73xx_read_page, + .write_page = vsc73xx_write_page, + .get_tunable = vsc73xx_get_tunable, + .set_tunable = vsc73xx_set_tunable, +}, { + .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_status = vsc73xx_read_status, + .read_page = vsc73xx_read_page, + .write_page = vsc73xx_write_page, + .get_tunable = vsc73xx_get_tunable, + .set_tunable = vsc73xx_set_tunable, +}, { + .phy_id = PHY_ID_VSC8662, + .name = "Vitesse VSC8662", + .phy_id_mask = 0x000ffff0, + /* PHY_GBIT_FEATURES */ + .config_init = &vsc824x_config_init, + .inband_caps = vsc8662_inband_caps, + .config_inband = vsc8662_config_inband, + .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 const 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..2024d8ef3 --- /dev/null +++ b/drivers/net/phy/xilinx_gmii2rgmii.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Xilinx GMII2RGMII Converter driver + * + * Copyright (C) 2016 Xilinx, Inc. + * Copyright (C) 2016 Andrew Lunn + * + * Author: Andrew Lunn + * Author: Kedareswara rao Appana + * + * Description: + * This driver is developed for Xilinx GMII2RGMII Converter + */ +#include +#include +#include +#include +#include +#include +#include + +#define XILINX_GMII2RGMII_REG 0x10 +#define XILINX_GMII2RGMII_SPEED_MASK (BMCR_SPEED1000 | BMCR_SPEED100) + +struct gmii2rgmii { + struct phy_device *phy_dev; + const 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, + int speed) +{ + struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio); + int err; + + if (priv->phy_drv->set_loopback) + err = priv->phy_drv->set_loopback(phydev, enable, speed); + else + err = genphy_loopback(phydev, enable, speed); + 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; + struct clk *clkin; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + clkin = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clkin)) + return dev_err_probe(dev, PTR_ERR(clkin), + "Failed to get and enable clock from Device Tree\n"); + + 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"); -- cgit v1.3.1