From 94e86ef1b801d213dfb8543633dec86abb1a457d Mon Sep 17 00:00:00 2001 From: Grygorii Strashko Date: Mon, 8 May 2023 12:33:59 +0530 Subject: net: phy: dp83869: support mii mode when rgmii strap cfg is used The DP83869 PHY on TI's k3-am642-evm supports both MII and RGMII interfaces and is configured by default to use RGMII interface (strap). However, the board design allows switching dynamically to MII interface for testing purposes by applying different set of pinmuxes. To support switching to MII interface, update the DP83869 PHY driver to configure OP_MODE_DECODE.RGMII_MII_SEL(bit 5) properly when MII PHY interface mode is requested. Signed-off-by: Grygorii Strashko Signed-off-by: Siddharth Vadapalli Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/20230508070359.357474-1-s-vadapalli@ti.com Signed-off-by: Paolo Abeni --- drivers/net/phy/dp83869.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/dp83869.c b/drivers/net/phy/dp83869.c index 9ab5eff502b7..fa8c6fdcf301 100644 --- a/drivers/net/phy/dp83869.c +++ b/drivers/net/phy/dp83869.c @@ -692,8 +692,19 @@ static int dp83869_configure_mode(struct phy_device *phydev, /* Below init sequence for each operational mode is defined in * section 9.4.8 of the datasheet. */ + phy_ctrl_val = dp83869->mode; + if (phydev->interface == PHY_INTERFACE_MODE_MII) { + if (dp83869->mode == DP83869_100M_MEDIA_CONVERT || + dp83869->mode == DP83869_RGMII_100_BASE) { + phy_ctrl_val |= DP83869_OP_MODE_MII; + } else { + phydev_err(phydev, "selected op-mode is not valid with MII mode\n"); + return -EINVAL; + } + } + ret = phy_write_mmd(phydev, DP83869_DEVADDR, DP83869_OP_MODE, - dp83869->mode); + phy_ctrl_val); if (ret) return ret; -- cgit v1.2.3 From a7e3448086d580abadccff399316c6eb5ecdedbf Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 11 May 2023 10:21:08 -0700 Subject: net: phy: Allow drivers to always call into ->suspend() A few PHY drivers are currently attempting to not suspend the PHY when Wake-on-LAN is enabled, however that code is not currently executing at all due to an early check in phy_suspend(). This prevents PHY drivers from making an appropriate decisions and put the hardware into a low power state if desired. In order to allow the PHY drivers to opt into getting their ->suspend routine to be called, add a PHY_ALWAYS_CALL_SUSPEND bit which can be set. A boolean that tracks whether the PHY or the attached MAC has Wake-on-LAN enabled is also provided for convenience. If phydev::wol_enabled then the PHY shall not prevent its own Wake-on-LAN detection logic from working and shall not prevent the Ethernet MAC from receiving packets for matching. Reviewed-by: Simon Horman Reviewed-by: Andrew Lunn Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/phy/phy_device.c | 5 +++-- include/linux/phy.h | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 17d0d0555a79..8852b0c53114 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1860,9 +1860,10 @@ int phy_suspend(struct phy_device *phydev) if (phydev->suspended) return 0; - /* If the device has WOL enabled, we cannot suspend the PHY */ phy_ethtool_get_wol(phydev, &wol); - if (wol.wolopts || (netdev && netdev->wol_enabled)) + phydev->wol_enabled = wol.wolopts || (netdev && netdev->wol_enabled); + /* If the device has WOL enabled, we cannot suspend the PHY */ + if (phydev->wol_enabled && !(phydrv->flags & PHY_ALWAYS_CALL_SUSPEND)) return -EBUSY; if (!phydrv || !phydrv->suspend) diff --git a/include/linux/phy.h b/include/linux/phy.h index c5a0dc829714..e0df8b3c2bdb 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -86,6 +86,7 @@ extern const int phy_10gbit_features_array[1]; #define PHY_IS_INTERNAL 0x00000001 #define PHY_RST_AFTER_CLK_EN 0x00000002 #define PHY_POLL_CABLE_TEST 0x00000004 +#define PHY_ALWAYS_CALL_SUSPEND 0x00000008 #define MDIO_DEVICE_IS_PHY 0x80000000 /** @@ -548,6 +549,8 @@ struct macsec_ops; * @downshifted_rate: Set true if link speed has been downshifted. * @is_on_sfp_module: Set true if PHY is located on an SFP module. * @mac_managed_pm: Set true if MAC driver takes of suspending/resuming PHY + * @wol_enabled: Set to true if the PHY or the attached MAC have Wake-on-LAN + * enabled. * @state: State of the PHY for management purposes * @dev_flags: Device-specific flags used by the PHY driver. * @@ -644,6 +647,7 @@ struct phy_device { unsigned downshifted_rate:1; unsigned is_on_sfp_module:1; unsigned mac_managed_pm:1; + unsigned wol_enabled:1; unsigned autoneg:1; /* The most recently read link state */ -- cgit v1.2.3 From 8baddaa9d4bac939004b5058f3ade7e2bf0a6e43 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 11 May 2023 10:21:09 -0700 Subject: net: phy: broadcom: Add support for Wake-on-LAN Add support for WAKE_UCAST, WAKE_MCAST, WAKE_BCAST, WAKE_MAGIC and WAKE_MAGICSECURE. This is only supported with the BCM54210E and compatible Ethernet PHYs. Using the in-band interrupt or an out of band GPIO interrupts are supported. Broadcom PHYs will generate a Wake-on-LAN level low interrupt on LED4 as soon as one of the supported patterns is being matched. That includes generating such an interrupt even if the PHY is operated during normal modes. If WAKE_UCAST is selected, this could lead to the LED4 interrupt firing up for every packet being received which is absolutely undesirable from a performance point of view. Because the Wake-on-LAN configuration can be set long before the system is actually put to sleep, we cannot have an interrupt service routine to clear on read the interrupt status register and ensure that new packet matches will be detected. It is desirable to enable the Wake-on-LAN interrupt as late as possible during the system suspend process such that we limit the number of interrupts to be handled by the system, but also conversely feed into the Linux's system suspend way of dealing with interrupts in and around the points of no return. Reviewed-by: Simon Horman Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/phy/bcm-phy-lib.c | 212 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/bcm-phy-lib.h | 5 + drivers/net/phy/broadcom.c | 126 ++++++++++++++++++++++++- include/linux/brcmphy.h | 55 +++++++++++ 4 files changed, 395 insertions(+), 3 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c index b2c0baa51f39..27c57f6ab211 100644 --- a/drivers/net/phy/bcm-phy-lib.c +++ b/drivers/net/phy/bcm-phy-lib.c @@ -6,12 +6,14 @@ #include "bcm-phy-lib.h" #include #include +#include #include #include #include #include #include #include +#include #define MII_BCM_CHANNEL_WIDTH 0x2000 #define BCM_CL45VEN_EEE_ADV 0x3c @@ -816,6 +818,216 @@ int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb); +#define BCM54XX_WOL_SUPPORTED_MASK (WAKE_UCAST | \ + WAKE_MCAST | \ + WAKE_BCAST | \ + WAKE_MAGIC | \ + WAKE_MAGICSECURE) + +int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + struct net_device *ndev = phydev->attached_dev; + u8 da[ETH_ALEN], mask[ETH_ALEN]; + unsigned int i; + u16 ctl; + int ret; + + /* Allow a MAC driver to play through its own Wake-on-LAN + * implementation + */ + if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK) + return -EOPNOTSUPP; + + /* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's + * ethtool only supports 6, for now. + */ + BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN); + + /* Clear previous interrupts */ + ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS); + if (ret < 0) + return ret; + + ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL); + if (ret < 0) + return ret; + + ctl = ret; + + if (!wol->wolopts) { + if (phy_interrupt_is_valid(phydev)) + disable_irq_wake(phydev->irq); + + /* Leave all interrupts disabled */ + ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, + BCM54XX_WOL_ALL_INTRS); + if (ret < 0) + return ret; + + /* Disable the global Wake-on-LAN enable bit */ + ctl &= ~BCM54XX_WOL_EN; + + return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl); + } + + /* Clear the previously configured mode and mask mode for Wake-on-LAN */ + ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT); + ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT); + ctl &= ~BCM54XX_WOL_DIR_PKT_EN; + ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT); + + /* When using WAKE_MAGIC, we program the magic pattern filter to match + * the device's MAC address and we accept any MAC DA in the Ethernet + * frame. + * + * When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the + * following: + * - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match + * - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care + * - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match + * + * Note that the Broadcast MAC DA is inherently going to match the + * multicast pattern being matched. + */ + memset(mask, 0, sizeof(mask)); + + if (wol->wolopts & WAKE_MCAST) { + memset(da, 0, sizeof(da)); + memset(mask, 0xff, sizeof(mask)); + da[0] = 0x01; + mask[0] = ~da[0]; + } else { + if (wol->wolopts & WAKE_UCAST) { + ether_addr_copy(da, ndev->dev_addr); + } else if (wol->wolopts & WAKE_BCAST) { + eth_broadcast_addr(da); + } else if (wol->wolopts & WAKE_MAGICSECURE) { + ether_addr_copy(da, wol->sopass); + } else if (wol->wolopts & WAKE_MAGIC) { + memset(da, 0, sizeof(da)); + memset(mask, 0xff, sizeof(mask)); + } + } + + for (i = 0; i < ETH_ALEN / 2; i++) { + if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) { + ret = bcm_phy_write_exp(phydev, + BCM54XX_WOL_MPD_DATA1(2 - i), + ndev->dev_addr[i * 2] << 8 | + ndev->dev_addr[i * 2 + 1]); + if (ret < 0) + return ret; + } + + ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i), + da[i * 2] << 8 | da[i * 2 + 1]); + if (ret < 0) + return ret; + + ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i), + mask[i * 2] << 8 | mask[i * 2 + 1]); + if (ret) + return ret; + } + + if (wol->wolopts & WAKE_MAGICSECURE) { + ctl |= BCM54XX_WOL_SECKEY_OPT_6B << + BCM54XX_WOL_SECKEY_OPT_SHIFT; + ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT; + ctl |= BCM54XX_WOL_MASK_MODE_DA_FF << + BCM54XX_WOL_MASK_MODE_SHIFT; + } else { + if (wol->wolopts & WAKE_MAGIC) + ctl |= BCM54XX_WOL_MODE_SINGLE_MPD; + else + ctl |= BCM54XX_WOL_DIR_PKT_EN; + ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY << + BCM54XX_WOL_MASK_MODE_SHIFT; + } + + /* Globally enable Wake-on-LAN */ + ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK; + + ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl); + if (ret < 0) + return ret; + + /* Enable WOL interrupt on LED4 */ + ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL); + if (ret < 0) + return ret; + + ret |= BCM54XX_LED4_SEL_INTR; + ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret); + if (ret < 0) + return ret; + + /* Enable all Wake-on-LAN interrupt sources */ + ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0); + if (ret < 0) + return ret; + + if (phy_interrupt_is_valid(phydev)) + enable_irq_wake(phydev->irq); + + return 0; +} +EXPORT_SYMBOL_GPL(bcm_phy_set_wol); + +void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + struct net_device *ndev = phydev->attached_dev; + u8 da[ETH_ALEN]; + unsigned int i; + int ret; + u16 ctl; + + wol->supported = BCM54XX_WOL_SUPPORTED_MASK; + wol->wolopts = 0; + + ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL); + if (ret < 0) + return; + + ctl = ret; + + if (!(ctl & BCM54XX_WOL_EN)) + return; + + for (i = 0; i < sizeof(da) / 2; i++) { + ret = bcm_phy_read_exp(phydev, + BCM54XX_WOL_MPD_DATA2(2 - i)); + if (ret < 0) + return; + + da[i * 2] = ret >> 8; + da[i * 2 + 1] = ret & 0xff; + } + + if (ctl & BCM54XX_WOL_DIR_PKT_EN) { + if (is_broadcast_ether_addr(da)) + wol->wolopts |= WAKE_BCAST; + else if (is_multicast_ether_addr(da)) + wol->wolopts |= WAKE_MCAST; + else if (ether_addr_equal(da, ndev->dev_addr)) + wol->wolopts |= WAKE_UCAST; + } else { + ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK; + switch (ctl) { + case BCM54XX_WOL_MODE_SINGLE_MPD: + wol->wolopts |= WAKE_MAGIC; + break; + case BCM54XX_WOL_MODE_SINGLE_MPDSEC: + wol->wolopts |= WAKE_MAGICSECURE; + memcpy(wol->sopass, da, sizeof(da)); + break; + default: + break; + } + } +} +EXPORT_SYMBOL_GPL(bcm_phy_get_wol); + MODULE_DESCRIPTION("Broadcom PHY Library"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Broadcom Corporation"); diff --git a/drivers/net/phy/bcm-phy-lib.h b/drivers/net/phy/bcm-phy-lib.h index 729db441797a..c6fed43ec913 100644 --- a/drivers/net/phy/bcm-phy-lib.h +++ b/drivers/net/phy/bcm-phy-lib.h @@ -9,6 +9,8 @@ #include #include +struct ethtool_wolinfo; + /* 28nm only register definitions */ #define MISC_ADDR(base, channel) base, channel @@ -111,4 +113,7 @@ static inline void bcm_ptp_stop(struct bcm_ptp_private *priv) } #endif +int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); +void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); + #endif /* _LINUX_BCM_PHY_LIB_H */ diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index ad71c88c87e7..418e6bc0e998 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -14,8 +14,12 @@ #include #include #include +#include #include #include +#include +#include +#include #define BRCM_PHY_MODEL(phydev) \ ((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask) @@ -30,8 +34,17 @@ MODULE_LICENSE("GPL"); struct bcm54xx_phy_priv { u64 *stats; struct bcm_ptp_private *ptp; + int wake_irq; + bool wake_irq_enabled; }; +static bool bcm54xx_phy_can_wakeup(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + + return phy_interrupt_is_valid(phydev) || priv->wake_irq >= 0; +} + static int bcm54xx_config_clock_delay(struct phy_device *phydev) { int rc, val; @@ -413,6 +426,16 @@ static int bcm54xx_config_init(struct phy_device *phydev) bcm54xx_ptp_config_init(phydev); + /* Acknowledge any left over interrupt and charge the device for + * wake-up. + */ + err = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS); + if (err < 0) + return err; + + if (err) + pm_wakeup_event(&phydev->mdio.dev, 0); + return 0; } @@ -437,12 +460,39 @@ out: return ret; } +static int bcm54xx_set_wakeup_irq(struct phy_device *phydev, bool state) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int ret = 0; + + if (!bcm54xx_phy_can_wakeup(phydev)) + return ret; + + if (priv->wake_irq_enabled != state) { + if (state) + ret = enable_irq_wake(priv->wake_irq); + else + ret = disable_irq_wake(priv->wake_irq); + priv->wake_irq_enabled = state; + } + + return ret; +} + static int bcm54xx_suspend(struct phy_device *phydev) { - int ret; + int ret = 0; bcm54xx_ptp_stop(phydev); + /* Acknowledge any Wake-on-LAN interrupt prior to suspend */ + ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS); + if (ret < 0) + return ret; + + if (phydev->wol_enabled) + return bcm54xx_set_wakeup_irq(phydev, true); + /* We cannot use a read/modify/write here otherwise the PHY gets into * a bad state where its LEDs keep flashing, thus defeating the purpose * of low power mode. @@ -456,7 +506,13 @@ static int bcm54xx_suspend(struct phy_device *phydev) static int bcm54xx_resume(struct phy_device *phydev) { - int ret; + int ret = 0; + + if (phydev->wol_enabled) { + ret = bcm54xx_set_wakeup_irq(phydev, false); + if (ret) + return ret; + } ret = bcm54xx_iddq_set(phydev, false); if (ret < 0) @@ -801,14 +857,54 @@ static int brcm_fet_suspend(struct phy_device *phydev) return err; } +static void bcm54xx_phy_get_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + /* We cannot wake-up if we do not have a dedicated PHY interrupt line + * or an out of band GPIO descriptor for wake-up. Zeroing + * wol->supported allows the caller (MAC driver) to play through and + * offer its own Wake-on-LAN scheme if available. + */ + if (!bcm54xx_phy_can_wakeup(phydev)) { + wol->supported = 0; + return; + } + + bcm_phy_get_wol(phydev, wol); +} + +static int bcm54xx_phy_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + int ret; + + /* We cannot wake-up if we do not have a dedicated PHY interrupt line + * or an out of band GPIO descriptor for wake-up. Returning -EOPNOTSUPP + * allows the caller (MAC driver) to play through and offer its own + * Wake-on-LAN scheme if available. + */ + if (!bcm54xx_phy_can_wakeup(phydev)) + return -EOPNOTSUPP; + + ret = bcm_phy_set_wol(phydev, wol); + if (ret < 0) + return ret; + + return 0; +} + static int bcm54xx_phy_probe(struct phy_device *phydev) { struct bcm54xx_phy_priv *priv; + struct gpio_desc *wakeup_gpio; + int ret = 0; priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + priv->wake_irq = -ENXIO; + phydev->priv = priv; priv->stats = devm_kcalloc(&phydev->mdio.dev, @@ -821,7 +917,28 @@ static int bcm54xx_phy_probe(struct phy_device *phydev) if (IS_ERR(priv->ptp)) return PTR_ERR(priv->ptp); - return 0; + /* We cannot utilize the _optional variant here since we want to know + * whether the GPIO descriptor exists or not to advertise Wake-on-LAN + * support or not. + */ + wakeup_gpio = devm_gpiod_get(&phydev->mdio.dev, "wakeup", GPIOD_IN); + if (PTR_ERR(wakeup_gpio) == -EPROBE_DEFER) + return PTR_ERR(wakeup_gpio); + + if (!IS_ERR(wakeup_gpio)) { + priv->wake_irq = gpiod_to_irq(wakeup_gpio); + ret = irq_set_irq_type(priv->wake_irq, IRQ_TYPE_LEVEL_LOW); + if (ret) + return ret; + } + + /* If we do not have a main interrupt or a side-band wake-up interrupt, + * then the device cannot be marked as wake-up capable. + */ + if (!bcm54xx_phy_can_wakeup(phydev)) + return 0; + + return device_init_wakeup(&phydev->mdio.dev, true); } static void bcm54xx_get_stats(struct phy_device *phydev, @@ -894,6 +1011,7 @@ static struct phy_driver broadcom_drivers[] = { .phy_id_mask = 0xfffffff0, .name = "Broadcom BCM54210E", /* PHY_GBIT_FEATURES */ + .flags = PHY_ALWAYS_CALL_SUSPEND, .get_sset_count = bcm_phy_get_sset_count, .get_strings = bcm_phy_get_strings, .get_stats = bcm54xx_get_stats, @@ -904,6 +1022,8 @@ static struct phy_driver broadcom_drivers[] = { .link_change_notify = bcm54xx_link_change_notify, .suspend = bcm54xx_suspend, .resume = bcm54xx_resume, + .get_wol = bcm54xx_phy_get_wol, + .set_wol = bcm54xx_phy_set_wol, }, { .phy_id = PHY_ID_BCM5461, .phy_id_mask = 0xfffffff0, diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index 9e77165f3ef6..e9afbfb6d7a5 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -89,6 +89,7 @@ #define MII_BCM54XX_EXP_SEL 0x17 /* Expansion register select */ #define MII_BCM54XX_EXP_SEL_TOP 0x0d00 /* TOP_MISC expansion register select */ #define MII_BCM54XX_EXP_SEL_SSD 0x0e00 /* Secondary SerDes select */ +#define MII_BCM54XX_EXP_SEL_WOL 0x0e00 /* Wake-on-LAN expansion select register */ #define MII_BCM54XX_EXP_SEL_ER 0x0f00 /* Expansion register select */ #define MII_BCM54XX_EXP_SEL_ETC 0x0d00 /* Expansion register spare + 2k mem */ @@ -253,6 +254,9 @@ #define BCM54XX_TOP_MISC_IDDQ_SD (1 << 2) #define BCM54XX_TOP_MISC_IDDQ_SR (1 << 3) +#define BCM54XX_TOP_MISC_LED_CTL (MII_BCM54XX_EXP_SEL_TOP + 0x0C) +#define BCM54XX_LED4_SEL_INTR BIT(1) + /* * BCM5482: Secondary SerDes registers */ @@ -272,6 +276,57 @@ #define BCM54612E_EXP_SPARE0 (MII_BCM54XX_EXP_SEL_ETC + 0x34) #define BCM54612E_LED4_CLK125OUT_EN (1 << 1) + +/* Wake-on-LAN registers */ +#define BCM54XX_WOL_MAIN_CTL (MII_BCM54XX_EXP_SEL_WOL + 0x80) +#define BCM54XX_WOL_EN BIT(0) +#define BCM54XX_WOL_MODE_SINGLE_MPD 0 +#define BCM54XX_WOL_MODE_SINGLE_MPDSEC 1 +#define BCM54XX_WOL_MODE_DUAL 2 +#define BCM54XX_WOL_MODE_SHIFT 1 +#define BCM54XX_WOL_MODE_MASK 0x3 +#define BCM54XX_WOL_MP_MSB_FF_EN BIT(3) +#define BCM54XX_WOL_SECKEY_OPT_4B 0 +#define BCM54XX_WOL_SECKEY_OPT_6B 1 +#define BCM54XX_WOL_SECKEY_OPT_8B 2 +#define BCM54XX_WOL_SECKEY_OPT_SHIFT 4 +#define BCM54XX_WOL_SECKEY_OPT_MASK 0x3 +#define BCM54XX_WOL_L2_TYPE_CHK BIT(6) +#define BCM54XX_WOL_L4IPV4UDP_CHK BIT(7) +#define BCM54XX_WOL_L4IPV6UDP_CHK BIT(8) +#define BCM54XX_WOL_UDPPORT_CHK BIT(9) +#define BCM54XX_WOL_CRC_CHK BIT(10) +#define BCM54XX_WOL_SECKEY_MODE BIT(11) +#define BCM54XX_WOL_RST BIT(12) +#define BCM54XX_WOL_DIR_PKT_EN BIT(13) +#define BCM54XX_WOL_MASK_MODE_DA_FF 0 +#define BCM54XX_WOL_MASK_MODE_DA_MPD 1 +#define BCM54XX_WOL_MASK_MODE_DA_ONLY 2 +#define BCM54XX_WOL_MASK_MODE_MPD 3 +#define BCM54XX_WOL_MASK_MODE_SHIFT 14 +#define BCM54XX_WOL_MASK_MODE_MASK 0x3 + +#define BCM54XX_WOL_INNER_PROTO (MII_BCM54XX_EXP_SEL_WOL + 0x81) +#define BCM54XX_WOL_OUTER_PROTO (MII_BCM54XX_EXP_SEL_WOL + 0x82) +#define BCM54XX_WOL_OUTER_PROTO2 (MII_BCM54XX_EXP_SEL_WOL + 0x83) + +#define BCM54XX_WOL_MPD_DATA1(x) (MII_BCM54XX_EXP_SEL_WOL + 0x84 + (x)) +#define BCM54XX_WOL_MPD_DATA2(x) (MII_BCM54XX_EXP_SEL_WOL + 0x87 + (x)) +#define BCM54XX_WOL_SEC_KEY_8B (MII_BCM54XX_EXP_SEL_WOL + 0x8A) +#define BCM54XX_WOL_MASK(x) (MII_BCM54XX_EXP_SEL_WOL + 0x8B + (x)) +#define BCM54XX_SEC_KEY_STORE(x) (MII_BCM54XX_EXP_SEL_WOL + 0x8E) +#define BCM54XX_WOL_SHARED_CNT (MII_BCM54XX_EXP_SEL_WOL + 0x92) + +#define BCM54XX_WOL_INT_MASK (MII_BCM54XX_EXP_SEL_WOL + 0x93) +#define BCM54XX_WOL_PKT1 BIT(0) +#define BCM54XX_WOL_PKT2 BIT(1) +#define BCM54XX_WOL_DIR BIT(2) +#define BCM54XX_WOL_ALL_INTRS (BCM54XX_WOL_PKT1 | \ + BCM54XX_WOL_PKT2 | \ + BCM54XX_WOL_DIR) + +#define BCM54XX_WOL_INT_STATUS (MII_BCM54XX_EXP_SEL_WOL + 0x94) + /*****************************************************************************/ /* Fast Ethernet Transceiver definitions. */ /*****************************************************************************/ -- cgit v1.2.3 From a0b7955310a445fc0d45a0ac576bad8720cd6057 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Fri, 12 May 2023 17:58:37 +0100 Subject: net: phylink: constify fwnode arguments Both phylink_create() and phylink_fwnode_phy_connect() do not modify the fwnode argument that they are passed, so lets constify these. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: David S. Miller --- drivers/net/phy/phylink.c | 11 ++++++----- include/linux/phylink.h | 9 +++++---- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a4111f1be375..cf53096047e6 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -708,7 +708,7 @@ static int phylink_validate(struct phylink *pl, unsigned long *supported, } static int phylink_parse_fixedlink(struct phylink *pl, - struct fwnode_handle *fwnode) + const struct fwnode_handle *fwnode) { struct fwnode_handle *fixed_node; bool pause, asym_pause, autoneg; @@ -819,7 +819,8 @@ static int phylink_parse_fixedlink(struct phylink *pl, return 0; } -static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode) +static int phylink_parse_mode(struct phylink *pl, + const struct fwnode_handle *fwnode) { struct fwnode_handle *dn; const char *managed; @@ -1441,7 +1442,7 @@ static void phylink_fixed_poll(struct timer_list *t) static const struct sfp_upstream_ops sfp_phylink_ops; static int phylink_register_sfp(struct phylink *pl, - struct fwnode_handle *fwnode) + const struct fwnode_handle *fwnode) { struct sfp_bus *bus; int ret; @@ -1480,7 +1481,7 @@ static int phylink_register_sfp(struct phylink *pl, * must use IS_ERR() to check for errors from this function. */ struct phylink *phylink_create(struct phylink_config *config, - struct fwnode_handle *fwnode, + const struct fwnode_handle *fwnode, phy_interface_t iface, const struct phylink_mac_ops *mac_ops) { @@ -1809,7 +1810,7 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect); * Returns 0 on success or a negative errno. */ int phylink_fwnode_phy_connect(struct phylink *pl, - struct fwnode_handle *fwnode, + const struct fwnode_handle *fwnode, u32 flags) { struct fwnode_handle *phy_fwnode; diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 71755c66c162..bb782f05ad08 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -568,16 +568,17 @@ void phylink_generic_validate(struct phylink_config *config, unsigned long *supported, struct phylink_link_state *state); -struct phylink *phylink_create(struct phylink_config *, struct fwnode_handle *, - phy_interface_t iface, - const struct phylink_mac_ops *mac_ops); +struct phylink *phylink_create(struct phylink_config *, + const struct fwnode_handle *, + phy_interface_t, + const struct phylink_mac_ops *); void phylink_destroy(struct phylink *); bool phylink_expects_phy(struct phylink *pl); int phylink_connect_phy(struct phylink *, struct phy_device *); int phylink_of_phy_connect(struct phylink *, struct device_node *, u32 flags); int phylink_fwnode_phy_connect(struct phylink *pl, - struct fwnode_handle *fwnode, + const struct fwnode_handle *fwnode, u32 flags); void phylink_disconnect_phy(struct phylink *); -- cgit v1.2.3 From 418c1214741cf3ac5e420382a85c4f8a40586024 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:37:47 +0100 Subject: net: sfp: add helper to modify signal states There are a couple of locations in the code where we modify sfp->state, and then call sfp_set_state(, sfp->state) to update the outputs/soft state to control the module. Provide a helper which takes a mask and new state so that this is encapsulated in one location. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 89636dc71e48..c97a87393fdb 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -734,6 +734,12 @@ static void sfp_set_state(struct sfp *sfp, unsigned int state) sfp_soft_set_state(sfp, state); } +static void sfp_mod_state(struct sfp *sfp, unsigned int mask, unsigned int set) +{ + sfp->state = (sfp->state & ~mask) | set; + sfp_set_state(sfp, sfp->state); +} + static unsigned int sfp_check(void *buf, size_t len) { u8 *p, check; @@ -1537,16 +1543,14 @@ static void sfp_module_tx_disable(struct sfp *sfp) { dev_dbg(sfp->dev, "tx disable %u -> %u\n", sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1); - sfp->state |= SFP_F_TX_DISABLE; - sfp_set_state(sfp, sfp->state); + sfp_mod_state(sfp, SFP_F_TX_DISABLE, SFP_F_TX_DISABLE); } static void sfp_module_tx_enable(struct sfp *sfp) { dev_dbg(sfp->dev, "tx disable %u -> %u\n", sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0); - sfp->state &= ~SFP_F_TX_DISABLE; - sfp_set_state(sfp, sfp->state); + sfp_mod_state(sfp, SFP_F_TX_DISABLE, 0); } #if IS_ENABLED(CONFIG_DEBUG_FS) -- cgit v1.2.3 From d47e5a430dfd8b15739a118e4a2add5fa90347fd Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:37:52 +0100 Subject: net: sfp: move rtnl lock to cover reading state As preparation to moving st_mutex inside rtnl_lock, we need to first move the rtnl lock to cover reading the state. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index c97a87393fdb..3fc703e4dd21 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -2601,6 +2601,7 @@ static void sfp_check_state(struct sfp *sfp) unsigned int state, i, changed; mutex_lock(&sfp->st_mutex); + rtnl_lock(); state = sfp_get_state(sfp); changed = state ^ sfp->state; if (sfp->tx_fault_ignore) @@ -2616,7 +2617,6 @@ static void sfp_check_state(struct sfp *sfp) state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); sfp->state = state; - rtnl_lock(); if (changed & SFP_F_PRESENT) sfp_sm_event(sfp, state & SFP_F_PRESENT ? SFP_E_INSERT : SFP_E_REMOVE); -- cgit v1.2.3 From a9fe964e7aaeb3fc06a91c21269d0ac8b5afcea8 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:37:57 +0100 Subject: net: sfp: swap order of rtnl and st_mutex locks Swap the order of the rtnl and st_mutex locks - st_mutex is now nested beneath rtnl lock instead of rtnl being beneath st_mutex. This will allow us to hold st_mutex only while manipulating the module's hardware or software control state. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 3fc703e4dd21..ffb6c37dac96 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -2600,8 +2600,8 @@ static void sfp_check_state(struct sfp *sfp) { unsigned int state, i, changed; - mutex_lock(&sfp->st_mutex); rtnl_lock(); + mutex_lock(&sfp->st_mutex); state = sfp_get_state(sfp); changed = state ^ sfp->state; if (sfp->tx_fault_ignore) @@ -2628,8 +2628,8 @@ static void sfp_check_state(struct sfp *sfp) if (changed & SFP_F_LOS) sfp_sm_event(sfp, state & SFP_F_LOS ? SFP_E_LOS_HIGH : SFP_E_LOS_LOW); - rtnl_unlock(); mutex_unlock(&sfp->st_mutex); + rtnl_unlock(); } static irqreturn_t sfp_irq(int irq, void *data) -- cgit v1.2.3 From 97a492050aa5e15507fd7b8774e7adaf8d6e4bb5 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:38:02 +0100 Subject: net: sfp: move sm_mutex into sfp_check_state() Provide an unlocked version of sfp_sm_event() which can be used by sfp_check_state() to avoid having to keep re-taking the lock if several signals have changed state. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index ffb6c37dac96..e5cd36e3a421 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -2456,10 +2456,8 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event) } } -static void sfp_sm_event(struct sfp *sfp, unsigned int event) +static void __sfp_sm_event(struct sfp *sfp, unsigned int event) { - mutex_lock(&sfp->sm_mutex); - dev_dbg(sfp->dev, "SM: enter %s:%s:%s event %s\n", mod_state_to_str(sfp->sm_mod_state), dev_state_to_str(sfp->sm_dev_state), @@ -2474,7 +2472,12 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) mod_state_to_str(sfp->sm_mod_state), dev_state_to_str(sfp->sm_dev_state), sm_state_to_str(sfp->sm_state)); +} +static void sfp_sm_event(struct sfp *sfp, unsigned int event) +{ + mutex_lock(&sfp->sm_mutex); + __sfp_sm_event(sfp, event); mutex_unlock(&sfp->sm_mutex); } @@ -2617,17 +2620,19 @@ static void sfp_check_state(struct sfp *sfp) state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); sfp->state = state; + mutex_lock(&sfp->sm_mutex); if (changed & SFP_F_PRESENT) - sfp_sm_event(sfp, state & SFP_F_PRESENT ? - SFP_E_INSERT : SFP_E_REMOVE); + __sfp_sm_event(sfp, state & SFP_F_PRESENT ? + SFP_E_INSERT : SFP_E_REMOVE); if (changed & SFP_F_TX_FAULT) - sfp_sm_event(sfp, state & SFP_F_TX_FAULT ? - SFP_E_TX_FAULT : SFP_E_TX_CLEAR); + __sfp_sm_event(sfp, state & SFP_F_TX_FAULT ? + SFP_E_TX_FAULT : SFP_E_TX_CLEAR); if (changed & SFP_F_LOS) - sfp_sm_event(sfp, state & SFP_F_LOS ? - SFP_E_LOS_HIGH : SFP_E_LOS_LOW); + __sfp_sm_event(sfp, state & SFP_F_LOS ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW); + mutex_unlock(&sfp->sm_mutex); mutex_unlock(&sfp->st_mutex); rtnl_unlock(); } -- cgit v1.2.3 From 1974fd3bf0f04589dea1a4a76273bbc8fd5760f6 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:38:07 +0100 Subject: net: sfp: change st_mutex locking Change st_mutex's use within SFP such that it only protects the various state members, as it was originally supposed to, and isn't held while making various calls outside the driver. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 56 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 14 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index e5cd36e3a421..bf7dac9977e1 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -242,10 +242,17 @@ struct sfp { bool need_poll; + /* Access rules: + * state_hw_drive: st_mutex held + * state_hw_mask: st_mutex held + * state_soft_mask: st_mutex held + * state: st_mutex held unless reading input bits + */ struct mutex st_mutex; /* Protects state */ unsigned int state_hw_mask; unsigned int state_soft_mask; unsigned int state; + struct delayed_work poll; struct delayed_work timeout; struct mutex sm_mutex; /* Protects state machine */ @@ -692,7 +699,6 @@ static void sfp_soft_start_poll(struct sfp *sfp) const struct sfp_eeprom_id *id = &sfp->id; unsigned int mask = 0; - sfp->state_soft_mask = 0; if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE) mask |= SFP_F_TX_DISABLE; if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT) @@ -700,19 +706,26 @@ static void sfp_soft_start_poll(struct sfp *sfp) if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS) mask |= SFP_F_LOS; + mutex_lock(&sfp->st_mutex); // Poll the soft state for hardware pins we want to ignore sfp->state_soft_mask = ~sfp->state_hw_mask & mask; if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) && !sfp->need_poll) mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); + mutex_unlock(&sfp->st_mutex); } static void sfp_soft_stop_poll(struct sfp *sfp) { + mutex_lock(&sfp->st_mutex); sfp->state_soft_mask = 0; + mutex_unlock(&sfp->st_mutex); } +/* sfp_get_state() - must be called with st_mutex held, or in the + * initialisation path. + */ static unsigned int sfp_get_state(struct sfp *sfp) { unsigned int soft = sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT); @@ -725,6 +738,9 @@ static unsigned int sfp_get_state(struct sfp *sfp) return state; } +/* sfp_set_state() - must be called with st_mutex held, or in the + * initialisation path. + */ static void sfp_set_state(struct sfp *sfp, unsigned int state) { sfp->set_state(sfp, state); @@ -736,8 +752,10 @@ static void sfp_set_state(struct sfp *sfp, unsigned int state) static void sfp_mod_state(struct sfp *sfp, unsigned int mask, unsigned int set) { + mutex_lock(&sfp->st_mutex); sfp->state = (sfp->state & ~mask) | set; sfp_set_state(sfp, sfp->state); + mutex_unlock(&sfp->st_mutex); } static unsigned int sfp_check(void *buf, size_t len) @@ -1603,16 +1621,18 @@ static void sfp_debugfs_exit(struct sfp *sfp) static void sfp_module_tx_fault_reset(struct sfp *sfp) { - unsigned int state = sfp->state; - - if (state & SFP_F_TX_DISABLE) - return; + unsigned int state; - sfp_set_state(sfp, state | SFP_F_TX_DISABLE); + mutex_lock(&sfp->st_mutex); + state = sfp->state; + if (!(state & SFP_F_TX_DISABLE)) { + sfp_set_state(sfp, state | SFP_F_TX_DISABLE); - udelay(T_RESET_US); + udelay(T_RESET_US); - sfp_set_state(sfp, state); + sfp_set_state(sfp, state); + } + mutex_unlock(&sfp->st_mutex); } /* SFP state machine */ @@ -1957,6 +1977,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report) /* SFP module inserted - read I2C data */ struct sfp_eeprom_id id; bool cotsworks_sfbg; + unsigned int mask; bool cotsworks; u8 check; int ret; @@ -2096,14 +2117,13 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report) if (ret < 0) return ret; - /* Initialise state bits to use from hardware */ - sfp->state_hw_mask = SFP_F_PRESENT; + mask = SFP_F_PRESENT; if (sfp->gpio[GPIO_TX_DISABLE]) - sfp->state_hw_mask |= SFP_F_TX_DISABLE; + mask |= SFP_F_TX_DISABLE; if (sfp->gpio[GPIO_TX_FAULT]) - sfp->state_hw_mask |= SFP_F_TX_FAULT; + mask |= SFP_F_TX_FAULT; if (sfp->gpio[GPIO_LOS]) - sfp->state_hw_mask |= SFP_F_LOS; + mask |= SFP_F_LOS; sfp->module_t_start_up = T_START_UP; sfp->module_t_wait = T_WAIT; @@ -2121,8 +2141,14 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report) sfp->mdio_protocol = MDIO_I2C_NONE; sfp->quirk = sfp_lookup_quirk(&id); + + mutex_lock(&sfp->st_mutex); + /* Initialise state bits to use from hardware */ + sfp->state_hw_mask = mask; + if (sfp->quirk && sfp->quirk->fixup) sfp->quirk->fixup(sfp); + mutex_unlock(&sfp->st_mutex); return 0; } @@ -2619,6 +2645,7 @@ static void sfp_check_state(struct sfp *sfp) state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); sfp->state = state; + mutex_unlock(&sfp->st_mutex); mutex_lock(&sfp->sm_mutex); if (changed & SFP_F_PRESENT) @@ -2633,7 +2660,6 @@ static void sfp_check_state(struct sfp *sfp) __sfp_sm_event(sfp, state & SFP_F_LOS ? SFP_E_LOS_HIGH : SFP_E_LOS_LOW); mutex_unlock(&sfp->sm_mutex); - mutex_unlock(&sfp->st_mutex); rtnl_unlock(); } @@ -2652,6 +2678,8 @@ static void sfp_poll(struct work_struct *work) sfp_check_state(sfp); + // st_mutex doesn't need to be held here for state_soft_mask, + // it's unimportant if we race while reading this. if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) || sfp->need_poll) mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); -- cgit v1.2.3 From dc18582211b34bce8250ddf3cac2a2230e192120 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:38:12 +0100 Subject: net: sfp: add support for setting signalling rate Add support to the SFP layer to allow phylink to set the signalling rate for a SFP module. The rate given will be in units of kilo-baud (1000 baud). Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 24 ++++++++++++++++++++++++ drivers/net/phy/sfp-bus.c | 20 ++++++++++++++++++++ drivers/net/phy/sfp.c | 5 +++++ drivers/net/phy/sfp.h | 1 + include/linux/sfp.h | 6 ++++++ 5 files changed, 56 insertions(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index dc9a740b1ff7..f2106d17847a 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -156,6 +156,23 @@ static const char *phylink_an_mode_str(unsigned int mode) return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; } +static unsigned int phylink_interface_signal_rate(phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: /* 1.25Mbd */ + return 1250; + case PHY_INTERFACE_MODE_2500BASEX: /* 3.125Mbd */ + return 3125; + case PHY_INTERFACE_MODE_5GBASER: /* 5.15625Mbd */ + return 5156; + case PHY_INTERFACE_MODE_10GBASER: /* 10.3125Mbd */ + return 10313; + default: + return 0; + } +} + /** * phylink_interface_max_speed() - get the maximum speed of a phy interface * @interface: phy interface mode defined by &typedef phy_interface_t @@ -1025,6 +1042,7 @@ static void phylink_major_config(struct phylink *pl, bool restart, { struct phylink_pcs *pcs = NULL; bool pcs_changed = false; + unsigned int rate_kbd; int err; phylink_dbg(pl, "major config %s\n", phy_modes(state->interface)); @@ -1084,6 +1102,12 @@ static void phylink_major_config(struct phylink *pl, bool restart, ERR_PTR(err)); } + if (pl->sfp_bus) { + rate_kbd = phylink_interface_signal_rate(state->interface); + if (rate_kbd) + sfp_upstream_set_signal_rate(pl->sfp_bus, rate_kbd); + } + phylink_pcs_poll_start(pl); } diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c index 9372e5a4cadc..e8dd47bffe43 100644 --- a/drivers/net/phy/sfp-bus.c +++ b/drivers/net/phy/sfp-bus.c @@ -575,6 +575,26 @@ static void sfp_upstream_clear(struct sfp_bus *bus) bus->upstream = NULL; } +/** + * sfp_upstream_set_signal_rate() - set data signalling rate + * @bus: a pointer to the &struct sfp_bus structure for the sfp module + * @rate_kbd: signalling rate in units of 1000 baud + * + * Configure the rate select settings on the SFP module for the signalling + * rate (not the same as the data rate). + * + * Locks that may be held: + * Phylink's state_mutex + * rtnl lock + * SFP's sm_mutex + */ +void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd) +{ + if (bus->registered) + bus->socket_ops->set_signal_rate(bus->sfp, rate_kbd); +} +EXPORT_SYMBOL_GPL(sfp_upstream_set_signal_rate); + /** * sfp_bus_find_fwnode() - parse and locate the SFP bus from fwnode * @fwnode: firmware node for the parent device (MAC or PHY) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index bf7dac9977e1..34bf724c00c7 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -2527,6 +2527,10 @@ static void sfp_stop(struct sfp *sfp) sfp_sm_event(sfp, SFP_E_DEV_DOWN); } +static void sfp_set_signal_rate(struct sfp *sfp, unsigned int rate_kbd) +{ +} + static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo) { /* locking... and check module is present */ @@ -2611,6 +2615,7 @@ static const struct sfp_socket_ops sfp_module_ops = { .detach = sfp_detach, .start = sfp_start, .stop = sfp_stop, + .set_signal_rate = sfp_set_signal_rate, .module_info = sfp_module_info, .module_eeprom = sfp_module_eeprom, .module_eeprom_by_page = sfp_module_eeprom_by_page, diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h index 6cf1643214d3..c7cb50d10099 100644 --- a/drivers/net/phy/sfp.h +++ b/drivers/net/phy/sfp.h @@ -19,6 +19,7 @@ struct sfp_socket_ops { void (*detach)(struct sfp *sfp); void (*start)(struct sfp *sfp); void (*stop)(struct sfp *sfp); + void (*set_signal_rate)(struct sfp *sfp, unsigned int rate_kbd); int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo); int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee, u8 *data); diff --git a/include/linux/sfp.h b/include/linux/sfp.h index ef06a195b3c2..2f66e03e9dbd 100644 --- a/include/linux/sfp.h +++ b/include/linux/sfp.h @@ -556,6 +556,7 @@ int sfp_get_module_eeprom_by_page(struct sfp_bus *bus, struct netlink_ext_ack *extack); void sfp_upstream_start(struct sfp_bus *bus); void sfp_upstream_stop(struct sfp_bus *bus); +void sfp_upstream_set_signal_rate(struct sfp_bus *bus, unsigned int rate_kbd); void sfp_bus_put(struct sfp_bus *bus); struct sfp_bus *sfp_bus_find_fwnode(const struct fwnode_handle *fwnode); int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream, @@ -615,6 +616,11 @@ static inline void sfp_upstream_stop(struct sfp_bus *bus) { } +static inline void sfp_upstream_set_signal_rate(struct sfp_bus *bus, + unsigned int rate_kbd) +{ +} + static inline void sfp_bus_put(struct sfp_bus *bus) { } -- cgit v1.2.3 From fc082b39d0a29891ab4b54c88a40f42385103f71 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 17 May 2023 11:38:17 +0100 Subject: net: sfp: add support for rate selection Add support for parsing the rate select thresholds and switching of the RS0 and RS1 signals to the transceiver. This is complicated by various revisions of SFF-8472 and interaction of SFF-8431, SFF-8079 and INF-8074. Reviewed-by: Simon Horman Signed-off-by: Russell King (Oracle) Reviewed-by: Andrew Lunn Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 212 ++++++++++++++++++++++++++++++++++++++++++++------ include/linux/sfp.h | 8 ++ 2 files changed, 196 insertions(+), 24 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 34bf724c00c7..4799976a1609 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -24,14 +24,18 @@ enum { GPIO_LOS, GPIO_TX_FAULT, GPIO_TX_DISABLE, - GPIO_RATE_SELECT, + GPIO_RS0, + GPIO_RS1, GPIO_MAX, SFP_F_PRESENT = BIT(GPIO_MODDEF0), SFP_F_LOS = BIT(GPIO_LOS), SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT), SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE), - SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT), + SFP_F_RS0 = BIT(GPIO_RS0), + SFP_F_RS1 = BIT(GPIO_RS1), + + SFP_F_OUTPUTS = SFP_F_TX_DISABLE | SFP_F_RS0 | SFP_F_RS1, SFP_E_INSERT = 0, SFP_E_REMOVE, @@ -148,6 +152,7 @@ static const char *gpio_names[] = { "tx-fault", "tx-disable", "rate-select0", + "rate-select1", }; static const enum gpiod_flags gpio_flags[] = { @@ -156,6 +161,7 @@ static const enum gpiod_flags gpio_flags[] = { GPIOD_IN, GPIOD_ASIS, GPIOD_ASIS, + GPIOD_ASIS, }; /* t_start_up (SFF-8431) or t_init (SFF-8472) is the time required for a @@ -249,6 +255,7 @@ struct sfp { * state: st_mutex held unless reading input bits */ struct mutex st_mutex; /* Protects state */ + unsigned int state_hw_drive; unsigned int state_hw_mask; unsigned int state_soft_mask; unsigned int state; @@ -269,6 +276,10 @@ struct sfp { unsigned int module_t_start_up; unsigned int module_t_wait; + unsigned int rate_kbd; + unsigned int rs_threshold_kbd; + unsigned int rs_state_mask; + bool have_a2; bool tx_fault_ignore; @@ -319,7 +330,7 @@ static bool sfp_module_supported(const struct sfp_eeprom_id *id) static const struct sff_data sfp_data = { .gpios = SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT | - SFP_F_TX_DISABLE | SFP_F_RATE_SELECT, + SFP_F_TX_DISABLE | SFP_F_RS0 | SFP_F_RS1, .module_supported = sfp_module_supported, }; @@ -507,20 +518,37 @@ static unsigned int sff_gpio_get_state(struct sfp *sfp) static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state) { - if (state & SFP_F_PRESENT) { - /* If the module is present, drive the signals */ - if (sfp->gpio[GPIO_TX_DISABLE]) + unsigned int drive; + + if (state & SFP_F_PRESENT) + /* If the module is present, drive the requested signals */ + drive = sfp->state_hw_drive; + else + /* Otherwise, let them float to the pull-ups */ + drive = 0; + + if (sfp->gpio[GPIO_TX_DISABLE]) { + if (drive & SFP_F_TX_DISABLE) gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE], state & SFP_F_TX_DISABLE); - if (state & SFP_F_RATE_SELECT) - gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT], - state & SFP_F_RATE_SELECT); - } else { - /* Otherwise, let them float to the pull-ups */ - if (sfp->gpio[GPIO_TX_DISABLE]) + else gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]); - if (state & SFP_F_RATE_SELECT) - gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]); + } + + if (sfp->gpio[GPIO_RS0]) { + if (drive & SFP_F_RS0) + gpiod_direction_output(sfp->gpio[GPIO_RS0], + state & SFP_F_RS0); + else + gpiod_direction_input(sfp->gpio[GPIO_RS0]); + } + + if (sfp->gpio[GPIO_RS1]) { + if (drive & SFP_F_RS1) + gpiod_direction_output(sfp->gpio[GPIO_RS1], + state & SFP_F_RS1); + else + gpiod_direction_input(sfp->gpio[GPIO_RS1]); } } @@ -682,16 +710,33 @@ static unsigned int sfp_soft_get_state(struct sfp *sfp) return state & sfp->state_soft_mask; } -static void sfp_soft_set_state(struct sfp *sfp, unsigned int state) +static void sfp_soft_set_state(struct sfp *sfp, unsigned int state, + unsigned int soft) { - u8 mask = SFP_STATUS_TX_DISABLE_FORCE; + u8 mask = 0; u8 val = 0; + if (soft & SFP_F_TX_DISABLE) + mask |= SFP_STATUS_TX_DISABLE_FORCE; if (state & SFP_F_TX_DISABLE) val |= SFP_STATUS_TX_DISABLE_FORCE; + if (soft & SFP_F_RS0) + mask |= SFP_STATUS_RS0_SELECT; + if (state & SFP_F_RS0) + val |= SFP_STATUS_RS0_SELECT; + + if (mask) + sfp_modify_u8(sfp, true, SFP_STATUS, mask, val); - sfp_modify_u8(sfp, true, SFP_STATUS, mask, val); + val = mask = 0; + if (soft & SFP_F_RS1) + mask |= SFP_EXT_STATUS_RS1_SELECT; + if (state & SFP_F_RS1) + val |= SFP_EXT_STATUS_RS1_SELECT; + + if (mask) + sfp_modify_u8(sfp, true, SFP_EXT_STATUS, mask, val); } static void sfp_soft_start_poll(struct sfp *sfp) @@ -705,6 +750,8 @@ static void sfp_soft_start_poll(struct sfp *sfp) mask |= SFP_F_TX_FAULT; if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS) mask |= SFP_F_LOS; + if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RATE_SELECT) + mask |= sfp->rs_state_mask; mutex_lock(&sfp->st_mutex); // Poll the soft state for hardware pins we want to ignore @@ -743,11 +790,13 @@ static unsigned int sfp_get_state(struct sfp *sfp) */ static void sfp_set_state(struct sfp *sfp, unsigned int state) { + unsigned int soft; + sfp->set_state(sfp, state); - if (state & SFP_F_PRESENT && - sfp->state_soft_mask & SFP_F_TX_DISABLE) - sfp_soft_set_state(sfp, state); + soft = sfp->state_soft_mask & SFP_F_OUTPUTS; + if (state & SFP_F_PRESENT && soft) + sfp_soft_set_state(sfp, state, soft); } static void sfp_mod_state(struct sfp *sfp, unsigned int mask, unsigned int set) @@ -1589,10 +1638,15 @@ static int sfp_debug_state_show(struct seq_file *s, void *data) sfp->sm_fault_retries); seq_printf(s, "PHY probe remaining retries: %d\n", sfp->sm_phy_retries); + seq_printf(s, "Signalling rate: %u kBd\n", sfp->rate_kbd); + seq_printf(s, "Rate select threshold: %u kBd\n", + sfp->rs_threshold_kbd); seq_printf(s, "moddef0: %d\n", !!(sfp->state & SFP_F_PRESENT)); seq_printf(s, "rx_los: %d\n", !!(sfp->state & SFP_F_LOS)); seq_printf(s, "tx_fault: %d\n", !!(sfp->state & SFP_F_TX_FAULT)); seq_printf(s, "tx_disable: %d\n", !!(sfp->state & SFP_F_TX_DISABLE)); + seq_printf(s, "rs0: %d\n", !!(sfp->state & SFP_F_RS0)); + seq_printf(s, "rs1: %d\n", !!(sfp->state & SFP_F_RS1)); return 0; } DEFINE_SHOW_ATTRIBUTE(sfp_debug_state); @@ -1898,6 +1952,95 @@ static int sfp_sm_mod_hpower(struct sfp *sfp, bool enable) return 0; } +static void sfp_module_parse_rate_select(struct sfp *sfp) +{ + u8 rate_id; + + sfp->rs_threshold_kbd = 0; + sfp->rs_state_mask = 0; + + if (!(sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_RATE_SELECT))) + /* No support for RateSelect */ + return; + + /* Default to INF-8074 RateSelect operation. The signalling threshold + * rate is not well specified, so always select "Full Bandwidth", but + * SFF-8079 reveals that it is understood that RS0 will be low for + * 1.0625Gb/s and high for 2.125Gb/s. Choose a value half-way between. + * This method exists prior to SFF-8472. + */ + sfp->rs_state_mask = SFP_F_RS0; + sfp->rs_threshold_kbd = 1594; + + /* Parse the rate identifier, which is complicated due to history: + * SFF-8472 rev 9.5 marks this field as reserved. + * SFF-8079 references SFF-8472 rev 9.5 and defines bit 0. SFF-8472 + * compliance is not required. + * SFF-8472 rev 10.2 defines this field using values 0..4 + * SFF-8472 rev 11.0 redefines this field with bit 0 for SFF-8079 + * and even values. + */ + rate_id = sfp->id.base.rate_id; + if (rate_id == 0) + /* Unspecified */ + return; + + /* SFF-8472 rev 10.0..10.4 did not account for SFF-8079 using bit 0, + * and allocated value 3 to SFF-8431 independent tx/rx rate select. + * Convert this to a SFF-8472 rev 11.0 rate identifier. + */ + if (sfp->id.ext.sff8472_compliance >= SFP_SFF8472_COMPLIANCE_REV10_2 && + sfp->id.ext.sff8472_compliance < SFP_SFF8472_COMPLIANCE_REV11_0 && + rate_id == 3) + rate_id = SFF_RID_8431; + + if (rate_id & SFF_RID_8079) { + /* SFF-8079 RateSelect / Application Select in conjunction with + * SFF-8472 rev 9.5. SFF-8079 defines rate_id as a bitfield + * with only bit 0 used, which takes precedence over SFF-8472. + */ + if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_APP_SELECT_SFF8079)) { + /* SFF-8079 Part 1 - rate selection between Fibre + * Channel 1.0625/2.125/4.25 Gbd modes. Note that RS0 + * is high for 2125, so we have to subtract 1 to + * include it. + */ + sfp->rs_threshold_kbd = 2125 - 1; + sfp->rs_state_mask = SFP_F_RS0; + } + return; + } + + /* SFF-8472 rev 9.5 does not define the rate identifier */ + if (sfp->id.ext.sff8472_compliance <= SFP_SFF8472_COMPLIANCE_REV9_5) + return; + + /* SFF-8472 rev 11.0 defines rate_id as a numerical value which will + * always have bit 0 clear due to SFF-8079's bitfield usage of rate_id. + */ + switch (rate_id) { + case SFF_RID_8431_RX_ONLY: + sfp->rs_threshold_kbd = 4250; + sfp->rs_state_mask = SFP_F_RS0; + break; + + case SFF_RID_8431_TX_ONLY: + sfp->rs_threshold_kbd = 4250; + sfp->rs_state_mask = SFP_F_RS1; + break; + + case SFF_RID_8431: + sfp->rs_threshold_kbd = 4250; + sfp->rs_state_mask = SFP_F_RS0 | SFP_F_RS1; + break; + + case SFF_RID_10G8G: + sfp->rs_threshold_kbd = 9000; + sfp->rs_state_mask = SFP_F_RS0 | SFP_F_RS1; + break; + } +} + /* GPON modules based on Realtek RTL8672 and RTL9601C chips (e.g. V-SOL * V2801F, CarlitoxxPro CPGOS03-0490, Ubiquiti U-Fiber Instant, ...) do * not support multibyte reads from the EEPROM. Each multi-byte read @@ -2117,6 +2260,8 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report) if (ret < 0) return ret; + sfp_module_parse_rate_select(sfp); + mask = SFP_F_PRESENT; if (sfp->gpio[GPIO_TX_DISABLE]) mask |= SFP_F_TX_DISABLE; @@ -2124,6 +2269,10 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report) mask |= SFP_F_TX_FAULT; if (sfp->gpio[GPIO_LOS]) mask |= SFP_F_LOS; + if (sfp->gpio[GPIO_RS0]) + mask |= SFP_F_RS0; + if (sfp->gpio[GPIO_RS1]) + mask |= SFP_F_RS1; sfp->module_t_start_up = T_START_UP; sfp->module_t_wait = T_WAIT; @@ -2146,6 +2295,9 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report) /* Initialise state bits to use from hardware */ sfp->state_hw_mask = mask; + /* We want to drive the rate select pins that the module is using */ + sfp->state_hw_drive |= sfp->rs_state_mask; + if (sfp->quirk && sfp->quirk->fixup) sfp->quirk->fixup(sfp); mutex_unlock(&sfp->st_mutex); @@ -2162,6 +2314,7 @@ static void sfp_sm_mod_remove(struct sfp *sfp) memset(&sfp->id, 0, sizeof(sfp->id)); sfp->module_power_mW = 0; + sfp->state_hw_drive = SFP_F_TX_DISABLE; sfp->have_a2 = false; dev_info(sfp->dev, "module removed\n"); @@ -2529,6 +2682,16 @@ static void sfp_stop(struct sfp *sfp) static void sfp_set_signal_rate(struct sfp *sfp, unsigned int rate_kbd) { + unsigned int set; + + sfp->rate_kbd = rate_kbd; + + if (rate_kbd > sfp->rs_threshold_kbd) + set = sfp->rs_state_mask; + else + set = 0; + + sfp_mod_state(sfp, SFP_F_RS0 | SFP_F_RS1, set); } static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo) @@ -2648,7 +2811,7 @@ static void sfp_check_state(struct sfp *sfp) dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_names[i], !!(sfp->state & BIT(i)), !!(state & BIT(i))); - state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); + state |= sfp->state & SFP_F_OUTPUTS; sfp->state = state; mutex_unlock(&sfp->st_mutex); @@ -2790,6 +2953,7 @@ static int sfp_probe(struct platform_device *pdev) } sfp->state_hw_mask = SFP_F_PRESENT; + sfp->state_hw_drive = SFP_F_TX_DISABLE; sfp->get_state = sfp_gpio_get_state; sfp->set_state = sfp_gpio_set_state; @@ -2815,9 +2979,9 @@ static int sfp_probe(struct platform_device *pdev) */ sfp->state = sfp_get_state(sfp) | SFP_F_TX_DISABLE; - if (sfp->gpio[GPIO_RATE_SELECT] && - gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT])) - sfp->state |= SFP_F_RATE_SELECT; + if (sfp->gpio[GPIO_RS0] && + gpiod_get_value_cansleep(sfp->gpio[GPIO_RS0])) + sfp->state |= SFP_F_RS0; sfp_set_state(sfp, sfp->state); sfp_module_tx_disable(sfp); if (sfp->state & SFP_F_PRESENT) { diff --git a/include/linux/sfp.h b/include/linux/sfp.h index 2f66e03e9dbd..9346cd44814d 100644 --- a/include/linux/sfp.h +++ b/include/linux/sfp.h @@ -342,6 +342,12 @@ enum { SFP_ENCODING = 11, SFP_BR_NOMINAL = 12, SFP_RATE_ID = 13, + SFF_RID_8079 = 0x01, + SFF_RID_8431_RX_ONLY = 0x02, + SFF_RID_8431_TX_ONLY = 0x04, + SFF_RID_8431 = 0x06, + SFF_RID_10G8G = 0x0e, + SFP_LINK_LEN_SM_KM = 14, SFP_LINK_LEN_SM_100M = 15, SFP_LINK_LEN_50UM_OM2_10M = 16, @@ -465,6 +471,7 @@ enum { SFP_STATUS = 110, SFP_STATUS_TX_DISABLE = BIT(7), SFP_STATUS_TX_DISABLE_FORCE = BIT(6), + SFP_STATUS_RS0_SELECT = BIT(3), SFP_STATUS_TX_FAULT = BIT(2), SFP_STATUS_RX_LOS = BIT(1), SFP_ALARM0 = 112, @@ -496,6 +503,7 @@ enum { SFP_WARN1_RXPWR_LOW = BIT(6), SFP_EXT_STATUS = 118, + SFP_EXT_STATUS_RS1_SELECT = BIT(3), SFP_EXT_STATUS_PWRLVL_SELECT = BIT(0), SFP_VSL = 120, -- cgit v1.2.3 From 4b159f5048b90844679dad08afb3240c1957aba1 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 19 May 2023 14:03:59 +0100 Subject: net: phy: add helpers for comparing phy IDs There are several places which open code comparing PHY IDs. Provide a couple of helpers to assist with this, using a slightly simpler test than the original: - phy_id_compare() compares two arbitary PHY IDs and a mask of the significant bits in the ID. - phydev_id_compare() compares the bound phydev with the specified PHY ID, using the bound driver's mask. Signed-off-by: Russell King Reviewed-by: Simon Horman Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/phy/micrel.c | 6 +++--- drivers/net/phy/phy_device.c | 16 +++++++--------- drivers/net/phy/phylink.c | 4 ++-- include/linux/phy.h | 28 ++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 14 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 3f81bb8dac44..2094d49025a7 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -637,7 +637,7 @@ static int ksz8051_ksz8795_match_phy_device(struct phy_device *phydev, { int ret; - if ((phydev->phy_id & MICREL_PHY_ID_MASK) != PHY_ID_KSZ8051) + if (!phy_id_compare(phydev->phy_id, PHY_ID_KSZ8051, MICREL_PHY_ID_MASK)) return 0; ret = phy_read(phydev, MII_BMSR); @@ -1566,7 +1566,7 @@ static int ksz9x31_cable_test_fault_length(struct phy_device *phydev, u16 stat) * * distance to fault = (VCT_DATA - 22) * 4 / cable propagation velocity */ - if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_KSZ9131) + if (phydev_id_compare(phydev, PHY_ID_KSZ9131)) dt = clamp(dt - 22, 0, 255); return (dt * 400) / 10; @@ -1998,7 +1998,7 @@ static __always_inline int ksz886x_cable_test_fault_length(struct phy_device *ph */ dt = FIELD_GET(data_mask, status); - if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_LAN8814) + if (phydev_id_compare(phydev, PHY_ID_LAN8814)) return ((dt - 22) * 800) / 10; else return (dt * 400) / 10; diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 8852b0c53114..2cad9cc3f6b8 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -454,8 +454,7 @@ int phy_unregister_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask) fixup = list_entry(pos, struct phy_fixup, list); if ((!strcmp(fixup->bus_id, bus_id)) && - ((fixup->phy_uid & phy_uid_mask) == - (phy_uid & phy_uid_mask))) { + phy_id_compare(fixup->phy_uid, phy_uid, phy_uid_mask)) { list_del(&fixup->list); kfree(fixup); ret = 0; @@ -491,8 +490,8 @@ static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) return 0; - if ((fixup->phy_uid & fixup->phy_uid_mask) != - (phydev->phy_id & fixup->phy_uid_mask)) + if (!phy_id_compare(phydev->phy_id, fixup->phy_uid, + fixup->phy_uid_mask)) if (fixup->phy_uid != PHY_ANY_UID) return 0; @@ -539,15 +538,14 @@ static int phy_bus_match(struct device *dev, struct device_driver *drv) if (phydev->c45_ids.device_ids[i] == 0xffffffff) continue; - if ((phydrv->phy_id & phydrv->phy_id_mask) == - (phydev->c45_ids.device_ids[i] & - phydrv->phy_id_mask)) + if (phy_id_compare(phydev->c45_ids.device_ids[i], + phydrv->phy_id, phydrv->phy_id_mask)) return 1; } return 0; } else { - return (phydrv->phy_id & phydrv->phy_id_mask) == - (phydev->phy_id & phydrv->phy_id_mask); + return phy_id_compare(phydev->phy_id, phydrv->phy_id, + phydrv->phy_id_mask); } } diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index f2106d17847a..a4dd5197355a 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -3151,8 +3151,8 @@ static void phylink_sfp_link_up(void *upstream) */ static bool phylink_phy_no_inband(struct phy_device *phy) { - return phy->is_c45 && - (phy->c45_ids.device_ids[1] & 0xfffffff0) == 0xae025150; + return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1], + 0xae025150, 0xfffffff0); } static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) diff --git a/include/linux/phy.h b/include/linux/phy.h index d8cd7115c773..2da87a36200d 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -1112,6 +1112,34 @@ struct phy_driver { #define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 4) #define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 10) +/** + * phy_id_compare - compare @id1 with @id2 taking account of @mask + * @id1: first PHY ID + * @id2: second PHY ID + * @mask: the PHY ID mask, set bits are significant in matching + * + * Return true if the bits from @id1 and @id2 specified by @mask match. + * This uses an equivalent test to (@id & @mask) == (@phy_id & @mask). + */ +static inline bool phy_id_compare(u32 id1, u32 id2, u32 mask) +{ + return !((id1 ^ id2) & mask); +} + +/** + * phydev_id_compare - compare @id with the PHY's Clause 22 ID + * @phydev: the PHY device + * @id: the PHY ID to be matched + * + * Compare the @phydev clause 22 ID with the provided @id and return true or + * false depending whether it matches, using the bound driver mask. The + * @phydev must be bound to a driver. + */ +static inline bool phydev_id_compare(struct phy_device *phydev, u32 id) +{ + return phy_id_compare(id, phydev->phy_id, phydev->drv->phy_id_mask); +} + /* A Structure for boards to register fixups with the PHY Lib */ struct phy_fixup { struct list_head list; -- cgit v1.2.3 From 5859a99b52254be356d3cca2e40f7f371ef24b0a Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Sat, 20 May 2023 11:18:30 +0100 Subject: net: sfp: add support for a couple of copper multi-rate modules Add support for the Fiberstore SFP-10G-T and Walsun HXSX-ATRC-1 modules. Internally, the PCB silkscreen has what seems to be a part number of WT_502. Fiberstore use v2.2 whereas Walsun use v2.6. These modules contain a Marvell AQrate AQR113C PHY, accessible through I2C 0x51 using the "rollball" protocol. In both cases, the PHY is programmed to use 10GBASE-R with pause-mode rate adaption. Unlike the other rollball modules, these only need a four second delay before we can talk to the PHY. Signed-off-by: Russell King (Oracle) Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/E1q0JfS-006Dqc-8t@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 4799976a1609..7ddebf38ef3d 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -170,7 +170,6 @@ static const enum gpiod_flags gpio_flags[] = { * on board (for a copper SFP) time to initialise. */ #define T_WAIT msecs_to_jiffies(50) -#define T_WAIT_ROLLBALL msecs_to_jiffies(25000) #define T_START_UP msecs_to_jiffies(300) #define T_START_UP_BAD_GPON msecs_to_jiffies(60000) @@ -351,6 +350,27 @@ static void sfp_fixup_ignore_tx_fault(struct sfp *sfp) sfp->tx_fault_ignore = true; } +// For 10GBASE-T short-reach modules +static void sfp_fixup_10gbaset_30m(struct sfp *sfp) +{ + sfp->id.base.connector = SFF8024_CONNECTOR_RJ45; + sfp->id.base.extended_cc = SFF8024_ECC_10GBASE_T_SR; +} + +static void sfp_fixup_rollball_proto(struct sfp *sfp, unsigned int secs) +{ + sfp->mdio_protocol = MDIO_I2C_ROLLBALL; + sfp->module_t_wait = msecs_to_jiffies(secs * 1000); +} + +static void sfp_fixup_fs_10gt(struct sfp *sfp) +{ + sfp_fixup_10gbaset_30m(sfp); + + // These SFPs need 4 seconds before the PHY can be accessed + sfp_fixup_rollball_proto(sfp, 4); +} + static void sfp_fixup_halny_gsfp(struct sfp *sfp) { /* Ignore the TX_FAULT and LOS signals on this module. @@ -362,8 +382,8 @@ static void sfp_fixup_halny_gsfp(struct sfp *sfp) static void sfp_fixup_rollball(struct sfp *sfp) { - sfp->mdio_protocol = MDIO_I2C_ROLLBALL; - sfp->module_t_wait = T_WAIT_ROLLBALL; + // Rollball SFPs need 25 seconds before the PHY can be accessed + sfp_fixup_rollball_proto(sfp, 25); } static void sfp_fixup_rollball_cc(struct sfp *sfp) @@ -428,6 +448,10 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex, sfp_fixup_long_startup), + // Fiberstore SFP-10G-T doesn't identify as copper, and uses the + // Rollball protocol to talk to the PHY. + SFP_QUIRK_F("FS", "SFP-10G-T", sfp_fixup_fs_10gt), + SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp), // HG MXPD-483II-F 2.5G supports 2500Base-X, but incorrectly reports @@ -445,6 +469,10 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_M("UBNT", "UF-INSTANT", sfp_quirk_ubnt_uf_instant), + // Walsun HXSX-ATRC-1 doesn't identify as copper, and uses the + // Rollball protocol to talk to the PHY. + SFP_QUIRK_F("Walsun", "HXSX-ATRC-1", sfp_fixup_fs_10gt), + SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc), SFP_QUIRK_M("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g), SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc), -- cgit v1.2.3 From de5c9bf40c4582729f64f66d9cf4920d50beb897 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Sat, 20 May 2023 11:41:42 +0100 Subject: net: phylink: require supported_interfaces to be filled We have been requiring the supported_interfaces bitmap to be filled in by MAC drivers that have a mac_select_pcs() method. Now that all MAC drivers fill in the supported_interfaces bitmap, it is time to enforce this. We have already required supported_interfaces to be set in order for optical SFPs to be configured in commit f81fa96d8a6c ("net: phylink: use phy_interface_t bitmaps for optical modules"). Refuse phylink creation if supported_interfaces is empty, and remove code to deal with cases where this mask is empty. Signed-off-by: Russell King (Oracle) Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/E1q0K1u-006EIP-ET@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a4dd5197355a..093b7b6e0263 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -712,14 +712,11 @@ static int phylink_validate(struct phylink *pl, unsigned long *supported, { const unsigned long *interfaces = pl->config->supported_interfaces; - if (!phy_interface_empty(interfaces)) { - if (state->interface == PHY_INTERFACE_MODE_NA) - return phylink_validate_mask(pl, supported, state, - interfaces); + if (state->interface == PHY_INTERFACE_MODE_NA) + return phylink_validate_mask(pl, supported, state, interfaces); - if (!test_bit(state->interface, interfaces)) - return -EINVAL; - } + if (!test_bit(state->interface, interfaces)) + return -EINVAL; return phylink_validate_mac_and_pcs(pl, supported, state); } @@ -1513,19 +1510,18 @@ struct phylink *phylink_create(struct phylink_config *config, struct phylink *pl; int ret; - if (mac_ops->mac_select_pcs && - mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) != - ERR_PTR(-EOPNOTSUPP)) - using_mac_select_pcs = true; - /* Validate the supplied configuration */ - if (using_mac_select_pcs && - phy_interface_empty(config->supported_interfaces)) { + if (phy_interface_empty(config->supported_interfaces)) { dev_err(config->dev, - "phylink: error: empty supported_interfaces but mac_select_pcs() method present\n"); + "phylink: error: empty supported_interfaces\n"); return ERR_PTR(-EINVAL); } + if (mac_ops->mac_select_pcs && + mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) != + ERR_PTR(-EOPNOTSUPP)) + using_mac_select_pcs = true; + pl = kzalloc(sizeof(*pl), GFP_KERNEL); if (!pl) return ERR_PTR(-ENOMEM); -- cgit v1.2.3 From ac2e8e3cfe48439a2403b3d616fb654db313e362 Mon Sep 17 00:00:00 2001 From: Josua Mayer Date: Mon, 22 May 2023 17:52:42 +0300 Subject: net: sfp: add support for HXSX-ATRI-1 copper SFP+ module Walsun offers commercial ("C") and industrial ("I") variants of multi-rate copper SFP+ modules. Add quirk for HXSX-ATRI-1 using same parameters as the already supported commercial variant HXSX-ATRC-1. Signed-off-by: Josua Mayer Reviewed-by: Russell King (Oracle) Link: https://lore.kernel.org/r/20230522145242.30192-2-josua@solid-run.com/ Signed-off-by: Jakub Kicinski --- drivers/net/phy/sfp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 7ddebf38ef3d..d855a18308d7 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -469,9 +469,10 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_M("UBNT", "UF-INSTANT", sfp_quirk_ubnt_uf_instant), - // Walsun HXSX-ATRC-1 doesn't identify as copper, and uses the + // Walsun HXSX-ATR[CI]-1 don't identify as copper, and use the // Rollball protocol to talk to the PHY. SFP_QUIRK_F("Walsun", "HXSX-ATRC-1", sfp_fixup_fs_10gt), + SFP_QUIRK_F("Walsun", "HXSX-ATRI-1", sfp_fixup_fs_10gt), SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc), SFP_QUIRK_M("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g), -- cgit v1.2.3 From 59088b5a946ee8a6603a9a84781670cedb01c40d Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Mon, 22 May 2023 16:58:08 +0100 Subject: net: phy: avoid kernel warning dump when stopping an errored PHY When taking a network interface down (or removing a SFP module) after the PHY has encountered an error, phy_stop() complains incorrectly that it was called from HALTED state. The reason this is incorrect is that the network driver will have called phy_start() when the interface was brought up, and the fact that the PHY has a problem bears no relationship to the administrative state of the interface. Taking the interface administratively down (which calls phy_stop()) is always the right thing to do after a successful phy_start() call, whether or not the PHY has encountered an error. Signed-off-by: Russell King (Oracle) Acked-by: Florian Fainelli Signed-off-by: David S. Miller --- drivers/net/phy/phy.c | 11 +++++++---- include/linux/phy.h | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 0c0df38cd1ab..bdf00b2b2c1d 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -52,6 +52,7 @@ static const char *phy_state_to_str(enum phy_state st) PHY_STATE_STR(NOLINK) PHY_STATE_STR(CABLETEST) PHY_STATE_STR(HALTED) + PHY_STATE_STR(ERROR) } return NULL; @@ -1184,7 +1185,7 @@ void phy_stop_machine(struct phy_device *phydev) static void phy_process_error(struct phy_device *phydev) { mutex_lock(&phydev->lock); - phydev->state = PHY_HALTED; + phydev->state = PHY_ERROR; mutex_unlock(&phydev->lock); phy_trigger_machine(phydev); @@ -1198,10 +1199,10 @@ static void phy_error_precise(struct phy_device *phydev, } /** - * phy_error - enter HALTED state for this PHY device + * phy_error - enter ERROR state for this PHY device * @phydev: target phy_device struct * - * Moves the PHY to the HALTED state in response to a read + * Moves the PHY to the ERROR state in response to a read * or write error, and tells the controller the link is down. * Must not be called from interrupt context, or while the * phydev->lock is held. @@ -1326,7 +1327,8 @@ void phy_stop(struct phy_device *phydev) struct net_device *dev = phydev->attached_dev; enum phy_state old_state; - if (!phy_is_started(phydev) && phydev->state != PHY_DOWN) { + if (!phy_is_started(phydev) && phydev->state != PHY_DOWN && + phydev->state != PHY_ERROR) { WARN(1, "called from state %s\n", phy_state_to_str(phydev->state)); return; @@ -1443,6 +1445,7 @@ void phy_state_machine(struct work_struct *work) } break; case PHY_HALTED: + case PHY_ERROR: if (phydev->link) { phydev->link = 0; phy_link_down(phydev); diff --git a/include/linux/phy.h b/include/linux/phy.h index 2da87a36200d..7addde5d14c0 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -497,14 +497,17 @@ struct phy_device *mdiobus_scan_c22(struct mii_bus *bus, int addr); * Once complete, move to UP to restart the PHY. * - phy_stop aborts the running test and moves to @PHY_HALTED * - * @PHY_HALTED: PHY is up, but no polling or interrupts are done. Or - * PHY is in an error state. + * @PHY_HALTED: PHY is up, but no polling or interrupts are done. * - phy_start moves to @PHY_UP + * + * @PHY_ERROR: PHY is up, but is in an error state. + * - phy_stop moves to @PHY_HALTED */ enum phy_state { PHY_DOWN = 0, PHY_READY, PHY_HALTED, + PHY_ERROR, PHY_UP, PHY_RUNNING, PHY_NOLINK, -- cgit v1.2.3 From dc7a51411ec5381a567d02bee683c99713c411d9 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Tue, 23 May 2023 11:15:53 +0100 Subject: net: phylink: remove duplicated linkmode pause resolution Phylink had two chunks of code virtually the same for resolving the negotiated pause modes. Factor this down to one function. Reviewed-by: Andrew Lunn Signed-off-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 093b7b6e0263..d68015db0f2f 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -977,11 +977,10 @@ static void phylink_apply_manual_flow(struct phylink *pl, state->pause = pl->link_config.pause; } -static void phylink_resolve_flow(struct phylink_link_state *state) +static void phylink_resolve_an_pause(struct phylink_link_state *state) { bool tx_pause, rx_pause; - state->pause = MLO_PAUSE_NONE; if (state->duplex == DUPLEX_FULL) { linkmode_resolve_pause(state->advertising, state->lp_advertising, @@ -1193,7 +1192,8 @@ static void phylink_get_fixed_state(struct phylink *pl, else if (pl->link_gpio) state->link = !!gpiod_get_value_cansleep(pl->link_gpio); - phylink_resolve_flow(state); + state->pause = MLO_PAUSE_NONE; + phylink_resolve_an_pause(state); } static void phylink_mac_initial_config(struct phylink *pl, bool force_restart) @@ -3215,7 +3215,6 @@ static const struct sfp_upstream_ops sfp_phylink_ops = { static void phylink_decode_c37_word(struct phylink_link_state *state, uint16_t config_reg, int speed) { - bool tx_pause, rx_pause; int fd_bit; if (speed == SPEED_2500) @@ -3234,13 +3233,7 @@ static void phylink_decode_c37_word(struct phylink_link_state *state, state->link = false; } - linkmode_resolve_pause(state->advertising, state->lp_advertising, - &tx_pause, &rx_pause); - - if (tx_pause) - state->pause |= MLO_PAUSE_TX; - if (rx_pause) - state->pause |= MLO_PAUSE_RX; + phylink_resolve_an_pause(state); } static void phylink_decode_sgmii_word(struct phylink_link_state *state, -- cgit v1.2.3 From dad987484eaaa7cd7f7f7459f4aee1470d8ec8ef Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Tue, 23 May 2023 11:15:58 +0100 Subject: net: phylink: add function to resolve clause 73 negotiation Add a function to resolve clause 73 negotiation according to the priority resolution function described in clause 73.3.6. Signed-off-by: Russell King (Oracle) Reviewed-by: Andrew Lunn Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/phylink.h | 2 ++ 2 files changed, 41 insertions(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index d68015db0f2f..28417d307cf0 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -3212,6 +3212,45 @@ static const struct sfp_upstream_ops sfp_phylink_ops = { /* Helpers for MAC drivers */ +static struct { + int bit; + int speed; +} phylink_c73_priority_resolution[] = { + { ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, SPEED_100000 }, + { ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, SPEED_100000 }, + /* 100GBASE-KP4 and 100GBASE-CR10 not supported */ + { ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, SPEED_40000 }, + { ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, SPEED_40000 }, + { ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, SPEED_10000 }, + { ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, SPEED_10000 }, + /* 5GBASE-KR not supported */ + { ETHTOOL_LINK_MODE_2500baseX_Full_BIT, SPEED_2500 }, + { ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, SPEED_1000 }, +}; + +void phylink_resolve_c73(struct phylink_link_state *state) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(phylink_c73_priority_resolution); i++) { + int bit = phylink_c73_priority_resolution[i].bit; + if (linkmode_test_bit(bit, state->advertising) && + linkmode_test_bit(bit, state->lp_advertising)) + break; + } + + if (i < ARRAY_SIZE(phylink_c73_priority_resolution)) { + state->speed = phylink_c73_priority_resolution[i].speed; + state->duplex = DUPLEX_FULL; + } else { + /* negotiation failure */ + state->link = false; + } + + phylink_resolve_an_pause(state); +} +EXPORT_SYMBOL_GPL(phylink_resolve_c73); + static void phylink_decode_c37_word(struct phylink_link_state *state, uint16_t config_reg, int speed) { diff --git a/include/linux/phylink.h b/include/linux/phylink.h index bb782f05ad08..0cf07d7d11b8 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -656,6 +656,8 @@ int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode, const unsigned long *advertising); void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs); +void phylink_resolve_c73(struct phylink_link_state *state); + void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs, struct phylink_link_state *state); -- cgit v1.2.3 From 57fb54ab9f6945e204740b696bd4cee61ee04e5e Mon Sep 17 00:00:00 2001 From: David Epping Date: Tue, 23 May 2023 17:31:05 +0200 Subject: net: phy: mscc: add VSC8502 to MODULE_DEVICE_TABLE The mscc driver implements support for VSC8502, so its ID should be in the MODULE_DEVICE_TABLE for automatic loading. Signed-off-by: David Epping Fixes: d3169863310d ("net: phy: mscc: add support for VSC8502") Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc_main.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index 62bf99e45af1..bd81a4b041e5 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -2656,6 +2656,7 @@ static struct phy_driver vsc85xx_driver[] = { module_phy_driver(vsc85xx_driver); static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = { + { PHY_ID_VSC8502, 0xfffffff0, }, { PHY_ID_VSC8504, 0xfffffff0, }, { PHY_ID_VSC8514, 0xfffffff0, }, { PHY_ID_VSC8530, 0xfffffff0, }, -- cgit v1.2.3 From fb055ce4a9e3a115f5dc42011a97cf0cfc7820e4 Mon Sep 17 00:00:00 2001 From: David Epping Date: Tue, 23 May 2023 17:31:06 +0200 Subject: net: phy: mscc: add support for VSC8501 The VSC8501 PHY can use the same driver implementation as the VSC8502. Adding the PHY ID and copying the handler functions of VSC8502 is sufficient to operate it. Signed-off-by: David Epping Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc.h | 1 + drivers/net/phy/mscc/mscc_main.c | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc.h b/drivers/net/phy/mscc/mscc.h index a50235fdf7d9..79cbb2418664 100644 --- a/drivers/net/phy/mscc/mscc.h +++ b/drivers/net/phy/mscc/mscc.h @@ -276,6 +276,7 @@ enum rgmii_clock_delay { /* Microsemi PHY ID's * Code assumes lowest nibble is 0 */ +#define PHY_ID_VSC8501 0x00070530 #define PHY_ID_VSC8502 0x00070630 #define PHY_ID_VSC8504 0x000704c0 #define PHY_ID_VSC8514 0x00070670 diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index bd81a4b041e5..29fc27a16805 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -2316,6 +2316,30 @@ static int vsc85xx_probe(struct phy_device *phydev) /* Microsemi VSC85xx PHYs */ static struct phy_driver vsc85xx_driver[] = { +{ + .phy_id = PHY_ID_VSC8501, + .name = "Microsemi GE VSC8501 SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_BASIC_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc85xx_config_init, + .config_aneg = &vsc85xx_config_aneg, + .read_status = &vsc85xx_read_status, + .handle_interrupt = vsc85xx_handle_interrupt, + .config_intr = &vsc85xx_config_intr, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc85xx_probe, + .set_wol = &vsc85xx_wol_set, + .get_wol = &vsc85xx_wol_get, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, { .phy_id = PHY_ID_VSC8502, .name = "Microsemi GE VSC8502 SyncE", @@ -2656,6 +2680,7 @@ static struct phy_driver vsc85xx_driver[] = { module_phy_driver(vsc85xx_driver); static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = { + { PHY_ID_VSC8501, 0xfffffff0, }, { PHY_ID_VSC8502, 0xfffffff0, }, { PHY_ID_VSC8504, 0xfffffff0, }, { PHY_ID_VSC8514, 0xfffffff0, }, -- cgit v1.2.3 From 7df0b33d7993338a06e4039ec025bb67851ee41d Mon Sep 17 00:00:00 2001 From: David Epping Date: Tue, 23 May 2023 17:31:07 +0200 Subject: net: phy: mscc: remove unnecessary phydev locking Holding the struct phy_device (phydev) lock is unnecessary when accessing phydev->interface in the PHY driver .config_init method, which is the only place that vsc85xx_rgmii_set_skews() is called from. The phy_modify_paged() function implements required MDIO bus level locking, which can not be achieved by a phydev lock. Signed-off-by: David Epping Reviewed-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc_main.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index 29fc27a16805..0c39b3ecb1f2 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -528,8 +528,6 @@ static int vsc85xx_rgmii_set_skews(struct phy_device *phydev, u32 rgmii_cntl, u16 reg_val = 0; int rc; - mutex_lock(&phydev->lock); - if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID || phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) reg_val |= RGMII_CLK_DELAY_2_0_NS << rgmii_rx_delay_pos; @@ -542,8 +540,6 @@ static int vsc85xx_rgmii_set_skews(struct phy_device *phydev, u32 rgmii_cntl, rgmii_rx_delay_mask | rgmii_tx_delay_mask, reg_val); - mutex_unlock(&phydev->lock); - return rc; } -- cgit v1.2.3 From 71460c9ec5c743e9ffffca3c874d66267c36345e Mon Sep 17 00:00:00 2001 From: David Epping Date: Tue, 23 May 2023 17:31:08 +0200 Subject: net: phy: mscc: enable VSC8501/2 RGMII RX clock By default the VSC8501 and VSC8502 RGMII/GMII/MII RX_CLK output is disabled. To allow packet forwarding towards the MAC it needs to be enabled. For other PHYs supported by this driver the clock output is enabled by default. Fixes: d3169863310d ("net: phy: mscc: add support for VSC8502") Signed-off-by: David Epping Reviewed-by: Russell King (Oracle) Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc.h | 1 + drivers/net/phy/mscc/mscc_main.c | 54 +++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 26 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc.h b/drivers/net/phy/mscc/mscc.h index 79cbb2418664..defe5cc6d4fc 100644 --- a/drivers/net/phy/mscc/mscc.h +++ b/drivers/net/phy/mscc/mscc.h @@ -179,6 +179,7 @@ enum rgmii_clock_delay { #define VSC8502_RGMII_CNTL 20 #define VSC8502_RGMII_RX_DELAY_MASK 0x0070 #define VSC8502_RGMII_TX_DELAY_MASK 0x0007 +#define VSC8502_RGMII_RX_CLK_DISABLE 0x0800 #define MSCC_PHY_WOL_LOWER_MAC_ADDR 21 #define MSCC_PHY_WOL_MID_MAC_ADDR 22 diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index 0c39b3ecb1f2..28df8a2e4230 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -519,14 +519,27 @@ out_unlock: * * 2.0 ns (which causes the data to be sampled at exactly half way between * clock transitions at 1000 Mbps) if delays should be enabled */ -static int vsc85xx_rgmii_set_skews(struct phy_device *phydev, u32 rgmii_cntl, - u16 rgmii_rx_delay_mask, - u16 rgmii_tx_delay_mask) +static int vsc85xx_update_rgmii_cntl(struct phy_device *phydev, u32 rgmii_cntl, + u16 rgmii_rx_delay_mask, + u16 rgmii_tx_delay_mask) { u16 rgmii_rx_delay_pos = ffs(rgmii_rx_delay_mask) - 1; u16 rgmii_tx_delay_pos = ffs(rgmii_tx_delay_mask) - 1; u16 reg_val = 0; - int rc; + u16 mask = 0; + int rc = 0; + + /* For traffic to pass, the VSC8502 family needs the RX_CLK disable bit + * to be unset for all PHY modes, so do that as part of the paged + * register modification. + * For some family members (like VSC8530/31/40/41) this bit is reserved + * and read-only, and the RX clock is enabled by default. + */ + if (rgmii_cntl == VSC8502_RGMII_CNTL) + mask |= VSC8502_RGMII_RX_CLK_DISABLE; + + if (phy_interface_is_rgmii(phydev)) + mask |= rgmii_rx_delay_mask | rgmii_tx_delay_mask; if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID || phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) @@ -535,29 +548,20 @@ static int vsc85xx_rgmii_set_skews(struct phy_device *phydev, u32 rgmii_cntl, phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) reg_val |= RGMII_CLK_DELAY_2_0_NS << rgmii_tx_delay_pos; - rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED_2, - rgmii_cntl, - rgmii_rx_delay_mask | rgmii_tx_delay_mask, - reg_val); + if (mask) + rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED_2, + rgmii_cntl, mask, reg_val); return rc; } static int vsc85xx_default_config(struct phy_device *phydev) { - int rc; - phydev->mdix_ctrl = ETH_TP_MDI_AUTO; - if (phy_interface_mode_is_rgmii(phydev->interface)) { - rc = vsc85xx_rgmii_set_skews(phydev, VSC8502_RGMII_CNTL, - VSC8502_RGMII_RX_DELAY_MASK, - VSC8502_RGMII_TX_DELAY_MASK); - if (rc) - return rc; - } - - return 0; + return vsc85xx_update_rgmii_cntl(phydev, VSC8502_RGMII_CNTL, + VSC8502_RGMII_RX_DELAY_MASK, + VSC8502_RGMII_TX_DELAY_MASK); } static int vsc85xx_get_tunable(struct phy_device *phydev, @@ -1754,13 +1758,11 @@ static int vsc8584_config_init(struct phy_device *phydev) if (ret) return ret; - if (phy_interface_is_rgmii(phydev)) { - ret = vsc85xx_rgmii_set_skews(phydev, VSC8572_RGMII_CNTL, - VSC8572_RGMII_RX_DELAY_MASK, - VSC8572_RGMII_TX_DELAY_MASK); - if (ret) - return ret; - } + ret = vsc85xx_update_rgmii_cntl(phydev, VSC8572_RGMII_CNTL, + VSC8572_RGMII_RX_DELAY_MASK, + VSC8572_RGMII_TX_DELAY_MASK); + if (ret) + return ret; ret = genphy_soft_reset(phydev); if (ret) -- cgit v1.2.3 From ae4899bb486f73e6d3d55a82b40482b2c5529a98 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Tue, 23 May 2023 16:31:50 +0100 Subject: net: phylink: provide phylink_pcs_config() and phylink_pcs_link_up() Add two helper functions for calling PCS methods. phylink_pcs_config() allows us to handle PCS configuration specifics in one location, rather than the two call sites. phylink_pcs_link_up() gives us consistency. Signed-off-by: Russell King (Oracle) Link: https://lore.kernel.org/r/E1q1TzK-007Exd-Rs@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 53 +++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 20 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 28417d307cf0..508434fd4da8 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -992,6 +992,25 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state) } } +static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + const struct phylink_link_state *state, + bool permit_pause_to_mac) +{ + if (!pcs) + return 0; + + return pcs->ops->pcs_config(pcs, mode, state->interface, + state->advertising, permit_pause_to_mac); +} + +static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, int speed, + int duplex) +{ + if (pcs && pcs->ops->pcs_link_up) + pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex); +} + static void phylink_pcs_poll_stop(struct phylink *pl) { if (pl->cfg_link_an_mode == MLO_AN_INBAND) @@ -1075,18 +1094,15 @@ static void phylink_major_config(struct phylink *pl, bool restart, phylink_mac_config(pl, state); - if (pl->pcs) { - err = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode, - state->interface, - state->advertising, - !!(pl->link_config.pause & - MLO_PAUSE_AN)); - if (err < 0) - phylink_err(pl, "pcs_config failed: %pe\n", - ERR_PTR(err)); - if (err > 0) - restart = true; - } + err = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, state, + !!(pl->link_config.pause & + MLO_PAUSE_AN)); + if (err < 0) + phylink_err(pl, "pcs_config failed: %pe\n", + ERR_PTR(err)); + else if (err > 0) + restart = true; + if (restart) phylink_mac_pcs_an_restart(pl); @@ -1137,11 +1153,9 @@ static int phylink_change_inband_advert(struct phylink *pl) * restart negotiation if the pcs_config() helper indicates that * the programmed advertisement has changed. */ - ret = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode, - pl->link_config.interface, - pl->link_config.advertising, - !!(pl->link_config.pause & - MLO_PAUSE_AN)); + ret = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, + &pl->link_config, + !!(pl->link_config.pause & MLO_PAUSE_AN)); if (ret < 0) return ret; @@ -1273,9 +1287,8 @@ static void phylink_link_up(struct phylink *pl, pl->cur_interface = link_state.interface; - if (pl->pcs && pl->pcs->ops->pcs_link_up) - pl->pcs->ops->pcs_link_up(pl->pcs, pl->cur_link_an_mode, - pl->cur_interface, speed, duplex); + phylink_pcs_link_up(pl->pcs, pl->cur_link_an_mode, pl->cur_interface, + speed, duplex); pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode, pl->cur_interface, speed, duplex, -- cgit v1.2.3 From 4781e965e655b0f1736856908f861939dac79b4e Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 25 May 2023 10:59:15 -0700 Subject: net: phy: broadcom: Register dummy IRQ handler In order to have our interrupt descriptor fully setup and in particular the action, ensure that we register a full fledged interrupt handler. This also allow us to set the interrupt polarity and flow through the same call. This is specifically necessary for kernel/irq/pm.c::suspend_device_irq to set the interrupt descriptor to the IRQD_WAKEUP_ARMED state and enable the interrupt for wake-up since it was still in a disabled state. Without an interrupt descriptor we would have ran into cases where the wake-up interrupt is not capable of waking up the system, specifically if we resumed the system ACPI S5 using the Ethernet PHY. In that case the Ethernet PHY interrupt would be pending by the time the kernel booted, which it would acknowledge but then we could never use it as a wake-up source again. Fixes: 8baddaa9d4ba ("net: phy: broadcom: Add support for Wake-on-LAN") Suggested-by: Doug Berger Debugged-by: Doug Berger Signed-off-by: Florian Fainelli Reviewed-by: Simon Horman Signed-off-by: David S. Miller --- drivers/net/phy/bcm-phy-lib.c | 6 ++++++ drivers/net/phy/bcm-phy-lib.h | 2 ++ drivers/net/phy/broadcom.c | 9 ++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c index 27c57f6ab211..5603d0a9ce96 100644 --- a/drivers/net/phy/bcm-phy-lib.c +++ b/drivers/net/phy/bcm-phy-lib.c @@ -1028,6 +1028,12 @@ void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) } EXPORT_SYMBOL_GPL(bcm_phy_get_wol); +irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(bcm_phy_wol_isr); + MODULE_DESCRIPTION("Broadcom PHY Library"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Broadcom Corporation"); diff --git a/drivers/net/phy/bcm-phy-lib.h b/drivers/net/phy/bcm-phy-lib.h index c6fed43ec913..2f30ce0cab0e 100644 --- a/drivers/net/phy/bcm-phy-lib.h +++ b/drivers/net/phy/bcm-phy-lib.h @@ -8,6 +8,7 @@ #include #include +#include struct ethtool_wolinfo; @@ -115,5 +116,6 @@ static inline void bcm_ptp_stop(struct bcm_ptp_private *priv) int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); +irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id); #endif /* _LINUX_BCM_PHY_LIB_H */ diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 418e6bc0e998..822c8b01dc53 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -927,7 +927,14 @@ static int bcm54xx_phy_probe(struct phy_device *phydev) if (!IS_ERR(wakeup_gpio)) { priv->wake_irq = gpiod_to_irq(wakeup_gpio); - ret = irq_set_irq_type(priv->wake_irq, IRQ_TYPE_LEVEL_LOW); + + /* Dummy interrupt handler which is not enabled but is provided + * in order for the interrupt descriptor to be fully set-up. + */ + ret = devm_request_irq(&phydev->mdio.dev, priv->wake_irq, + bcm_phy_wol_isr, + IRQF_TRIGGER_LOW | IRQF_NO_AUTOEN, + dev_name(&phydev->mdio.dev), phydev); if (ret) return ret; } -- cgit v1.2.3 From ca33db4a86023f1158dc2b3587ca3c90d2a0705e Mon Sep 17 00:00:00 2001 From: Parthiban Veerasooran Date: Fri, 26 May 2023 20:53:43 +0530 Subject: net: phy: microchip_t1s: modify driver description to be more generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove LAN867X from the driver description as this driver is common for all the Microchip 10BASE-T1S PHYs. Reviewed-by: Ramón Nordin Rodriguez Reviewed-by: Andrew Lunn Signed-off-by: Parthiban Veerasooran Signed-off-by: Paolo Abeni --- drivers/net/phy/Kconfig | 2 +- drivers/net/phy/microchip_t1s.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 93b8efc79227..f6829d1bcf42 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -243,7 +243,7 @@ config MICREL_PHY Supports the KSZ9021, VSC8201, KS8001 PHYs. config MICROCHIP_T1S_PHY - tristate "Microchip 10BASE-T1S Ethernet PHY" + tristate "Microchip 10BASE-T1S Ethernet PHYs" help Currently supports the LAN8670, LAN8671, LAN8672 diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index 094967b3c111..a42a6bb6e3bd 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Driver for Microchip 10BASE-T1S LAN867X PHY + * Driver for Microchip 10BASE-T1S PHYs * * Support: Microchip Phys: * lan8670, lan8671, lan8672 @@ -111,7 +111,7 @@ static int lan867x_read_status(struct phy_device *phydev) return 0; } -static struct phy_driver lan867x_driver[] = { +static struct phy_driver microchip_t1s_driver[] = { { PHY_ID_MATCH_MODEL(PHY_ID_LAN867X), .name = "LAN867X", @@ -124,7 +124,7 @@ static struct phy_driver lan867x_driver[] = { } }; -module_phy_driver(lan867x_driver); +module_phy_driver(microchip_t1s_driver); static struct mdio_device_id __maybe_unused tbl[] = { { PHY_ID_MATCH_MODEL(PHY_ID_LAN867X) }, @@ -133,6 +133,6 @@ static struct mdio_device_id __maybe_unused tbl[] = { MODULE_DEVICE_TABLE(mdio, tbl); -MODULE_DESCRIPTION("Microchip 10BASE-T1S lan867x Phy driver"); +MODULE_DESCRIPTION("Microchip 10BASE-T1S PHYs driver"); MODULE_AUTHOR("Ramón Nordin Rodriguez"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 221a5344806c3b8c2b172777b1b59f65228dbd8a Mon Sep 17 00:00:00 2001 From: Parthiban Veerasooran Date: Fri, 26 May 2023 20:53:44 +0530 Subject: net: phy: microchip_t1s: replace read-modify-write code with phy_modify_mmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace read-modify-write code in the lan867x_config_init function to avoid handling data type mismatch and to simplify the code. Reviewed-by: Andrew Lunn Signed-off-by: Parthiban Veerasooran Reviewed-by: Ramón Nordin Rodriguez Signed-off-by: Paolo Abeni --- drivers/net/phy/microchip_t1s.c | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index a42a6bb6e3bd..fd27e94c9ee5 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -31,19 +31,19 @@ * W 0x1F 0x0099 0x7F80 ------ */ -static const int lan867x_fixup_registers[12] = { +static const u32 lan867x_fixup_registers[12] = { 0x00D0, 0x00D1, 0x0084, 0x0085, 0x008A, 0x0087, 0x0088, 0x008B, 0x0080, 0x00F1, 0x0096, 0x0099, }; -static const int lan867x_fixup_values[12] = { +static const u16 lan867x_fixup_values[12] = { 0x0002, 0x0000, 0x3380, 0x0006, 0xC000, 0x801C, 0x033F, 0x0404, 0x0600, 0x2400, 0x2000, 0x7F80, }; -static const int lan867x_fixup_masks[12] = { +static const u16 lan867x_fixup_masks[12] = { 0x0E03, 0x0300, 0xFFC0, 0x000F, 0xF800, 0x801C, 0x1FFF, 0xFFFF, 0x0600, 0x7F00, 0x2000, 0xFFFF, @@ -51,35 +51,20 @@ static const int lan867x_fixup_masks[12] = { static int lan867x_config_init(struct phy_device *phydev) { - /* HW quirk: Microchip states in the application note (AN1699) for the phy - * that a set of read-modify-write (rmw) operations has to be performed - * on a set of seemingly magic registers. - * The result of these operations is just described as 'optimal performance' - * Microchip gives no explanation as to what these mmd regs do, - * in fact they are marked as reserved in the datasheet. - * It is unclear if phy_modify_mmd would be safe to use or if a write - * really has to happen to each register. - * In order to exactly conform to what is stated in the AN phy_write_mmd is - * used, which might then write the same value back as read + modified. - */ - - int reg_value; int err; - int reg; - /* Read-Modified Write Pseudocode (from AN1699) - * current_val = read_register(mmd, addr) // Read current register value - * new_val = current_val AND (NOT mask) // Clear bit fields to be written - * new_val = new_val OR value // Set bits - * write_register(mmd, addr, new_val) // Write back updated register value + /* Reference to AN1699 + * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8670-1-2-config-60001699.pdf + * AN1699 says Read, Modify, Write, but the Write is not required if the + * register already has the required value. So it is safe to use + * phy_modify_mmd here. */ for (int i = 0; i < ARRAY_SIZE(lan867x_fixup_registers); i++) { - reg = lan867x_fixup_registers[i]; - reg_value = phy_read_mmd(phydev, MDIO_MMD_VEND2, reg); - reg_value &= ~lan867x_fixup_masks[i]; - reg_value |= lan867x_fixup_values[i]; - err = phy_write_mmd(phydev, MDIO_MMD_VEND2, reg, reg_value); - if (err != 0) + err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, + lan867x_fixup_registers[i], + lan867x_fixup_masks[i], + lan867x_fixup_values[i]); + if (err) return err; } -- cgit v1.2.3 From 6f12765ecad399d3cc5f58fcaf7f3c69c275326c Mon Sep 17 00:00:00 2001 From: Parthiban Veerasooran Date: Fri, 26 May 2023 20:53:45 +0530 Subject: net: phy: microchip_t1s: update LAN867x PHY supported revision number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per AN1699, the initial configuration in the driver applies to LAN867x Rev.B1 hardware revision. 0x0007C160 (Rev.A0) and 0x0007C161 (Rev.B0) never released to production and hence they don't need to be supported. Reviewed-by: Ramón Nordin Rodriguez Reviewed-by: Andrew Lunn Signed-off-by: Parthiban Veerasooran Signed-off-by: Paolo Abeni --- drivers/net/phy/Kconfig | 2 +- drivers/net/phy/microchip_t1s.c | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index f6829d1bcf42..47596ada3183 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -245,7 +245,7 @@ config MICREL_PHY config MICROCHIP_T1S_PHY tristate "Microchip 10BASE-T1S Ethernet PHYs" help - Currently supports the LAN8670, LAN8671, LAN8672 + Currently supports the LAN8670/1/2 Rev.B1 config MICROCHIP_PHY tristate "Microchip PHYs" diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index fd27e94c9ee5..7abecad28bf1 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -3,14 +3,14 @@ * Driver for Microchip 10BASE-T1S PHYs * * Support: Microchip Phys: - * lan8670, lan8671, lan8672 + * lan8670/1/2 Rev.B1 */ #include #include #include -#define PHY_ID_LAN867X 0x0007C160 +#define PHY_ID_LAN867X_REVB1 0x0007C162 #define LAN867X_REG_IRQ_1_CTL 0x001C #define LAN867X_REG_IRQ_2_CTL 0x001D @@ -31,25 +31,25 @@ * W 0x1F 0x0099 0x7F80 ------ */ -static const u32 lan867x_fixup_registers[12] = { +static const u32 lan867x_revb1_fixup_registers[12] = { 0x00D0, 0x00D1, 0x0084, 0x0085, 0x008A, 0x0087, 0x0088, 0x008B, 0x0080, 0x00F1, 0x0096, 0x0099, }; -static const u16 lan867x_fixup_values[12] = { +static const u16 lan867x_revb1_fixup_values[12] = { 0x0002, 0x0000, 0x3380, 0x0006, 0xC000, 0x801C, 0x033F, 0x0404, 0x0600, 0x2400, 0x2000, 0x7F80, }; -static const u16 lan867x_fixup_masks[12] = { +static const u16 lan867x_revb1_fixup_masks[12] = { 0x0E03, 0x0300, 0xFFC0, 0x000F, 0xF800, 0x801C, 0x1FFF, 0xFFFF, 0x0600, 0x7F00, 0x2000, 0xFFFF, }; -static int lan867x_config_init(struct phy_device *phydev) +static int lan867x_revb1_config_init(struct phy_device *phydev) { int err; @@ -59,11 +59,11 @@ static int lan867x_config_init(struct phy_device *phydev) * register already has the required value. So it is safe to use * phy_modify_mmd here. */ - for (int i = 0; i < ARRAY_SIZE(lan867x_fixup_registers); i++) { + for (int i = 0; i < ARRAY_SIZE(lan867x_revb1_fixup_registers); i++) { err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, - lan867x_fixup_registers[i], - lan867x_fixup_masks[i], - lan867x_fixup_values[i]); + lan867x_revb1_fixup_registers[i], + lan867x_revb1_fixup_masks[i], + lan867x_revb1_fixup_values[i]); if (err) return err; } @@ -98,10 +98,10 @@ static int lan867x_read_status(struct phy_device *phydev) static struct phy_driver microchip_t1s_driver[] = { { - PHY_ID_MATCH_MODEL(PHY_ID_LAN867X), - .name = "LAN867X", + PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1), + .name = "LAN867X Rev.B1", .features = PHY_BASIC_T1S_P2MP_FEATURES, - .config_init = lan867x_config_init, + .config_init = lan867x_revb1_config_init, .read_status = lan867x_read_status, .get_plca_cfg = genphy_c45_plca_get_cfg, .set_plca_cfg = genphy_c45_plca_set_cfg, @@ -112,7 +112,7 @@ static struct phy_driver microchip_t1s_driver[] = { module_phy_driver(microchip_t1s_driver); static struct mdio_device_id __maybe_unused tbl[] = { - { PHY_ID_MATCH_MODEL(PHY_ID_LAN867X) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) }, { } }; -- cgit v1.2.3 From 1d7650b8ce6041be4ea0436ad7a69c057b4d22f8 Mon Sep 17 00:00:00 2001 From: Parthiban Veerasooran Date: Fri, 26 May 2023 20:53:46 +0530 Subject: net: phy: microchip_t1s: fix reset complete status handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per the datasheet DS-LAN8670-1-2-60001573C.pdf, the Reset Complete status bit in the STS2 register has to be checked before proceeding to the initial configuration. Reading STS2 register will also clear the Reset Complete interrupt which is non-maskable. Reviewed-by: Andrew Lunn Signed-off-by: Parthiban Veerasooran Reviewed-by: Ramón Nordin Rodriguez Tested-by: Ramón Nordin Rodriguez Signed-off-by: Paolo Abeni --- drivers/net/phy/microchip_t1s.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index 7abecad28bf1..0ecef87e5882 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -14,6 +14,9 @@ #define LAN867X_REG_IRQ_1_CTL 0x001C #define LAN867X_REG_IRQ_2_CTL 0x001D +#define LAN867X_REG_STS2 0x0019 + +#define LAN867x_RESET_COMPLETE_STS BIT(11) /* The arrays below are pulled from the following table from AN1699 * Access MMD Address Value Mask @@ -53,6 +56,24 @@ static int lan867x_revb1_config_init(struct phy_device *phydev) { int err; + /* The chip completes a reset in 3us, we might get here earlier than + * that, as an added margin we'll conditionally sleep 5us. + */ + err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2); + if (err < 0) + return err; + + if (!(err & LAN867x_RESET_COMPLETE_STS)) { + udelay(5); + err = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_STS2); + if (err < 0) + return err; + if (!(err & LAN867x_RESET_COMPLETE_STS)) { + phydev_err(phydev, "PHY reset failed\n"); + return -ENODEV; + } + } + /* Reference to AN1699 * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8670-1-2-config-60001699.pdf * AN1699 says Read, Modify, Write, but the Write is not required if the -- cgit v1.2.3 From b4010beb347d63acd5715cd66eb791988128b7b8 Mon Sep 17 00:00:00 2001 From: Parthiban Veerasooran Date: Fri, 26 May 2023 20:53:47 +0530 Subject: net: phy: microchip_t1s: remove unnecessary interrupts disabling code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By default, except Reset Complete interrupt in the Interrupt Mask 2 Register all other interrupts are disabled/masked. As Reset Complete status is already handled, it doesn't make sense to disable it. Reviewed-by: Ramón Nordin Rodriguez Tested-by: Ramón Nordin Rodriguez Reviewed-by: Andrew Lunn Signed-off-by: Parthiban Veerasooran Signed-off-by: Paolo Abeni --- drivers/net/phy/microchip_t1s.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index 0ecef87e5882..bcfcec56a6c7 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -12,8 +12,6 @@ #define PHY_ID_LAN867X_REVB1 0x0007C162 -#define LAN867X_REG_IRQ_1_CTL 0x001C -#define LAN867X_REG_IRQ_2_CTL 0x001D #define LAN867X_REG_STS2 0x0019 #define LAN867x_RESET_COMPLETE_STS BIT(11) @@ -89,17 +87,7 @@ static int lan867x_revb1_config_init(struct phy_device *phydev) return err; } - /* None of the interrupts in the lan867x phy seem relevant. - * Other phys inspect the link status and call phy_trigger_machine - * in the interrupt handler. - * This phy does not support link status, and thus has no interrupt - * for it either. - * So we'll just disable all interrupts on the chip. - */ - err = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_IRQ_1_CTL, 0xFFFF); - if (err != 0) - return err; - return phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN867X_REG_IRQ_2_CTL, 0xFFFF); + return 0; } static int lan867x_read_status(struct phy_device *phydev) -- cgit v1.2.3 From 972c6d8346333437ae784271e74129b3f0583248 Mon Sep 17 00:00:00 2001 From: Parthiban Veerasooran Date: Fri, 26 May 2023 20:53:48 +0530 Subject: net: phy: microchip_t1s: add support for Microchip LAN865x Rev.B0 PHYs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the Microchip LAN865x Rev.B0 10BASE-T1S Internal PHYs (LAN8650/1). The LAN865x combines a Media Access Controller (MAC) and an internal 10BASE-T1S Ethernet PHY to access 10BASE‑T1S networks. As LAN867X and LAN865X are using the same function for the read_status, rename the function as lan86xx_read_status. Reviewed-by: Andrew Lunn Signed-off-by: Parthiban Veerasooran Reviewed-by: Ramón Nordin Rodriguez Signed-off-by: Paolo Abeni --- drivers/net/phy/Kconfig | 3 +- drivers/net/phy/microchip_t1s.c | 180 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 4 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 47596ada3183..059bd06a8cce 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -245,7 +245,8 @@ config MICREL_PHY config MICROCHIP_T1S_PHY tristate "Microchip 10BASE-T1S Ethernet PHYs" help - Currently supports the LAN8670/1/2 Rev.B1 + Currently supports the LAN8670/1/2 Rev.B1 and LAN8650/1 Rev.B0 Internal + PHYs. config MICROCHIP_PHY tristate "Microchip PHYs" diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index bcfcec56a6c7..534ca7d1b061 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -4,6 +4,7 @@ * * Support: Microchip Phys: * lan8670/1/2 Rev.B1 + * lan8650/1 Rev.B0 Internal PHYs */ #include @@ -11,11 +12,19 @@ #include #define PHY_ID_LAN867X_REVB1 0x0007C162 +#define PHY_ID_LAN865X_REVB0 0x0007C1B3 #define LAN867X_REG_STS2 0x0019 #define LAN867x_RESET_COMPLETE_STS BIT(11) +#define LAN865X_REG_CFGPARAM_ADDR 0x00D8 +#define LAN865X_REG_CFGPARAM_DATA 0x00D9 +#define LAN865X_REG_CFGPARAM_CTRL 0x00DA +#define LAN865X_REG_STS2 0x0019 + +#define LAN865X_CFGPARAM_READ_ENABLE BIT(1) + /* The arrays below are pulled from the following table from AN1699 * Access MMD Address Value Mask * RMW 0x1F 0x00D0 0x0002 0x0E03 @@ -50,6 +59,160 @@ static const u16 lan867x_revb1_fixup_masks[12] = { 0x0600, 0x7F00, 0x2000, 0xFFFF, }; +/* LAN865x Rev.B0 configuration parameters from AN1760 */ +static const u32 lan865x_revb0_fixup_registers[28] = { + 0x0091, 0x0081, 0x0043, 0x0044, + 0x0045, 0x0053, 0x0054, 0x0055, + 0x0040, 0x0050, 0x00D0, 0x00E9, + 0x00F5, 0x00F4, 0x00F8, 0x00F9, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, + 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, +}; + +static const u16 lan865x_revb0_fixup_values[28] = { + 0x9660, 0x00C0, 0x00FF, 0xFFFF, + 0x0000, 0x00FF, 0xFFFF, 0x0000, + 0x0002, 0x0002, 0x5F21, 0x9E50, + 0x1CF8, 0xC020, 0x9B00, 0x4E53, + 0x0103, 0x0910, 0x1D26, 0x002A, + 0x0103, 0x070D, 0x1720, 0x0027, + 0x0509, 0x0E13, 0x1C25, 0x002B, +}; + +static const u16 lan865x_revb0_fixup_cfg_regs[5] = { + 0x0084, 0x008A, 0x00AD, 0x00AE, 0x00AF +}; + +/* Pulled from AN1760 describing 'indirect read' + * + * write_register(0x4, 0x00D8, addr) + * write_register(0x4, 0x00DA, 0x2) + * return (int8)(read_register(0x4, 0x00D9)) + * + * 0x4 refers to memory map selector 4, which maps to MDIO_MMD_VEND2 + */ +static int lan865x_revb0_indirect_read(struct phy_device *phydev, u16 addr) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_ADDR, + addr); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_CTRL, + LAN865X_CFGPARAM_READ_ENABLE); + if (ret) + return ret; + + return phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN865X_REG_CFGPARAM_DATA); +} + +/* This is pulled straight from AN1760 from 'calculation of offset 1' & + * 'calculation of offset 2' + */ +static int lan865x_generate_cfg_offsets(struct phy_device *phydev, s8 offsets[2]) +{ + const u16 fixup_regs[2] = {0x0004, 0x0008}; + int ret; + + for (int i = 0; i < ARRAY_SIZE(fixup_regs); i++) { + ret = lan865x_revb0_indirect_read(phydev, fixup_regs[i]); + if (ret < 0) + return ret; + if (ret & BIT(4)) + offsets[i] = ret | 0xE0; + else + offsets[i] = ret; + } + + return 0; +} + +static int lan865x_read_cfg_params(struct phy_device *phydev, u16 cfg_params[]) +{ + int ret; + + for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb0_fixup_cfg_regs[i]); + if (ret < 0) + return ret; + cfg_params[i] = (u16)ret; + } + + return 0; +} + +static int lan865x_write_cfg_params(struct phy_device *phydev, u16 cfg_params[]) +{ + int ret; + + for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb0_fixup_cfg_regs[i], + cfg_params[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lan865x_setup_cfgparam(struct phy_device *phydev) +{ + u16 cfg_params[ARRAY_SIZE(lan865x_revb0_fixup_cfg_regs)]; + u16 cfg_results[5]; + s8 offsets[2]; + int ret; + + ret = lan865x_generate_cfg_offsets(phydev, offsets); + if (ret) + return ret; + + ret = lan865x_read_cfg_params(phydev, cfg_params); + if (ret) + return ret; + + cfg_results[0] = (cfg_params[0] & 0x000F) | + FIELD_PREP(GENMASK(15, 10), 9 + offsets[0]) | + FIELD_PREP(GENMASK(15, 4), 14 + offsets[0]); + cfg_results[1] = (cfg_params[1] & 0x03FF) | + FIELD_PREP(GENMASK(15, 10), 40 + offsets[1]); + cfg_results[2] = (cfg_params[2] & 0xC0C0) | + FIELD_PREP(GENMASK(15, 8), 5 + offsets[0]) | + (9 + offsets[0]); + cfg_results[3] = (cfg_params[3] & 0xC0C0) | + FIELD_PREP(GENMASK(15, 8), 9 + offsets[0]) | + (14 + offsets[0]); + cfg_results[4] = (cfg_params[4] & 0xC0C0) | + FIELD_PREP(GENMASK(15, 8), 17 + offsets[0]) | + (22 + offsets[0]); + + return lan865x_write_cfg_params(phydev, cfg_results); +} + +static int lan865x_revb0_config_init(struct phy_device *phydev) +{ + int ret; + + /* Reference to AN1760 + * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/SupportingCollateral/AN-LAN8650-1-Configuration-60001760.pdf + */ + for (int i = 0; i < ARRAY_SIZE(lan865x_revb0_fixup_registers); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan865x_revb0_fixup_registers[i], + lan865x_revb0_fixup_values[i]); + if (ret) + return ret; + } + /* Function to calculate and write the configuration parameters in the + * 0x0084, 0x008A, 0x00AD, 0x00AE and 0x00AF registers (from AN1760) + */ + return lan865x_setup_cfgparam(phydev); +} + static int lan867x_revb1_config_init(struct phy_device *phydev) { int err; @@ -90,7 +253,7 @@ static int lan867x_revb1_config_init(struct phy_device *phydev) return 0; } -static int lan867x_read_status(struct phy_device *phydev) +static int lan86xx_read_status(struct phy_device *phydev) { /* The phy has some limitations, namely: * - always reports link up @@ -111,17 +274,28 @@ static struct phy_driver microchip_t1s_driver[] = { .name = "LAN867X Rev.B1", .features = PHY_BASIC_T1S_P2MP_FEATURES, .config_init = lan867x_revb1_config_init, - .read_status = lan867x_read_status, + .read_status = lan86xx_read_status, .get_plca_cfg = genphy_c45_plca_get_cfg, .set_plca_cfg = genphy_c45_plca_set_cfg, .get_plca_status = genphy_c45_plca_get_status, - } + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0), + .name = "LAN865X Rev.B0 Internal Phy", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .config_init = lan865x_revb0_config_init, + .read_status = lan86xx_read_status, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = genphy_c45_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, }; module_phy_driver(microchip_t1s_driver); static struct mdio_device_id __maybe_unused tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB0) }, { } }; -- cgit v1.2.3 From 31605c01fb242806f5b8c9d08abe11328d514206 Mon Sep 17 00:00:00 2001 From: Harini Katakam Date: Mon, 29 May 2023 17:50:16 +0530 Subject: phy: mscc: Use PHY_ID_MATCH_VENDOR to minimize PHY ID table All the PHY devices variants specified have the same mask and hence can be simplified to one vendor look up for 0x00070400. Any individual config can be identified by PHY_ID_MATCH_EXACT in the respective structure. Signed-off-by: Harini Katakam Reviewed-by: Andrew Lunn Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc.h | 1 + drivers/net/phy/mscc/mscc_main.c | 16 +--------------- 2 files changed, 2 insertions(+), 15 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc.h b/drivers/net/phy/mscc/mscc.h index defe5cc6d4fc..7a962050a4d4 100644 --- a/drivers/net/phy/mscc/mscc.h +++ b/drivers/net/phy/mscc/mscc.h @@ -292,6 +292,7 @@ enum rgmii_clock_delay { #define PHY_ID_VSC8575 0x000707d0 #define PHY_ID_VSC8582 0x000707b0 #define PHY_ID_VSC8584 0x000707c0 +#define PHY_VENDOR_MSCC 0x00070400 #define MSCC_VDDMAC_1500 1500 #define MSCC_VDDMAC_1800 1800 diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index 28df8a2e4230..fc074bcc894d 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -2678,21 +2678,7 @@ static struct phy_driver vsc85xx_driver[] = { module_phy_driver(vsc85xx_driver); static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = { - { PHY_ID_VSC8501, 0xfffffff0, }, - { PHY_ID_VSC8502, 0xfffffff0, }, - { PHY_ID_VSC8504, 0xfffffff0, }, - { PHY_ID_VSC8514, 0xfffffff0, }, - { PHY_ID_VSC8530, 0xfffffff0, }, - { PHY_ID_VSC8531, 0xfffffff0, }, - { PHY_ID_VSC8540, 0xfffffff0, }, - { PHY_ID_VSC8541, 0xfffffff0, }, - { PHY_ID_VSC8552, 0xfffffff0, }, - { PHY_ID_VSC856X, 0xfffffff0, }, - { PHY_ID_VSC8572, 0xfffffff0, }, - { PHY_ID_VSC8574, 0xfffffff0, }, - { PHY_ID_VSC8575, 0xfffffff0, }, - { PHY_ID_VSC8582, 0xfffffff0, }, - { PHY_ID_VSC8584, 0xfffffff0, }, + { PHY_ID_MATCH_VENDOR(PHY_VENDOR_MSCC) }, { } }; -- cgit v1.2.3 From dbb050d2bfc8a91dfa3d020c51633c077a53d18b Mon Sep 17 00:00:00 2001 From: Harini Katakam Date: Mon, 29 May 2023 17:50:17 +0530 Subject: phy: mscc: Add support for RGMII delay configuration Add support for optional rx/tx-internal-delay-ps from devicetree. - When rx/tx-internal-delay-ps is/are specified, these take priority - When either is absent, 1) use 2ns for respective settings if rgmii-id/rxid/txid is/are present 2) use 0.2ns for respective settings if mode is rgmii Signed-off-by: Harini Katakam Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc_main.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index fc074bcc894d..669a4a7a28ce 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -107,6 +107,9 @@ static const struct vsc8531_edge_rate_table edge_table[] = { }; #endif +static const int vsc85xx_internal_delay[] = {200, 800, 1100, 1700, 2000, 2300, + 2600, 3400}; + static int vsc85xx_phy_read_page(struct phy_device *phydev) { return __phy_read(phydev, MSCC_EXT_PAGE_ACCESS); @@ -525,8 +528,12 @@ static int vsc85xx_update_rgmii_cntl(struct phy_device *phydev, u32 rgmii_cntl, { u16 rgmii_rx_delay_pos = ffs(rgmii_rx_delay_mask) - 1; u16 rgmii_tx_delay_pos = ffs(rgmii_tx_delay_mask) - 1; + int delay_size = ARRAY_SIZE(vsc85xx_internal_delay); + struct device *dev = &phydev->mdio.dev; u16 reg_val = 0; u16 mask = 0; + s32 rx_delay; + s32 tx_delay; int rc = 0; /* For traffic to pass, the VSC8502 family needs the RX_CLK disable bit @@ -541,12 +548,28 @@ static int vsc85xx_update_rgmii_cntl(struct phy_device *phydev, u32 rgmii_cntl, if (phy_interface_is_rgmii(phydev)) mask |= rgmii_rx_delay_mask | rgmii_tx_delay_mask; - if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID || - phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) - reg_val |= RGMII_CLK_DELAY_2_0_NS << rgmii_rx_delay_pos; - if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID || - phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) - reg_val |= RGMII_CLK_DELAY_2_0_NS << rgmii_tx_delay_pos; + rx_delay = phy_get_internal_delay(phydev, dev, vsc85xx_internal_delay, + delay_size, true); + if (rx_delay < 0) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) + rx_delay = RGMII_CLK_DELAY_2_0_NS; + else + rx_delay = RGMII_CLK_DELAY_0_2_NS; + } + + tx_delay = phy_get_internal_delay(phydev, dev, vsc85xx_internal_delay, + delay_size, false); + if (tx_delay < 0) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) + rx_delay = RGMII_CLK_DELAY_2_0_NS; + else + rx_delay = RGMII_CLK_DELAY_0_2_NS; + } + + reg_val |= rx_delay << rgmii_rx_delay_pos; + reg_val |= tx_delay << rgmii_tx_delay_pos; if (mask) rc = phy_modify_paged(phydev, MSCC_PHY_PAGE_EXTENDED_2, -- cgit v1.2.3 From 519d6487640835d19461817c75907e6308074a73 Mon Sep 17 00:00:00 2001 From: Xu Liang Date: Wed, 31 May 2023 15:48:22 +0800 Subject: net: phy: mxl-gpy: extend interrupt fix to all impacted variants The interrupt fix in commit 97a89ed101bb should be applied on all variants of GPY2xx PHY and GPY115C. Fixes: 97a89ed101bb ("net: phy: mxl-gpy: disable interrupts on GPY215 by default") Signed-off-by: Xu Liang Reviewed-by: Simon Horman Link: https://lore.kernel.org/r/20230531074822.39136-1-lxu@maxlinear.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mxl-gpy.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c index 6301a9abfb95..ea1073adc5a1 100644 --- a/drivers/net/phy/mxl-gpy.c +++ b/drivers/net/phy/mxl-gpy.c @@ -274,13 +274,6 @@ static int gpy_config_init(struct phy_device *phydev) return ret < 0 ? ret : 0; } -static bool gpy_has_broken_mdint(struct phy_device *phydev) -{ - /* At least these PHYs are known to have broken interrupt handling */ - return phydev->drv->phy_id == PHY_ID_GPY215B || - phydev->drv->phy_id == PHY_ID_GPY215C; -} - static int gpy_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -300,8 +293,7 @@ static int gpy_probe(struct phy_device *phydev) phydev->priv = priv; mutex_init(&priv->mbox_lock); - if (gpy_has_broken_mdint(phydev) && - !device_property_present(dev, "maxlinear,use-broken-interrupts")) + if (!device_property_present(dev, "maxlinear,use-broken-interrupts")) phydev->dev_flags |= PHY_F_NO_IRQ; fw_version = phy_read(phydev, PHY_FWV); @@ -659,11 +651,9 @@ static irqreturn_t gpy_handle_interrupt(struct phy_device *phydev) * frame. Therefore, polling is the best we can do and won't do any more * harm. * It was observed that this bug happens on link state and link speed - * changes on a GPY215B and GYP215C independent of the firmware version - * (which doesn't mean that this list is exhaustive). + * changes independent of the firmware version. */ - if (gpy_has_broken_mdint(phydev) && - (reg & (PHY_IMASK_LSTC | PHY_IMASK_LSPC))) { + if (reg & (PHY_IMASK_LSTC | PHY_IMASK_LSPC)) { reg = gpy_mbox_read(phydev, REG_GPIO0_OUT); if (reg < 0) { phy_error(phydev); -- cgit v1.2.3 From e8b6f79b41840c542b7ef45c16b31dd17e1cc6e1 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 31 May 2023 16:17:29 -0700 Subject: net: phy: broadcom: Add LPI counter Add the ability to read the PHY maintained LPI counter which is in the Clause 45 vendor space, device address 7, offset 0x803F. The counter is cleared on read. Signed-off-by: Florian Fainelli Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/20230531231729.1873932-1-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/bcm-phy-lib.c | 19 ++++++++++++------- include/linux/brcmphy.h | 2 ++ 2 files changed, 14 insertions(+), 7 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c index 5603d0a9ce96..c6e2e5f636d4 100644 --- a/drivers/net/phy/bcm-phy-lib.c +++ b/drivers/net/phy/bcm-phy-lib.c @@ -496,18 +496,20 @@ EXPORT_SYMBOL_GPL(bcm_phy_downshift_set); struct bcm_phy_hw_stat { const char *string; - u8 reg; + int devad; + u16 reg; u8 shift; u8 bits; }; /* Counters freeze at either 0xffff or 0xff, better than nothing */ static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = { - { "phy_receive_errors", MII_BRCM_CORE_BASE12, 0, 16 }, - { "phy_serdes_ber_errors", MII_BRCM_CORE_BASE13, 8, 8 }, - { "phy_false_carrier_sense_errors", MII_BRCM_CORE_BASE13, 0, 8 }, - { "phy_local_rcvr_nok", MII_BRCM_CORE_BASE14, 8, 8 }, - { "phy_remote_rcv_nok", MII_BRCM_CORE_BASE14, 0, 8 }, + { "phy_receive_errors", -1, MII_BRCM_CORE_BASE12, 0, 16 }, + { "phy_serdes_ber_errors", -1, MII_BRCM_CORE_BASE13, 8, 8 }, + { "phy_false_carrier_sense_errors", -1, MII_BRCM_CORE_BASE13, 0, 8 }, + { "phy_local_rcvr_nok", -1, MII_BRCM_CORE_BASE14, 8, 8 }, + { "phy_remote_rcv_nok", -1, MII_BRCM_CORE_BASE14, 0, 8 }, + { "phy_lpi_count", MDIO_MMD_AN, BRCM_CL45VEN_EEE_LPI_CNT, 0, 16 }, }; int bcm_phy_get_sset_count(struct phy_device *phydev) @@ -536,7 +538,10 @@ static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow, int val; u64 ret; - val = phy_read(phydev, stat.reg); + if (stat.devad < 0) + val = phy_read(phydev, stat.reg); + else + val = phy_read_mmd(phydev, stat.devad, stat.reg); if (val < 0) { ret = U64_MAX; } else { diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index e9afbfb6d7a5..251833ab271f 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -359,6 +359,8 @@ #define LPI_FEATURE_EN 0x8000 #define LPI_FEATURE_EN_DIG1000X 0x4000 +#define BRCM_CL45VEN_EEE_LPI_CNT 0x803f + /* Core register definitions*/ #define MII_BRCM_CORE_BASE12 0x12 #define MII_BRCM_CORE_BASE13 0x13 -- cgit v1.2.3 From 03c44a21d0333a2f469680bc9caaf96a9bfaea87 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Thu, 1 Jun 2023 10:12:06 +0100 Subject: net: phylink: actually fix ksettings_set() ethtool call Raju Lakkaraju reported that the below commit caused a regression with Lan743x drivers and a 2.5G SFP. Sadly, this is because the commit was utterly wrong. Let's fix this properly by not moving the linkmode_and(), but instead copying the link ksettings and then modifying the advertising mask before passing the modified link ksettings to phylib. Fixes: df0acdc59b09 ("net: phylink: fix ksettings_set() ethtool call") Signed-off-by: Russell King (Oracle) Link: https://lore.kernel.org/r/E1q4eLm-00Ayxk-GZ@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index e237949deee6..b4831110003c 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2225,11 +2225,13 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, ASSERT_RTNL(); - /* Mask out unsupported advertisements */ - linkmode_and(config.advertising, kset->link_modes.advertising, - pl->supported); - if (pl->phydev) { + struct ethtool_link_ksettings phy_kset = *kset; + + linkmode_and(phy_kset.link_modes.advertising, + phy_kset.link_modes.advertising, + pl->supported); + /* We can rely on phylib for this update; we also do not need * to update the pl->link_config settings: * - the configuration returned via ksettings_get() will come @@ -2248,10 +2250,13 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, * the presence of a PHY, this should not be changed as that * should be determined from the media side advertisement. */ - return phy_ethtool_ksettings_set(pl->phydev, kset); + return phy_ethtool_ksettings_set(pl->phydev, &phy_kset); } config = pl->link_config; + /* Mask out unsupported advertisements */ + linkmode_and(config.advertising, kset->link_modes.advertising, + pl->supported); /* FIXME: should we reject autoneg if phy/mac does not support it? */ switch (kset->base.autoneg) { -- cgit v1.2.3 From 26dd2974c5b5caef358784530c9e72715adc8f5b Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Mon, 5 Jun 2023 09:39:42 -0600 Subject: net: phy: micrel: Move KSZ9477 errata fixes to PHY driver The ksz9477 DSA switch driver is currently updating some MMD registers on the internal port PHYs to address some chip errata. However, these errata are really a property of the PHY itself, not the switch they are part of, so this is kind of a layering violation. It makes more sense for these writes to be done inside the driver which binds to the PHY and not the driver for the containing device. This also addresses some issues where the ordering of when these writes are done may have been incorrect, causing the link to erratically fail to come up at the proper speed or at all. Doing this in the PHY driver during config_init ensures that they happen before anything else tries to change the state of the PHY on the port. The new code also ensures that autonegotiation is disabled during the register writes and re-enabled afterwards, as indicated by the latest version of the errata documentation from Microchip. Signed-off-by: Robert Hancock Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli Signed-off-by: Jakub Kicinski --- drivers/net/phy/micrel.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 2094d49025a7..6d18ea19e442 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1774,6 +1774,79 @@ static int ksz886x_read_status(struct phy_device *phydev) return genphy_read_status(phydev); } +struct ksz9477_errata_write { + u8 dev_addr; + u8 reg_addr; + u16 val; +}; + +static const struct ksz9477_errata_write ksz9477_errata_writes[] = { + /* Register settings are needed to improve PHY receive performance */ + {0x01, 0x6f, 0xdd0b}, + {0x01, 0x8f, 0x6032}, + {0x01, 0x9d, 0x248c}, + {0x01, 0x75, 0x0060}, + {0x01, 0xd3, 0x7777}, + {0x1c, 0x06, 0x3008}, + {0x1c, 0x08, 0x2000}, + + /* Transmit waveform amplitude can be improved (1000BASE-T, 100BASE-TX, 10BASE-Te) */ + {0x1c, 0x04, 0x00d0}, + + /* Energy Efficient Ethernet (EEE) feature select must be manually disabled */ + {0x07, 0x3c, 0x0000}, + + /* Register settings are required to meet data sheet supply current specifications */ + {0x1c, 0x13, 0x6eff}, + {0x1c, 0x14, 0xe6ff}, + {0x1c, 0x15, 0x6eff}, + {0x1c, 0x16, 0xe6ff}, + {0x1c, 0x17, 0x00ff}, + {0x1c, 0x18, 0x43ff}, + {0x1c, 0x19, 0xc3ff}, + {0x1c, 0x1a, 0x6fff}, + {0x1c, 0x1b, 0x07ff}, + {0x1c, 0x1c, 0x0fff}, + {0x1c, 0x1d, 0xe7ff}, + {0x1c, 0x1e, 0xefff}, + {0x1c, 0x20, 0xeeee}, +}; + +static int ksz9477_config_init(struct phy_device *phydev) +{ + int err; + int i; + + /* Apply PHY settings to address errata listed in + * KSZ9477, KSZ9897, KSZ9896, KSZ9567, KSZ8565 + * Silicon Errata and Data Sheet Clarification documents. + * + * Document notes: Before configuring the PHY MMD registers, it is + * necessary to set the PHY to 100 Mbps speed with auto-negotiation + * disabled by writing to register 0xN100-0xN101. After writing the + * MMD registers, and after all errata workarounds that involve PHY + * register settings, write register 0xN100-0xN101 again to enable + * and restart auto-negotiation. + */ + err = phy_write(phydev, MII_BMCR, BMCR_SPEED100 | BMCR_FULLDPLX); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(ksz9477_errata_writes); ++i) { + const struct ksz9477_errata_write *errata = &ksz9477_errata_writes[i]; + + err = phy_write_mmd(phydev, errata->dev_addr, errata->reg_addr, errata->val); + if (err) + return err; + } + + err = genphy_restart_aneg(phydev); + if (err) + return err; + + return kszphy_config_init(phydev); +} + static int kszphy_get_sset_count(struct phy_device *phydev) { return ARRAY_SIZE(kszphy_hw_stats); @@ -4735,7 +4808,7 @@ static struct phy_driver ksphy_driver[] = { .phy_id_mask = MICREL_PHY_ID_MASK, .name = "Microchip KSZ9477", /* PHY_GBIT_FEATURES */ - .config_init = kszphy_config_init, + .config_init = ksz9477_config_init, .config_intr = kszphy_config_intr, .handle_interrupt = kszphy_handle_interrupt, .suspend = genphy_suspend, -- cgit v1.2.3 From 7300c9b574cc2b259ef112d34affa0671ae4810a Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Mon, 5 Jun 2023 11:40:08 -0400 Subject: net: phy: realtek: Add optional external PHY clock In some cases, the PHY can use an external clock source instead of a crystal. Add an optional clock in the phy node to make sure that the clock source is enabled, if specified, before probing. Reviewed-by: Florian Fainelli Reviewed-by: Andrew Lunn Signed-off-by: Detlev Casanova Signed-off-by: David S. Miller --- drivers/net/phy/realtek.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c index 3d99fd6664d7..b13dd0b3c99e 100644 --- a/drivers/net/phy/realtek.c +++ b/drivers/net/phy/realtek.c @@ -12,6 +12,7 @@ #include #include #include +#include #define RTL821x_PHYSR 0x11 #define RTL821x_PHYSR_DUPLEX BIT(13) @@ -80,6 +81,7 @@ struct rtl821x_priv { u16 phycr1; u16 phycr2; bool has_phycr2; + struct clk *clk; }; static int rtl821x_read_page(struct phy_device *phydev) @@ -103,6 +105,11 @@ static int rtl821x_probe(struct phy_device *phydev) if (!priv) return -ENOMEM; + priv->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "failed to get phy clock\n"); + ret = phy_read_paged(phydev, 0xa43, RTL8211F_PHYCR1); if (ret < 0) return ret; -- cgit v1.2.3 From 59e227e2894b28c4409e89d04a33868c176587b2 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Mon, 5 Jun 2023 11:40:10 -0400 Subject: net: phy: realtek: Disable clock on suspend For PHYs that call rtl821x_probe() where an external clock can be configured, make sure that the clock is disabled when ->suspend() is called and enabled on resume. The PHY_ALWAYS_CALL_SUSPEND is added to ensure that the suspend function is actually always called. Reviewed-by: Florian Fainelli Signed-off-by: Detlev Casanova Signed-off-by: David S. Miller --- drivers/net/phy/realtek.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c index b13dd0b3c99e..894172a3e15f 100644 --- a/drivers/net/phy/realtek.c +++ b/drivers/net/phy/realtek.c @@ -426,10 +426,31 @@ static int rtl8211f_config_init(struct phy_device *phydev) return genphy_soft_reset(phydev); } +static int rtl821x_suspend(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + int ret = 0; + + if (!phydev->wol_enabled) { + ret = genphy_suspend(phydev); + + if (ret) + return ret; + + clk_disable_unprepare(priv->clk); + } + + return ret; +} + static int rtl821x_resume(struct phy_device *phydev) { + struct rtl821x_priv *priv = phydev->priv; int ret; + if (!phydev->wol_enabled) + clk_prepare_enable(priv->clk); + ret = genphy_resume(phydev); if (ret < 0) return ret; @@ -934,10 +955,11 @@ static struct phy_driver realtek_drvs[] = { .read_status = rtlgen_read_status, .config_intr = &rtl8211f_config_intr, .handle_interrupt = rtl8211f_handle_interrupt, - .suspend = genphy_suspend, + .suspend = rtl821x_suspend, .resume = rtl821x_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, + .flags = PHY_ALWAYS_CALL_SUSPEND, }, { PHY_ID_MATCH_EXACT(RTL_8211FVD_PHYID), .name = "RTL8211F-VD Gigabit Ethernet", @@ -946,10 +968,11 @@ static struct phy_driver realtek_drvs[] = { .read_status = rtlgen_read_status, .config_intr = &rtl8211f_config_intr, .handle_interrupt = rtl8211f_handle_interrupt, - .suspend = genphy_suspend, + .suspend = rtl821x_suspend, .resume = rtl821x_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, + .flags = PHY_ALWAYS_CALL_SUSPEND, }, { .name = "Generic FE-GE Realtek PHY", .match_phy_device = rtlgen_match_phy_device, -- cgit v1.2.3 From 57fd7d59b1c7d6f6a1c34863a2bc4ff1f6c92d40 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 7 Jun 2023 11:34:52 -0700 Subject: net: phy: broadcom: Rename LED registers These registers are common to most PHYs and are not specific to the BCM5482, renamed the constants accordingly, no functional change. Signed-off-by: Florian Fainelli Reviewed-by: Simon Horman Signed-off-by: David S. Miller --- drivers/net/phy/broadcom.c | 10 +++++----- include/linux/brcmphy.h | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 822c8b01dc53..57a865aa1fe5 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -414,13 +414,13 @@ static int bcm54xx_config_init(struct phy_device *phydev) * these settings will cause LOS to malfunction. */ if (!phy_on_sfp(phydev)) { - val = BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1) | - BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_MULTICOLOR1); - bcm_phy_write_shadow(phydev, BCM5482_SHD_LEDS1, val); + val = BCM54XX_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1) | + BCM54XX_SHD_LEDS1_LED3(BCM_LED_SRC_MULTICOLOR1); + bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS1, val); val = BCM_LED_MULTICOLOR_IN_PHASE | - BCM5482_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) | - BCM5482_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT); + BCM54XX_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) | + BCM54XX_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT); bcm_phy_write_exp(phydev, BCM_EXP_MULTICOLOR, val); } diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index 251833ab271f..ab21b8a1b2c8 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -206,11 +206,11 @@ #define BCM_NO_ANEG_APD_EN 0x0060 /* bits 5 & 6 */ #define BCM_APD_SINGLELP_EN 0x0100 /* Bit 8 */ -#define BCM5482_SHD_LEDS1 0x0d /* 01101: LED Selector 1 */ +#define BCM54XX_SHD_LEDS1 0x0d /* 01101: LED Selector 1 */ /* LED3 / ~LINKSPD[2] selector */ -#define BCM5482_SHD_LEDS1_LED3(src) ((src & 0xf) << 4) +#define BCM54XX_SHD_LEDS1_LED3(src) ((src & 0xf) << 4) /* LED1 / ~LINKSPD[1] selector */ -#define BCM5482_SHD_LEDS1_LED1(src) ((src & 0xf) << 0) +#define BCM54XX_SHD_LEDS1_LED1(src) ((src & 0xf) << 0) #define BCM54XX_SHD_RGMII_MODE 0x0b /* 01011: RGMII Mode Selector */ #define BCM5482_SHD_SSD 0x14 /* 10100: Secondary SerDes control */ #define BCM5482_SHD_SSD_LEDM 0x0008 /* SSD LED Mode enable */ -- cgit v1.2.3 From bd5736e146e35f9eabe8c1bfc0ab00979ae62930 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 7 Jun 2023 11:34:53 -0700 Subject: net: phy: broadcom: Add support for setting LED brightness Broadcom PHYs have two LEDs selector registers which allow us to control the LED assignment, including how to turn them on/off. Signed-off-by: Florian Fainelli Reviewed-by: Simon Horman Signed-off-by: David S. Miller --- drivers/net/phy/bcm-phy-lib.c | 27 +++++++++++++++++++++++++++ drivers/net/phy/bcm-phy-lib.h | 3 +++ drivers/net/phy/broadcom.c | 15 +++++++++++++++ include/linux/brcmphy.h | 3 +++ 4 files changed, 48 insertions(+) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c index c6e2e5f636d4..876f28fd8256 100644 --- a/drivers/net/phy/bcm-phy-lib.c +++ b/drivers/net/phy/bcm-phy-lib.c @@ -1039,6 +1039,33 @@ irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id) } EXPORT_SYMBOL_GPL(bcm_phy_wol_isr); +int bcm_phy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + u8 led_num; + int ret; + u16 reg; + + if (index >= 4) + return -EINVAL; + + /* Two LEDS per register */ + led_num = index % 2; + reg = index >= 2 ? BCM54XX_SHD_LEDS2 : BCM54XX_SHD_LEDS1; + + ret = bcm_phy_read_shadow(phydev, reg); + if (ret < 0) + return ret; + + ret &= ~(BCM_LED_SRC_MASK << BCM54XX_SHD_LEDS_SHIFT(led_num)); + if (value == LED_OFF) + ret |= BCM_LED_SRC_OFF << BCM54XX_SHD_LEDS_SHIFT(led_num); + else + ret |= BCM_LED_SRC_ON << BCM54XX_SHD_LEDS_SHIFT(led_num); + return bcm_phy_write_shadow(phydev, reg, ret); +} +EXPORT_SYMBOL_GPL(bcm_phy_led_brightness_set); + MODULE_DESCRIPTION("Broadcom PHY Library"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Broadcom Corporation"); diff --git a/drivers/net/phy/bcm-phy-lib.h b/drivers/net/phy/bcm-phy-lib.h index 2f30ce0cab0e..b52189e45a84 100644 --- a/drivers/net/phy/bcm-phy-lib.h +++ b/drivers/net/phy/bcm-phy-lib.h @@ -118,4 +118,7 @@ int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id); +int bcm_phy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value); + #endif /* _LINUX_BCM_PHY_LIB_H */ diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 57a865aa1fe5..9f0a9c575bd7 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -1031,6 +1031,7 @@ static struct phy_driver broadcom_drivers[] = { .resume = bcm54xx_resume, .get_wol = bcm54xx_phy_get_wol, .set_wol = bcm54xx_phy_set_wol, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM5461, .phy_id_mask = 0xfffffff0, @@ -1044,6 +1045,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM54612E, .phy_id_mask = 0xfffffff0, @@ -1057,6 +1059,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM54616S, .phy_id_mask = 0xfffffff0, @@ -1070,6 +1073,7 @@ static struct phy_driver broadcom_drivers[] = { .read_status = bcm54616s_read_status, .probe = bcm54616s_probe, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM5464, .phy_id_mask = 0xfffffff0, @@ -1085,6 +1089,7 @@ static struct phy_driver broadcom_drivers[] = { .suspend = genphy_suspend, .resume = genphy_resume, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM5481, .phy_id_mask = 0xfffffff0, @@ -1099,6 +1104,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM54810, .phy_id_mask = 0xfffffff0, @@ -1115,6 +1121,7 @@ static struct phy_driver broadcom_drivers[] = { .suspend = bcm54xx_suspend, .resume = bcm54xx_resume, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM54811, .phy_id_mask = 0xfffffff0, @@ -1131,6 +1138,7 @@ static struct phy_driver broadcom_drivers[] = { .suspend = bcm54xx_suspend, .resume = bcm54xx_resume, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM5482, .phy_id_mask = 0xfffffff0, @@ -1144,6 +1152,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM50610, .phy_id_mask = 0xfffffff0, @@ -1159,6 +1168,7 @@ static struct phy_driver broadcom_drivers[] = { .link_change_notify = bcm54xx_link_change_notify, .suspend = bcm54xx_suspend, .resume = bcm54xx_resume, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM50610M, .phy_id_mask = 0xfffffff0, @@ -1174,6 +1184,7 @@ static struct phy_driver broadcom_drivers[] = { .link_change_notify = bcm54xx_link_change_notify, .suspend = bcm54xx_suspend, .resume = bcm54xx_resume, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM57780, .phy_id_mask = 0xfffffff0, @@ -1187,6 +1198,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCMAC131, .phy_id_mask = 0xfffffff0, @@ -1218,6 +1230,7 @@ static struct phy_driver broadcom_drivers[] = { .get_stats = bcm54xx_get_stats, .probe = bcm54xx_phy_probe, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM53125, .phy_id_mask = 0xfffffff0, @@ -1232,6 +1245,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM53128, .phy_id_mask = 0xfffffff0, @@ -1246,6 +1260,7 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .led_brightness_set = bcm_phy_led_brightness_set, }, { .phy_id = PHY_ID_BCM89610, .phy_id_mask = 0xfffffff0, diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index ab21b8a1b2c8..5d732f48f787 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -161,6 +161,7 @@ #define BCM_LED_SRC_OPENSHORT 0xb #define BCM_LED_SRC_OFF 0xe /* Tied high */ #define BCM_LED_SRC_ON 0xf /* Tied low */ +#define BCM_LED_SRC_MASK GENMASK(3, 0) /* * Broadcom Multicolor LED configurations (expansion register 4) @@ -208,9 +209,11 @@ #define BCM54XX_SHD_LEDS1 0x0d /* 01101: LED Selector 1 */ /* LED3 / ~LINKSPD[2] selector */ +#define BCM54XX_SHD_LEDS_SHIFT(led) (4 * (led)) #define BCM54XX_SHD_LEDS1_LED3(src) ((src & 0xf) << 4) /* LED1 / ~LINKSPD[1] selector */ #define BCM54XX_SHD_LEDS1_LED1(src) ((src & 0xf) << 0) +#define BCM54XX_SHD_LEDS2 0x0e /* 01110: LED Selector 2 */ #define BCM54XX_SHD_RGMII_MODE 0x0b /* 01011: RGMII Mode Selector */ #define BCM5482_SHD_SSD 0x14 /* 10100: Secondary SerDes control */ #define BCM5482_SHD_SSD_LEDM 0x0008 /* SSD LED Mode enable */ -- cgit v1.2.3 From 98c485eaf509bc0e2a85f9b58d17cd501f274c4e Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Sun, 11 Jun 2023 00:48:10 +0100 Subject: net: phy: add driver for MediaTek SoC built-in GE PHYs Some of MediaTek's Filogic SoCs come with built-in gigabit Ethernet PHYs which require calibration data from the SoC's efuse. Despite the similar design the driver doesn't share any code with the existing mediatek-ge.c. Add support for such PHYs by introducing a new driver with basic support for MediaTek SoCs MT7981 and MT7988 built-in 1GE PHYs. Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- MAINTAINERS | 9 + drivers/net/phy/Kconfig | 12 + drivers/net/phy/Makefile | 1 + drivers/net/phy/mediatek-ge-soc.c | 1116 +++++++++++++++++++++++++++++++++++++ drivers/net/phy/mediatek-ge.c | 3 +- 5 files changed, 1140 insertions(+), 1 deletion(-) create mode 100644 drivers/net/phy/mediatek-ge-soc.c (limited to 'drivers/net/phy') diff --git a/MAINTAINERS b/MAINTAINERS index ecac69046d30..cd9752472d77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13151,6 +13151,15 @@ S: Maintained F: drivers/net/pcs/pcs-mtk-lynxi.c F: include/linux/pcs/pcs-mtk-lynxi.h +MEDIATEK ETHERNET PHY DRIVERS +M: Daniel Golle +M: Qingfang Deng +M: SkyLake Huang +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/phy/mediatek-ge-soc.c +F: drivers/net/phy/mediatek-ge.c + MEDIATEK I2C CONTROLLER DRIVER M: Qii Wang L: linux-i2c@vger.kernel.org diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 059bd06a8cce..a40269c17597 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -236,6 +236,18 @@ config MEDIATEK_GE_PHY help Supports the MediaTek Gigabit Ethernet PHYs. +config MEDIATEK_GE_SOC_PHY + tristate "MediaTek SoC Ethernet PHYs" + depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST + select NVMEM_MTK_EFUSE + help + Supports MediaTek SoC built-in Gigabit Ethernet PHYs. + + Include support for built-in Ethernet PHYs which are present in + the MT7981 and MT7988 SoCs. These PHYs need calibration data + present in the SoCs efuse and will dynamically calibrate VCM + (common-mode voltage) during startup. + config MICREL_PHY tristate "Micrel PHYs" depends on PTP_1588_CLOCK_OPTIONAL diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index f289ab16a1da..2fe51ea83bab 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_MARVELL_PHY) += marvell.o obj-$(CONFIG_MARVELL_88X2222_PHY) += marvell-88x2222.o obj-$(CONFIG_MAXLINEAR_GPHY) += mxl-gpy.o obj-$(CONFIG_MEDIATEK_GE_PHY) += mediatek-ge.o +obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mediatek-ge-soc.o obj-$(CONFIG_MESON_GXL_PHY) += meson-gxl.o obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o obj-$(CONFIG_MICREL_PHY) += micrel.o diff --git a/drivers/net/phy/mediatek-ge-soc.c b/drivers/net/phy/mediatek-ge-soc.c new file mode 100644 index 000000000000..95369171a7ba --- /dev/null +++ b/drivers/net/phy/mediatek-ge-soc.c @@ -0,0 +1,1116 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include +#include +#include +#include +#include + +#define MTK_GPHY_ID_MT7981 0x03a29461 +#define MTK_GPHY_ID_MT7988 0x03a29481 + +#define MTK_EXT_PAGE_ACCESS 0x1f +#define MTK_PHY_PAGE_STANDARD 0x0000 +#define MTK_PHY_PAGE_EXTENDED_3 0x0003 + +#define MTK_PHY_LPI_REG_14 0x14 +#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK GENMASK(8, 0) + +#define MTK_PHY_LPI_REG_1c 0x1c +#define MTK_PHY_SMI_DET_ON_THRESH_MASK GENMASK(13, 8) + +#define MTK_PHY_PAGE_EXTENDED_2A30 0x2a30 +#define MTK_PHY_PAGE_EXTENDED_52B5 0x52b5 + +#define ANALOG_INTERNAL_OPERATION_MAX_US 20 +#define TXRESERVE_MIN 0 +#define TXRESERVE_MAX 7 + +#define MTK_PHY_ANARG_RG 0x10 +#define MTK_PHY_TCLKOFFSET_MASK GENMASK(12, 8) + +/* Registers on MDIO_MMD_VEND1 */ +#define MTK_PHY_TXVLD_DA_RG 0x12 +#define MTK_PHY_DA_TX_I2MPB_A_GBE_MASK GENMASK(15, 10) +#define MTK_PHY_DA_TX_I2MPB_A_TBT_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_A2 0x16 +#define MTK_PHY_DA_TX_I2MPB_A_HBT_MASK GENMASK(15, 10) +#define MTK_PHY_DA_TX_I2MPB_A_TST_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_B1 0x17 +#define MTK_PHY_DA_TX_I2MPB_B_GBE_MASK GENMASK(13, 8) +#define MTK_PHY_DA_TX_I2MPB_B_TBT_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_B2 0x18 +#define MTK_PHY_DA_TX_I2MPB_B_HBT_MASK GENMASK(13, 8) +#define MTK_PHY_DA_TX_I2MPB_B_TST_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_C1 0x19 +#define MTK_PHY_DA_TX_I2MPB_C_GBE_MASK GENMASK(13, 8) +#define MTK_PHY_DA_TX_I2MPB_C_TBT_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_C2 0x20 +#define MTK_PHY_DA_TX_I2MPB_C_HBT_MASK GENMASK(13, 8) +#define MTK_PHY_DA_TX_I2MPB_C_TST_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_D1 0x21 +#define MTK_PHY_DA_TX_I2MPB_D_GBE_MASK GENMASK(13, 8) +#define MTK_PHY_DA_TX_I2MPB_D_TBT_MASK GENMASK(5, 0) + +#define MTK_PHY_TX_I2MPB_TEST_MODE_D2 0x22 +#define MTK_PHY_DA_TX_I2MPB_D_HBT_MASK GENMASK(13, 8) +#define MTK_PHY_DA_TX_I2MPB_D_TST_MASK GENMASK(5, 0) + +#define MTK_PHY_RXADC_CTRL_RG7 0xc6 +#define MTK_PHY_DA_AD_BUF_BIAS_LP_MASK GENMASK(9, 8) + +#define MTK_PHY_RXADC_CTRL_RG9 0xc8 +#define MTK_PHY_DA_RX_PSBN_TBT_MASK GENMASK(14, 12) +#define MTK_PHY_DA_RX_PSBN_HBT_MASK GENMASK(10, 8) +#define MTK_PHY_DA_RX_PSBN_GBE_MASK GENMASK(6, 4) +#define MTK_PHY_DA_RX_PSBN_LP_MASK GENMASK(2, 0) + +#define MTK_PHY_LDO_OUTPUT_V 0xd7 + +#define MTK_PHY_RG_ANA_CAL_RG0 0xdb +#define MTK_PHY_RG_CAL_CKINV BIT(12) +#define MTK_PHY_RG_ANA_CALEN BIT(8) +#define MTK_PHY_RG_ZCALEN_A BIT(0) + +#define MTK_PHY_RG_ANA_CAL_RG1 0xdc +#define MTK_PHY_RG_ZCALEN_B BIT(12) +#define MTK_PHY_RG_ZCALEN_C BIT(8) +#define MTK_PHY_RG_ZCALEN_D BIT(4) +#define MTK_PHY_RG_TXVOS_CALEN BIT(0) + +#define MTK_PHY_RG_ANA_CAL_RG5 0xe0 +#define MTK_PHY_RG_REXT_TRIM_MASK GENMASK(13, 8) + +#define MTK_PHY_RG_TX_FILTER 0xfe + +#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120 0x120 +#define MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK GENMASK(12, 8) +#define MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK GENMASK(4, 0) + +#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122 0x122 +#define MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK GENMASK(7, 0) + +#define MTK_PHY_RG_TESTMUX_ADC_CTRL 0x144 +#define MTK_PHY_RG_TXEN_DIG_MASK GENMASK(5, 5) + +#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B 0x172 +#define MTK_PHY_CR_TX_AMP_OFFSET_A_MASK GENMASK(13, 8) +#define MTK_PHY_CR_TX_AMP_OFFSET_B_MASK GENMASK(6, 0) + +#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D 0x173 +#define MTK_PHY_CR_TX_AMP_OFFSET_C_MASK GENMASK(13, 8) +#define MTK_PHY_CR_TX_AMP_OFFSET_D_MASK GENMASK(6, 0) + +#define MTK_PHY_RG_AD_CAL_COMP 0x17a +#define MTK_PHY_AD_CAL_COMP_OUT_SHIFT (8) + +#define MTK_PHY_RG_AD_CAL_CLK 0x17b +#define MTK_PHY_DA_CAL_CLK BIT(0) + +#define MTK_PHY_RG_AD_CALIN 0x17c +#define MTK_PHY_DA_CALIN_FLAG BIT(0) + +#define MTK_PHY_RG_DASN_DAC_IN0_A 0x17d +#define MTK_PHY_DASN_DAC_IN0_A_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN0_B 0x17e +#define MTK_PHY_DASN_DAC_IN0_B_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN0_C 0x17f +#define MTK_PHY_DASN_DAC_IN0_C_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN0_D 0x180 +#define MTK_PHY_DASN_DAC_IN0_D_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN1_A 0x181 +#define MTK_PHY_DASN_DAC_IN1_A_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN1_B 0x182 +#define MTK_PHY_DASN_DAC_IN1_B_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN1_C 0x183 +#define MTK_PHY_DASN_DAC_IN1_C_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DASN_DAC_IN1_D 0x184 +#define MTK_PHY_DASN_DAC_IN1_D_MASK GENMASK(9, 0) + +#define MTK_PHY_RG_DEV1E_REG19b 0x19b +#define MTK_PHY_BYPASS_DSP_LPI_READY BIT(8) + +#define MTK_PHY_RG_LP_IIR2_K1_L 0x22a +#define MTK_PHY_RG_LP_IIR2_K1_U 0x22b +#define MTK_PHY_RG_LP_IIR2_K2_L 0x22c +#define MTK_PHY_RG_LP_IIR2_K2_U 0x22d +#define MTK_PHY_RG_LP_IIR2_K3_L 0x22e +#define MTK_PHY_RG_LP_IIR2_K3_U 0x22f +#define MTK_PHY_RG_LP_IIR2_K4_L 0x230 +#define MTK_PHY_RG_LP_IIR2_K4_U 0x231 +#define MTK_PHY_RG_LP_IIR2_K5_L 0x232 +#define MTK_PHY_RG_LP_IIR2_K5_U 0x233 + +#define MTK_PHY_RG_DEV1E_REG234 0x234 +#define MTK_PHY_TR_OPEN_LOOP_EN_MASK GENMASK(0, 0) +#define MTK_PHY_LPF_X_AVERAGE_MASK GENMASK(7, 4) +#define MTK_PHY_TR_LP_IIR_EEE_EN BIT(12) + +#define MTK_PHY_RG_LPF_CNT_VAL 0x235 + +#define MTK_PHY_RG_DEV1E_REG238 0x238 +#define MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK GENMASK(8, 0) +#define MTK_PHY_LPI_SLV_SEND_TX_EN BIT(12) + +#define MTK_PHY_RG_DEV1E_REG239 0x239 +#define MTK_PHY_LPI_SEND_LOC_TIMER_MASK GENMASK(8, 0) +#define MTK_PHY_LPI_TXPCS_LOC_RCV BIT(12) + +#define MTK_PHY_RG_DEV1E_REG27C 0x27c +#define MTK_PHY_VGASTATE_FFE_THR_ST1_MASK GENMASK(12, 8) +#define MTK_PHY_RG_DEV1E_REG27D 0x27d +#define MTK_PHY_VGASTATE_FFE_THR_ST2_MASK GENMASK(4, 0) + +#define MTK_PHY_RG_DEV1E_REG2C7 0x2c7 +#define MTK_PHY_MAX_GAIN_MASK GENMASK(4, 0) +#define MTK_PHY_MIN_GAIN_MASK GENMASK(12, 8) + +#define MTK_PHY_RG_DEV1E_REG2D1 0x2d1 +#define MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK GENMASK(7, 0) +#define MTK_PHY_LPI_SKIP_SD_SLV_TR BIT(8) +#define MTK_PHY_LPI_TR_READY BIT(9) +#define MTK_PHY_LPI_VCO_EEE_STG0_EN BIT(10) + +#define MTK_PHY_RG_DEV1E_REG323 0x323 +#define MTK_PHY_EEE_WAKE_MAS_INT_DC BIT(0) +#define MTK_PHY_EEE_WAKE_SLV_INT_DC BIT(4) + +#define MTK_PHY_RG_DEV1E_REG324 0x324 +#define MTK_PHY_SMI_DETCNT_MAX_MASK GENMASK(5, 0) +#define MTK_PHY_SMI_DET_MAX_EN BIT(8) + +#define MTK_PHY_RG_DEV1E_REG326 0x326 +#define MTK_PHY_LPI_MODE_SD_ON BIT(0) +#define MTK_PHY_RESET_RANDUPD_CNT BIT(1) +#define MTK_PHY_TREC_UPDATE_ENAB_CLR BIT(2) +#define MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF BIT(4) +#define MTK_PHY_TR_READY_SKIP_AFE_WAKEUP BIT(5) + +#define MTK_PHY_LDO_PUMP_EN_PAIRAB 0x502 +#define MTK_PHY_LDO_PUMP_EN_PAIRCD 0x503 + +#define MTK_PHY_DA_TX_R50_PAIR_A 0x53d +#define MTK_PHY_DA_TX_R50_PAIR_B 0x53e +#define MTK_PHY_DA_TX_R50_PAIR_C 0x53f +#define MTK_PHY_DA_TX_R50_PAIR_D 0x540 + +#define MTK_PHY_RG_BG_RASEL 0x115 +#define MTK_PHY_RG_BG_RASEL_MASK GENMASK(2, 0) + +/* These macro privides efuse parsing for internal phy. */ +#define EFS_DA_TX_I2MPB_A(x) (((x) >> 0) & GENMASK(5, 0)) +#define EFS_DA_TX_I2MPB_B(x) (((x) >> 6) & GENMASK(5, 0)) +#define EFS_DA_TX_I2MPB_C(x) (((x) >> 12) & GENMASK(5, 0)) +#define EFS_DA_TX_I2MPB_D(x) (((x) >> 18) & GENMASK(5, 0)) +#define EFS_DA_TX_AMP_OFFSET_A(x) (((x) >> 24) & GENMASK(5, 0)) + +#define EFS_DA_TX_AMP_OFFSET_B(x) (((x) >> 0) & GENMASK(5, 0)) +#define EFS_DA_TX_AMP_OFFSET_C(x) (((x) >> 6) & GENMASK(5, 0)) +#define EFS_DA_TX_AMP_OFFSET_D(x) (((x) >> 12) & GENMASK(5, 0)) +#define EFS_DA_TX_R50_A(x) (((x) >> 18) & GENMASK(5, 0)) +#define EFS_DA_TX_R50_B(x) (((x) >> 24) & GENMASK(5, 0)) + +#define EFS_DA_TX_R50_C(x) (((x) >> 0) & GENMASK(5, 0)) +#define EFS_DA_TX_R50_D(x) (((x) >> 6) & GENMASK(5, 0)) + +#define EFS_RG_BG_RASEL(x) (((x) >> 4) & GENMASK(2, 0)) +#define EFS_RG_REXT_TRIM(x) (((x) >> 7) & GENMASK(5, 0)) + +enum { + NO_PAIR, + PAIR_A, + PAIR_B, + PAIR_C, + PAIR_D, +}; + +enum { + GPHY_PORT0, + GPHY_PORT1, + GPHY_PORT2, + GPHY_PORT3, +}; + +enum calibration_mode { + EFUSE_K, + SW_K +}; + +enum CAL_ITEM { + REXT, + TX_OFFSET, + TX_AMP, + TX_R50, + TX_VCM +}; + +enum CAL_MODE { + EFUSE_M, + SW_M +}; + +static int mtk_socphy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, MTK_EXT_PAGE_ACCESS); +} + +static int mtk_socphy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page); +} + +/* One calibration cycle consists of: + * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high + * until AD_CAL_COMP is ready to output calibration result. + * 2.Wait until DA_CAL_CLK is available. + * 3.Fetch AD_CAL_COMP_OUT. + */ +static int cal_cycle(struct phy_device *phydev, int devad, + u32 regnum, u16 mask, u16 cal_val) +{ + int reg_val; + int ret; + + phy_modify_mmd(phydev, devad, regnum, + mask, cal_val); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN, + MTK_PHY_DA_CALIN_FLAG); + + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_AD_CAL_CLK, reg_val, + reg_val & MTK_PHY_DA_CAL_CLK, 500, + ANALOG_INTERNAL_OPERATION_MAX_US, false); + if (ret) { + phydev_err(phydev, "Calibration cycle timeout\n"); + return ret; + } + + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN, + MTK_PHY_DA_CALIN_FLAG); + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP) >> + MTK_PHY_AD_CAL_COMP_OUT_SHIFT; + phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret); + + return ret; +} + +static int rext_fill_result(struct phy_device *phydev, u16 *buf) +{ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5, + MTK_PHY_RG_REXT_TRIM_MASK, buf[0] << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL, + MTK_PHY_RG_BG_RASEL_MASK, buf[1]); + + return 0; +} + +static int rext_cal_efuse(struct phy_device *phydev, u32 *buf) +{ + u16 rext_cal_val[2]; + + rext_cal_val[0] = EFS_RG_REXT_TRIM(buf[3]); + rext_cal_val[1] = EFS_RG_BG_RASEL(buf[3]); + rext_fill_result(phydev, rext_cal_val); + + return 0; +} + +static int tx_offset_fill_result(struct phy_device *phydev, u16 *buf) +{ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B, + MTK_PHY_CR_TX_AMP_OFFSET_A_MASK, buf[0] << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B, + MTK_PHY_CR_TX_AMP_OFFSET_B_MASK, buf[1]); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D, + MTK_PHY_CR_TX_AMP_OFFSET_C_MASK, buf[2] << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D, + MTK_PHY_CR_TX_AMP_OFFSET_D_MASK, buf[3]); + + return 0; +} + +static int tx_offset_cal_efuse(struct phy_device *phydev, u32 *buf) +{ + u16 tx_offset_cal_val[4]; + + tx_offset_cal_val[0] = EFS_DA_TX_AMP_OFFSET_A(buf[0]); + tx_offset_cal_val[1] = EFS_DA_TX_AMP_OFFSET_B(buf[1]); + tx_offset_cal_val[2] = EFS_DA_TX_AMP_OFFSET_C(buf[1]); + tx_offset_cal_val[3] = EFS_DA_TX_AMP_OFFSET_D(buf[1]); + + tx_offset_fill_result(phydev, tx_offset_cal_val); + + return 0; +} + +static int tx_amp_fill_result(struct phy_device *phydev, u16 *buf) +{ + int i; + int bias[16] = {}; + const int vals_9461[16] = { 7, 1, 4, 7, + 7, 1, 4, 7, + 7, 1, 4, 7, + 7, 1, 4, 7 }; + const int vals_9481[16] = { 10, 6, 6, 10, + 10, 6, 6, 10, + 10, 6, 6, 10, + 10, 6, 6, 10 }; + switch (phydev->drv->phy_id) { + case MTK_GPHY_ID_MT7981: + /* We add some calibration to efuse values + * due to board level influence. + * GBE: +7, TBT: +1, HBT: +4, TST: +7 + */ + memcpy(bias, (const void *)vals_9461, sizeof(bias)); + break; + case MTK_GPHY_ID_MT7988: + memcpy(bias, (const void *)vals_9481, sizeof(bias)); + break; + } + + /* Prevent overflow */ + for (i = 0; i < 12; i++) { + if (buf[i >> 2] + bias[i] > 63) { + buf[i >> 2] = 63; + bias[i] = 0; + } + } + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG, + MTK_PHY_DA_TX_I2MPB_A_GBE_MASK, (buf[0] + bias[0]) << 10); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TXVLD_DA_RG, + MTK_PHY_DA_TX_I2MPB_A_TBT_MASK, buf[0] + bias[1]); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2, + MTK_PHY_DA_TX_I2MPB_A_HBT_MASK, (buf[0] + bias[2]) << 10); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_A2, + MTK_PHY_DA_TX_I2MPB_A_TST_MASK, buf[0] + bias[3]); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1, + MTK_PHY_DA_TX_I2MPB_B_GBE_MASK, (buf[1] + bias[4]) << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B1, + MTK_PHY_DA_TX_I2MPB_B_TBT_MASK, buf[1] + bias[5]); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2, + MTK_PHY_DA_TX_I2MPB_B_HBT_MASK, (buf[1] + bias[6]) << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_B2, + MTK_PHY_DA_TX_I2MPB_B_TST_MASK, buf[1] + bias[7]); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1, + MTK_PHY_DA_TX_I2MPB_C_GBE_MASK, (buf[2] + bias[8]) << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C1, + MTK_PHY_DA_TX_I2MPB_C_TBT_MASK, buf[2] + bias[9]); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2, + MTK_PHY_DA_TX_I2MPB_C_HBT_MASK, (buf[2] + bias[10]) << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_C2, + MTK_PHY_DA_TX_I2MPB_C_TST_MASK, buf[2] + bias[11]); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1, + MTK_PHY_DA_TX_I2MPB_D_GBE_MASK, (buf[3] + bias[12]) << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D1, + MTK_PHY_DA_TX_I2MPB_D_TBT_MASK, buf[3] + bias[13]); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2, + MTK_PHY_DA_TX_I2MPB_D_HBT_MASK, (buf[3] + bias[14]) << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_TX_I2MPB_TEST_MODE_D2, + MTK_PHY_DA_TX_I2MPB_D_TST_MASK, buf[3] + bias[15]); + + return 0; +} + +static int tx_amp_cal_efuse(struct phy_device *phydev, u32 *buf) +{ + u16 tx_amp_cal_val[4]; + + tx_amp_cal_val[0] = EFS_DA_TX_I2MPB_A(buf[0]); + tx_amp_cal_val[1] = EFS_DA_TX_I2MPB_B(buf[0]); + tx_amp_cal_val[2] = EFS_DA_TX_I2MPB_C(buf[0]); + tx_amp_cal_val[3] = EFS_DA_TX_I2MPB_D(buf[0]); + tx_amp_fill_result(phydev, tx_amp_cal_val); + + return 0; +} + +static int tx_r50_fill_result(struct phy_device *phydev, u16 tx_r50_cal_val, + u8 txg_calen_x) +{ + int bias = 0; + u16 reg, val; + + if (phydev->drv->phy_id == MTK_GPHY_ID_MT7988) + bias = -2; + + val = clamp_val(bias + tx_r50_cal_val, 0, 63); + + switch (txg_calen_x) { + case PAIR_A: + reg = MTK_PHY_DA_TX_R50_PAIR_A; + break; + case PAIR_B: + reg = MTK_PHY_DA_TX_R50_PAIR_B; + break; + case PAIR_C: + reg = MTK_PHY_DA_TX_R50_PAIR_C; + break; + case PAIR_D: + reg = MTK_PHY_DA_TX_R50_PAIR_D; + break; + default: + return -EINVAL; + } + + phy_write_mmd(phydev, MDIO_MMD_VEND1, reg, val | val << 8); + + return 0; +} + +static int tx_r50_cal_efuse(struct phy_device *phydev, u32 *buf, + u8 txg_calen_x) +{ + u16 tx_r50_cal_val; + + switch (txg_calen_x) { + case PAIR_A: + tx_r50_cal_val = EFS_DA_TX_R50_A(buf[1]); + break; + case PAIR_B: + tx_r50_cal_val = EFS_DA_TX_R50_B(buf[1]); + break; + case PAIR_C: + tx_r50_cal_val = EFS_DA_TX_R50_C(buf[2]); + break; + case PAIR_D: + tx_r50_cal_val = EFS_DA_TX_R50_D(buf[2]); + break; + default: + return -EINVAL; + } + tx_r50_fill_result(phydev, tx_r50_cal_val, txg_calen_x); + + return 0; +} + +static int tx_vcm_cal_sw(struct phy_device *phydev, u8 rg_txreserve_x) +{ + u8 lower_idx, upper_idx, txreserve_val; + u8 lower_ret, upper_ret; + int ret; + + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ANA_CALEN); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_CAL_CKINV); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_TXVOS_CALEN); + + switch (rg_txreserve_x) { + case PAIR_A: + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN0_A, + MTK_PHY_DASN_DAC_IN0_A_MASK); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN1_A, + MTK_PHY_DASN_DAC_IN1_A_MASK); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ZCALEN_A); + break; + case PAIR_B: + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN0_B, + MTK_PHY_DASN_DAC_IN0_B_MASK); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN1_B, + MTK_PHY_DASN_DAC_IN1_B_MASK); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_B); + break; + case PAIR_C: + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN0_C, + MTK_PHY_DASN_DAC_IN0_C_MASK); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN1_C, + MTK_PHY_DASN_DAC_IN1_C_MASK); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_C); + break; + case PAIR_D: + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN0_D, + MTK_PHY_DASN_DAC_IN0_D_MASK); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DASN_DAC_IN1_D, + MTK_PHY_DASN_DAC_IN1_D_MASK); + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_D); + break; + default: + ret = -EINVAL; + goto restore; + } + + lower_idx = TXRESERVE_MIN; + upper_idx = TXRESERVE_MAX; + + phydev_dbg(phydev, "Start TX-VCM SW cal.\n"); + while ((upper_idx - lower_idx) > 1) { + txreserve_val = DIV_ROUND_CLOSEST(lower_idx + upper_idx, 2); + ret = cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9, + MTK_PHY_DA_RX_PSBN_TBT_MASK | + MTK_PHY_DA_RX_PSBN_HBT_MASK | + MTK_PHY_DA_RX_PSBN_GBE_MASK | + MTK_PHY_DA_RX_PSBN_LP_MASK, + txreserve_val << 12 | txreserve_val << 8 | + txreserve_val << 4 | txreserve_val); + if (ret == 1) { + upper_idx = txreserve_val; + upper_ret = ret; + } else if (ret == 0) { + lower_idx = txreserve_val; + lower_ret = ret; + } else { + goto restore; + } + } + + if (lower_idx == TXRESERVE_MIN) { + lower_ret = cal_cycle(phydev, MDIO_MMD_VEND1, + MTK_PHY_RXADC_CTRL_RG9, + MTK_PHY_DA_RX_PSBN_TBT_MASK | + MTK_PHY_DA_RX_PSBN_HBT_MASK | + MTK_PHY_DA_RX_PSBN_GBE_MASK | + MTK_PHY_DA_RX_PSBN_LP_MASK, + lower_idx << 12 | lower_idx << 8 | + lower_idx << 4 | lower_idx); + ret = lower_ret; + } else if (upper_idx == TXRESERVE_MAX) { + upper_ret = cal_cycle(phydev, MDIO_MMD_VEND1, + MTK_PHY_RXADC_CTRL_RG9, + MTK_PHY_DA_RX_PSBN_TBT_MASK | + MTK_PHY_DA_RX_PSBN_HBT_MASK | + MTK_PHY_DA_RX_PSBN_GBE_MASK | + MTK_PHY_DA_RX_PSBN_LP_MASK, + upper_idx << 12 | upper_idx << 8 | + upper_idx << 4 | upper_idx); + ret = upper_ret; + } + if (ret < 0) + goto restore; + + /* We calibrate TX-VCM in different logic. Check upper index and then + * lower index. If this calibration is valid, apply lower index's result. + */ + ret = upper_ret - lower_ret; + if (ret == 1) { + ret = 0; + /* Make sure we use upper_idx in our calibration system */ + cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9, + MTK_PHY_DA_RX_PSBN_TBT_MASK | + MTK_PHY_DA_RX_PSBN_HBT_MASK | + MTK_PHY_DA_RX_PSBN_GBE_MASK | + MTK_PHY_DA_RX_PSBN_LP_MASK, + upper_idx << 12 | upper_idx << 8 | + upper_idx << 4 | upper_idx); + phydev_dbg(phydev, "TX-VCM SW cal result: 0x%x\n", upper_idx); + } else if (lower_idx == TXRESERVE_MIN && upper_ret == 1 && + lower_ret == 1) { + ret = 0; + cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9, + MTK_PHY_DA_RX_PSBN_TBT_MASK | + MTK_PHY_DA_RX_PSBN_HBT_MASK | + MTK_PHY_DA_RX_PSBN_GBE_MASK | + MTK_PHY_DA_RX_PSBN_LP_MASK, + lower_idx << 12 | lower_idx << 8 | + lower_idx << 4 | lower_idx); + phydev_warn(phydev, "TX-VCM SW cal result at low margin 0x%x\n", + lower_idx); + } else if (upper_idx == TXRESERVE_MAX && upper_ret == 0 && + lower_ret == 0) { + ret = 0; + phydev_warn(phydev, "TX-VCM SW cal result at high margin 0x%x\n", + upper_idx); + } else { + ret = -EINVAL; + } + +restore: + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ANA_CALEN); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_TXVOS_CALEN); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ZCALEN_A); + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_B | MTK_PHY_RG_ZCALEN_C | + MTK_PHY_RG_ZCALEN_D); + + return ret; +} + +static void mt798x_phy_common_finetune(struct phy_device *phydev) +{ + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + /* EnabRandUpdTrig = 1 */ + __phy_write(phydev, 0x11, 0x2f00); + __phy_write(phydev, 0x12, 0xe); + __phy_write(phydev, 0x10, 0x8fb0); + + /* NormMseLoThresh = 85 */ + __phy_write(phydev, 0x11, 0x55a0); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x83aa); + + /* TrFreeze = 0 */ + __phy_write(phydev, 0x11, 0x0); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x9686); + + /* SSTrKp1000Slv = 5 */ + __phy_write(phydev, 0x11, 0xbaef); + __phy_write(phydev, 0x12, 0x2e); + __phy_write(phydev, 0x10, 0x968c); + + /* MrvlTrFix100Kp = 3, MrvlTrFix100Kf = 2, + * MrvlTrFix1000Kp = 3, MrvlTrFix1000Kf = 2 + */ + __phy_write(phydev, 0x11, 0xd10a); + __phy_write(phydev, 0x12, 0x34); + __phy_write(phydev, 0x10, 0x8f82); + + /* VcoSlicerThreshBitsHigh */ + __phy_write(phydev, 0x11, 0x5555); + __phy_write(phydev, 0x12, 0x55); + __phy_write(phydev, 0x10, 0x8ec0); + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); + + /* TR_OPEN_LOOP_EN = 1, lpf_x_average = 9*/ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG234, + MTK_PHY_TR_OPEN_LOOP_EN_MASK | MTK_PHY_LPF_X_AVERAGE_MASK, + BIT(0) | FIELD_PREP(MTK_PHY_LPF_X_AVERAGE_MASK, 0x9)); + + /* rg_tr_lpf_cnt_val = 512 */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LPF_CNT_VAL, 0x200); + + /* IIR2 related */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_L, 0x82); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K1_U, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_L, 0x103); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K2_U, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_L, 0x82); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K3_U, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_L, 0xd177); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K4_U, 0x3); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_L, 0x2c82); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_LP_IIR2_K5_U, 0xe); + + /* FFE peaking */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27C, + MTK_PHY_VGASTATE_FFE_THR_ST1_MASK, 0x1b << 8); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG27D, + MTK_PHY_VGASTATE_FFE_THR_ST2_MASK, 0x1e); + + /* Disable LDO pump */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRAB, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_PUMP_EN_PAIRCD, 0x0); + /* Adjust LDO output voltage */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LDO_OUTPUT_V, 0x2222); +} + +static void mt7981_phy_finetune(struct phy_device *phydev) +{ + u16 val[8] = { 0x01ce, 0x01c1, + 0x020f, 0x0202, + 0x03d0, 0x03c0, + 0x0013, 0x0005 }; + int i, k; + + /* 100M eye finetune: + * Keep middle level of TX MLT3 shapper as default. + * Only change TX MLT3 overshoot level here. + */ + for (k = 0, i = 1; i < 12; i++) { + if (i % 3 == 0) + continue; + phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[k++]); + } + + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + /* SlvDSPreadyTime = 24, MasDSPreadyTime = 24 */ + __phy_write(phydev, 0x11, 0xc71); + __phy_write(phydev, 0x12, 0xc); + __phy_write(phydev, 0x10, 0x8fae); + + /* ResetSyncOffset = 6 */ + __phy_write(phydev, 0x11, 0x600); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x8fc0); + + /* VgaDecRate = 1 */ + __phy_write(phydev, 0x11, 0x4c2a); + __phy_write(phydev, 0x12, 0x3e); + __phy_write(phydev, 0x10, 0x8fa4); + + /* FfeUpdGainForce = 4 */ + __phy_write(phydev, 0x11, 0x240); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x9680); + + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); +} + +static void mt7988_phy_finetune(struct phy_device *phydev) +{ + u16 val[12] = { 0x0187, 0x01cd, 0x01c8, 0x0182, + 0x020d, 0x0206, 0x0384, 0x03d0, + 0x03c6, 0x030a, 0x0011, 0x0005 }; + int i; + + /* Set default MLT3 shaper first */ + for (i = 0; i < 12; i++) + phy_write_mmd(phydev, MDIO_MMD_VEND1, i, val[i]); + + /* TCT finetune */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_TX_FILTER, 0x5); + + /* Disable TX power saving */ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG7, + MTK_PHY_DA_AD_BUF_BIAS_LP_MASK, 0x3 << 8); + + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + + /* SlvDSPreadyTime = 24, MasDSPreadyTime = 12 */ + __phy_write(phydev, 0x11, 0x671); + __phy_write(phydev, 0x12, 0xc); + __phy_write(phydev, 0x10, 0x8fae); + + /* ResetSyncOffset = 5 */ + __phy_write(phydev, 0x11, 0x500); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x8fc0); + + /* VgaDecRate is 1 at default on mt7988 */ + + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); + + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_2A30); + /* TxClkOffset = 2 */ + __phy_modify(phydev, MTK_PHY_ANARG_RG, MTK_PHY_TCLKOFFSET_MASK, + FIELD_PREP(MTK_PHY_TCLKOFFSET_MASK, 0x2)); + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); +} + +static void mt798x_phy_eee(struct phy_device *phydev) +{ + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120, + MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK | + MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK, + FIELD_PREP(MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK, 0x0) | + FIELD_PREP(MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK, 0x14)); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122, + MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK, + FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK, + 0xff)); + + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_TESTMUX_ADC_CTRL, + MTK_PHY_RG_TXEN_DIG_MASK); + + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DEV1E_REG19b, MTK_PHY_BYPASS_DSP_LPI_READY); + + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_DEV1E_REG234, MTK_PHY_TR_LP_IIR_EEE_EN); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG238, + MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK | + MTK_PHY_LPI_SLV_SEND_TX_EN, + FIELD_PREP(MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK, 0x120)); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG239, + MTK_PHY_LPI_SEND_LOC_TIMER_MASK | + MTK_PHY_LPI_TXPCS_LOC_RCV, + FIELD_PREP(MTK_PHY_LPI_SEND_LOC_TIMER_MASK, 0x117)); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2C7, + MTK_PHY_MAX_GAIN_MASK | MTK_PHY_MIN_GAIN_MASK, + FIELD_PREP(MTK_PHY_MAX_GAIN_MASK, 0x8) | + FIELD_PREP(MTK_PHY_MIN_GAIN_MASK, 0x13)); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG2D1, + MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK, + FIELD_PREP(MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK, + 0x33) | + MTK_PHY_LPI_SKIP_SD_SLV_TR | MTK_PHY_LPI_TR_READY | + MTK_PHY_LPI_VCO_EEE_STG0_EN); + + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG323, + MTK_PHY_EEE_WAKE_MAS_INT_DC | + MTK_PHY_EEE_WAKE_SLV_INT_DC); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG324, + MTK_PHY_SMI_DETCNT_MAX_MASK, + FIELD_PREP(MTK_PHY_SMI_DETCNT_MAX_MASK, 0x3f) | + MTK_PHY_SMI_DET_MAX_EN); + + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DEV1E_REG326, + MTK_PHY_LPI_MODE_SD_ON | MTK_PHY_RESET_RANDUPD_CNT | + MTK_PHY_TREC_UPDATE_ENAB_CLR | + MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF | + MTK_PHY_TR_READY_SKIP_AFE_WAKEUP); + + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + /* Regsigdet_sel_1000 = 0 */ + __phy_write(phydev, 0x11, 0xb); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x9690); + + /* REG_EEE_st2TrKf1000 = 3 */ + __phy_write(phydev, 0x11, 0x114f); + __phy_write(phydev, 0x12, 0x2); + __phy_write(phydev, 0x10, 0x969a); + + /* RegEEE_slv_wake_tr_timer_tar = 6, RegEEE_slv_remtx_timer_tar = 20 */ + __phy_write(phydev, 0x11, 0x3028); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x969e); + + /* RegEEE_slv_wake_int_timer_tar = 8 */ + __phy_write(phydev, 0x11, 0x5010); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x96a0); + + /* RegEEE_trfreeze_timer2 = 586 */ + __phy_write(phydev, 0x11, 0x24a); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x96a8); + + /* RegEEE100Stg1_tar = 16 */ + __phy_write(phydev, 0x11, 0x3210); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x96b8); + + /* REGEEE_wake_slv_tr_wait_dfesigdet_en = 1 */ + __phy_write(phydev, 0x11, 0x1463); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x96ca); + + /* DfeTailEnableVgaThresh1000 = 27 */ + __phy_write(phydev, 0x11, 0x36); + __phy_write(phydev, 0x12, 0x0); + __phy_write(phydev, 0x10, 0x8f80); + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); + + phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3); + __phy_modify(phydev, MTK_PHY_LPI_REG_14, MTK_PHY_LPI_WAKE_TIMER_1000_MASK, + FIELD_PREP(MTK_PHY_LPI_WAKE_TIMER_1000_MASK, 0x19c)); + + __phy_modify(phydev, MTK_PHY_LPI_REG_1c, MTK_PHY_SMI_DET_ON_THRESH_MASK, + FIELD_PREP(MTK_PHY_SMI_DET_ON_THRESH_MASK, 0xc)); + phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122, + MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK, + FIELD_PREP(MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK, 0xff)); +} + +static int cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item, + u8 start_pair, u8 end_pair) +{ + u8 pair_n; + int ret; + + for (pair_n = start_pair; pair_n <= end_pair; pair_n++) { + /* TX_OFFSET & TX_AMP have no SW calibration. */ + switch (cal_item) { + case TX_VCM: + ret = tx_vcm_cal_sw(phydev, pair_n); + break; + default: + return -EINVAL; + } + if (ret) + return ret; + } + return 0; +} + +static int cal_efuse(struct phy_device *phydev, enum CAL_ITEM cal_item, + u8 start_pair, u8 end_pair, u32 *buf) +{ + u8 pair_n; + int ret; + + for (pair_n = start_pair; pair_n <= end_pair; pair_n++) { + /* TX_VCM has no efuse calibration. */ + switch (cal_item) { + case REXT: + ret = rext_cal_efuse(phydev, buf); + break; + case TX_OFFSET: + ret = tx_offset_cal_efuse(phydev, buf); + break; + case TX_AMP: + ret = tx_amp_cal_efuse(phydev, buf); + break; + case TX_R50: + ret = tx_r50_cal_efuse(phydev, buf, pair_n); + break; + default: + return -EINVAL; + } + if (ret) + return ret; + } + + return 0; +} + +static int start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item, + enum CAL_MODE cal_mode, u8 start_pair, + u8 end_pair, u32 *buf) +{ + int ret; + + switch (cal_mode) { + case EFUSE_M: + ret = cal_efuse(phydev, cal_item, start_pair, + end_pair, buf); + break; + case SW_M: + ret = cal_sw(phydev, cal_item, start_pair, end_pair); + break; + default: + return -EINVAL; + } + + if (ret) { + phydev_err(phydev, "cal %d failed\n", cal_item); + return -EIO; + } + + return 0; +} + +static int mt798x_phy_calibration(struct phy_device *phydev) +{ + int ret = 0; + u32 *buf; + size_t len; + struct nvmem_cell *cell; + + cell = nvmem_cell_get(&phydev->mdio.dev, "phy-cal-data"); + if (IS_ERR(cell)) { + if (PTR_ERR(cell) == -EPROBE_DEFER) + return PTR_ERR(cell); + return 0; + } + + buf = (u32 *)nvmem_cell_read(cell, &len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + nvmem_cell_put(cell); + + if (!buf[0] || !buf[1] || !buf[2] || !buf[3] || len < 4 * sizeof(u32)) { + phydev_err(phydev, "invalid efuse data\n"); + ret = -EINVAL; + goto out; + } + + ret = start_cal(phydev, REXT, EFUSE_M, NO_PAIR, NO_PAIR, buf); + if (ret) + goto out; + ret = start_cal(phydev, TX_OFFSET, EFUSE_M, NO_PAIR, NO_PAIR, buf); + if (ret) + goto out; + ret = start_cal(phydev, TX_AMP, EFUSE_M, NO_PAIR, NO_PAIR, buf); + if (ret) + goto out; + ret = start_cal(phydev, TX_R50, EFUSE_M, PAIR_A, PAIR_D, buf); + if (ret) + goto out; + ret = start_cal(phydev, TX_VCM, SW_M, PAIR_A, PAIR_A, buf); + if (ret) + goto out; + +out: + kfree(buf); + return ret; +} + +static int mt798x_phy_config_init(struct phy_device *phydev) +{ + switch (phydev->drv->phy_id) { + case MTK_GPHY_ID_MT7981: + mt7981_phy_finetune(phydev); + break; + case MTK_GPHY_ID_MT7988: + mt7988_phy_finetune(phydev); + break; + } + + mt798x_phy_common_finetune(phydev); + mt798x_phy_eee(phydev); + + return mt798x_phy_calibration(phydev); +} + +static struct phy_driver mtk_socphy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981), + .name = "MediaTek MT7981 PHY", + .config_init = mt798x_phy_config_init, + .config_intr = genphy_no_config_intr, + .handle_interrupt = genphy_handle_interrupt_no_ack, + .probe = mt798x_phy_calibration, + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_page = mtk_socphy_read_page, + .write_page = mtk_socphy_write_page, + }, + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988), + .name = "MediaTek MT7988 PHY", + .config_init = mt798x_phy_config_init, + .config_intr = genphy_no_config_intr, + .handle_interrupt = genphy_handle_interrupt_no_ack, + .probe = mt798x_phy_calibration, + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_page = mtk_socphy_read_page, + .write_page = mtk_socphy_write_page, + }, +}; + +module_phy_driver(mtk_socphy_driver); + +static struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = { + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) }, + { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) }, + { } +}; + +MODULE_DESCRIPTION("MediaTek SoC Gigabit Ethernet PHY driver"); +MODULE_AUTHOR("Daniel Golle "); +MODULE_AUTHOR("SkyLake Huang "); +MODULE_LICENSE("GPL"); + +MODULE_DEVICE_TABLE(mdio, mtk_socphy_tbl); diff --git a/drivers/net/phy/mediatek-ge.c b/drivers/net/phy/mediatek-ge.c index 68ee434f9dea..a493ae01b267 100644 --- a/drivers/net/phy/mediatek-ge.c +++ b/drivers/net/phy/mediatek-ge.c @@ -102,7 +102,8 @@ static struct phy_driver mtk_gephy_driver[] = { module_phy_driver(mtk_gephy_driver); static struct mdio_device_id __maybe_unused mtk_gephy_tbl[] = { - { PHY_ID_MATCH_VENDOR(0x03a29400) }, + { PHY_ID_MATCH_EXACT(0x03a29441) }, + { PHY_ID_MATCH_EXACT(0x03a29412) }, { } }; -- cgit v1.2.3 From b9dc1046edfeb7d9dbc2272c8d9ad5a8c47f3199 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Fri, 9 Jun 2023 10:03:04 +0200 Subject: net: phylink: report correct max speed for QUSGMII Q-USGMII is the quad port version of USGMII, and supports a max speed of 1Gbps on each line. Make so that phylink_interface_max_speed() reports this information correctly. Fixes: ae0e4bb2a0e0 ("net: phylink: Adjust link settings based on rate matching") Signed-off-by: Maxime Chevallier Reviewed-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index b4831110003c..809e6d5216dc 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -188,6 +188,7 @@ static int phylink_interface_max_speed(phy_interface_t interface) case PHY_INTERFACE_MODE_RGMII_ID: case PHY_INTERFACE_MODE_RGMII: case PHY_INTERFACE_MODE_QSGMII: + case PHY_INTERFACE_MODE_QUSGMII: case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_GMII: return SPEED_1000; @@ -204,7 +205,6 @@ static int phylink_interface_max_speed(phy_interface_t interface) case PHY_INTERFACE_MODE_10GBASER: case PHY_INTERFACE_MODE_10GKR: case PHY_INTERFACE_MODE_USXGMII: - case PHY_INTERFACE_MODE_QUSGMII: return SPEED_10000; case PHY_INTERFACE_MODE_25GBASER: -- cgit v1.2.3 From 923454c0368b8092e9d05c020f50abca577e7290 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Fri, 9 Jun 2023 10:03:05 +0200 Subject: net: phylink: use a dedicated helper to parse usgmii control word Q-USGMII is a derivative of USGMII, that uses a specific formatting for the control word. The layout is close to the USXGMII control word, but doesn't support speeds over 1Gbps. Use a dedicated decoding logic for the USGMII control word, re-using USXGMII definitions but only considering 10/100/1000Mbps speeds Fixes: 5e61fe157a27 ("net: phy: Introduce QUSGMII PHY mode") Signed-off-by: Maxime Chevallier Reviewed-by: Russell King (Oracle) Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 809e6d5216dc..5efdeb59f4b2 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -3298,6 +3298,41 @@ void phylink_decode_usxgmii_word(struct phylink_link_state *state, } EXPORT_SYMBOL_GPL(phylink_decode_usxgmii_word); +/** + * phylink_decode_usgmii_word() - decode the USGMII word from a MAC PCS + * @state: a pointer to a struct phylink_link_state. + * @lpa: a 16 bit value which stores the USGMII auto-negotiation word + * + * Helper for MAC PCS supporting the USGMII protocol and the auto-negotiation + * code word. Decode the USGMII code word and populate the corresponding fields + * (speed, duplex) into the phylink_link_state structure. The structure for this + * word is the same as the USXGMII word, except it only supports speeds up to + * 1Gbps. + */ +static void phylink_decode_usgmii_word(struct phylink_link_state *state, + uint16_t lpa) +{ + switch (lpa & MDIO_USXGMII_SPD_MASK) { + case MDIO_USXGMII_10: + state->speed = SPEED_10; + break; + case MDIO_USXGMII_100: + state->speed = SPEED_100; + break; + case MDIO_USXGMII_1000: + state->speed = SPEED_1000; + break; + default: + state->link = false; + return; + } + + if (lpa & MDIO_USXGMII_FULL_DUPLEX) + state->duplex = DUPLEX_FULL; + else + state->duplex = DUPLEX_HALF; +} + /** * phylink_mii_c22_pcs_decode_state() - Decode MAC PCS state from MII registers * @state: a pointer to a &struct phylink_link_state. @@ -3335,9 +3370,11 @@ void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state, case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_QSGMII: - case PHY_INTERFACE_MODE_QUSGMII: phylink_decode_sgmii_word(state, lpa); break; + case PHY_INTERFACE_MODE_QUSGMII: + phylink_decode_usgmii_word(state, lpa); + break; default: state->link = false; -- cgit v1.2.3 From c938ab4da0eb1620ae3243b0b24c572ddfc318fc Mon Sep 17 00:00:00 2001 From: Andrew Lunn Date: Sat, 17 Jun 2023 17:55:00 +0200 Subject: net: phy: Manual remove LEDs to ensure correct ordering If the core is left to remove the LEDs via devm_, it is performed too late, after the PHY driver is removed from the PHY. This results in dereferencing a NULL pointer when the LED core tries to turn the LED off before destroying the LED. Manually unregister the LEDs at a safe point in phy_remove. Cc: stable@vger.kernel.org Reported-by: Florian Fainelli Suggested-by: Florian Fainelli Fixes: 01e5b728e9e4 ("net: phy: Add a binding for PHY LEDs") Signed-off-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/phy/phy_device.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 17d0d0555a79..53598210be6c 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -3021,6 +3021,15 @@ static int phy_led_blink_set(struct led_classdev *led_cdev, return err; } +static void phy_leds_unregister(struct phy_device *phydev) +{ + struct phy_led *phyled; + + list_for_each_entry(phyled, &phydev->leds, list) { + led_classdev_unregister(&phyled->led_cdev); + } +} + static int of_phy_led(struct phy_device *phydev, struct device_node *led) { @@ -3054,7 +3063,7 @@ static int of_phy_led(struct phy_device *phydev, init_data.fwnode = of_fwnode_handle(led); init_data.devname_mandatory = true; - err = devm_led_classdev_register_ext(dev, cdev, &init_data); + err = led_classdev_register_ext(dev, cdev, &init_data); if (err) return err; @@ -3083,6 +3092,7 @@ static int of_phy_leds(struct phy_device *phydev) err = of_phy_led(phydev, led); if (err) { of_node_put(led); + phy_leds_unregister(phydev); return err; } } @@ -3305,6 +3315,9 @@ static int phy_remove(struct device *dev) cancel_delayed_work_sync(&phydev->state_queue); + if (IS_ENABLED(CONFIG_PHYLIB_LEDS)) + phy_leds_unregister(phydev); + phydev->state = PHY_DOWN; sfp_bus_del_upstream(phydev->sfp_bus); -- cgit v1.2.3 From 988e8d90b3dc482637532e61bc2d58bfc4af5167 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 17 Jun 2023 16:24:37 +0200 Subject: net: phy: at803x: Use devm_regulator_get_enable_optional() Use devm_regulator_get_enable_optional() instead of hand writing it. It saves some line of code. Signed-off-by: Christophe JAILLET Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/phy/at803x.c | 44 +++++++------------------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 656136628ffd..c1f307d90518 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -304,7 +304,6 @@ struct at803x_priv { bool is_1000basex; struct regulator_dev *vddio_rdev; struct regulator_dev *vddh_rdev; - struct regulator *vddio; u64 stats[ARRAY_SIZE(at803x_hw_stats)]; }; @@ -824,11 +823,11 @@ static int at803x_parse_dt(struct phy_device *phydev) if (ret < 0) return ret; - priv->vddio = devm_regulator_get_optional(&phydev->mdio.dev, - "vddio"); - if (IS_ERR(priv->vddio)) { + ret = devm_regulator_get_enable_optional(&phydev->mdio.dev, + "vddio"); + if (ret) { phydev_err(phydev, "failed to get VDDIO regulator\n"); - return PTR_ERR(priv->vddio); + return ret; } /* Only AR8031/8033 support 1000Base-X for SFP modules */ @@ -856,12 +855,6 @@ static int at803x_probe(struct phy_device *phydev) if (ret) return ret; - if (priv->vddio) { - ret = regulator_enable(priv->vddio); - if (ret < 0) - return ret; - } - if (phydev->drv->phy_id == ATH8031_PHY_ID) { int ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG); int mode_cfg; @@ -869,10 +862,8 @@ static int at803x_probe(struct phy_device *phydev) .wolopts = 0, }; - if (ccr < 0) { - ret = ccr; - goto err; - } + if (ccr < 0) + return ccr; mode_cfg = ccr & AT803X_MODE_CFG_MASK; switch (mode_cfg) { @@ -890,25 +881,11 @@ static int at803x_probe(struct phy_device *phydev) ret = at803x_set_wol(phydev, &wol); if (ret < 0) { phydev_err(phydev, "failed to disable WOL on probe: %d\n", ret); - goto err; + return ret; } } return 0; - -err: - if (priv->vddio) - regulator_disable(priv->vddio); - - return ret; -} - -static void at803x_remove(struct phy_device *phydev) -{ - struct at803x_priv *priv = phydev->priv; - - if (priv->vddio) - regulator_disable(priv->vddio); } static int at803x_get_features(struct phy_device *phydev) @@ -2021,7 +1998,6 @@ static struct phy_driver at803x_driver[] = { .name = "Qualcomm Atheros AR8035", .flags = PHY_POLL_CABLE_TEST, .probe = at803x_probe, - .remove = at803x_remove, .config_aneg = at803x_config_aneg, .config_init = at803x_config_init, .soft_reset = genphy_soft_reset, @@ -2043,7 +2019,6 @@ static struct phy_driver at803x_driver[] = { .name = "Qualcomm Atheros AR8030", .phy_id_mask = AT8030_PHY_ID_MASK, .probe = at803x_probe, - .remove = at803x_remove, .config_init = at803x_config_init, .link_change_notify = at803x_link_change_notify, .set_wol = at803x_set_wol, @@ -2059,7 +2034,6 @@ static struct phy_driver at803x_driver[] = { .name = "Qualcomm Atheros AR8031/AR8033", .flags = PHY_POLL_CABLE_TEST, .probe = at803x_probe, - .remove = at803x_remove, .config_init = at803x_config_init, .config_aneg = at803x_config_aneg, .soft_reset = genphy_soft_reset, @@ -2082,7 +2056,6 @@ static struct phy_driver at803x_driver[] = { PHY_ID_MATCH_EXACT(ATH8032_PHY_ID), .name = "Qualcomm Atheros AR8032", .probe = at803x_probe, - .remove = at803x_remove, .flags = PHY_POLL_CABLE_TEST, .config_init = at803x_config_init, .link_change_notify = at803x_link_change_notify, @@ -2100,7 +2073,6 @@ static struct phy_driver at803x_driver[] = { PHY_ID_MATCH_EXACT(ATH9331_PHY_ID), .name = "Qualcomm Atheros AR9331 built-in PHY", .probe = at803x_probe, - .remove = at803x_remove, .suspend = at803x_suspend, .resume = at803x_resume, .flags = PHY_POLL_CABLE_TEST, @@ -2117,7 +2089,6 @@ static struct phy_driver at803x_driver[] = { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID), .name = "Qualcomm Atheros QCA9561 built-in PHY", .probe = at803x_probe, - .remove = at803x_remove, .suspend = at803x_suspend, .resume = at803x_resume, .flags = PHY_POLL_CABLE_TEST, @@ -2183,7 +2154,6 @@ static struct phy_driver at803x_driver[] = { .name = "Qualcomm QCA8081", .flags = PHY_POLL_CABLE_TEST, .probe = at803x_probe, - .remove = at803x_remove, .config_intr = at803x_config_intr, .handle_interrupt = at803x_handle_interrupt, .get_tunable = at803x_get_tunable, -- cgit v1.2.3 From 462a3daad679406eed5d31b6bed8a19c236e1352 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 16 Jun 2023 11:29:54 +0200 Subject: net: phy: mediatek: fix compile-test dependencies The new phy driver attempts to select a driver from another subsystem, but that fails when the NVMEM subsystem is disabled: WARNING: unmet direct dependencies detected for NVMEM_MTK_EFUSE Depends on [n]: NVMEM [=n] && (ARCH_MEDIATEK [=n] || COMPILE_TEST [=y]) && HAS_IOMEM [=y] Selected by [y]: - MEDIATEK_GE_SOC_PHY [=y] && NETDEVICES [=y] && PHYLIB [=y] && (ARM64 && ARCH_MEDIATEK [=n] || COMPILE_TEST [=y]) I could not see an actual compile time dependency, so presumably this is only needed for for working correctly but not technically a dependency on that particular nvmem driver implementation, so it would likely be safe to remove the select for compile testing. To keep the spirit of the original 'select', just replace this with a 'depends on' that ensures that the driver will work but does not get in the way of build testing. Fixes: 98c485eaf509b ("net: phy: add driver for MediaTek SoC built-in GE PHYs") Signed-off-by: Arnd Bergmann Reviewed-by: Simon Horman Acked-by: Randy Dunlap Tested-by: Randy Dunlap # build-tested Reviewed-by: Daniel Golle Link: https://lore.kernel.org/r/20230616093009.3511692-1-arnd@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/phy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index a40269c17597..78e6981650d9 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -239,7 +239,7 @@ config MEDIATEK_GE_PHY config MEDIATEK_GE_SOC_PHY tristate "MediaTek SoC Ethernet PHYs" depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST - select NVMEM_MTK_EFUSE + depends on NVMEM_MTK_EFUSE help Supports MediaTek SoC built-in Gigabit Ethernet PHYs. -- cgit v1.2.3 From 408c090002c8ca5da3da1417d1d675583379fae6 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 19 Jun 2023 17:49:48 +0800 Subject: net: mdio: fix the wrong parameters PHY address and device address are passed in the wrong order. Cc: stable@vger.kernel.org Fixes: 4e4aafcddbbf ("net: mdio: Add dedicated C45 API to MDIO bus drivers") Signed-off-by: Jiawen Wu Reviewed-by: Andrew Lunn Reviewed-by: Russell King (Oracle) Link: https://lore.kernel.org/r/20230619094948.84452-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mdio_bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 389f33a12534..8b3618d3da4a 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -1287,7 +1287,7 @@ EXPORT_SYMBOL_GPL(mdiobus_modify_changed); * @mask: bit mask of bits to clear * @set: bit mask of bits to set */ -int mdiobus_c45_modify_changed(struct mii_bus *bus, int devad, int addr, +int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad, u32 regnum, u16 mask, u16 set) { int err; -- cgit v1.2.3 From a129b41fe0a8b4da828c46b10f5244ca07a3fec3 Mon Sep 17 00:00:00 2001 From: Francesco Dolcini Date: Mon, 19 Jun 2023 17:44:35 +0200 Subject: Revert "net: phy: dp83867: perform soft reset and retain established link" This reverts commit da9ef50f545f86ffe6ff786174d26500c4db737a. This fixes a regression in which the link would come up, but no communication was possible. The reverted commit was also removing a comment about DP83867_PHYCR_FORCE_LINK_GOOD, this is not added back in this commits since it seems that this is unrelated to the original code change. Closes: https://lore.kernel.org/all/ZGuDJos8D7N0J6Z2@francesco-nb.int.toradex.com/ Fixes: da9ef50f545f ("net: phy: dp83867: perform soft reset and retain established link") Signed-off-by: Francesco Dolcini Reviewed-by: Andrew Lunn Reviewed-by: Praneeth Bajjuri Link: https://lore.kernel.org/r/20230619154435.355485-1-francesco@dolcini.it Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83867.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 76f5a2402fb0..e397e7d642d9 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -936,7 +936,7 @@ static int dp83867_phy_reset(struct phy_device *phydev) { int err; - err = phy_write(phydev, DP83867_CTRL, DP83867_SW_RESTART); + err = phy_write(phydev, DP83867_CTRL, DP83867_SW_RESET); if (err < 0) return err; -- cgit v1.2.3 From b7c31ccd60d1df4634670e3eb9902baa19b9517b Mon Sep 17 00:00:00 2001 From: Andrew Lunn Date: Tue, 20 Jun 2023 00:03:32 +0200 Subject: net: phy-c45: Fix genphy_c45_ethtool_set_eee description The text has been cut/paste from genphy_c45_ethtool_get_eee but not changed to reflect it performs set. Additionally, extend the comment. This function implements the logic that eee_enabled has global control over EEE. When eee_enabled is false, no link modes will be advertised, and as a result, the MAC should not transmit LPI. Signed-off-by: Andrew Lunn Link: https://lore.kernel.org/r/20230619220332.4038924-1-andrew@lunn.ch Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-c45.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index fee514b96ab1..93ed07223377 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -1425,12 +1425,15 @@ int genphy_c45_ethtool_get_eee(struct phy_device *phydev, EXPORT_SYMBOL(genphy_c45_ethtool_get_eee); /** - * genphy_c45_ethtool_set_eee - get EEE supported and status + * genphy_c45_ethtool_set_eee - set EEE supported and status * @phydev: target phy_device struct * @data: ethtool_eee data * - * Description: it reportes the Supported/Advertisement/LP Advertisement - * capabilities. + * Description: sets the Supported/Advertisement/LP Advertisement + * capabilities. If eee_enabled is false, no links modes are + * advertised, but the previously advertised link modes are + * retained. This allows EEE to be enabled/disabled in a + * non-destructive way. */ int genphy_c45_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) -- cgit v1.2.3 From cc75549548482ed653c23f212544e58cb38ea980 Mon Sep 17 00:00:00 2001 From: Horatiu Vultur Date: Thu, 15 Jun 2023 11:47:40 +0200 Subject: net: micrel: Change to receive timestamp in the frame for lan8841 Currently for each timestamp frame, the SW needs to go and read the received timestamp over the MDIO bus. But the HW has the capability to store the received nanoseconds part and the least significant two bits of the seconds in the reserved field of the PTP header. In this way we could save few MDIO transactions (actually a little more transactions because the access to the PTP registers are indirect) for each received frame. Instead of reading the rest of seconds part of the timestamp of the frame using MDIO transactions schedule PTP worker thread to read the seconds part every 500ms and then for each of the received frames use this information. Because if for example running with 512 frames per second, there is no point to read 512 times the second part. Doing all these changes will give a great CPU usage performance. Running ptp4l with logSyncInterval of -9 will give a ~60% CPU improvement. Signed-off-by: Horatiu Vultur Acked-by: Richard Cochran Signed-off-by: David S. Miller --- drivers/net/phy/micrel.c | 250 +++++++++++++++++++++++++++++------------------ 1 file changed, 154 insertions(+), 96 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 6d18ea19e442..b6d7981b2d1e 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -252,6 +252,9 @@ #define PS_TO_REG 200 #define FIFO_SIZE 8 +/* Delay used to get the second part from the LTC */ +#define LAN8841_GET_SEC_LTC_DELAY (500 * NSEC_PER_MSEC) + struct kszphy_hw_stat { const char *string; u8 reg; @@ -319,6 +322,10 @@ struct kszphy_ptp_priv { /* Lock for ptp_clock */ struct mutex ptp_lock; struct ptp_pin_desc *pin_config; + + s64 seconds; + /* Lock for accessing seconds */ + spinlock_t seconds_lock; }; struct kszphy_priv { @@ -3311,6 +3318,9 @@ static int lan8814_probe(struct phy_device *phydev) #define LAN8841_PTP_CMD_CTL_PTP_RESET BIT(0) #define LAN8841_PTP_RX_PARSE_CONFIG 368 #define LAN8841_PTP_TX_PARSE_CONFIG 432 +#define LAN8841_PTP_RX_MODE 381 +#define LAN8841_PTP_INSERT_TS_EN BIT(0) +#define LAN8841_PTP_INSERT_TS_32BIT BIT(1) static int lan8841_config_init(struct phy_device *phydev) { @@ -3459,68 +3469,18 @@ static void lan8841_ptp_process_tx_ts(struct kszphy_ptp_priv *ptp_priv) lan8814_match_tx_skb(ptp_priv, sec, nsec, seq); } -#define LAN8841_PTP_RX_INGRESS_SEC_LO 389 -#define LAN8841_PTP_RX_INGRESS_SEC_HI 388 -#define LAN8841_PTP_RX_INGRESS_NS_LO 387 -#define LAN8841_PTP_RX_INGRESS_NS_HI 386 -#define LAN8841_PTP_RX_INGRESS_NSEC_HI_VALID BIT(15) -#define LAN8841_PTP_RX_MSG_HEADER2 391 - -static struct lan8814_ptp_rx_ts *lan8841_ptp_get_rx_ts(struct kszphy_ptp_priv *ptp_priv) -{ - struct phy_device *phydev = ptp_priv->phydev; - struct lan8814_ptp_rx_ts *rx_ts; - u32 sec, nsec; - u16 seq; - - nsec = phy_read_mmd(phydev, 2, LAN8841_PTP_RX_INGRESS_NS_HI); - if (!(nsec & LAN8841_PTP_RX_INGRESS_NSEC_HI_VALID)) - return NULL; - - nsec = ((nsec & 0x3fff) << 16); - nsec = nsec | phy_read_mmd(phydev, 2, LAN8841_PTP_RX_INGRESS_NS_LO); - - sec = phy_read_mmd(phydev, 2, LAN8841_PTP_RX_INGRESS_SEC_HI); - sec = sec << 16; - sec = sec | phy_read_mmd(phydev, 2, LAN8841_PTP_RX_INGRESS_SEC_LO); - - seq = phy_read_mmd(phydev, 2, LAN8841_PTP_RX_MSG_HEADER2); - - rx_ts = kzalloc(sizeof(*rx_ts), GFP_KERNEL); - if (!rx_ts) - return NULL; - - rx_ts->seconds = sec; - rx_ts->nsec = nsec; - rx_ts->seq_id = seq; - - return rx_ts; -} - -static void lan8841_ptp_process_rx_ts(struct kszphy_ptp_priv *ptp_priv) -{ - struct lan8814_ptp_rx_ts *rx_ts; - - while ((rx_ts = lan8841_ptp_get_rx_ts(ptp_priv)) != NULL) - lan8814_match_rx_ts(ptp_priv, rx_ts); -} - #define LAN8841_PTP_INT_STS 259 #define LAN8841_PTP_INT_STS_PTP_TX_TS_OVRFL_INT BIT(13) #define LAN8841_PTP_INT_STS_PTP_TX_TS_INT BIT(12) -#define LAN8841_PTP_INT_STS_PTP_RX_TS_OVRFL_INT BIT(9) -#define LAN8841_PTP_INT_STS_PTP_RX_TS_INT BIT(8) #define LAN8841_PTP_INT_STS_PTP_GPIO_CAP_INT BIT(2) -static void lan8841_ptp_flush_fifo(struct kszphy_ptp_priv *ptp_priv, bool egress) +static void lan8841_ptp_flush_fifo(struct kszphy_ptp_priv *ptp_priv) { struct phy_device *phydev = ptp_priv->phydev; int i; for (i = 0; i < FIFO_SIZE; ++i) - phy_read_mmd(phydev, 2, - egress ? LAN8841_PTP_TX_MSG_HEADER2 : - LAN8841_PTP_RX_MSG_HEADER2); + phy_read_mmd(phydev, 2, LAN8841_PTP_TX_MSG_HEADER2); phy_read_mmd(phydev, 2, LAN8841_PTP_INT_STS); } @@ -3598,23 +3558,17 @@ static void lan8841_handle_ptp_interrupt(struct phy_device *phydev) if (status & LAN8841_PTP_INT_STS_PTP_TX_TS_INT) lan8841_ptp_process_tx_ts(ptp_priv); - if (status & LAN8841_PTP_INT_STS_PTP_RX_TS_INT) - lan8841_ptp_process_rx_ts(ptp_priv); - if (status & LAN8841_PTP_INT_STS_PTP_GPIO_CAP_INT) lan8841_gpio_process_cap(ptp_priv); if (status & LAN8841_PTP_INT_STS_PTP_TX_TS_OVRFL_INT) { - lan8841_ptp_flush_fifo(ptp_priv, true); + lan8841_ptp_flush_fifo(ptp_priv); skb_queue_purge(&ptp_priv->tx_queue); } - if (status & LAN8841_PTP_INT_STS_PTP_RX_TS_OVRFL_INT) { - lan8841_ptp_flush_fifo(ptp_priv, false); - skb_queue_purge(&ptp_priv->rx_queue); - } - - } while (status); + } while (status & (LAN8841_PTP_INT_STS_PTP_TX_TS_INT | + LAN8841_PTP_INT_STS_PTP_GPIO_CAP_INT | + LAN8841_PTP_INT_STS_PTP_TX_TS_OVRFL_INT)); } #define LAN8841_INTS_PTP BIT(9) @@ -3678,32 +3632,46 @@ static int lan8841_ts_info(struct mii_timestamper *mii_ts, #define LAN8841_PTP_INT_EN 260 #define LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN BIT(13) #define LAN8841_PTP_INT_EN_PTP_TX_TS_EN BIT(12) -#define LAN8841_PTP_INT_EN_PTP_RX_TS_OVRFL_EN BIT(9) -#define LAN8841_PTP_INT_EN_PTP_RX_TS_EN BIT(8) -static void lan8841_ptp_enable_int(struct kszphy_ptp_priv *ptp_priv, - bool enable) +static void lan8841_ptp_enable_processing(struct kszphy_ptp_priv *ptp_priv, + bool enable) { struct phy_device *phydev = ptp_priv->phydev; - if (enable) - /* Enable interrupts */ + if (enable) { + /* Enable interrupts on the TX side */ phy_modify_mmd(phydev, 2, LAN8841_PTP_INT_EN, LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN | - LAN8841_PTP_INT_EN_PTP_RX_TS_OVRFL_EN | - LAN8841_PTP_INT_EN_PTP_TX_TS_EN | - LAN8841_PTP_INT_EN_PTP_RX_TS_EN, + LAN8841_PTP_INT_EN_PTP_TX_TS_EN, LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN | - LAN8841_PTP_INT_EN_PTP_RX_TS_OVRFL_EN | - LAN8841_PTP_INT_EN_PTP_TX_TS_EN | - LAN8841_PTP_INT_EN_PTP_RX_TS_EN); - else - /* Disable interrupts */ + LAN8841_PTP_INT_EN_PTP_TX_TS_EN); + + /* Enable the modification of the frame on RX side, + * this will add the ns and 2 bits of sec in the reserved field + * of the PTP header + */ + phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG, + LAN8841_PTP_RX_MODE, + LAN8841_PTP_INSERT_TS_EN | + LAN8841_PTP_INSERT_TS_32BIT, + LAN8841_PTP_INSERT_TS_EN | + LAN8841_PTP_INSERT_TS_32BIT); + + ptp_schedule_worker(ptp_priv->ptp_clock, 0); + } else { + /* Disable interrupts on the TX side */ phy_modify_mmd(phydev, 2, LAN8841_PTP_INT_EN, LAN8841_PTP_INT_EN_PTP_TX_TS_OVRFL_EN | - LAN8841_PTP_INT_EN_PTP_RX_TS_OVRFL_EN | - LAN8841_PTP_INT_EN_PTP_TX_TS_EN | - LAN8841_PTP_INT_EN_PTP_RX_TS_EN, 0); + LAN8841_PTP_INT_EN_PTP_TX_TS_EN, 0); + + /* Disable modification of the RX frames */ + phy_modify_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG, + LAN8841_PTP_RX_MODE, + LAN8841_PTP_INSERT_TS_EN | + LAN8841_PTP_INSERT_TS_32BIT, 0); + + ptp_cancel_worker_sync(ptp_priv->ptp_clock); + } } #define LAN8841_PTP_RX_TIMESTAMP_EN 379 @@ -3714,7 +3682,6 @@ static int lan8841_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr) { struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); struct phy_device *phydev = ptp_priv->phydev; - struct lan8814_ptp_rx_ts *rx_ts, *tmp; struct hwtstamp_config config; int txcfg = 0, rxcfg = 0; int pkt_ts_enable; @@ -3778,24 +3745,61 @@ static int lan8841_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr) PTP_TX_MOD_TX_PTP_SYNC_TS_INSERT_ : 0); /* Now enable/disable the timestamping */ - lan8841_ptp_enable_int(ptp_priv, - config.rx_filter != HWTSTAMP_FILTER_NONE); - - /* In case of multiple starts and stops, these needs to be cleared */ - list_for_each_entry_safe(rx_ts, tmp, &ptp_priv->rx_ts_list, list) { - list_del(&rx_ts->list); - kfree(rx_ts); - } + lan8841_ptp_enable_processing(ptp_priv, + config.rx_filter != HWTSTAMP_FILTER_NONE); - skb_queue_purge(&ptp_priv->rx_queue); skb_queue_purge(&ptp_priv->tx_queue); - lan8841_ptp_flush_fifo(ptp_priv, false); - lan8841_ptp_flush_fifo(ptp_priv, true); + lan8841_ptp_flush_fifo(ptp_priv); return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? -EFAULT : 0; } +static bool lan8841_rxtstamp(struct mii_timestamper *mii_ts, + struct sk_buff *skb, int type) +{ + struct kszphy_ptp_priv *ptp_priv = + container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); + struct ptp_header *header = ptp_parse_header(skb, type); + struct skb_shared_hwtstamps *shhwtstamps; + struct timespec64 ts; + unsigned long flags; + u32 ts_header; + + if (!header) + return false; + + if (ptp_priv->rx_filter == HWTSTAMP_FILTER_NONE || + type == PTP_CLASS_NONE) + return false; + + if ((type & ptp_priv->version) == 0 || (type & ptp_priv->layer) == 0) + return false; + + spin_lock_irqsave(&ptp_priv->seconds_lock, flags); + ts.tv_sec = ptp_priv->seconds; + spin_unlock_irqrestore(&ptp_priv->seconds_lock, flags); + ts_header = __be32_to_cpu(header->reserved2); + + shhwtstamps = skb_hwtstamps(skb); + memset(shhwtstamps, 0, sizeof(*shhwtstamps)); + + /* Check for any wrap arounds for the second part */ + if ((ts.tv_sec & GENMASK(1, 0)) == 0 && (ts_header >> 30) == 3) + ts.tv_sec -= GENMASK(1, 0) + 1; + else if ((ts.tv_sec & GENMASK(1, 0)) == 3 && (ts_header >> 30) == 0) + ts.tv_sec += 1; + + shhwtstamps->hwtstamp = + ktime_set((ts.tv_sec & ~(GENMASK(1, 0))) | ts_header >> 30, + ts_header & GENMASK(29, 0)); + header->reserved2 = 0; + + netif_rx(skb); + + return true; +} + #define LAN8841_EVENT_A 0 #define LAN8841_EVENT_B 1 #define LAN8841_PTP_LTC_TARGET_SEC_HI(event) ((event) == LAN8841_EVENT_A ? 278 : 288) @@ -3880,6 +3884,7 @@ static int lan8841_ptp_settime64(struct ptp_clock_info *ptp, struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv, ptp_clock_info); struct phy_device *phydev = ptp_priv->phydev; + unsigned long flags; int ret; /* Set the value to be stored */ @@ -3896,6 +3901,10 @@ static int lan8841_ptp_settime64(struct ptp_clock_info *ptp, ret = lan8841_ptp_update_target(ptp_priv, ts); mutex_unlock(&ptp_priv->ptp_lock); + spin_lock_irqsave(&ptp_priv->seconds_lock, flags); + ptp_priv->seconds = ts->tv_sec; + spin_unlock_irqrestore(&ptp_priv->seconds_lock, flags); + return ret; } @@ -3936,6 +3945,30 @@ static int lan8841_ptp_gettime64(struct ptp_clock_info *ptp, return 0; } +static void lan8841_ptp_getseconds(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv, + ptp_clock_info); + struct phy_device *phydev = ptp_priv->phydev; + time64_t s; + + mutex_lock(&ptp_priv->ptp_lock); + /* Issue the command to read the LTC */ + phy_write_mmd(phydev, 2, LAN8841_PTP_CMD_CTL, + LAN8841_PTP_CMD_CTL_PTP_LTC_READ); + + /* Read the LTC */ + s = phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_HI); + s <<= 16; + s |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_MID); + s <<= 16; + s |= phy_read_mmd(phydev, 2, LAN8841_PTP_LTC_RD_SEC_LO); + mutex_unlock(&ptp_priv->ptp_lock); + + set_normalized_timespec64(ts, s, 0); +} + #define LAN8841_PTP_LTC_STEP_ADJ_LO 276 #define LAN8841_PTP_LTC_STEP_ADJ_HI 275 #define LAN8841_PTP_LTC_STEP_ADJ_DIR BIT(15) @@ -4438,6 +4471,22 @@ static int lan8841_ptp_enable(struct ptp_clock_info *ptp, return 0; } +static long lan8841_ptp_do_aux_work(struct ptp_clock_info *ptp) +{ + struct kszphy_ptp_priv *ptp_priv = container_of(ptp, struct kszphy_ptp_priv, + ptp_clock_info); + struct timespec64 ts; + unsigned long flags; + + lan8841_ptp_getseconds(&ptp_priv->ptp_clock_info, &ts); + + spin_lock_irqsave(&ptp_priv->seconds_lock, flags); + ptp_priv->seconds = ts.tv_sec; + spin_unlock_irqrestore(&ptp_priv->seconds_lock, flags); + + return nsecs_to_jiffies(LAN8841_GET_SEC_LTC_DELAY); +} + static struct ptp_clock_info lan8841_ptp_clock_info = { .owner = THIS_MODULE, .name = "lan8841 ptp", @@ -4448,6 +4497,7 @@ static struct ptp_clock_info lan8841_ptp_clock_info = { .adjfine = lan8841_ptp_adjfine, .verify = lan8841_ptp_verify, .enable = lan8841_ptp_enable, + .do_aux_work = lan8841_ptp_do_aux_work, .n_per_out = LAN8841_PTP_GPIO_NUM, .n_ext_ts = LAN8841_PTP_GPIO_NUM, .n_pins = LAN8841_PTP_GPIO_NUM, @@ -4508,13 +4558,11 @@ static int lan8841_probe(struct phy_device *phydev) /* Initialize the SW */ skb_queue_head_init(&ptp_priv->tx_queue); - skb_queue_head_init(&ptp_priv->rx_queue); - INIT_LIST_HEAD(&ptp_priv->rx_ts_list); - spin_lock_init(&ptp_priv->rx_ts_lock); ptp_priv->phydev = phydev; mutex_init(&ptp_priv->ptp_lock); + spin_lock_init(&ptp_priv->seconds_lock); - ptp_priv->mii_ts.rxtstamp = lan8814_rxtstamp; + ptp_priv->mii_ts.rxtstamp = lan8841_rxtstamp; ptp_priv->mii_ts.txtstamp = lan8814_txtstamp; ptp_priv->mii_ts.hwtstamp = lan8841_hwtstamp; ptp_priv->mii_ts.ts_info = lan8841_ts_info; @@ -4524,6 +4572,16 @@ static int lan8841_probe(struct phy_device *phydev) return 0; } +static int lan8841_suspend(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv; + + ptp_cancel_worker_sync(ptp_priv->ptp_clock); + + return genphy_suspend(phydev); +} + static struct phy_driver ksphy_driver[] = { { .phy_id = PHY_ID_KS8737, @@ -4747,7 +4805,7 @@ static struct phy_driver ksphy_driver[] = { .get_sset_count = kszphy_get_sset_count, .get_strings = kszphy_get_strings, .get_stats = kszphy_get_stats, - .suspend = genphy_suspend, + .suspend = lan8841_suspend, .resume = genphy_resume, .cable_test_start = lan8814_cable_test_start, .cable_test_get_status = ksz886x_cable_test_get_status, -- cgit v1.2.3 From f99d471afa03f770149f1cc60a288b9a08285903 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Fri, 16 Jun 2023 13:06:22 +0100 Subject: net: phylink: add PCS negotiation mode PCS have to work out whether they should enable PCS negotiation by looking at the "mode" and "interface" arguments, and the Autoneg bit in the advertising mask. This leads to some complex logic, so lets pull that out into phylink and instead pass a "neg_mode" argument to the PCS configuration and link up methods, instead of the "mode" argument. In order to transition drivers, add a "neg_mode" flag to the phylink PCS structure to PCS can indicate whether they want to be passed the neg_mode or the old mode argument. Signed-off-by: Russell King (Oracle) Link: https://lore.kernel.org/r/E1qA8De-00EaFA-Ht@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 45 +++++++++++++++----- include/linux/phylink.h | 104 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 132 insertions(+), 17 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 97c15e1f81de..5d8f8b84908c 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -71,6 +71,7 @@ struct phylink { struct mutex state_mutex; struct phylink_link_state phy_state; struct work_struct resolve; + unsigned int pcs_neg_mode; bool mac_link_dropped; bool using_mac_select_pcs; @@ -992,23 +993,23 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state) } } -static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int mode, +static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, const struct phylink_link_state *state, bool permit_pause_to_mac) { if (!pcs) return 0; - return pcs->ops->pcs_config(pcs, mode, state->interface, + return pcs->ops->pcs_config(pcs, neg_mode, state->interface, state->advertising, permit_pause_to_mac); } -static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, +static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, int speed, int duplex) { if (pcs && pcs->ops->pcs_link_up) - pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex); + pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex); } static void phylink_pcs_poll_stop(struct phylink *pl) @@ -1058,10 +1059,15 @@ static void phylink_major_config(struct phylink *pl, bool restart, struct phylink_pcs *pcs = NULL; bool pcs_changed = false; unsigned int rate_kbd; + unsigned int neg_mode; int err; phylink_dbg(pl, "major config %s\n", phy_modes(state->interface)); + pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode, + state->interface, + state->advertising); + if (pl->using_mac_select_pcs) { pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface); if (IS_ERR(pcs)) { @@ -1094,9 +1100,12 @@ static void phylink_major_config(struct phylink *pl, bool restart, phylink_mac_config(pl, state); - err = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, state, - !!(pl->link_config.pause & - MLO_PAUSE_AN)); + neg_mode = pl->cur_link_an_mode; + if (pl->pcs && pl->pcs->neg_mode) + neg_mode = pl->pcs_neg_mode; + + err = phylink_pcs_config(pl->pcs, neg_mode, state, + !!(pl->link_config.pause & MLO_PAUSE_AN)); if (err < 0) phylink_err(pl, "pcs_config failed: %pe\n", ERR_PTR(err)); @@ -1131,6 +1140,7 @@ static void phylink_major_config(struct phylink *pl, bool restart, */ static int phylink_change_inband_advert(struct phylink *pl) { + unsigned int neg_mode; int ret; if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) @@ -1149,12 +1159,20 @@ static int phylink_change_inband_advert(struct phylink *pl) __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising, pl->link_config.pause); + /* Recompute the PCS neg mode */ + pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode, + pl->link_config.interface, + pl->link_config.advertising); + + neg_mode = pl->cur_link_an_mode; + if (pl->pcs->neg_mode) + neg_mode = pl->pcs_neg_mode; + /* Modern PCS-based method; update the advert at the PCS, and * restart negotiation if the pcs_config() helper indicates that * the programmed advertisement has changed. */ - ret = phylink_pcs_config(pl->pcs, pl->cur_link_an_mode, - &pl->link_config, + ret = phylink_pcs_config(pl->pcs, neg_mode, &pl->link_config, !!(pl->link_config.pause & MLO_PAUSE_AN)); if (ret < 0) return ret; @@ -1257,6 +1275,7 @@ static void phylink_link_up(struct phylink *pl, struct phylink_link_state link_state) { struct net_device *ndev = pl->netdev; + unsigned int neg_mode; int speed, duplex; bool rx_pause; @@ -1287,8 +1306,12 @@ static void phylink_link_up(struct phylink *pl, pl->cur_interface = link_state.interface; - phylink_pcs_link_up(pl->pcs, pl->cur_link_an_mode, pl->cur_interface, - speed, duplex); + neg_mode = pl->cur_link_an_mode; + if (pl->pcs && pl->pcs->neg_mode) + neg_mode = pl->pcs_neg_mode; + + phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed, + duplex); pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode, pl->cur_interface, speed, duplex, diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 0cf07d7d11b8..2b322d7fa51a 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -21,6 +21,24 @@ enum { MLO_AN_FIXED, /* Fixed-link mode */ MLO_AN_INBAND, /* In-band protocol */ + /* PCS "negotiation" mode. + * PHYLINK_PCS_NEG_NONE - protocol has no inband capability + * PHYLINK_PCS_NEG_OUTBAND - some out of band or fixed link setting + * PHYLINK_PCS_NEG_INBAND_DISABLED - inband mode disabled, e.g. + * 1000base-X with autoneg off + * PHYLINK_PCS_NEG_INBAND_ENABLED - inband mode enabled + * Additionally, this can be tested using bitmasks: + * PHYLINK_PCS_NEG_INBAND - inband mode selected + * PHYLINK_PCS_NEG_ENABLED - negotiation mode enabled + */ + PHYLINK_PCS_NEG_NONE = 0, + PHYLINK_PCS_NEG_ENABLED = BIT(4), + PHYLINK_PCS_NEG_OUTBAND = BIT(5), + PHYLINK_PCS_NEG_INBAND = BIT(6), + PHYLINK_PCS_NEG_INBAND_DISABLED = PHYLINK_PCS_NEG_INBAND, + PHYLINK_PCS_NEG_INBAND_ENABLED = PHYLINK_PCS_NEG_INBAND | + PHYLINK_PCS_NEG_ENABLED, + /* MAC_SYM_PAUSE and MAC_ASYM_PAUSE are used when configuring our * autonegotiation advertisement. They correspond to the PAUSE and * ASM_DIR bits defined by 802.3, respectively. @@ -79,6 +97,70 @@ static inline bool phylink_autoneg_inband(unsigned int mode) return mode == MLO_AN_INBAND; } +/** + * phylink_pcs_neg_mode() - helper to determine PCS inband mode + * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND. + * @interface: interface mode to be used + * @advertising: adertisement ethtool link mode mask + * + * Determines the negotiation mode to be used by the PCS, and returns + * one of: + * %PHYLINK_PCS_NEG_NONE: interface mode does not support inband + * %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY) + * will be used. + * %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg disabled + * %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled + * + * Note: this is for cases where the PCS itself is involved in negotiation + * (e.g. Clause 37, SGMII and similar) not Clause 73. + */ +static inline unsigned int phylink_pcs_neg_mode(unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising) +{ + unsigned int neg_mode; + + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + case PHY_INTERFACE_MODE_QUSGMII: + case PHY_INTERFACE_MODE_USXGMII: + /* These protocols are designed for use with a PHY which + * communicates its negotiation result back to the MAC via + * inband communication. Note: there exist PHYs that run + * with SGMII but do not send the inband data. + */ + if (!phylink_autoneg_inband(mode)) + neg_mode = PHYLINK_PCS_NEG_OUTBAND; + else + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + break; + + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + /* 1000base-X is designed for use media-side for Fibre + * connections, and thus the Autoneg bit needs to be + * taken into account. We also do this for 2500base-X + * as well, but drivers may not support this, so may + * need to override this. + */ + if (!phylink_autoneg_inband(mode)) + neg_mode = PHYLINK_PCS_NEG_OUTBAND; + else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + advertising)) + neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; + else + neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED; + break; + + default: + neg_mode = PHYLINK_PCS_NEG_NONE; + break; + } + + return neg_mode; +} + /** * struct phylink_link_state - link state structure * @advertising: ethtool bitmask containing advertised link modes @@ -436,6 +518,7 @@ struct phylink_pcs_ops; /** * struct phylink_pcs - PHYLINK PCS instance * @ops: a pointer to the &struct phylink_pcs_ops structure + * @neg_mode: provide PCS neg mode via "mode" argument * @poll: poll the PCS for link changes * * This structure is designed to be embedded within the PCS private data, @@ -443,6 +526,7 @@ struct phylink_pcs_ops; */ struct phylink_pcs { const struct phylink_pcs_ops *ops; + bool neg_mode; bool poll; }; @@ -460,12 +544,12 @@ struct phylink_pcs_ops { const struct phylink_link_state *state); void (*pcs_get_state)(struct phylink_pcs *pcs, struct phylink_link_state *state); - int (*pcs_config)(struct phylink_pcs *pcs, unsigned int mode, + int (*pcs_config)(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac); void (*pcs_an_restart)(struct phylink_pcs *pcs); - void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int mode, + void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, int speed, int duplex); }; @@ -508,7 +592,7 @@ void pcs_get_state(struct phylink_pcs *pcs, /** * pcs_config() - Configure the PCS mode and advertisement * @pcs: a pointer to a &struct phylink_pcs. - * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND. + * @neg_mode: link negotiation mode (see below) * @interface: interface mode to be used * @advertising: adertisement ethtool link mode mask * @permit_pause_to_mac: permit forwarding pause resolution to MAC @@ -526,8 +610,12 @@ void pcs_get_state(struct phylink_pcs *pcs, * For 1000BASE-X, the advertisement should be programmed into the PCS. * * For most 10GBASE-R, there is no advertisement. + * + * The %neg_mode argument should be tested via the phylink_mode_*() family of + * functions, or for PCS that set pcs->neg_mode true, should be tested + * against the %PHYLINK_PCS_NEG_* definitions. */ -int pcs_config(struct phylink_pcs *pcs, unsigned int mode, +int pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac); @@ -543,7 +631,7 @@ void pcs_an_restart(struct phylink_pcs *pcs); /** * pcs_link_up() - program the PCS for the resolved link configuration * @pcs: a pointer to a &struct phylink_pcs. - * @mode: link autonegotiation mode + * @neg_mode: link negotiation mode (see below) * @interface: link &typedef phy_interface_t mode * @speed: link speed * @duplex: link duplex @@ -552,8 +640,12 @@ void pcs_an_restart(struct phylink_pcs *pcs); * the resolved link parameters. For example, a PCS operating in SGMII * mode without in-band AN needs to be manually configured for the link * and duplex setting. Otherwise, this should be a no-op. + * + * The %mode argument should be tested via the phylink_mode_*() family of + * functions, or for PCS that set pcs->neg_mode true, should be tested + * against the %PHYLINK_PCS_NEG_* definitions. */ -void pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, +void pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, int speed, int duplex); #endif -- cgit v1.2.3 From cdb08aa0473730315dbc088d5394e59622314034 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Fri, 16 Jun 2023 13:06:27 +0100 Subject: net: phylink: convert phylink_mii_c22_pcs_config() to neg_mode Use phylink_pcs_neg_mode() for phylink_mii_c22_pcs_config(). This results in no functional change. Signed-off-by: Russell King (Oracle) Link: https://lore.kernel.org/r/E1qA8Dj-00EaFG-Mt@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 5d8f8b84908c..567fd22a8924 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -3558,6 +3558,7 @@ int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode, phy_interface_t interface, const unsigned long *advertising) { + unsigned int neg_mode; bool changed = 0; u16 bmcr; int ret, adv; @@ -3571,15 +3572,13 @@ int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode, changed = ret; } - /* Ensure ISOLATE bit is disabled */ - if (mode == MLO_AN_INBAND && - (interface == PHY_INTERFACE_MODE_SGMII || - interface == PHY_INTERFACE_MODE_QSGMII || - linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising))) + neg_mode = phylink_pcs_neg_mode(mode, interface, advertising); + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) bmcr = BMCR_ANENABLE; else bmcr = 0; + /* Configure the inband state. Ensure ISOLATE bit is disabled */ ret = mdiodev_modify(pcs, MII_BMCR, BMCR_ANENABLE | BMCR_ISOLATE, bmcr); if (ret < 0) return ret; -- cgit v1.2.3 From febf2aaf05641f3258cc30e072aff65cffc7c82c Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Fri, 16 Jun 2023 13:06:32 +0100 Subject: net: phylink: pass neg_mode into phylink_mii_c22_pcs_config() Convert fman_dtsec, xilinx_axienet and pcs-lynx to pass the neg_mode into phylink_mii_c22_pcs_config(). Where appropriate, drivers are updated to have neg_mode passed into their pcs_config() and pcs_link_up() functions. For other drivers, we just hoist the call to phylink_pcs_neg_mode() to their pcs_config() method out of phylink_mii_c22_pcs_config(). Signed-off-by: Russell King (Oracle) Link: https://lore.kernel.org/r/E1qA8Do-00EaFM-Ra@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/fman/fman_dtsec.c | 7 ++++--- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 6 ++++-- drivers/net/pcs/pcs-lynx.c | 18 ++++++++++++------ drivers/net/phy/phylink.c | 9 ++++----- include/linux/phylink.h | 5 +++-- 5 files changed, 27 insertions(+), 18 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/ethernet/freescale/fman/fman_dtsec.c b/drivers/net/ethernet/freescale/fman/fman_dtsec.c index d528ca681b6f..3088da7adf0f 100644 --- a/drivers/net/ethernet/freescale/fman/fman_dtsec.c +++ b/drivers/net/ethernet/freescale/fman/fman_dtsec.c @@ -763,15 +763,15 @@ static void dtsec_pcs_get_state(struct phylink_pcs *pcs, phylink_mii_c22_pcs_get_state(dtsec->tbidev, state); } -static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int mode, +static int dtsec_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac) { struct fman_mac *dtsec = pcs_to_dtsec(pcs); - return phylink_mii_c22_pcs_config(dtsec->tbidev, mode, interface, - advertising); + return phylink_mii_c22_pcs_config(dtsec->tbidev, interface, + advertising, neg_mode); } static void dtsec_pcs_an_restart(struct phylink_pcs *pcs) @@ -1447,6 +1447,7 @@ int dtsec_initialization(struct mac_device *mac_dev, goto _return_fm_mac_free; } dtsec->pcs.ops = &dtsec_pcs_ops; + dtsec->pcs.neg_mode = true; dtsec->pcs.poll = true; supported = mac_dev->phylink_config.supported_interfaces; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 3e310b55bce2..ae7b9af7b7d7 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1631,7 +1631,7 @@ static void axienet_pcs_an_restart(struct phylink_pcs *pcs) phylink_mii_c22_pcs_an_restart(pcs_phy); } -static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int mode, +static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac) @@ -1653,7 +1653,8 @@ static int axienet_pcs_config(struct phylink_pcs *pcs, unsigned int mode, } } - ret = phylink_mii_c22_pcs_config(pcs_phy, mode, interface, advertising); + ret = phylink_mii_c22_pcs_config(pcs_phy, interface, advertising, + neg_mode); if (ret < 0) netdev_warn(ndev, "Failed to configure PCS: %d\n", ret); @@ -2129,6 +2130,7 @@ static int axienet_probe(struct platform_device *pdev) } of_node_put(np); lp->pcs.ops = &axienet_pcs_ops; + lp->pcs.neg_mode = true; lp->pcs.poll = true; } diff --git a/drivers/net/pcs/pcs-lynx.c b/drivers/net/pcs/pcs-lynx.c index fca48ebf0b81..25bd4b45eb7b 100644 --- a/drivers/net/pcs/pcs-lynx.c +++ b/drivers/net/pcs/pcs-lynx.c @@ -112,9 +112,10 @@ static void lynx_pcs_get_state(struct phylink_pcs *pcs, state->link, state->an_complete); } -static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode, +static int lynx_pcs_config_giga(struct mdio_device *pcs, phy_interface_t interface, - const unsigned long *advertising) + const unsigned long *advertising, + unsigned int neg_mode) { int link_timer_ns; u32 link_timer; @@ -132,8 +133,9 @@ static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode, if (interface == PHY_INTERFACE_MODE_1000BASEX) { if_mode = 0; } else { + /* SGMII and QSGMII */ if_mode = IF_MODE_SGMII_EN; - if (mode == MLO_AN_INBAND) + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) if_mode |= IF_MODE_USE_SGMII_AN; } @@ -143,7 +145,8 @@ static int lynx_pcs_config_giga(struct mdio_device *pcs, unsigned int mode, if (err) return err; - return phylink_mii_c22_pcs_config(pcs, mode, interface, advertising); + return phylink_mii_c22_pcs_config(pcs, interface, advertising, + neg_mode); } static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, unsigned int mode, @@ -170,13 +173,16 @@ static int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int mode, bool permit) { struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + unsigned int neg_mode; + + neg_mode = phylink_pcs_neg_mode(mode, ifmode, advertising); switch (ifmode) { case PHY_INTERFACE_MODE_1000BASEX: case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_QSGMII: - return lynx_pcs_config_giga(lynx->mdio, mode, ifmode, - advertising); + return lynx_pcs_config_giga(lynx->mdio, ifmode, advertising, + neg_mode); case PHY_INTERFACE_MODE_2500BASEX: if (phylink_autoneg_inband(mode)) { dev_err(&lynx->mdio->dev, diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 567fd22a8924..d0aaa5cad853 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -3545,20 +3545,20 @@ EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_encode_advertisement); /** * phylink_mii_c22_pcs_config() - configure clause 22 PCS * @pcs: a pointer to a &struct mdio_device. - * @mode: link autonegotiation mode * @interface: the PHY interface mode being configured * @advertising: the ethtool advertisement mask + * @neg_mode: PCS negotiation mode * * Configure a Clause 22 PCS PHY with the appropriate negotiation * parameters for the @mode, @interface and @advertising parameters. * Returns negative error number on failure, zero if the advertisement * has not changed, or positive if there is a change. */ -int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode, +int phylink_mii_c22_pcs_config(struct mdio_device *pcs, phy_interface_t interface, - const unsigned long *advertising) + const unsigned long *advertising, + unsigned int neg_mode) { - unsigned int neg_mode; bool changed = 0; u16 bmcr; int ret, adv; @@ -3572,7 +3572,6 @@ int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode, changed = ret; } - neg_mode = phylink_pcs_neg_mode(mode, interface, advertising); if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) bmcr = BMCR_ANENABLE; else diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 2b322d7fa51a..516240f1e950 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -743,9 +743,10 @@ void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs, struct phylink_link_state *state); int phylink_mii_c22_pcs_encode_advertisement(phy_interface_t interface, const unsigned long *advertising); -int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode, +int phylink_mii_c22_pcs_config(struct mdio_device *pcs, phy_interface_t interface, - const unsigned long *advertising); + const unsigned long *advertising, + unsigned int neg_mode); void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs); void phylink_resolve_c73(struct phylink_link_state *state); -- cgit v1.2.3 From fc0649395dca81f2b3b02d9b248acb38cbcee55c Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Wed, 21 Jun 2023 06:38:48 +0200 Subject: net: phy: dp83td510: fix kernel stall during netboot in DP83TD510E PHY driver Fix an issue where the kernel would stall during netboot, showing the "sched: RT throttling activated" message. This stall was triggered by the behavior of the mii_interrupt bit (Bit 7 - DP83TD510E_STS_MII_INT) in the DP83TD510E's PHY_STS Register (Address = 0x10). The DP83TD510E datasheet (2020) states that the bit clears on write, however, in practice, the bit clears on read. This discrepancy had significant implications on the driver's interrupt handling. The PHY_STS Register was used by handle_interrupt() to check for pending interrupts and by read_status() to get the current link status. The call to read_status() was unintentionally clearing the mii_interrupt status bit without deasserting the IRQ pin, causing handle_interrupt() to miss other pending interrupts. This issue was most apparent during netboot. The fix refrains from using the PHY_STS Register for interrupt handling. Instead, we now solely rely on the INTERRUPT_REG_1 Register (Address = 0x12) and INTERRUPT_REG_2 Register (Address = 0x13) for this purpose. These registers directly influence the IRQ pin state and are latched high until read. Note: The INTERRUPT_REG_2 Register (Address = 0x13) exists and can also be used for interrupt handling, specifically for "Aneg page received interrupt" and "Polarity change interrupt". However, these features are currently not supported by this driver. Fixes: 165cd04fe253 ("net: phy: dp83td510: Add support for the DP83TD510 Ethernet PHY") Cc: Signed-off-by: Oleksij Rempel Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/20230621043848.3806124-1-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83td510.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c index 3cd9a77f9532..d7616b13c594 100644 --- a/drivers/net/phy/dp83td510.c +++ b/drivers/net/phy/dp83td510.c @@ -12,6 +12,11 @@ /* MDIO_MMD_VEND2 registers */ #define DP83TD510E_PHY_STS 0x10 +/* Bit 7 - mii_interrupt, active high. Clears on read. + * Note: Clearing does not necessarily deactivate IRQ pin if interrupts pending. + * This differs from the DP83TD510E datasheet (2020) which states this bit + * clears on write 0. + */ #define DP83TD510E_STS_MII_INT BIT(7) #define DP83TD510E_LINK_STATUS BIT(0) @@ -53,12 +58,6 @@ static int dp83td510_config_intr(struct phy_device *phydev) int ret; if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { - /* Clear any pending interrupts */ - ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS, - 0x0); - if (ret) - return ret; - ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_INTERRUPT_REG_1, DP83TD510E_INT1_LINK_EN); @@ -81,10 +80,6 @@ static int dp83td510_config_intr(struct phy_device *phydev) DP83TD510E_GENCFG_INT_EN); if (ret) return ret; - - /* Clear any pending interrupts */ - ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS, - 0x0); } return ret; @@ -94,14 +89,6 @@ static irqreturn_t dp83td510_handle_interrupt(struct phy_device *phydev) { int ret; - ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS); - if (ret < 0) { - phy_error(phydev); - return IRQ_NONE; - } else if (!(ret & DP83TD510E_STS_MII_INT)) { - return IRQ_NONE; - } - /* Read the current enabled interrupts */ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_INTERRUPT_REG_1); if (ret < 0) { -- cgit v1.2.3 From 28e219aea0b9ec374de27179c3d45b2d86bfe562 Mon Sep 17 00:00:00 2001 From: Giulio Benetti Date: Thu, 22 Jun 2023 20:47:21 +0200 Subject: net: phy: broadcom: drop brcm_phy_setbits() and use phy_set_bits() instead Linux provides phy_set_bits() helper so let's drop brcm_phy_setbits() and use phy_set_bits() in its place. Signed-off-by: Giulio Benetti Reviewed-by: Simon Horman Reviewed-by: Florian Fainelli Link: https://lore.kernel.org/r/20230622184721.24368-1-giulio.benetti@benettiengineering.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/broadcom.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 9f0a9c575bd7..59cae0d808aa 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -664,17 +664,6 @@ static int bcm54616s_read_status(struct phy_device *phydev) return err; } -static int brcm_phy_setbits(struct phy_device *phydev, int reg, int set) -{ - int val; - - val = phy_read(phydev, reg); - if (val < 0) - return val; - - return phy_write(phydev, reg, val | set); -} - static int brcm_fet_config_init(struct phy_device *phydev) { int reg, err, err2, brcmtest; @@ -745,15 +734,15 @@ static int brcm_fet_config_init(struct phy_device *phydev) goto done; /* Enable auto MDIX */ - err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_MISCCTRL, - MII_BRCM_FET_SHDW_MC_FAME); + err = phy_set_bits(phydev, MII_BRCM_FET_SHDW_MISCCTRL, + MII_BRCM_FET_SHDW_MC_FAME); if (err < 0) goto done; if (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE) { /* Enable auto power down */ - err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2, - MII_BRCM_FET_SHDW_AS2_APDE); + err = phy_set_bits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2, + MII_BRCM_FET_SHDW_AS2_APDE); } done: -- cgit v1.2.3 From 528a08bcd820d07887edeae706df88ceb06db109 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 27 Jun 2023 16:42:35 +0300 Subject: net: phy: mscc: fix packet loss due to RGMII delays Two deadly typos break RX and TX traffic on the VSC8502 PHY using RGMII if phy-mode = "rgmii-id" or "rgmii-txid", and no "tx-internal-delay-ps" override exists. The negative error code from phy_get_internal_delay() does not get overridden with the delay deduced from the phy-mode, and later gets committed to hardware. Also, the rx_delay gets overridden by what should have been the tx_delay. Fixes: dbb050d2bfc8 ("phy: mscc: Add support for RGMII delay configuration") Signed-off-by: Vladimir Oltean Reviewed-by: Harini Katakam Reviewed-by: Simon Horman Link: https://lore.kernel.org/r/20230627134235.3453358-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mscc/mscc_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index 669a4a7a28ce..4171f01d34e5 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -563,9 +563,9 @@ static int vsc85xx_update_rgmii_cntl(struct phy_device *phydev, u32 rgmii_cntl, if (tx_delay < 0) { if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID || phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) - rx_delay = RGMII_CLK_DELAY_2_0_NS; + tx_delay = RGMII_CLK_DELAY_2_0_NS; else - rx_delay = RGMII_CLK_DELAY_0_2_NS; + tx_delay = RGMII_CLK_DELAY_0_2_NS; } reg_val |= rx_delay << rgmii_rx_delay_pos; -- cgit v1.2.3