diff options
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 11 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 4 | ||||
-rw-r--r-- | drivers/net/phy/ax88796b_rust.rs | 7 | ||||
-rw-r--r-- | drivers/net/phy/dp83822.c | 35 | ||||
-rw-r--r-- | drivers/net/phy/dp83td510.c | 119 | ||||
-rw-r--r-- | drivers/net/phy/dp83tg720.c | 154 | ||||
-rw-r--r-- | drivers/net/phy/marvell-88x2222.c | 2 | ||||
-rw-r--r-- | drivers/net/phy/marvell.c | 2 | ||||
-rw-r--r-- | drivers/net/phy/marvell10g.c | 2 | ||||
-rw-r--r-- | drivers/net/phy/microchip_t1.c | 990 | ||||
-rw-r--r-- | drivers/net/phy/microchip_t1s.c | 30 | ||||
-rw-r--r-- | drivers/net/phy/motorcomm.c | 684 | ||||
-rw-r--r-- | drivers/net/phy/open_alliance_helpers.c | 77 | ||||
-rw-r--r-- | drivers/net/phy/open_alliance_helpers.h | 47 | ||||
-rw-r--r-- | drivers/net/phy/phy.c | 22 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 108 | ||||
-rw-r--r-- | drivers/net/phy/phy_link_topology.c | 105 | ||||
-rw-r--r-- | drivers/net/phy/phylink.c | 45 | ||||
-rw-r--r-- | drivers/net/phy/qcom/at803x.c | 2 | ||||
-rw-r--r-- | drivers/net/phy/qcom/qca807x.c | 12 | ||||
-rw-r--r-- | drivers/net/phy/qcom/qca83xx.c | 10 | ||||
-rw-r--r-- | drivers/net/phy/qt2025.rs | 103 | ||||
-rw-r--r-- | drivers/net/phy/sfp-bus.c | 26 | ||||
-rw-r--r-- | drivers/net/phy/vitesse.c | 183 |
24 files changed, 2701 insertions, 79 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 7fddc8306d82..01b235b3bb7e 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -44,6 +44,9 @@ config LED_TRIGGER_PHY <Speed in megabits>Mbps OR <Speed in gigabits>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 @@ -109,6 +112,13 @@ config ADIN1100_PHY Currently supports the: - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet 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 @@ -414,6 +424,7 @@ config DP83TD510_PHY 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 diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 202ed7f450da..90f886844381 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -2,7 +2,7 @@ # Makefile for Linux PHY drivers libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \ - linkmode.o + linkmode.o phy_link_topology.o mdio-bus-y += mdio_bus.o mdio_device.o ifdef CONFIG_MDIO_DEVICE @@ -22,6 +22,7 @@ endif obj-$(CONFIG_MDIO_DEVRES) += mdio_devres.o 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_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o @@ -36,6 +37,7 @@ obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_ADIN1100_PHY) += adin1100.o obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o obj-$(CONFIG_AMD_PHY) += amd.o +obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o obj-$(CONFIG_AQUANTIA_PHY) += aquantia/ ifdef CONFIG_AX88796B_RUST_PHY obj-$(CONFIG_AX88796B_PHY) += ax88796b_rust.o diff --git a/drivers/net/phy/ax88796b_rust.rs b/drivers/net/phy/ax88796b_rust.rs index 5c92572962dc..8c7eb009d9fc 100644 --- a/drivers/net/phy/ax88796b_rust.rs +++ b/drivers/net/phy/ax88796b_rust.rs @@ -6,7 +6,7 @@ //! C version of this driver: [`drivers/net/phy/ax88796b.c`](./ax88796b.c) use kernel::{ c_str, - net::phy::{self, DeviceId, Driver}, + net::phy::{self, reg::C22, DeviceId, Driver}, prelude::*, uapi, }; @@ -24,7 +24,6 @@ kernel::module_phy_driver! { license: "GPL", } -const MII_BMCR: u16 = uapi::MII_BMCR as u16; const BMCR_SPEED100: u16 = uapi::BMCR_SPEED100 as u16; const BMCR_FULLDPLX: u16 = uapi::BMCR_FULLDPLX as u16; @@ -33,7 +32,7 @@ const BMCR_FULLDPLX: u16 = uapi::BMCR_FULLDPLX as u16; // 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(uapi::MII_BMCR as u16, 0)?; + dev.write(C22::BMCR, 0)?; dev.genphy_soft_reset() } @@ -55,7 +54,7 @@ impl Driver for PhyAX88772A { } // 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(MII_BMCR)?; + let ret = dev.read(C22::BMCR)?; if ret & BMCR_SPEED100 != 0 { dev.set_speed(uapi::SPEED_100); diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c index efeb643c1373..fc247f479257 100644 --- a/drivers/net/phy/dp83822.c +++ b/drivers/net/phy/dp83822.c @@ -271,8 +271,7 @@ static int dp83822_config_intr(struct phy_device *phydev) DP83822_ENERGY_DET_INT_EN | DP83822_LINK_QUAL_INT_EN); - /* Private data pointer is NULL on DP83825 */ - if (!dp83822 || !dp83822->fx_enabled) + if (!dp83822->fx_enabled) misr_status |= DP83822_ANEG_COMPLETE_INT_EN | DP83822_DUP_MODE_CHANGE_INT_EN | DP83822_SPEED_CHANGED_INT_EN; @@ -292,8 +291,7 @@ static int dp83822_config_intr(struct phy_device *phydev) DP83822_PAGE_RX_INT_EN | DP83822_EEE_ERROR_CHANGE_INT_EN); - /* Private data pointer is NULL on DP83825 */ - if (!dp83822 || !dp83822->fx_enabled) + if (!dp83822->fx_enabled) misr_status |= DP83822_ANEG_ERR_INT_EN | DP83822_WOL_PKT_INT_EN; @@ -691,10 +689,9 @@ static int dp83822_read_straps(struct phy_device *phydev) return 0; } -static int dp83822_probe(struct phy_device *phydev) +static int dp8382x_probe(struct phy_device *phydev) { struct dp83822_private *dp83822; - int ret; dp83822 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83822), GFP_KERNEL); @@ -703,6 +700,20 @@ static int dp83822_probe(struct phy_device *phydev) phydev->priv = dp83822; + return 0; +} + +static int dp83822_probe(struct phy_device *phydev) +{ + struct dp83822_private *dp83822; + int ret; + + ret = dp8382x_probe(phydev); + if (ret) + return ret; + + dp83822 = phydev->priv; + ret = dp83822_read_straps(phydev); if (ret) return ret; @@ -717,14 +728,11 @@ static int dp83822_probe(struct phy_device *phydev) static int dp83826_probe(struct phy_device *phydev) { - struct dp83822_private *dp83822; - - dp83822 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83822), - GFP_KERNEL); - if (!dp83822) - return -ENOMEM; + int ret; - phydev->priv = dp83822; + ret = dp8382x_probe(phydev); + if (ret) + return ret; dp83826_of_init(phydev); @@ -795,6 +803,7 @@ static int dp83822_resume(struct phy_device *phydev) PHY_ID_MATCH_MODEL(_id), \ .name = (_name), \ /* PHY_BASIC_FEATURES */ \ + .probe = dp8382x_probe, \ .soft_reset = dp83822_phy_reset, \ .config_init = dp8382x_config_init, \ .get_wol = dp83822_get_wol, \ diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c index 551e37786c2d..92aa3a2b9744 100644 --- a/drivers/net/phy/dp83td510.c +++ b/drivers/net/phy/dp83td510.c @@ -58,6 +58,10 @@ static const u16 dp83td510_mse_sqi_map[] = { 0x0000 /* 24dB =< SNR */ }; +struct dp83td510_priv { + bool alcd_test_active; +}; + /* Time Domain Reflectometry (TDR) Functionality of DP83TD510 PHY * * I assume that this PHY is using a variation of Spread Spectrum Time Domain @@ -169,6 +173,10 @@ static const u16 dp83td510_mse_sqi_map[] = { #define DP83TD510E_UNKN_030E 0x30e #define DP83TD510E_030E_VAL 0x2520 +#define DP83TD510E_ALCD_STAT 0xa9f +#define DP83TD510E_ALCD_COMPLETE BIT(15) +#define DP83TD510E_ALCD_CABLE_LENGTH GENMASK(10, 0) + static int dp83td510_config_intr(struct phy_device *phydev) { int ret; @@ -325,8 +333,23 @@ static int dp83td510_get_sqi_max(struct phy_device *phydev) */ 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) @@ -402,8 +425,8 @@ static int dp83td510_cable_test_start(struct phy_device *phydev) } /** - * dp83td510_cable_test_get_status - Get the status of the cable test for the - * DP83TD510 PHY. + * 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. * @@ -411,13 +434,11 @@ static int dp83td510_cable_test_start(struct phy_device *phydev) * * Returns: 0 on success or a negative error code on failure. */ -static int dp83td510_cable_test_get_status(struct phy_device *phydev, - bool *finished) +static int dp83td510_cable_test_get_tdr_status(struct phy_device *phydev, + bool *finished) { int ret, stat; - *finished = false; - ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_TDR_CFG); if (ret < 0) return ret; @@ -459,6 +480,77 @@ static int dp83td510_cable_test_get_status(struct phy_device *phydev, 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. @@ -477,12 +569,27 @@ static int dp83td510_get_features(struct phy_device *phydev) 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, diff --git a/drivers/net/phy/dp83tg720.c b/drivers/net/phy/dp83tg720.c index c706429b225a..0ef4d7dba065 100644 --- a/drivers/net/phy/dp83tg720.c +++ b/drivers/net/phy/dp83tg720.c @@ -3,10 +3,13 @@ * Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> */ #include <linux/bitfield.h> +#include <linux/ethtool_netlink.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/phy.h> +#include "open_alliance_helpers.h" + #define DP83TG720S_PHY_ID 0x2000a284 /* MDIO_MMD_VEND2 registers */ @@ -14,6 +17,17 @@ #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) @@ -22,18 +36,155 @@ /* 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 + +/* 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) +/* 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 +/** + * 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); + + return phy_init_hw(phydev); +} + static int dp83tg720_config_aneg(struct phy_device *phydev) { int ret; @@ -195,12 +346,15 @@ static struct phy_driver dp83tg720_driver[] = { PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID), .name = "TI DP83TG720S", + .flags = PHY_POLL_CABLE_TEST, .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, .suspend = genphy_suspend, .resume = genphy_resume, diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index b88398e6872b..0b777cdd7078 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -553,6 +553,8 @@ static const struct sfp_upstream_ops sfp_phy_ops = { .link_down = mv2222_sfp_link_down, .attach = phy_sfp_attach, .detach = phy_sfp_detach, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, }; static int mv2222_probe(struct phy_device *phydev) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index b89fbffa6a93..9964bf3dea2f 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -3613,6 +3613,8 @@ static const struct sfp_upstream_ops m88e1510_sfp_ops = { .module_remove = m88e1510_sfp_remove, .attach = phy_sfp_attach, .detach = phy_sfp_detach, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, }; static int m88e1510_probe(struct phy_device *phydev) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index ad43e280930c..6642eb642d4b 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -503,6 +503,8 @@ static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) static const struct sfp_upstream_ops mv3310_sfp_ops = { .attach = phy_sfp_attach, .detach = phy_sfp_detach, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, .module_insert = mv3310_sfp_insert, }; diff --git a/drivers/net/phy/microchip_t1.c b/drivers/net/phy/microchip_t1.c index a35528497a57..a5ef8fe50704 100644 --- a/drivers/net/phy/microchip_t1.c +++ b/drivers/net/phy/microchip_t1.c @@ -12,6 +12,7 @@ #define PHY_ID_LAN87XX 0x0007c150 #define PHY_ID_LAN937X 0x0007c180 +#define PHY_ID_LAN887X 0x0007c1f0 /* External Register Control Register */ #define LAN87XX_EXT_REG_CTL (0x14) @@ -94,8 +95,155 @@ /* 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 DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>" -#define DRIVER_DESC "Microchip LAN87XX/LAN937x T1 PHY driver" +#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; @@ -105,6 +253,32 @@ struct access_ereg_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)]; +}; + static int lan937x_dsp_workaround(struct phy_device *phydev, u16 ereg, u8 bank) { u8 prev_bank; @@ -860,6 +1034,802 @@ 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) +{ + int ret; + + /* 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; + + 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_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_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); +} + static struct phy_driver microchip_t1_phy_driver[] = { { PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX), @@ -894,6 +1864,23 @@ static struct phy_driver microchip_t1_phy_driver[] = { .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, } }; @@ -902,6 +1889,7 @@ 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) }, { } }; diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index 534ca7d1b061..3614839a8e51 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -268,6 +268,34 @@ static int lan86xx_read_status(struct phy_device *phydev) return 0; } +/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and + * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes + * it uses C22 protocol and always uses C22 registers indirect access to access + * C45 registers. This is because, we don't have a clean separation between + * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45 + * registers direct access can't be used which can save multiple SPI bus access. + * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call + * .read_c45/.write_c45 in the OPEN Alliance framework + * drivers/net/ethernet/oa_tc6.c + */ +static int lan865x_phy_read_mmd(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); +} + +static int lan865x_phy_write_mmd(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); +} + static struct phy_driver microchip_t1s_driver[] = { { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1), @@ -285,6 +313,8 @@ static struct phy_driver microchip_t1s_driver[] = { .features = PHY_BASIC_T1S_P2MP_FEATURES, .config_init = lan865x_revb0_config_init, .read_status = lan86xx_read_status, + .read_mmd = lan865x_phy_read_mmd, + .write_mmd = lan865x_phy_write_mmd, .get_plca_cfg = genphy_c45_plca_get_cfg, .set_plca_cfg = genphy_c45_plca_set_cfg, .get_plca_status = genphy_c45_plca_get_status, diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 7a11fdb687cc..0e91f5d1a4fd 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Motorcomm 8511/8521/8531/8531S PHY driver. + * Motorcomm 8511/8521/8531/8531S/8821 PHY driver. * * Author: Peter Geis <pgwipeout@gmail.com> * Author: Frank <Frank.Sae@motor-comm.com> @@ -16,8 +16,8 @@ #define PHY_ID_YT8521 0x0000011a #define PHY_ID_YT8531 0x4f51e91b #define PHY_ID_YT8531S 0x4f51e91a - -/* YT8521/YT8531S Register Overview +#define PHY_ID_YT8821 0x4f51ea19 +/* YT8521/YT8531S/YT8821 Register Overview * UTP Register space | FIBER Register space * ------------------------------------------------------------ * | UTP MII | FIBER MII | @@ -46,12 +46,12 @@ /* Specific Status Register */ #define YTPHY_SPECIFIC_STATUS_REG 0x11 -#define YTPHY_SSR_SPEED_MODE_OFFSET 14 - -#define YTPHY_SSR_SPEED_MODE_MASK (BIT(15) | BIT(14)) -#define YTPHY_SSR_SPEED_10M 0x0 -#define YTPHY_SSR_SPEED_100M 0x1 -#define YTPHY_SSR_SPEED_1000M 0x2 +#define YTPHY_SSR_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) @@ -270,12 +270,89 @@ #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 @@ -1187,8 +1264,7 @@ static int yt8521_adjust_status(struct phy_device *phydev, int status, else duplex = DUPLEX_FULL; /* for fiber, it always DUPLEX_FULL */ - speed_mode = (status & YTPHY_SSR_SPEED_MODE_MASK) >> - YTPHY_SSR_SPEED_MODE_OFFSET; + speed_mode = status & YTPHY_SSR_SPEED_MASK; switch (speed_mode) { case YTPHY_SSR_SPEED_10M: @@ -2252,6 +2328,572 @@ static int yt8521_get_features(struct phy_device *phydev) 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), @@ -2307,11 +2949,28 @@ static struct phy_driver motorcomm_phy_drvs[] = { .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 PHY driver"); +MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821 PHY driver"); MODULE_AUTHOR("Peter Geis"); MODULE_AUTHOR("Frank"); MODULE_LICENSE("GPL"); @@ -2321,6 +2980,7 @@ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8531) }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) }, + { PHY_ID_MATCH_EXACT(PHY_ID_YT8821) }, { /* sentinel */ } }; diff --git a/drivers/net/phy/open_alliance_helpers.c b/drivers/net/phy/open_alliance_helpers.c new file mode 100644 index 000000000000..36a70451d7da --- /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 <linux/bitfield.h> +#include <linux/ethtool_netlink.h> + +#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 000000000000..8b7d97bc6f18 --- /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.c b/drivers/net/phy/phy.c index 785182fa5fe0..4f3e742907cb 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -342,14 +342,19 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd) if (mdio_phy_id_is_c45(mii_data->phy_id)) { prtad = mdio_phy_id_prtad(mii_data->phy_id); devad = mdio_phy_id_devad(mii_data->phy_id); - mii_data->val_out = mdiobus_c45_read( - phydev->mdio.bus, prtad, devad, - mii_data->reg_num); + ret = mdiobus_c45_read(phydev->mdio.bus, prtad, devad, + mii_data->reg_num); + } else { - mii_data->val_out = mdiobus_read( - phydev->mdio.bus, mii_data->phy_id, - mii_data->reg_num); + 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: @@ -1089,7 +1094,10 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev, if (autoneg != AUTONEG_ENABLE && autoneg != AUTONEG_DISABLE) return -EINVAL; - if (autoneg == AUTONEG_ENABLE && linkmode_empty(advertising)) + if (autoneg == AUTONEG_ENABLE && + (linkmode_empty(advertising) || + !linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + phydev->supported))) return -EINVAL; if (autoneg == AUTONEG_DISABLE && diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 7752e9386b40..560e338b307a 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -29,6 +29,7 @@ #include <linux/phy.h> #include <linux/phylib_stubs.h> #include <linux/phy_led_triggers.h> +#include <linux/phy_link_topology.h> #include <linux/pse-pd/pse.h> #include <linux/property.h> #include <linux/rtnetlink.h> @@ -279,6 +280,15 @@ 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; +} + static bool mdio_bus_phy_may_suspend(struct phy_device *phydev) { struct device_driver *drv = phydev->mdio.dev.driver; @@ -288,6 +298,12 @@ static bool mdio_bus_phy_may_suspend(struct phy_device *phydev) 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_drv_wol_enabled(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 @@ -1370,6 +1386,48 @@ phy_standalone_show(struct device *dev, struct device_attribute *attr, 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. + */ +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; +} +EXPORT_SYMBOL(phy_sfp_connect_phy); + +/** + * 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. + */ +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); +} +EXPORT_SYMBOL(phy_sfp_disconnect_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 @@ -1511,6 +1569,10 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *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 @@ -1938,6 +2000,7 @@ void phy_detach(struct phy_device *phydev) if (dev) { phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; + phy_link_topo_del_phy(dev, phydev); } phydev->phylink = NULL; @@ -1975,7 +2038,6 @@ EXPORT_SYMBOL(phy_detach); int phy_suspend(struct phy_device *phydev) { - struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; struct net_device *netdev = phydev->attached_dev; const struct phy_driver *phydrv = phydev->drv; int ret; @@ -1983,8 +2045,7 @@ int phy_suspend(struct phy_device *phydev) if (phydev->suspended || !phydrv) return 0; - phy_ethtool_get_wol(phydev, &wol); - phydev->wol_enabled = wol.wolopts || + phydev->wol_enabled = phy_drv_wol_enabled(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)) @@ -2095,22 +2156,20 @@ EXPORT_SYMBOL(phy_reset_after_clk_enable); /** * 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) +static int genphy_config_advert(struct phy_device *phydev, + const unsigned long *advert) { 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); + adv = linkmode_adv_to_mii_adv_t(advert); /* Setup standard advertisement */ err = phy_modify_changed(phydev, MII_ADVERTISE, @@ -2133,7 +2192,7 @@ static int genphy_config_advert(struct phy_device *phydev) if (!(bmsr & BMSR_ESTATEN)) return changed; - adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); + adv = linkmode_adv_to_mii_ctrl1000_t(advert); err = phy_modify_changed(phydev, MII_CTRL1000, ADVERTISE_1000FULL | ADVERTISE_1000HALF, @@ -2357,6 +2416,9 @@ EXPORT_SYMBOL(genphy_check_and_restart_aneg); */ int __genphy_config_aneg(struct phy_device *phydev, bool changed) { + __ETHTOOL_DECLARE_LINK_MODE_MASK(fixed_advert); + const struct phy_setting *set; + unsigned long *advert; int err; err = genphy_c45_an_config_eee_aneg(phydev); @@ -2371,10 +2433,25 @@ int __genphy_config_aneg(struct phy_device *phydev, bool changed) else if (err) changed = true; - if (AUTONEG_ENABLE != phydev->autoneg) + 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); + + set = phy_lookup_setting(phydev->speed, phydev->duplex, + phydev->supported, true); + if (set) + linkmode_set_bit(set->bit, fixed_advert); + + advert = fixed_advert; + } - err = genphy_config_advert(phydev); + err = genphy_config_advert(phydev, advert); if (err < 0) /* error */ return err; else if (err) @@ -3330,7 +3407,7 @@ static int of_phy_led(struct phy_device *phydev, static int of_phy_leds(struct phy_device *phydev) { struct device_node *node = phydev->mdio.dev.of_node; - struct device_node *leds, *led; + struct device_node *leds; int err; if (!IS_ENABLED(CONFIG_OF_MDIO)) @@ -3343,15 +3420,16 @@ static int of_phy_leds(struct phy_device *phydev) if (!leds) return 0; - for_each_available_child_of_node(leds, led) { + for_each_available_child_of_node_scoped(leds, led) { err = of_phy_led(phydev, led); if (err) { - of_node_put(led); + of_node_put(leds); phy_leds_unregister(phydev); return err; } } + of_node_put(leds); return 0; } diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c new file mode 100644 index 000000000000..4a5d73002a1a --- /dev/null +++ b/drivers/net/phy/phy_link_topology.c @@ -0,0 +1,105 @@ +// 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<maxime.chevallier@bootlin.com> + */ + +#include <linux/phy_link_topology.h> +#include <linux/phy.h> +#include <linux/rtnetlink.h> +#include <linux/xarray.h> + +static int netdev_alloc_phy_link_topology(struct net_device *dev) +{ + struct phy_link_topology *topo; + + topo = kzalloc(sizeof(*topo), GFP_KERNEL); + 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; + + if (!topo) { + ret = netdev_alloc_phy_link_topology(dev); + if (ret) + return ret; + + topo = dev->link_topo; + } + + pdn = kzalloc(sizeof(*pdn), GFP_KERNEL); + 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) + 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/phylink.c b/drivers/net/phy/phylink.c index 51c526d227fa..4309317de3d1 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1636,6 +1636,48 @@ static int phylink_register_sfp(struct phylink *pl, } /** + * 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 phy_setting *s; + unsigned long *adv; + + if (pl->cfg_link_an_mode != MLO_AN_PHY || !state || + !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) + return -EINVAL; + + s = phy_lookup_setting(state->speed, state->duplex, + pl->supported, true); + if (!s) + return -EINVAL; + + adv = pl->link_config.advertising; + linkmode_zero(adv); + linkmode_set_bit(s->bit, adv); + 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->cur_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 @@ -3423,7 +3465,8 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) return ret; } -static void phylink_sfp_disconnect_phy(void *upstream) +static void phylink_sfp_disconnect_phy(void *upstream, + struct phy_device *phydev) { phylink_disconnect_phy(upstream); } diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index c8f83e5f78ab..105602581a03 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -770,6 +770,8 @@ static const struct sfp_upstream_ops at8031_sfp_ops = { .attach = phy_sfp_attach, .detach = phy_sfp_detach, .module_insert = at8031_sfp_insert, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, }; static int at8031_parse_dt(struct phy_device *phydev) diff --git a/drivers/net/phy/qcom/qca807x.c b/drivers/net/phy/qcom/qca807x.c index 672c6929119a..bd8a51ec0ecd 100644 --- a/drivers/net/phy/qcom/qca807x.c +++ b/drivers/net/phy/qcom/qca807x.c @@ -699,6 +699,8 @@ static const struct sfp_upstream_ops qca807x_sfp_ops = { .detach = phy_sfp_detach, .module_insert = qca807x_sfp_insert, .module_remove = qca807x_sfp_remove, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, }; static int qca807x_probe(struct phy_device *phydev) @@ -733,16 +735,6 @@ static int qca807x_probe(struct phy_device *phydev) "qcom,dac-disable-bias-current-tweak"); #if IS_ENABLED(CONFIG_GPIOLIB) - /* Make sure we don't have mixed leds node and gpio-controller - * to prevent registering leds and having gpio-controller usage - * conflicting with them. - */ - if (of_find_property(node, "leds", NULL) && - of_find_property(node, "gpio-controller", NULL)) { - phydev_err(phydev, "Invalid property detected. LEDs and gpio-controller are mutually exclusive."); - return -EINVAL; - } - /* Do not register a GPIO controller unless flagged for it */ if (of_property_read_bool(node, "gpio-controller")) { ret = qca807x_gpio(phydev); diff --git a/drivers/net/phy/qcom/qca83xx.c b/drivers/net/phy/qcom/qca83xx.c index 5d083ef0250e..a05d0df6fa16 100644 --- a/drivers/net/phy/qcom/qca83xx.c +++ b/drivers/net/phy/qcom/qca83xx.c @@ -15,7 +15,6 @@ #define QCA8327_A_PHY_ID 0x004dd033 #define QCA8327_B_PHY_ID 0x004dd034 #define QCA8337_PHY_ID 0x004dd036 -#define QCA8K_PHY_ID_MASK 0xffffffff #define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0) @@ -216,8 +215,7 @@ static int qca8327_suspend(struct phy_device *phydev) static struct phy_driver qca83xx_driver[] = { { /* QCA8337 */ - .phy_id = QCA8337_PHY_ID, - .phy_id_mask = QCA8K_PHY_ID_MASK, + PHY_ID_MATCH_EXACT(QCA8337_PHY_ID), .name = "Qualcomm Atheros 8337 internal PHY", /* PHY_GBIT_FEATURES */ .probe = qca83xx_probe, @@ -231,8 +229,7 @@ static struct phy_driver qca83xx_driver[] = { .resume = qca83xx_resume, }, { /* QCA8327-A from switch QCA8327-AL1A */ - .phy_id = QCA8327_A_PHY_ID, - .phy_id_mask = QCA8K_PHY_ID_MASK, + 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, @@ -247,8 +244,7 @@ static struct phy_driver qca83xx_driver[] = { .resume = qca83xx_resume, }, { /* QCA8327-B from switch QCA8327-BL1A */ - .phy_id = QCA8327_B_PHY_ID, - .phy_id_mask = QCA8K_PHY_ID_MASK, + 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, diff --git a/drivers/net/phy/qt2025.rs b/drivers/net/phy/qt2025.rs new file mode 100644 index 000000000000..28d8981f410b --- /dev/null +++ b/drivers/net/phy/qt2025.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) Tehuti Networks Ltd. +// Copyright (C) 2024 FUJITA Tomonori <fujita.tomonori@gmail.com> + +//! 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::c_str; +use kernel::error::code; +use kernel::firmware::Firmware; +use kernel::net::phy::{ + self, + reg::{Mmd, C45}, + DeviceId, Driver, +}; +use kernel::prelude::*; +use kernel::sizes::{SZ_16K, SZ_8K}; + +kernel::module_phy_driver! { + drivers: [PhyQT2025], + device_table: [ + DeviceId::new_with_driver::<PhyQT2025>(), + ], + name: "qt2025_phy", + author: "FUJITA Tomonori <fujita.tomonori@gmail.com>", + 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_str!("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 0x3b 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_str!("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)?; + + // TODO: sleep here until the hw becomes ready. + Ok(()) + } + + fn read_status(dev: &mut phy::Device) -> Result<u16> { + dev.genphy_read_status::<C45>() + } +} diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c index 2f44fc51848f..f13c00b5b449 100644 --- a/drivers/net/phy/sfp-bus.c +++ b/drivers/net/phy/sfp-bus.c @@ -487,7 +487,7 @@ static void sfp_unregister_bus(struct sfp_bus *bus) bus->socket_ops->stop(bus->sfp); bus->socket_ops->detach(bus->sfp); if (bus->phydev && ops && ops->disconnect_phy) - ops->disconnect_phy(bus->upstream); + ops->disconnect_phy(bus->upstream, bus->phydev); } bus->registered = false; } @@ -722,6 +722,28 @@ void sfp_bus_del_upstream(struct sfp_bus *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) { @@ -743,7 +765,7 @@ 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); + ops->disconnect_phy(bus->upstream, bus->phydev); bus->phydev = NULL; } EXPORT_SYMBOL_GPL(sfp_remove_phy); diff --git a/drivers/net/phy/vitesse.c b/drivers/net/phy/vitesse.c index 3b5fcaf0dd36..2377179de017 100644 --- a/drivers/net/phy/vitesse.c +++ b/drivers/net/phy/vitesse.c @@ -10,8 +10,10 @@ #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/phy.h> +#include <linux/bitfield.h> /* 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 @@ -60,6 +62,28 @@ /* Vitesse Extended Page Access Register */ #define MII_VSC82X4_EXT_PAGE_ACCESS 0x1f +/* 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) @@ -128,6 +152,74 @@ 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 */ @@ -137,6 +229,12 @@ static void vsc73xx_config_init(struct phy_device *phydev) /* 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) @@ -237,6 +335,75 @@ static int vsc739x_config_init(struct phy_device *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. @@ -434,32 +601,48 @@ static struct phy_driver vsc82xx_driver[] = { .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", |