diff options
Diffstat (limited to 'drivers/net/phy')
34 files changed, 1250 insertions, 293 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 53dad2482026..28acc6392cfc 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -25,6 +25,9 @@ if PHYLIB config SWPHY bool +config PHY_PACKAGE + tristate + config LED_TRIGGER_PHY bool "Support LED triggers for tracking link state" depends on LEDS_TRIGGERS @@ -157,6 +160,7 @@ config BCM54140_PHY tristate "Broadcom BCM54140 PHY" depends on HWMON || HWMON=n select BCM_NET_PHYLIB + select PHY_PACKAGE help Support the Broadcom BCM54140 Quad SGMII/QSGMII PHY. @@ -292,6 +296,7 @@ source "drivers/net/phy/mediatek/Kconfig" config MICREL_PHY tristate "Micrel PHYs" depends on PTP_1588_CLOCK_OPTIONAL + select PHY_PACKAGE help Supports the KSZ9021, VSC8201, KS8001 PHYs. @@ -323,6 +328,7 @@ config MICROSEMI_PHY depends on MACSEC || MACSEC=n depends on PTP_1588_CLOCK_OPTIONAL || !NETWORK_PHY_TIMESTAMPING select CRYPTO_LIB_AES if MACSEC + select PHY_PACKAGE help Currently supports VSC8514, VSC8530, VSC8531, VSC8540 and VSC8541 PHYs diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 7827609e9032..b4795aaf9c1c 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -3,7 +3,7 @@ libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \ linkmode.o phy_link_topology.o \ - phy_package.o phy_caps.o mdio_bus_provider.o + phy_caps.o mdio_bus_provider.o mdio-bus-y += mdio_bus.o mdio_device.o ifdef CONFIG_PHYLIB @@ -19,6 +19,7 @@ obj-$(CONFIG_MDIO_BUS) += mdio-bus.o obj-$(CONFIG_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o obj-$(CONFIG_PHYLIB) += mdio_devres.o +obj-$(CONFIG_PHY_PACKAGE) += phy_package.o obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c index 57fbd8df9438..badd65f0ccee 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -11,6 +11,7 @@ * Copyright (C) 2023 Airoha Technology Corp. */ +#include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/phy.h> #include <linux/firmware.h> @@ -157,6 +158,7 @@ struct en8811h_priv { struct led led[EN8811H_LED_COUNT]; struct clk_hw hw; struct phy_device *phydev; + unsigned int cko_is_enabled; }; enum { @@ -865,11 +867,30 @@ static int en8811h_clk_is_enabled(struct clk_hw *hw) return (pbus_value & EN8811H_CLK_CGM_CKO); } +static int en8811h_clk_save_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + priv->cko_is_enabled = en8811h_clk_is_enabled(hw); + + return 0; +} + +static void en8811h_clk_restore_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + if (!priv->cko_is_enabled) + en8811h_clk_disable(hw); +} + static const struct clk_ops en8811h_clk_ops = { - .recalc_rate = en8811h_clk_recalc_rate, - .enable = en8811h_clk_enable, - .disable = en8811h_clk_disable, - .is_enabled = en8811h_clk_is_enabled, + .recalc_rate = en8811h_clk_recalc_rate, + .enable = en8811h_clk_enable, + .disable = en8811h_clk_disable, + .is_enabled = en8811h_clk_is_enabled, + .save_context = en8811h_clk_save_context, + .restore_context = en8811h_clk_restore_context, }; static int en8811h_clk_provider_setup(struct device *dev, struct clk_hw *hw) @@ -1149,6 +1170,20 @@ static irqreturn_t en8811h_handle_interrupt(struct phy_device *phydev) return IRQ_HANDLED; } +static int en8811h_resume(struct phy_device *phydev) +{ + clk_restore_context(); + + return genphy_resume(phydev); +} + +static int en8811h_suspend(struct phy_device *phydev) +{ + clk_save_context(); + + return genphy_suspend(phydev); +} + static struct phy_driver en8811h_driver[] = { { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID), @@ -1159,6 +1194,8 @@ static struct phy_driver en8811h_driver[] = { .get_rate_matching = en8811h_get_rate_matching, .config_aneg = en8811h_config_aneg, .read_status = en8811h_read_status, + .resume = en8811h_resume, + .suspend = en8811h_suspend, .config_intr = en8811h_clear_intr, .handle_interrupt = en8811h_handle_interrupt, .led_hw_is_supported = en8811h_led_hw_is_supported, diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 9b1de54fd483..a60e58ef90c4 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -407,7 +407,7 @@ static int bcm5481x_set_brrmode(struct phy_device *phydev, bool on) static int bcm54811_config_init(struct phy_device *phydev) { struct bcm54xx_phy_priv *priv = phydev->priv; - int err, reg; + int err, reg, exp_sync_ethernet; /* Enable CLK125 MUX on LED4 if ref clock is enabled. */ if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) { @@ -424,6 +424,18 @@ static int bcm54811_config_init(struct phy_device *phydev) if (priv->brr_mode) phydev->autoneg = 0; + /* Enable MII Lite (No TXER, RXER, CRS, COL) if configured */ + if (phydev->interface == PHY_INTERFACE_MODE_MIILITE) + exp_sync_ethernet = BCM_EXP_SYNC_ETHERNET_MII_LITE; + else + exp_sync_ethernet = 0; + + err = bcm_phy_modify_exp(phydev, BCM_EXP_SYNC_ETHERNET, + BCM_EXP_SYNC_ETHERNET_MII_LITE, + exp_sync_ethernet); + if (err < 0) + return err; + return bcm5481x_set_brrmode(phydev, priv->brr_mode); } @@ -655,7 +667,7 @@ static int bcm5481x_read_abilities(struct phy_device *phydev) { struct device_node *np = phydev->mdio.dev.of_node; struct bcm54xx_phy_priv *priv = phydev->priv; - int i, val, err; + int i, val, err, aneg; for (i = 0; i < ARRAY_SIZE(bcm54811_linkmodes); i++) linkmode_clear_bit(bcm54811_linkmodes[i], phydev->supported); @@ -676,9 +688,19 @@ static int bcm5481x_read_abilities(struct phy_device *phydev) if (val < 0) return val; + /* BCM54811 is not capable of LDS but the corresponding bit + * in LRESR is set to 1 and marked "Ignore" in the datasheet. + * So we must read the bcm54811 as unable to auto-negotiate + * in BroadR-Reach mode. + */ + if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54811) + aneg = 0; + else + aneg = val & LRESR_LDSABILITY; + linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported, - val & LRESR_LDSABILITY); + aneg); linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, phydev->supported, val & LRESR_100_1PAIR); @@ -735,8 +757,15 @@ static int bcm54811_config_aneg(struct phy_device *phydev) /* Aneg firstly. */ if (priv->brr_mode) { - /* BCM54811 is only capable of autonegotiation in IEEE mode */ - phydev->autoneg = 0; + /* BCM54811 is only capable of autonegotiation in IEEE mode. + * In BroadR-Reach mode, disable the Long Distance Signaling, + * the BRR mode autoneg as supported in other Broadcom PHYs. + * This bit is marked as "Reserved" and "Default 1, must be + * written to 0 after every device reset" in the datasheet. + */ + ret = phy_modify(phydev, MII_BCM54XX_LRECR, LRECR_LDSEN, 0); + if (ret < 0) + return ret; ret = bcm_config_lre_aneg(phydev, false); } else { ret = genphy_config_aneg(phydev); diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c index 01255dada600..33db21251f2e 100644 --- a/drivers/net/phy/dp83822.c +++ b/drivers/net/phy/dp83822.c @@ -516,7 +516,6 @@ static int dp83822_config_init_leds(struct phy_device *phydev) static int dp83822_config_init(struct phy_device *phydev) { struct dp83822_private *dp83822 = phydev->priv; - struct device *dev = &phydev->mdio.dev; int rgmii_delay = 0; s32 rx_int_delay; s32 tx_int_delay; @@ -549,15 +548,13 @@ static int dp83822_config_init(struct phy_device *phydev) return err; if (phy_interface_is_rgmii(phydev)) { - rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0, - true); + rx_int_delay = phy_get_internal_delay(phydev, NULL, 0, true); /* Set DP83822_RX_CLK_SHIFT to enable rx clk internal delay */ if (rx_int_delay > 0) rgmii_delay |= DP83822_RX_CLK_SHIFT; - tx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0, - false); + tx_int_delay = phy_get_internal_delay(phydev, NULL, 0, false); /* Set DP83822_TX_CLK_SHIFT to disable tx clk internal delay */ if (tx_int_delay <= 0) diff --git a/drivers/net/phy/dp83869.c b/drivers/net/phy/dp83869.c index a62cd838a9ea..a2cd1cc35cde 100644 --- a/drivers/net/phy/dp83869.c +++ b/drivers/net/phy/dp83869.c @@ -540,9 +540,8 @@ static const int dp83869_internal_delay[] = {250, 500, 750, 1000, 1250, 1500, static int dp83869_of_init(struct phy_device *phydev) { + struct device_node *of_node = phydev->mdio.dev.of_node; struct dp83869_private *dp83869 = phydev->priv; - struct device *dev = &phydev->mdio.dev; - struct device_node *of_node = dev->of_node; int delay_size = ARRAY_SIZE(dp83869_internal_delay); int ret; @@ -597,13 +596,13 @@ static int dp83869_of_init(struct phy_device *phydev) &dp83869->tx_fifo_depth)) dp83869->tx_fifo_depth = DP83869_PHYCR_FIFO_DEPTH_4_B_NIB; - dp83869->rx_int_delay = phy_get_internal_delay(phydev, dev, + dp83869->rx_int_delay = phy_get_internal_delay(phydev, &dp83869_internal_delay[0], delay_size, true); if (dp83869->rx_int_delay < 0) dp83869->rx_int_delay = DP83869_CLK_DELAY_DEF; - dp83869->tx_int_delay = phy_get_internal_delay(phydev, dev, + dp83869->tx_int_delay = phy_get_internal_delay(phydev, &dp83869_internal_delay[0], delay_size, false); if (dp83869->tx_int_delay < 0) diff --git a/drivers/net/phy/dp83tg720.c b/drivers/net/phy/dp83tg720.c index 7e76323409c4..391c1d868808 100644 --- a/drivers/net/phy/dp83tg720.c +++ b/drivers/net/phy/dp83tg720.c @@ -13,21 +13,92 @@ #include "open_alliance_helpers.h" /* + * DP83TG720 PHY Limitations and Workarounds + * + * The DP83TG720 1000BASE-T1 PHY has several limitations that require + * software-side mitigations. These workarounds are implemented throughout + * this driver. This section documents the known issues and their corresponding + * mitigation strategies. + * + * 1. Unreliable Link Detection and Synchronized Reset Deadlock + * ------------------------------------------------------------ + * After a link loss or during link establishment, the DP83TG720 PHY may fail + * to detect or report link status correctly. As of June 2025, no public + * errata sheet for the DP83TG720 PHY documents this behavior. + * The "DP83TC81x, DP83TG72x Software Implementation Guide" application note + * (SNLA404, available at https://www.ti.com/lit/an/snla404/snla404.pdf) + * recommends performing a soft restart if polling for a link fails to establish + * a connection after 100ms. This procedure is adopted as the workaround for the + * observed link detection issue. + * + * However, in point-to-point setups where both link partners use the same + * driver (e.g. Linux on both sides), a synchronized reset pattern may emerge. + * This leads to a deadlock, where both PHYs reset at the same time and + * continuously miss each other during auto-negotiation. + * + * To address this, the reset procedure includes two components: + * + * - A **fixed minimum delay of 1ms** after a hardware reset. The datasheet + * "DP83TG720S-Q1 1000BASE-T1 Automotive Ethernet PHY with SGMII and RGMII" + * specifies this as the "Post reset stabilization-time prior to MDC preamble + * for register access" (T6.2), ensuring the PHY is ready for MDIO + * operations. + * + * - An **additional asymmetric delay**, empirically chosen based on + * master/slave role. This reduces the risk of synchronized resets on both + * link partners. Values are selected to avoid periodic overlap and ensure + * the link is re-established within a few cycles. + * + * The functions that implement this logic are: + * - dp83tg720_soft_reset() + * - dp83tg720_get_next_update_time() + * + * 2. Polling-Based Link Detection and IRQ Support + * ----------------------------------------------- + * Due to the PHY-specific limitation described in section 1, link-up events + * cannot be reliably detected via interrupts on the DP83TG720. Therefore, + * polling is required to detect transitions from link-down to link-up. + * + * While link-down events *can* be detected via IRQs on this PHY, this driver + * currently does **not** implement interrupt support. As a result, all link + * state changes must be detected using polling. + * + * Polling behavior: + * - When the link is up: slow polling (e.g. 1s). + * - When the link just went down: fast polling for a short time. + * - When the link stays down: fallback to slow polling. + * + * This design balances responsiveness and CPU usage. It sacrifices fast link-up + * times in cases where the link is expected to remain down for extended periods, + * assuming that such systems do not require immediate reactivity. + */ + +/* * DP83TG720S_POLL_ACTIVE_LINK - Polling interval in milliseconds when the link * is active. - * DP83TG720S_POLL_NO_LINK_MIN - Minimum polling interval in milliseconds when - * the link is down. - * DP83TG720S_POLL_NO_LINK_MAX - Maximum polling interval in milliseconds when - * the link is down. + * DP83TG720S_POLL_NO_LINK - Polling interval in milliseconds when the + * link is down. + * DP83TG720S_FAST_POLL_DURATION_MS - Timeout in milliseconds for no-link + * polling after which polling interval is + * increased. + * DP83TG720S_POLL_SLOW - Slow polling interval when there is no + * link for a prolongued period. + * DP83TG720S_RESET_DELAY_MS_MASTER - Delay after a reset before attempting + * to establish a link again for master phy. + * DP83TG720S_RESET_DELAY_MS_SLAVE - Delay after a reset before attempting + * to establish a link again for slave phy. * * These values are not documented or officially recommended by the vendor but * were determined through empirical testing. They achieve a good balance in * minimizing the number of reset retries while ensuring reliable link recovery * within a reasonable timeframe. */ -#define DP83TG720S_POLL_ACTIVE_LINK 1000 -#define DP83TG720S_POLL_NO_LINK_MIN 100 -#define DP83TG720S_POLL_NO_LINK_MAX 1000 +#define DP83TG720S_POLL_ACTIVE_LINK 421 +#define DP83TG720S_POLL_NO_LINK 149 +#define DP83TG720S_FAST_POLL_DURATION_MS 6000 +#define DP83TG720S_POLL_SLOW 1117 +#define DP83TG720S_RESET_DELAY_MS_MASTER 97 +#define DP83TG720S_RESET_DELAY_MS_SLAVE 149 #define DP83TG720S_PHY_ID 0x2000a284 @@ -124,6 +195,7 @@ struct dp83tg720_stats { struct dp83tg720_priv { struct dp83tg720_stats stats; + unsigned long last_link_down_jiffies; }; /** @@ -201,6 +273,26 @@ static int dp83tg720_update_stats(struct phy_device *phydev) return 0; } +static int dp83tg720_soft_reset(struct phy_device *phydev) +{ + int ret; + + ret = phy_write(phydev, DP83TG720S_PHY_RESET, DP83TG720S_HW_RESET); + if (ret) + return ret; + + /* Include mandatory MDC-access delay (1ms) + extra asymmetric delay to + * avoid synchronized reset deadlock. See section 1 in the top-of-file + * comment block. + */ + if (phydev->master_slave_state == MASTER_SLAVE_STATE_SLAVE) + msleep(DP83TG720S_RESET_DELAY_MS_SLAVE); + else + msleep(DP83TG720S_RESET_DELAY_MS_MASTER); + + return ret; +} + static void dp83tg720_get_link_stats(struct phy_device *phydev, struct ethtool_link_ext_stats *link_stats) { @@ -382,21 +474,11 @@ static int dp83tg720_read_status(struct phy_device *phydev) /* According to the "DP83TC81x, DP83TG72x Software * Implementation Guide", the PHY needs to be reset after a * link loss or if no link is created after at least 100ms. - * - * Currently we are polling with the PHY_STATE_TIME (1000ms) - * interval, which is still enough for not automotive use cases. */ ret = phy_init_hw(phydev); if (ret) return ret; - /* Sleep 600ms for PHY stabilization post-reset. - * Empirically chosen value (not documented). - * Helps reduce reset bounces with link partners having similar - * issues. - */ - msleep(600); - /* After HW reset we need to restore master/slave configuration. * genphy_c45_pma_baset1_read_master_slave() call will be done * by the dp83tg720_config_aneg() function. @@ -477,19 +559,11 @@ static int dp83tg720_config_init(struct phy_device *phydev) { int ret; - /* Software Restart is not enough to recover from a link failure. - * Using Hardware Reset instead. - */ - ret = phy_write(phydev, DP83TG720S_PHY_RESET, DP83TG720S_HW_RESET); + /* Reset the PHY to recover from a link failure */ + ret = dp83tg720_soft_reset(phydev); if (ret) return ret; - /* Wait until MDC can be used again. - * The wait value of one 1ms is documented in "DP83TG720S-Q1 1000BASE-T1 - * Automotive Ethernet PHY with SGMII and RGMII" datasheet. - */ - usleep_range(1000, 2000); - if (phy_interface_is_rgmii(phydev)) { ret = dp83tg720_config_rgmii_delay(phydev); if (ret) @@ -525,50 +599,42 @@ static int dp83tg720_probe(struct phy_device *phydev) } /** - * dp83tg720_get_next_update_time - Determine the next update time for PHY - * state + * dp83tg720_get_next_update_time - Return next polling interval for PHY state * @phydev: Pointer to the phy_device structure * - * This function addresses a limitation of the DP83TG720 PHY, which cannot - * reliably detect or report a stable link state. To recover from such - * scenarios, the PHY must be periodically reset when the link is down. However, - * if the link partner also runs Linux with the same driver, synchronized reset - * intervals can lead to a deadlock where the link never establishes due to - * simultaneous resets on both sides. - * - * To avoid this, the function implements randomized polling intervals when the - * link is down. It ensures that reset intervals are desynchronized by - * introducing a random delay between a configured minimum and maximum range. - * When the link is up, a fixed polling interval is used to minimize overhead. - * - * This mechanism guarantees that the link will reestablish within 10 seconds - * in the worst-case scenario. + * Implements adaptive polling interval logic depending on link state and + * downtime duration. See the "2. Polling-Based Link Detection and IRQ Support" + * section at the top of this file for details. * - * Return: Time (in jiffies) until the next update event for the PHY state - * machine. + * Return: Time (in jiffies) until the next poll */ static unsigned int dp83tg720_get_next_update_time(struct phy_device *phydev) { + struct dp83tg720_priv *priv = phydev->priv; unsigned int next_time_jiffies; if (phydev->link) { - /* When the link is up, use a fixed 1000ms interval - * (in jiffies) - */ + priv->last_link_down_jiffies = 0; + + /* When the link is up, use a slower interval (in jiffies) */ next_time_jiffies = msecs_to_jiffies(DP83TG720S_POLL_ACTIVE_LINK); } else { - unsigned int min_jiffies, max_jiffies, rand_jiffies; - - /* When the link is down, randomize interval between min/max - * (in jiffies) - */ - min_jiffies = msecs_to_jiffies(DP83TG720S_POLL_NO_LINK_MIN); - max_jiffies = msecs_to_jiffies(DP83TG720S_POLL_NO_LINK_MAX); - - rand_jiffies = min_jiffies + - get_random_u32_below(max_jiffies - min_jiffies + 1); - next_time_jiffies = rand_jiffies; + unsigned long now = jiffies; + + if (!priv->last_link_down_jiffies) + priv->last_link_down_jiffies = now; + + if (time_before(now, priv->last_link_down_jiffies + + msecs_to_jiffies(DP83TG720S_FAST_POLL_DURATION_MS))) { + /* Link recently went down: fast polling */ + next_time_jiffies = + msecs_to_jiffies(DP83TG720S_POLL_NO_LINK); + } else { + /* Link has been down for a while: slow polling */ + next_time_jiffies = + msecs_to_jiffies(DP83TG720S_POLL_SLOW); + } } /* Ensure the polling time is at least one jiffy */ @@ -582,6 +648,7 @@ static struct phy_driver dp83tg720_driver[] = { .flags = PHY_POLL_CABLE_TEST, .probe = dp83tg720_probe, + .soft_reset = dp83tg720_soft_reset, .config_aneg = dp83tg720_config_aneg, .read_status = dp83tg720_read_status, .get_features = genphy_c45_pma_read_ext_abilities, diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index a44771e8acdc..9766dd99afaa 100644 --- a/drivers/net/phy/intel-xway.c +++ b/drivers/net/phy/intel-xway.c @@ -174,7 +174,6 @@ static const int xway_internal_delay[] = {0, 500, 1000, 1500, 2000, 2500, static int xway_gphy_rgmii_init(struct phy_device *phydev) { - struct device *dev = &phydev->mdio.dev; unsigned int delay_size = ARRAY_SIZE(xway_internal_delay); s32 int_delay; int val = 0; @@ -207,8 +206,7 @@ static int xway_gphy_rgmii_init(struct phy_device *phydev) if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { - int_delay = phy_get_internal_delay(phydev, dev, - xway_internal_delay, + int_delay = phy_get_internal_delay(phydev, xway_internal_delay, delay_size, true); /* if rx-internal-delay-ps is missing, use default of 2.0 ns */ @@ -220,8 +218,7 @@ static int xway_gphy_rgmii_init(struct phy_device *phydev) if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { - int_delay = phy_get_internal_delay(phydev, dev, - xway_internal_delay, + int_delay = phy_get_internal_delay(phydev, xway_internal_delay, delay_size, false); /* if tx-internal-delay-ps is missing, use default of 2.0 ns */ diff --git a/drivers/net/phy/mdio-boardinfo.c b/drivers/net/phy/mdio-boardinfo.c index 2de679a68115..d3184e8f12ec 100644 --- a/drivers/net/phy/mdio-boardinfo.c +++ b/drivers/net/phy/mdio-boardinfo.c @@ -3,17 +3,23 @@ * mdio-boardinfo - Collect pre-declarations for MDIO devices */ -#include <linux/kernel.h> -#include <linux/slab.h> #include <linux/export.h> -#include <linux/mutex.h> +#include <linux/kernel.h> #include <linux/list.h> +#include <linux/mutex.h> +#include <linux/phy.h> +#include <linux/slab.h> #include "mdio-boardinfo.h" static LIST_HEAD(mdio_board_list); static DEFINE_MUTEX(mdio_board_lock); +struct mdio_board_entry { + struct list_head list; + struct mdio_board_info board_info; +}; + /** * mdiobus_setup_mdiodev_from_board_info - create and setup MDIO devices * from pre-collected board specific MDIO information @@ -26,24 +32,18 @@ void mdiobus_setup_mdiodev_from_board_info(struct mii_bus *bus, (struct mii_bus *bus, struct mdio_board_info *bi)) { - struct mdio_board_entry *be; - struct mdio_board_entry *tmp; - struct mdio_board_info *bi; - int ret; + struct mdio_board_entry *be, *tmp; mutex_lock(&mdio_board_lock); list_for_each_entry_safe(be, tmp, &mdio_board_list, list) { - bi = &be->board_info; + struct mdio_board_info *bi = &be->board_info; if (strcmp(bus->id, bi->bus_id)) continue; mutex_unlock(&mdio_board_lock); - ret = cb(bus, bi); + cb(bus, bi); mutex_lock(&mdio_board_lock); - if (ret) - continue; - } mutex_unlock(&mdio_board_lock); } @@ -62,14 +62,13 @@ int mdiobus_register_board_info(const struct mdio_board_info *info, unsigned int n) { struct mdio_board_entry *be; - unsigned int i; be = kcalloc(n, sizeof(*be), GFP_KERNEL); if (!be) return -ENOMEM; - for (i = 0; i < n; i++, be++, info++) { - memcpy(&be->board_info, info, sizeof(*info)); + for (int i = 0; i < n; i++, be++) { + be->board_info = info[i]; mutex_lock(&mdio_board_lock); list_add_tail(&be->list, &mdio_board_list); mutex_unlock(&mdio_board_lock); diff --git a/drivers/net/phy/mdio-boardinfo.h b/drivers/net/phy/mdio-boardinfo.h index 773bb51399be..0878b77878d4 100644 --- a/drivers/net/phy/mdio-boardinfo.h +++ b/drivers/net/phy/mdio-boardinfo.h @@ -7,13 +7,8 @@ #ifndef __MDIO_BOARD_INFO_H #define __MDIO_BOARD_INFO_H -#include <linux/phy.h> -#include <linux/mutex.h> - -struct mdio_board_entry { - struct list_head list; - struct mdio_board_info board_info; -}; +struct mii_bus; +struct mdio_board_info; void mdiobus_setup_mdiodev_from_board_info(struct mii_bus *bus, int (*cb) diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index a6bcb0fee863..fda2e27c1810 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -445,6 +445,9 @@ int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum) lockdep_assert_held_once(&bus->mdio_lock); + if (addr >= PHY_MAX_ADDR) + return -ENXIO; + if (bus->read) retval = bus->read(bus, addr, regnum); else @@ -474,6 +477,9 @@ int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val) lockdep_assert_held_once(&bus->mdio_lock); + if (addr >= PHY_MAX_ADDR) + return -ENXIO; + if (bus->write) err = bus->write(bus, addr, regnum, val); else @@ -535,6 +541,9 @@ int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum) lockdep_assert_held_once(&bus->mdio_lock); + if (addr >= PHY_MAX_ADDR) + return -ENXIO; + if (bus->read_c45) retval = bus->read_c45(bus, addr, devad, regnum); else @@ -566,6 +575,9 @@ int __mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum, lockdep_assert_held_once(&bus->mdio_lock); + if (addr >= PHY_MAX_ADDR) + return -ENXIO; + if (bus->write_c45) err = bus->write_c45(bus, addr, devad, regnum, val); else diff --git a/drivers/net/phy/mdio_bus_provider.c b/drivers/net/phy/mdio_bus_provider.c index 65850e36284d..48dc4bf85125 100644 --- a/drivers/net/phy/mdio_bus_provider.c +++ b/drivers/net/phy/mdio_bus_provider.c @@ -152,7 +152,6 @@ static int mdiobus_create_device(struct mii_bus *bus, strscpy(mdiodev->modalias, bi->modalias, sizeof(mdiodev->modalias)); - mdiodev->bus_match = mdio_device_bus_match; mdiodev->dev.platform_data = (void *)bi->platform_data; ret = mdio_device_register(mdiodev); diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index cce3f405d1a4..f64176e0e197 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -35,7 +35,8 @@ static void mdio_device_release(struct device *dev) kfree(to_mdio_device(dev)); } -int mdio_device_bus_match(struct device *dev, const struct device_driver *drv) +static int mdio_device_bus_match(struct device *dev, + const struct device_driver *drv) { struct mdio_device *mdiodev = to_mdio_device(dev); const struct mdio_driver *mdiodrv = to_mdio_driver(drv); @@ -45,7 +46,6 @@ int mdio_device_bus_match(struct device *dev, const struct device_driver *drv) return strcmp(mdiodev->modalias, drv->name) == 0; } -EXPORT_SYMBOL_GPL(mdio_device_bus_match); struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr) { @@ -59,6 +59,7 @@ struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr) mdiodev->dev.release = mdio_device_release; mdiodev->dev.parent = &bus->dev; mdiodev->dev.bus = &mdio_bus_type; + mdiodev->bus_match = mdio_device_bus_match; mdiodev->device_free = mdio_device_free; mdiodev->device_remove = mdio_device_remove; mdiodev->bus = bus; diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig index 9f30a91be8dd..bb7dc876271e 100644 --- a/drivers/net/phy/mediatek/Kconfig +++ b/drivers/net/phy/mediatek/Kconfig @@ -27,6 +27,7 @@ config MEDIATEK_GE_SOC_PHY depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \ COMPILE_TEST select MTK_NET_PHYLIB + select PHY_PACKAGE help Supports MediaTek SoC built-in Gigabit Ethernet PHYs. diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 64aa03aed770..605b0315b4cb 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -431,6 +431,10 @@ struct kszphy_ptp_priv { spinlock_t seconds_lock; }; +struct kszphy_phy_stats { + u64 rx_err_pkt_cnt; +}; + struct kszphy_priv { struct kszphy_ptp_priv ptp_priv; const struct kszphy_type *type; @@ -441,6 +445,7 @@ struct kszphy_priv { bool rmii_ref_clk_sel_val; bool clk_enable; u64 stats[ARRAY_SIZE(kszphy_hw_stats)]; + struct kszphy_phy_stats phy_stats; }; static const struct kszphy_type lan8814_type = { @@ -472,6 +477,8 @@ static const struct kszphy_type ksz8051_type = { static const struct kszphy_type ksz8081_type = { .led_mode_reg = MII_KSZPHY_CTRL_2, + .cable_diag_reg = KSZ8081_LMD, + .pair_mask = KSZPHY_WIRE_PAIR_MASK, .has_broadcast_disable = true, .has_nand_tree_disable = true, .has_rmii_ref_clk_sel = true, @@ -1718,7 +1725,8 @@ 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_id_compare(phydev, PHY_ID_KSZ9131)) + if (phydev_id_compare(phydev, PHY_ID_KSZ9131) || + phydev_id_compare(phydev, PHY_ID_KSZ9477)) dt = clamp(dt - 22, 0, 255); return (dt * 400) / 10; @@ -1792,12 +1800,20 @@ static int ksz9x31_cable_test_get_status(struct phy_device *phydev, bool *finished) { struct kszphy_priv *priv = phydev->priv; - unsigned long pair_mask = 0xf; + unsigned long pair_mask; int retries = 20; int pair, ret, rv; *finished = false; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->supported)) + pair_mask = 0xf; /* All pairs */ + else + pair_mask = 0x3; /* Pairs A and B only */ + /* Try harder if link partner is active */ while (pair_mask && retries--) { for_each_set_bit(pair, &pair_mask, 4) { @@ -1948,6 +1964,56 @@ static int ksz886x_read_status(struct phy_device *phydev) return genphy_read_status(phydev); } +static int ksz9477_mdix_update(struct phy_device *phydev) +{ + if (phydev->mdix_ctrl != ETH_TP_MDI_AUTO) + phydev->mdix = phydev->mdix_ctrl; + else + phydev->mdix = ETH_TP_MDI_INVALID; + + return 0; +} + +static int ksz9477_read_mdix_ctrl(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MII_KSZ9131_AUTO_MDIX); + if (val < 0) + return val; + + if (!(val & MII_KSZ9131_AUTO_MDIX_SWAP_OFF)) + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + else if (val & MII_KSZ9131_AUTO_MDI_SET) + phydev->mdix_ctrl = ETH_TP_MDI; + else + phydev->mdix_ctrl = ETH_TP_MDI_X; + + return 0; +} + +static int ksz9477_read_status(struct phy_device *phydev) +{ + int ret; + + ret = ksz9477_mdix_update(phydev); + if (ret) + return ret; + + return genphy_read_status(phydev); +} + +static int ksz9477_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = ksz9131_config_mdix(phydev, phydev->mdix_ctrl); + if (ret) + return ret; + + return genphy_config_aneg(phydev); +} + struct ksz9477_errata_write { u8 dev_addr; u8 reg_addr; @@ -2029,6 +2095,13 @@ static int ksz9477_config_init(struct phy_device *phydev) return err; } + /* Read initial MDI-X config state. So, we do not need to poll it + * later on. + */ + err = ksz9477_read_mdix_ctrl(phydev); + if (err) + return err; + return kszphy_config_init(phydev); } @@ -2073,6 +2146,165 @@ static void kszphy_get_stats(struct phy_device *phydev, data[i] = kszphy_get_stat(phydev, i); } +/* KSZ9477 PHY RXER Counter. Probably supported by other PHYs like KSZ9313, + * etc. The counter is incremented when the PHY receives a frame with one or + * more symbol errors. The counter is cleared when the register is read. + */ +#define MII_KSZ9477_PHY_RXER_COUNTER 0x15 + +static int kszphy_update_stats(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + int ret; + + ret = phy_read(phydev, MII_KSZ9477_PHY_RXER_COUNTER); + if (ret < 0) + return ret; + + priv->phy_stats.rx_err_pkt_cnt += ret; + + return 0; +} + +static void kszphy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct kszphy_priv *priv = phydev->priv; + + stats->rx_errors = priv->phy_stats.rx_err_pkt_cnt; +} + +/* Base register for Signal Quality Indicator (SQI) - Channel A + * + * MMD Address: MDIO_MMD_PMAPMD (0x01) + * Register: 0xAC (Channel A) + * Each channel (pair) has its own register: + * Channel A: 0xAC + * Channel B: 0xAD + * Channel C: 0xAE + * Channel D: 0xAF + */ +#define KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A 0xac + +/* SQI field mask for bits [14:8] + * + * SQI indicates relative quality of the signal. + * A lower value indicates better signal quality. + */ +#define KSZ9477_MMD_SQI_MASK GENMASK(14, 8) + +#define KSZ9477_MAX_CHANNELS 4 +#define KSZ9477_SQI_MAX 7 + +/* Number of SQI samples to average for a stable result. + * + * Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26) + * For noisy environments, a minimum of 30–50 readings is recommended. + */ +#define KSZ9477_SQI_SAMPLE_COUNT 40 + +/* The hardware SQI register provides a raw value from 0-127, where a lower + * value indicates better signal quality. However, empirical testing has + * shown that only the 0-7 range is relevant for a functional link. A raw + * value of 8 or higher was measured directly before link drop. This aligns + * with the OPEN Alliance recommendation that SQI=0 should represent the + * pre-failure state. + * + * This table provides a non-linear mapping from the useful raw hardware + * values (0-7) to the standard 0-7 SQI scale, where higher is better. + */ +static const u8 ksz_sqi_mapping[] = { + 7, /* raw 0 -> SQI 7 */ + 7, /* raw 1 -> SQI 7 */ + 6, /* raw 2 -> SQI 6 */ + 5, /* raw 3 -> SQI 5 */ + 4, /* raw 4 -> SQI 4 */ + 3, /* raw 5 -> SQI 3 */ + 2, /* raw 6 -> SQI 2 */ + 1, /* raw 7 -> SQI 1 */ +}; + +/** + * kszphy_get_sqi - Read, average, and map Signal Quality Index (SQI) + * @phydev: the PHY device + * + * This function reads and processes the raw Signal Quality Index from the + * PHY. Based on empirical testing, a raw value of 8 or higher indicates a + * pre-failure state and is mapped to SQI 0. Raw values from 0-7 are + * mapped to the standard 0-7 SQI scale via a lookup table. + * + * Return: SQI value (0–7), or a negative errno on failure. + */ +static int kszphy_get_sqi(struct phy_device *phydev) +{ + int sum[KSZ9477_MAX_CHANNELS] = { 0 }; + int worst_sqi = KSZ9477_SQI_MAX; + int i, val, raw_sqi, ch; + u8 channels; + + /* Determine applicable channels based on link speed */ + if (phydev->speed == SPEED_1000) + channels = 4; + else if (phydev->speed == SPEED_100) + channels = 1; + else + return -EOPNOTSUPP; + + /* Sample and accumulate SQI readings for each pair (currently only one). + * + * Reference: KSZ9477S Datasheet DS00002392C, Section 4.1.11 (page 26) + * - The SQI register is updated every 2 µs. + * - Values may fluctuate significantly, even in low-noise environments. + * - For reliable estimation, average a minimum of 30–50 samples + * (recommended for noisy environments) + * - In noisy environments, individual readings are highly unreliable. + * + * We use 40 samples per pair with a delay of 3 µs between each + * read to ensure new values are captured (2 µs update interval). + */ + for (i = 0; i < KSZ9477_SQI_SAMPLE_COUNT; i++) { + for (ch = 0; ch < channels; ch++) { + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + ch); + if (val < 0) + return val; + + raw_sqi = FIELD_GET(KSZ9477_MMD_SQI_MASK, val); + sum[ch] += raw_sqi; + + /* We communicate with the PHY via MDIO via SPI or + * I2C, which is relatively slow. At least slower than + * the update interval of the SQI register. + * So, we can skip the delay between reads. + */ + } + } + + /* Calculate average for each channel and find the worst SQI */ + for (ch = 0; ch < channels; ch++) { + int avg_raw_sqi = sum[ch] / KSZ9477_SQI_SAMPLE_COUNT; + int mapped_sqi; + + /* Handle the pre-fail/failed state first. */ + if (avg_raw_sqi >= ARRAY_SIZE(ksz_sqi_mapping)) + mapped_sqi = 0; + else + /* Use the lookup table for the good signal range. */ + mapped_sqi = ksz_sqi_mapping[avg_raw_sqi]; + + if (mapped_sqi < worst_sqi) + worst_sqi = mapped_sqi; + } + + return worst_sqi; +} + +static int kszphy_get_sqi_max(struct phy_device *phydev) +{ + return KSZ9477_SQI_MAX; +} + static void kszphy_enable_clk(struct phy_device *phydev) { struct kszphy_priv *priv = phydev->priv; @@ -5403,6 +5635,14 @@ static int lan8841_suspend(struct phy_device *phydev) return kszphy_generic_suspend(phydev); } +static int ksz9131_resume(struct phy_device *phydev) +{ + if (phydev->suspended && phy_interface_is_rgmii(phydev)) + ksz9131_config_rgmii_delay(phydev); + + return kszphy_resume(phydev); +} + static struct phy_driver ksphy_driver[] = { { .phy_id = PHY_ID_KS8737, @@ -5649,7 +5889,7 @@ static struct phy_driver ksphy_driver[] = { .get_strings = kszphy_get_strings, .get_stats = kszphy_get_stats, .suspend = kszphy_suspend, - .resume = kszphy_resume, + .resume = ksz9131_resume, .cable_test_start = ksz9x31_cable_test_start, .cable_test_get_status = ksz9x31_cable_test_get_status, .get_features = ksz9477_get_features, @@ -5688,12 +5928,21 @@ static struct phy_driver ksphy_driver[] = { .phy_id = PHY_ID_KSZ9477, .phy_id_mask = MICREL_PHY_ID_MASK, .name = "Microchip KSZ9477", + .probe = kszphy_probe, /* PHY_GBIT_FEATURES */ .config_init = ksz9477_config_init, .config_intr = kszphy_config_intr, + .config_aneg = ksz9477_config_aneg, + .read_status = ksz9477_read_status, .handle_interrupt = kszphy_handle_interrupt, .suspend = genphy_suspend, .resume = ksz9477_resume, + .get_phy_stats = kszphy_get_phy_stats, + .update_stats = kszphy_update_stats, + .cable_test_start = ksz9x31_cable_test_start, + .cable_test_get_status = ksz9x31_cable_test_get_status, + .get_sqi = kszphy_get_sqi, + .get_sqi_max = kszphy_get_sqi_max, } }; module_phy_driver(ksphy_driver); diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c index 13570f628aa5..dc8634e7bcbe 100644 --- a/drivers/net/phy/microchip.c +++ b/drivers/net/phy/microchip.c @@ -332,7 +332,7 @@ static void lan88xx_link_change_notify(struct phy_device *phydev) * As workaround, set to 10 before setting to 100 * at forced 100 F/H mode. */ - if (!phydev->autoneg && phydev->speed == 100) { + if (phydev->state == PHY_NOLINK && !phydev->autoneg && phydev->speed == 100) { /* disable phy interrupt */ temp = phy_read(phydev, LAN88XX_INT_MASK); temp &= ~LAN88XX_INT_MASK_MDINTPIN_EN_; @@ -488,6 +488,7 @@ static struct phy_driver microchip_phy_driver[] = { .config_init = lan88xx_config_init, .config_aneg = lan88xx_config_aneg, .link_change_notify = lan88xx_link_change_notify, + .soft_reset = genphy_soft_reset, /* Interrupt handling is broken, do not define related * functions to force polling. diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index 7ff975efd8e7..7ed6522fb0ef 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -530,7 +530,6 @@ 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; @@ -549,7 +548,7 @@ 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; - rx_delay = phy_get_internal_delay(phydev, dev, vsc85xx_internal_delay, + rx_delay = phy_get_internal_delay(phydev, vsc85xx_internal_delay, delay_size, true); if (rx_delay < 0) { if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID || @@ -559,7 +558,7 @@ static int vsc85xx_update_rgmii_cntl(struct phy_device *phydev, u32 rgmii_cntl, rx_delay = RGMII_CLK_DELAY_0_2_NS; } - tx_delay = phy_get_internal_delay(phydev, dev, vsc85xx_internal_delay, + tx_delay = phy_get_internal_delay(phydev, vsc85xx_internal_delay, delay_size, false); if (tx_delay < 0) { if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID || diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index bdd70d424491..61670be0f095 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -1573,10 +1573,3 @@ int genphy_c45_ethtool_set_eee(struct phy_device *phydev, return ret; } EXPORT_SYMBOL(genphy_c45_ethtool_set_eee); - -struct phy_driver genphy_c45_driver = { - .phy_id = 0xffffffff, - .phy_id_mask = 0xffffffff, - .name = "Generic Clause 45 PHY", - .read_status = genphy_c45_read_status, -}; diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index e177037f9110..605ca20ae192 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -115,6 +115,7 @@ int phy_interface_num_ports(phy_interface_t interface) return 0; case PHY_INTERFACE_MODE_INTERNAL: case PHY_INTERFACE_MODE_MII: + case PHY_INTERFACE_MODE_MIILITE: case PHY_INTERFACE_MODE_GMII: case PHY_INTERFACE_MODE_TBI: case PHY_INTERFACE_MODE_REVMII: @@ -142,6 +143,9 @@ int phy_interface_num_ports(phy_interface_t interface) case PHY_INTERFACE_MODE_RXAUI: case PHY_INTERFACE_MODE_XAUI: case PHY_INTERFACE_MODE_1000BASEKX: + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + case PHY_INTERFACE_MODE_100GBASEP: return 1; case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_QUSGMII: @@ -375,8 +379,8 @@ static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad, devad | MII_MMD_CTRL_NOINCR); } -static int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, - int devad, u32 regnum) +int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum) { if (is_c45) return __mdiobus_c45_read(bus, phy_addr, devad, regnum); @@ -385,9 +389,10 @@ static int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, /* Read the content of the MMD's selected register */ return __mdiobus_read(bus, phy_addr, MII_MMD_DATA); } +EXPORT_SYMBOL_GPL(mmd_phy_read); -static int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, - int devad, u32 regnum, u16 val) +int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum, u16 val) { if (is_c45) return __mdiobus_c45_write(bus, phy_addr, devad, regnum, val); @@ -396,6 +401,7 @@ static int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, /* Write the data into MMD's selected register */ return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val); } +EXPORT_SYMBOL_GPL(mmd_phy_write); /** * __phy_read_mmd - Convenience function for reading a register @@ -486,71 +492,6 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) EXPORT_SYMBOL(phy_write_mmd); /** - * __phy_package_read_mmd - read MMD reg relative to PHY package base addr - * @phydev: The phy_device struct - * @addr_offset: The offset to be added to PHY package base_addr - * @devad: The MMD to read from - * @regnum: The register on the MMD to read - * - * Convenience helper for reading a register of an MMD on a given PHY - * using the PHY package base address. The base address is added to - * the addr_offset value. - * - * Same calling rules as for __phy_read(); - * - * NOTE: It's assumed that the entire PHY package is either C22 or C45. - */ -int __phy_package_read_mmd(struct phy_device *phydev, - unsigned int addr_offset, int devad, - u32 regnum) -{ - int addr = phy_package_address(phydev, addr_offset); - - if (addr < 0) - return addr; - - if (regnum > (u16)~0 || devad > 32) - return -EINVAL; - - return mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad, - regnum); -} -EXPORT_SYMBOL(__phy_package_read_mmd); - -/** - * __phy_package_write_mmd - write MMD reg relative to PHY package base addr - * @phydev: The phy_device struct - * @addr_offset: The offset to be added to PHY package base_addr - * @devad: The MMD to write to - * @regnum: The register on the MMD to write - * @val: value to write to @regnum - * - * Convenience helper for writing a register of an MMD on a given PHY - * using the PHY package base address. The base address is added to - * the addr_offset value. - * - * Same calling rules as for __phy_write(); - * - * NOTE: It's assumed that the entire PHY package is either C22 or C45. - */ -int __phy_package_write_mmd(struct phy_device *phydev, - unsigned int addr_offset, int devad, - u32 regnum, u16 val) -{ - int addr = phy_package_address(phydev, addr_offset); - - if (addr < 0) - return addr; - - if (regnum > (u16)~0 || devad > 32) - return -EINVAL; - - return mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad, - regnum, val); -} -EXPORT_SYMBOL(__phy_package_write_mmd); - -/** * phy_modify_changed - Function for modifying a PHY register * @phydev: the phy_device struct * @regnum: register number to modify diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 703321689726..2cc9ee97e867 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -188,6 +188,9 @@ phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only) * When @exact is not set, we return either an exact match, or matching capabilities * at lower speed, or the lowest matching speed, or NULL. * + * Non-exact matches will try to return an exact speed and duplex match, but may + * return matching capabilities with same speed but a different duplex. + * * Returns: a matched link_capabilities according to the above process, NULL * otherwise. */ @@ -195,7 +198,7 @@ const struct link_capabilities * phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported, bool exact) { - const struct link_capabilities *lcap, *last = NULL; + const struct link_capabilities *lcap, *match = NULL, *last = NULL; for_each_link_caps_desc_speed(lcap) { if (linkmode_intersects(lcap->linkmodes, supported)) { @@ -204,16 +207,19 @@ phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported, if (lcap->speed == speed && lcap->duplex == duplex) { return lcap; } else if (!exact) { - if (lcap->speed <= speed) - return lcap; + if (!match && lcap->speed <= speed) + match = lcap; + + if (lcap->speed < speed) + break; } } } - if (!exact) - return last; + if (!match && !exact) + match = last; - return NULL; + return match; } EXPORT_SYMBOL_GPL(phy_caps_lookup); @@ -310,6 +316,10 @@ unsigned long phy_caps_from_interface(phy_interface_t interface) link_caps |= BIT(LINK_CAPA_100HD) | BIT(LINK_CAPA_100FD); break; + case PHY_INTERFACE_MODE_MIILITE: + link_caps |= BIT(LINK_CAPA_10FD) | BIT(LINK_CAPA_100FD); + break; + case PHY_INTERFACE_MODE_TBI: case PHY_INTERFACE_MODE_MOCA: case PHY_INTERFACE_MODE_RTBI: @@ -345,6 +355,15 @@ unsigned long phy_caps_from_interface(phy_interface_t interface) link_caps |= BIT(LINK_CAPA_40000FD); break; + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + link_caps |= BIT(LINK_CAPA_50000FD); + break; + + case PHY_INTERFACE_MODE_100GBASEP: + link_caps |= BIT(LINK_CAPA_100000FD); + break; + case PHY_INTERFACE_MODE_INTERNAL: link_caps |= LINK_CAPA_ALL; break; diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 73f9cb2e2844..7556aa3dd7ee 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -59,6 +59,13 @@ struct phy_fixup { int (*run)(struct phy_device *phydev); }; +static struct phy_driver genphy_c45_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic Clause 45 PHY", + .read_status = genphy_c45_read_status, +}; + __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_basic_features) __ro_after_init; EXPORT_SYMBOL_GPL(phy_basic_features); @@ -645,11 +652,119 @@ static struct attribute *phy_dev_attrs[] = { &dev_attr_phy_dev_flags.attr, NULL, }; -ATTRIBUTE_GROUPS(phy_dev); + +static const struct attribute_group phy_dev_group = { + .attrs = phy_dev_attrs, +}; + +#define MMD_DEVICE_ID_ATTR(n) \ +static ssize_t mmd##n##_device_id_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct phy_device *phydev = to_phy_device(dev); \ + return sysfs_emit(buf, "0x%.8lx\n", \ + (unsigned long)phydev->c45_ids.device_ids[n]); \ +} \ +static DEVICE_ATTR_RO(mmd##n##_device_id) + +MMD_DEVICE_ID_ATTR(1); +MMD_DEVICE_ID_ATTR(2); +MMD_DEVICE_ID_ATTR(3); +MMD_DEVICE_ID_ATTR(4); +MMD_DEVICE_ID_ATTR(5); +MMD_DEVICE_ID_ATTR(6); +MMD_DEVICE_ID_ATTR(7); +MMD_DEVICE_ID_ATTR(8); +MMD_DEVICE_ID_ATTR(9); +MMD_DEVICE_ID_ATTR(10); +MMD_DEVICE_ID_ATTR(11); +MMD_DEVICE_ID_ATTR(12); +MMD_DEVICE_ID_ATTR(13); +MMD_DEVICE_ID_ATTR(14); +MMD_DEVICE_ID_ATTR(15); +MMD_DEVICE_ID_ATTR(16); +MMD_DEVICE_ID_ATTR(17); +MMD_DEVICE_ID_ATTR(18); +MMD_DEVICE_ID_ATTR(19); +MMD_DEVICE_ID_ATTR(20); +MMD_DEVICE_ID_ATTR(21); +MMD_DEVICE_ID_ATTR(22); +MMD_DEVICE_ID_ATTR(23); +MMD_DEVICE_ID_ATTR(24); +MMD_DEVICE_ID_ATTR(25); +MMD_DEVICE_ID_ATTR(26); +MMD_DEVICE_ID_ATTR(27); +MMD_DEVICE_ID_ATTR(28); +MMD_DEVICE_ID_ATTR(29); +MMD_DEVICE_ID_ATTR(30); +MMD_DEVICE_ID_ATTR(31); + +static struct attribute *phy_mmd_attrs[] = { + &dev_attr_mmd1_device_id.attr, + &dev_attr_mmd2_device_id.attr, + &dev_attr_mmd3_device_id.attr, + &dev_attr_mmd4_device_id.attr, + &dev_attr_mmd5_device_id.attr, + &dev_attr_mmd6_device_id.attr, + &dev_attr_mmd7_device_id.attr, + &dev_attr_mmd8_device_id.attr, + &dev_attr_mmd9_device_id.attr, + &dev_attr_mmd10_device_id.attr, + &dev_attr_mmd11_device_id.attr, + &dev_attr_mmd12_device_id.attr, + &dev_attr_mmd13_device_id.attr, + &dev_attr_mmd14_device_id.attr, + &dev_attr_mmd15_device_id.attr, + &dev_attr_mmd16_device_id.attr, + &dev_attr_mmd17_device_id.attr, + &dev_attr_mmd18_device_id.attr, + &dev_attr_mmd19_device_id.attr, + &dev_attr_mmd20_device_id.attr, + &dev_attr_mmd21_device_id.attr, + &dev_attr_mmd22_device_id.attr, + &dev_attr_mmd23_device_id.attr, + &dev_attr_mmd24_device_id.attr, + &dev_attr_mmd25_device_id.attr, + &dev_attr_mmd26_device_id.attr, + &dev_attr_mmd27_device_id.attr, + &dev_attr_mmd28_device_id.attr, + &dev_attr_mmd29_device_id.attr, + &dev_attr_mmd30_device_id.attr, + &dev_attr_mmd31_device_id.attr, + NULL +}; + +static umode_t phy_mmd_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct phy_device *phydev = to_phy_device(dev); + const int i = index + 1; + + if (!phydev->is_c45) + return 0; + if (i >= ARRAY_SIZE(phydev->c45_ids.device_ids) || + phydev->c45_ids.device_ids[i] == 0xffffffff) + return 0; + + return attr->mode; +} + +static const struct attribute_group phy_mmd_group = { + .name = "c45_phy_ids", + .attrs = phy_mmd_attrs, + .is_visible = phy_mmd_is_visible, +}; + +static const struct attribute_group *phy_device_groups[] = { + &phy_dev_group, + &phy_mmd_group, + NULL, +}; static const struct device_type mdio_bus_phy_type = { .name = "PHY", - .groups = phy_dev_groups, + .groups = phy_device_groups, .release = phy_device_release, .pm = pm_ptr(&mdio_bus_phy_pm_ops), }; @@ -1515,7 +1630,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, struct mii_bus *bus = phydev->mdio.bus; struct device *d = &phydev->mdio.dev; struct module *ndev_owner = NULL; - bool using_genphy = false; int err; /* For Ethernet device drivers that register their own MDIO bus, we @@ -1541,7 +1655,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, else d->driver = &genphy_driver.mdiodrv.driver; - using_genphy = true; + phydev->is_genphy_driven = 1; } if (!try_module_get(d->driver->owner)) { @@ -1550,7 +1664,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, goto error_put_device; } - if (using_genphy) { + if (phydev->is_genphy_driven) { err = d->driver->probe(d); if (err >= 0) err = device_bind_driver(d); @@ -1620,7 +1734,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, * the generic PHY driver we can't figure it out, thus set the old * legacy PORT_MII value. */ - if (using_genphy) + if (phydev->is_genphy_driven) phydev->port = PORT_MII; /* Initial carrier state is off as the phy is about to be @@ -1659,6 +1773,7 @@ error: error_module_put: module_put(d->driver->owner); + phydev->is_genphy_driven = 0; d->driver = NULL; error_put_device: put_device(d); @@ -1706,36 +1821,6 @@ struct phy_device *phy_attach(struct net_device *dev, const char *bus_id, } EXPORT_SYMBOL(phy_attach); -static bool phy_driver_is_genphy_kind(struct phy_device *phydev, - struct device_driver *driver) -{ - struct device *d = &phydev->mdio.dev; - bool ret = false; - - if (!phydev->drv) - return ret; - - get_device(d); - ret = d->driver == driver; - put_device(d); - - return ret; -} - -bool phy_driver_is_genphy(struct phy_device *phydev) -{ - return phy_driver_is_genphy_kind(phydev, - &genphy_driver.mdiodrv.driver); -} -EXPORT_SYMBOL_GPL(phy_driver_is_genphy); - -bool phy_driver_is_genphy_10g(struct phy_device *phydev) -{ - return phy_driver_is_genphy_kind(phydev, - &genphy_c45_driver.mdiodrv.driver); -} -EXPORT_SYMBOL_GPL(phy_driver_is_genphy_10g); - /** * phy_detach - detach a PHY device from its network device * @phydev: target phy_device struct @@ -1792,9 +1877,10 @@ void phy_detach(struct phy_device *phydev) * from the generic driver so that there's a chance a * real driver could be loaded */ - if (phy_driver_is_genphy(phydev) || - phy_driver_is_genphy_10g(phydev)) + if (phydev->is_genphy_driven) { device_release_driver(&phydev->mdio.dev); + phydev->is_genphy_driven = 0; + } /* Assert the reset signal */ phy_device_reset(phydev, 1); @@ -2899,7 +2985,6 @@ static int phy_get_u32_property(struct device *dev, const char *name, u32 *val) /** * phy_get_internal_delay - returns the index of the internal delay * @phydev: phy_device struct - * @dev: pointer to the devices device struct * @delay_values: array of delays the PHY supports * @size: the size of the delay array * @is_rx: boolean to indicate to get the rx internal delay @@ -2912,9 +2997,10 @@ static int phy_get_u32_property(struct device *dev, const char *name, u32 *val) * array then size = 0 and the value of the delay property is returned. * Return -EINVAL if the delay is invalid or cannot be found. */ -s32 phy_get_internal_delay(struct phy_device *phydev, struct device *dev, - const int *delay_values, int size, bool is_rx) +s32 phy_get_internal_delay(struct phy_device *phydev, const int *delay_values, + int size, bool is_rx) { + struct device *dev = &phydev->mdio.dev; int i, ret; u32 delay; @@ -3416,7 +3502,7 @@ static int phy_probe(struct device *dev) /* Get the LEDs from the device tree, and instantiate standard * LEDs for them. */ - if (IS_ENABLED(CONFIG_PHYLIB_LEDS)) + if (IS_ENABLED(CONFIG_PHYLIB_LEDS) && !phy_driver_is_genphy(phydev)) err = of_phy_leds(phydev); out: @@ -3433,7 +3519,7 @@ static int phy_remove(struct device *dev) cancel_delayed_work_sync(&phydev->state_queue); - if (IS_ENABLED(CONFIG_PHYLIB_LEDS)) + if (IS_ENABLED(CONFIG_PHYLIB_LEDS) && !phy_driver_is_genphy(phydev)) phy_leds_unregister(phydev); phydev->state = PHY_DOWN; diff --git a/drivers/net/phy/phy_package.c b/drivers/net/phy/phy_package.c index c738f76e8664..3024da0bbf7b 100644 --- a/drivers/net/phy/phy_package.c +++ b/drivers/net/phy/phy_package.c @@ -52,7 +52,8 @@ void *phy_package_get_priv(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(phy_package_get_priv); -int phy_package_address(struct phy_device *phydev, unsigned int addr_offset) +static int phy_package_address(struct phy_device *phydev, + unsigned int addr_offset) { struct phy_package_shared *shared = phydev->shared; u8 base_addr = shared->base_addr; @@ -90,6 +91,71 @@ int __phy_package_write(struct phy_device *phydev, unsigned int addr_offset, } EXPORT_SYMBOL_GPL(__phy_package_write); +/** + * __phy_package_read_mmd - read MMD reg relative to PHY package base addr + * @phydev: The phy_device struct + * @addr_offset: The offset to be added to PHY package base_addr + * @devad: The MMD to read from + * @regnum: The register on the MMD to read + * + * Convenience helper for reading a register of an MMD on a given PHY + * using the PHY package base address. The base address is added to + * the addr_offset value. + * + * Same calling rules as for __phy_read(); + * + * NOTE: It's assumed that the entire PHY package is either C22 or C45. + */ +int __phy_package_read_mmd(struct phy_device *phydev, + unsigned int addr_offset, int devad, + u32 regnum) +{ + int addr = phy_package_address(phydev, addr_offset); + + if (addr < 0) + return addr; + + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; + + return mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad, + regnum); +} +EXPORT_SYMBOL(__phy_package_read_mmd); + +/** + * __phy_package_write_mmd - write MMD reg relative to PHY package base addr + * @phydev: The phy_device struct + * @addr_offset: The offset to be added to PHY package base_addr + * @devad: The MMD to write to + * @regnum: The register on the MMD to write + * @val: value to write to @regnum + * + * Convenience helper for writing a register of an MMD on a given PHY + * using the PHY package base address. The base address is added to + * the addr_offset value. + * + * Same calling rules as for __phy_write(); + * + * NOTE: It's assumed that the entire PHY package is either C22 or C45. + */ +int __phy_package_write_mmd(struct phy_device *phydev, + unsigned int addr_offset, int devad, + u32 regnum, u16 val) +{ + int addr = phy_package_address(phydev, addr_offset); + + if (addr < 0) + return addr; + + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; + + return mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad, + regnum, val); +} +EXPORT_SYMBOL(__phy_package_write_mmd); + static bool __phy_package_set_once(struct phy_device *phydev, unsigned int b) { struct phy_package_shared *shared = phydev->shared; @@ -348,3 +414,6 @@ int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev, return ret; } EXPORT_SYMBOL_GPL(devm_of_phy_package_join); + +MODULE_DESCRIPTION("PHY package support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/phylib-internal.h b/drivers/net/phy/phylib-internal.h index afac2bd15b50..ebda74eb60a5 100644 --- a/drivers/net/phy/phylib-internal.h +++ b/drivers/net/phy/phylib-internal.h @@ -7,6 +7,7 @@ #define __PHYLIB_INTERNAL_H struct phy_device; +struct mii_bus; /* * phy_supported_speeds - return all speeds currently supported by a PHY device @@ -20,7 +21,10 @@ void of_set_phy_timing_role(struct phy_device *phydev); int phy_speed_down_core(struct phy_device *phydev); void phy_check_downshift(struct phy_device *phydev); -int phy_package_address(struct phy_device *phydev, unsigned int addr_offset); +int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum); +int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum, u16 val); int genphy_c45_read_eee_adv(struct phy_device *phydev, unsigned long *adv); diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 0faa3d97e06b..c7f867b361dd 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -127,6 +127,9 @@ do { \ #endif static const phy_interface_t phylink_sfp_interface_preference[] = { + PHY_INTERFACE_MODE_100GBASEP, + PHY_INTERFACE_MODE_50GBASER, + PHY_INTERFACE_MODE_LAUI, PHY_INTERFACE_MODE_25GBASER, PHY_INTERFACE_MODE_USXGMII, PHY_INTERFACE_MODE_10GBASER, @@ -234,6 +237,7 @@ static int phylink_interface_max_speed(phy_interface_t interface) case PHY_INTERFACE_MODE_SMII: case PHY_INTERFACE_MODE_REVMII: case PHY_INTERFACE_MODE_MII: + case PHY_INTERFACE_MODE_MIILITE: return SPEED_100; case PHY_INTERFACE_MODE_TBI: @@ -274,6 +278,13 @@ static int phylink_interface_max_speed(phy_interface_t interface) case PHY_INTERFACE_MODE_XLGMII: return SPEED_40000; + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + return SPEED_50000; + + case PHY_INTERFACE_MODE_100GBASEP: + return SPEED_100000; + case PHY_INTERFACE_MODE_INTERNAL: case PHY_INTERFACE_MODE_NA: case PHY_INTERFACE_MODE_MAX: @@ -798,6 +809,9 @@ static int phylink_parse_mode(struct phylink *pl, case PHY_INTERFACE_MODE_10GKR: case PHY_INTERFACE_MODE_10GBASER: case PHY_INTERFACE_MODE_XLGMII: + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_LAUI: + case PHY_INTERFACE_MODE_100GBASEP: caps = ~(MAC_SYM_PAUSE | MAC_ASYM_PAUSE); caps = phylink_get_capabilities(pl->link_config.interface, caps, RATE_MATCH_NONE); @@ -2696,6 +2710,39 @@ static phy_interface_t phylink_sfp_select_interface(struct phylink *pl, return interface; } +static phy_interface_t phylink_sfp_select_interface_speed(struct phylink *pl, + u32 speed) +{ + phy_interface_t best_interface = PHY_INTERFACE_MODE_NA; + phy_interface_t interface; + u32 max_speed; + int i; + + for (i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); i++) { + interface = phylink_sfp_interface_preference[i]; + if (!test_bit(interface, pl->sfp_interfaces)) + continue; + + max_speed = phylink_interface_max_speed(interface); + + /* The logic here is: if speed == max_speed, then we've found + * the best interface. Otherwise we find the interface that + * can just support the requested speed. + */ + if (max_speed >= speed) + best_interface = interface; + + if (max_speed <= speed) + break; + } + + if (best_interface == PHY_INTERFACE_MODE_NA) + phylink_err(pl, "selection of interface failed, speed %u\n", + speed); + + return best_interface; +} + static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b) { __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); @@ -2898,8 +2945,14 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, * link can be configured correctly. */ if (pl->sfp_bus) { - config.interface = phylink_sfp_select_interface(pl, + if (kset->base.autoneg == AUTONEG_ENABLE) + config.interface = + phylink_sfp_select_interface(pl, config.advertising); + else + config.interface = + phylink_sfp_select_interface_speed(pl, + config.speed); if (config.interface == PHY_INTERFACE_MODE_NA) return -EINVAL; @@ -3523,6 +3576,8 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy) struct phylink_link_state config; int ret; + /* We're not using pl->sfp_interfaces, so clear it. */ + phy_interface_zero(pl->sfp_interfaces); linkmode_copy(support, phy->supported); memset(&config, 0, sizeof(config)); @@ -3569,7 +3624,6 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy) static int phylink_sfp_config_optical(struct phylink *pl) { __ETHTOOL_DECLARE_LINK_MODE_MASK(support); - DECLARE_PHY_INTERFACE_MASK(interfaces); struct phylink_link_state config; phy_interface_t interface; int ret; @@ -3583,9 +3637,9 @@ static int phylink_sfp_config_optical(struct phylink *pl) /* Find the union of the supported interfaces by the PCS/MAC and * the SFP module. */ - phy_interface_and(interfaces, pl->config->supported_interfaces, + phy_interface_and(pl->sfp_interfaces, pl->config->supported_interfaces, pl->sfp_interfaces); - if (phy_interface_empty(interfaces)) { + if (phy_interface_empty(pl->sfp_interfaces)) { phylink_err(pl, "unsupported SFP module: no common interface modes\n"); return -EINVAL; } @@ -3601,14 +3655,14 @@ static int phylink_sfp_config_optical(struct phylink *pl) * mask to only those link modes that can be supported. */ ret = phylink_validate_mask(pl, NULL, pl->sfp_support, &config, - interfaces); + pl->sfp_interfaces); if (ret) { phylink_err(pl, "unsupported SFP module: validation with support %*pb failed\n", __ETHTOOL_LINK_MODE_MASK_NBITS, support); return ret; } - interface = phylink_choose_sfp_interface(pl, interfaces); + interface = phylink_choose_sfp_interface(pl, pl->sfp_interfaces); if (interface == PHY_INTERFACE_MODE_NA) { phylink_err(pl, "failed to select SFP interface\n"); return -EINVAL; @@ -3661,6 +3715,13 @@ static int phylink_sfp_module_insert(void *upstream, return phylink_sfp_config_optical(pl); } +static void phylink_sfp_module_remove(void *upstream) +{ + struct phylink *pl = upstream; + + phy_interface_zero(pl->sfp_interfaces); +} + static int phylink_sfp_module_start(void *upstream) { struct phylink *pl = upstream; @@ -3745,6 +3806,7 @@ static const struct sfp_upstream_ops sfp_phylink_ops = { .attach = phylink_sfp_attach, .detach = phylink_sfp_detach, .module_insert = phylink_sfp_module_insert, + .module_remove = phylink_sfp_module_remove, .module_start = phylink_sfp_module_start, .module_stop = phylink_sfp_module_stop, .link_up = phylink_sfp_link_up, diff --git a/drivers/net/phy/qcom/Kconfig b/drivers/net/phy/qcom/Kconfig index 570626cc8e14..06e8430c13b1 100644 --- a/drivers/net/phy/qcom/Kconfig +++ b/drivers/net/phy/qcom/Kconfig @@ -7,7 +7,7 @@ config AT803X_PHY select QCOM_NET_PHYLIB depends on REGULATOR help - Currently supports the AR8030, AR8031, AR8033, AR8035 model + Currently supports the AR8030, AR8031, AR8033, AR8035, IPQ5018 model config QCA83XX_PHY tristate "Qualcomm Atheros QCA833x PHYs" @@ -24,6 +24,7 @@ config QCA808X_PHY config QCA807X_PHY tristate "Qualcomm QCA807x PHYs" select QCOM_NET_PHYLIB + select PHY_PACKAGE depends on OF_MDIO help Currently supports the Qualcomm QCA8072, QCA8075 and the PSGMII diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index 26350b962890..51a132242462 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -19,6 +19,7 @@ #include <linux/regulator/consumer.h> #include <linux/of.h> #include <linux/phylink.h> +#include <linux/reset.h> #include <linux/sfp.h> #include <dt-bindings/net/qca-ar803x.h> @@ -26,9 +27,6 @@ #define AT803X_LED_CONTROL 0x18 -#define AT803X_PHY_MMD3_WOL_CTRL 0x8012 -#define AT803X_WOL_EN BIT(5) - #define AT803X_REG_CHIP_CONFIG 0x1f #define AT803X_BT_BX_REG_SEL 0x8000 @@ -96,6 +94,8 @@ #define ATH8035_PHY_ID 0x004dd072 #define AT8030_PHY_ID_MASK 0xffffffef +#define IPQ5018_PHY_ID 0x004dd0c0 + #define QCA9561_PHY_ID 0x004dd042 #define AT803X_PAGE_FIBER 0 @@ -108,6 +108,48 @@ /* disable hibernation mode */ #define AT803X_DISABLE_HIBERNATION_MODE BIT(2) +#define IPQ5018_PHY_FIFO_CONTROL 0x19 +#define IPQ5018_PHY_FIFO_RESET GENMASK(1, 0) + +#define IPQ5018_PHY_DEBUG_EDAC 0x4380 +#define IPQ5018_PHY_MMD1_MDAC 0x8100 +#define IPQ5018_PHY_DAC_MASK GENMASK(15, 8) + +/* MDAC and EDAC values for short cable length */ +#define IPQ5018_PHY_DEBUG_EDAC_VAL 0x10 +#define IPQ5018_PHY_MMD1_MDAC_VAL 0x10 + +#define IPQ5018_PHY_MMD1_MSE_THRESH1 0x1000 +#define IPQ5018_PHY_MMD1_MSE_THRESH2 0x1001 +#define IPQ5018_PHY_PCS_EEE_TX_TIMER 0x8008 +#define IPQ5018_PHY_PCS_EEE_RX_TIMER 0x8009 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL3 0x8074 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL4 0x8075 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL5 0x8076 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL6 0x8077 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL7 0x8078 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL9 0x807a +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL13 0x807e +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL14 0x807f + +#define IPQ5018_PHY_MMD1_MSE_THRESH1_VAL 0xf1 +#define IPQ5018_PHY_MMD1_MSE_THRESH2_VAL 0x1f6 +#define IPQ5018_PHY_PCS_EEE_TX_TIMER_VAL 0x7880 +#define IPQ5018_PHY_PCS_EEE_RX_TIMER_VAL 0xc8 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL3_VAL 0xc040 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL4_VAL 0xa060 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL5_VAL 0xc040 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL6_VAL 0xa060 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL7_VAL 0xc24c +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL9_VAL 0xc060 +#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL13_VAL 0xb060 +#define IPQ5018_PHY_PCS_NEAR_ECHO_THRESH_VAL 0x90b0 + +#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE 0x1 +#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK GENMASK(7, 4) +#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT 0x50 +#define IPQ5018_PHY_DEBUG_ANA_DAC_FILTER 0xa080 + MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver"); MODULE_AUTHOR("Matus Ujhelyi"); MODULE_LICENSE("GPL"); @@ -133,6 +175,11 @@ struct at803x_context { u16 led_control; }; +struct ipq5018_priv { + struct reset_control *rst; + bool set_short_cable_dac; +}; + static int at803x_write_page(struct phy_device *phydev, int page) { int mask; @@ -866,30 +913,6 @@ static int at8031_config_init(struct phy_device *phydev) return at803x_config_init(phydev); } -static int at8031_set_wol(struct phy_device *phydev, - struct ethtool_wolinfo *wol) -{ - int ret; - - /* First setup MAC address and enable WOL interrupt */ - ret = at803x_set_wol(phydev, wol); - if (ret) - return ret; - - if (wol->wolopts & WAKE_MAGIC) - /* Enable WOL function for 1588 */ - ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, - AT803X_PHY_MMD3_WOL_CTRL, - 0, AT803X_WOL_EN); - else - /* Disable WoL function for 1588 */ - ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, - AT803X_PHY_MMD3_WOL_CTRL, - AT803X_WOL_EN, 0); - - return ret; -} - static int at8031_config_intr(struct phy_device *phydev) { struct at803x_priv *priv = phydev->priv; @@ -987,6 +1010,109 @@ static int at8035_probe(struct phy_device *phydev) return at8035_parse_dt(phydev); } +static int ipq5018_cable_test_start(struct phy_device *phydev) +{ + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL3, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL3_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL4, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL4_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL5, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL5_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL6, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL6_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL7, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL7_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL9, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL9_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL13, + IPQ5018_PHY_PCS_CDT_THRESH_CTRL13_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL3, + IPQ5018_PHY_PCS_NEAR_ECHO_THRESH_VAL); + + /* we do all the (time consuming) work later */ + return 0; +} + +static int ipq5018_config_init(struct phy_device *phydev) +{ + struct ipq5018_priv *priv = phydev->priv; + u16 val; + + /* + * set LDO efuse: first temporarily store ANA_DAC_FILTER value from + * debug register as it will be reset once the ANA_LDO_EFUSE register + * is written to + */ + val = at803x_debug_reg_read(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER); + at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE, + IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK, + IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT); + at803x_debug_reg_write(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER, val); + + /* set 8023AZ EEE TX and RX timer values */ + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_EEE_TX_TIMER, + IPQ5018_PHY_PCS_EEE_TX_TIMER_VAL); + phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_EEE_RX_TIMER, + IPQ5018_PHY_PCS_EEE_RX_TIMER_VAL); + + /* set MSE threshold values */ + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH1, + IPQ5018_PHY_MMD1_MSE_THRESH1_VAL); + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH2, + IPQ5018_PHY_MMD1_MSE_THRESH2_VAL); + + /* PHY DAC values are optional and only set in a PHY to PHY link architecture */ + if (priv->set_short_cable_dac) { + /* setting MDAC (Multi-level Digital-to-Analog Converter) in MMD1 */ + phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MDAC, + IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_MMD1_MDAC_VAL); + + /* setting EDAC (Error-detection and Correction) in debug register */ + at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_EDAC, + IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_DEBUG_EDAC_VAL); + } + + return 0; +} + +static void ipq5018_link_change_notify(struct phy_device *phydev) +{ + /* + * Reset the FIFO buffer upon link disconnects to clear any residual data + * which may cause issues with the FIFO which it cannot recover from. + */ + mdiobus_modify_changed(phydev->mdio.bus, phydev->mdio.addr, + IPQ5018_PHY_FIFO_CONTROL, IPQ5018_PHY_FIFO_RESET, + phydev->link ? IPQ5018_PHY_FIFO_RESET : 0); +} + +static int ipq5018_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct ipq5018_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->set_short_cable_dac = of_property_read_bool(dev->of_node, + "qcom,dac-preset-short-cable"); + + priv->rst = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(priv->rst)) + return dev_err_probe(dev, PTR_ERR(priv->rst), + "failed to acquire reset\n"); + + ret = reset_control_reset(priv->rst); + if (ret) + return dev_err_probe(dev, ret, "failed to reset\n"); + + phydev->priv = priv; + + return 0; +} + static struct phy_driver at803x_driver[] = { { /* Qualcomm Atheros AR8035 */ @@ -1079,6 +1205,19 @@ static struct phy_driver at803x_driver[] = { .soft_reset = genphy_soft_reset, .config_aneg = at803x_config_aneg, }, { + PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID), + .name = "Qualcomm Atheros IPQ5018 internal PHY", + .flags = PHY_IS_INTERNAL | PHY_POLL_CABLE_TEST, + .probe = ipq5018_probe, + .config_init = ipq5018_config_init, + .link_change_notify = ipq5018_link_change_notify, + .read_status = at803x_read_status, + .config_intr = at803x_config_intr, + .handle_interrupt = at803x_handle_interrupt, + .cable_test_start = ipq5018_cable_test_start, + .cable_test_get_status = qca808x_cable_test_get_status, + .soft_reset = genphy_soft_reset, +}, { /* Qualcomm Atheros QCA9561 */ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID), .name = "Qualcomm Atheros QCA9561 built-in PHY", @@ -1104,6 +1243,7 @@ static const struct mdio_device_id __maybe_unused atheros_tbl[] = { { PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) }, { PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) }, { PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) }, + { PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID) }, { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) }, { } }; diff --git a/drivers/net/phy/qcom/qca807x.c b/drivers/net/phy/qcom/qca807x.c index 1af6b5ead74b..04e84ebb646c 100644 --- a/drivers/net/phy/qcom/qca807x.c +++ b/drivers/net/phy/qcom/qca807x.c @@ -124,6 +124,7 @@ struct qca807x_priv { bool dac_full_amplitude; bool dac_full_bias_current; bool dac_disable_bias_current_tweak; + struct qcom_phy_hw_stats hw_stats; }; static int qca807x_cable_test_start(struct phy_device *phydev) @@ -377,7 +378,7 @@ static int qca807x_gpio_get(struct gpio_chip *gc, unsigned int offset) return FIELD_GET(QCA807X_GPIO_FORCE_MODE_MASK, val); } -static void qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +static int qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct qca807x_gpio_priv *priv = gpiochip_get_data(gc); u16 reg; @@ -386,18 +387,19 @@ static void qca807x_gpio_set(struct gpio_chip *gc, unsigned int offset, int valu reg = QCA807X_MMD7_LED_FORCE_CTRL(offset); val = phy_read_mmd(priv->phy, MDIO_MMD_AN, reg); + if (val < 0) + return val; + val &= ~QCA807X_GPIO_FORCE_MODE_MASK; val |= QCA807X_GPIO_FORCE_EN; val |= FIELD_PREP(QCA807X_GPIO_FORCE_MODE_MASK, value); - phy_write_mmd(priv->phy, MDIO_MMD_AN, reg, val); + return phy_write_mmd(priv->phy, MDIO_MMD_AN, reg, val); } static int qca807x_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int value) { - qca807x_gpio_set(gc, offset, value); - - return 0; + return qca807x_gpio_set(gc, offset, value); } static int qca807x_gpio(struct phy_device *phydev) @@ -425,7 +427,7 @@ static int qca807x_gpio(struct phy_device *phydev) gc->get_direction = qca807x_gpio_get_direction; gc->direction_output = qca807x_gpio_dir_out; gc->get = qca807x_gpio_get; - gc->set = qca807x_gpio_set; + gc->set_rv = qca807x_gpio_set; return devm_gpiochip_add_data(dev, gc, priv); } @@ -767,6 +769,10 @@ static int qca807x_config_init(struct phy_device *phydev) return ret; } + ret = qcom_phy_counter_config(phydev); + if (ret) + return ret; + control_dac = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH); control_dac &= ~QCA807X_CONTROL_DAC_MASK; @@ -781,6 +787,22 @@ static int qca807x_config_init(struct phy_device *phydev) control_dac); } +static int qca807x_update_stats(struct phy_device *phydev) +{ + struct qca807x_priv *priv = phydev->priv; + + return qcom_phy_update_stats(phydev, &priv->hw_stats); +} + +static void qca807x_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct qca807x_priv *priv = phydev->priv; + + qcom_phy_get_stats(stats, priv->hw_stats); +} + static struct phy_driver qca807x_drivers[] = { { PHY_ID_MATCH_EXACT(PHY_ID_QCA8072), @@ -799,6 +821,10 @@ static struct phy_driver qca807x_drivers[] = { .suspend = genphy_suspend, .cable_test_start = qca807x_cable_test_start, .cable_test_get_status = qca808x_cable_test_get_status, + .update_stats = qca807x_update_stats, + .get_phy_stats = qca807x_get_phy_stats, + .set_wol = at8031_set_wol, + .get_wol = at803x_get_wol, }, { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075), @@ -822,6 +848,10 @@ static struct phy_driver qca807x_drivers[] = { .led_hw_is_supported = qca807x_led_hw_is_supported, .led_hw_control_set = qca807x_led_hw_control_set, .led_hw_control_get = qca807x_led_hw_control_get, + .update_stats = qca807x_update_stats, + .get_phy_stats = qca807x_get_phy_stats, + .set_wol = at8031_set_wol, + .get_wol = at803x_get_wol, }, }; module_phy_driver(qca807x_drivers); diff --git a/drivers/net/phy/qcom/qca808x.c b/drivers/net/phy/qcom/qca808x.c index 71498c518f0f..8eb51b1a006c 100644 --- a/drivers/net/phy/qcom/qca808x.c +++ b/drivers/net/phy/qcom/qca808x.c @@ -93,6 +93,7 @@ MODULE_LICENSE("GPL"); struct qca808x_priv { int led_polarity_mode; + struct qcom_phy_hw_stats hw_stats; }; static int qca808x_phy_fast_retrain_config(struct phy_device *phydev) @@ -243,6 +244,10 @@ static int qca808x_config_init(struct phy_device *phydev) qca808x_fill_possible_interfaces(phydev); + ret = qcom_phy_counter_config(phydev); + if (ret) + return ret; + /* Configure adc threshold as 100mv for the link 10M */ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD, QCA808X_ADC_THRESHOLD_MASK, @@ -622,6 +627,22 @@ static int qca808x_led_polarity_set(struct phy_device *phydev, int index, active_low ? 0 : QCA808X_LED_ACTIVE_HIGH); } +static int qca808x_update_stats(struct phy_device *phydev) +{ + struct qca808x_priv *priv = phydev->priv; + + return qcom_phy_update_stats(phydev, &priv->hw_stats); +} + +static void qca808x_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct qca808x_priv *priv = phydev->priv; + + qcom_phy_get_stats(stats, priv->hw_stats); +} + static struct phy_driver qca808x_driver[] = { { /* Qualcomm QCA8081 */ @@ -633,7 +654,7 @@ static struct phy_driver qca808x_driver[] = { .handle_interrupt = at803x_handle_interrupt, .get_tunable = at803x_get_tunable, .set_tunable = at803x_set_tunable, - .set_wol = at803x_set_wol, + .set_wol = at8031_set_wol, .get_wol = at803x_get_wol, .get_features = qca808x_get_features, .config_aneg = qca808x_config_aneg, @@ -651,6 +672,8 @@ static struct phy_driver qca808x_driver[] = { .led_hw_control_set = qca808x_led_hw_control_set, .led_hw_control_get = qca808x_led_hw_control_get, .led_polarity_set = qca808x_led_polarity_set, + .update_stats = qca808x_update_stats, + .get_phy_stats = qca808x_get_phy_stats, }, }; module_phy_driver(qca808x_driver); diff --git a/drivers/net/phy/qcom/qcom-phy-lib.c b/drivers/net/phy/qcom/qcom-phy-lib.c index d28815ef56bb..965c2bb99a9b 100644 --- a/drivers/net/phy/qcom/qcom-phy-lib.c +++ b/drivers/net/phy/qcom/qcom-phy-lib.c @@ -115,6 +115,31 @@ int at803x_set_wol(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(at803x_set_wol); +int at8031_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + int ret; + + /* First setup MAC address and enable WOL interrupt */ + ret = at803x_set_wol(phydev, wol); + if (ret) + return ret; + + if (wol->wolopts & WAKE_MAGIC) + /* Enable WOL function for 1588 */ + ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, + AT803X_PHY_MMD3_WOL_CTRL, + 0, AT803X_WOL_EN); + else + /* Disable WoL function for 1588 */ + ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, + AT803X_PHY_MMD3_WOL_CTRL, + AT803X_WOL_EN, 0); + + return ret; +} +EXPORT_SYMBOL_GPL(at8031_set_wol); + void at803x_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { @@ -674,3 +699,78 @@ int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg, return 0; } EXPORT_SYMBOL_GPL(qca808x_led_reg_blink_set); + +/* Enable CRC checking for both received and transmitted frames to ensure + * accurate counter recording. The hardware supports a 32-bit counter, + * configure the counter to clear after it is read to facilitate the + * implementation of a 64-bit software counter + */ +int qcom_phy_counter_config(struct phy_device *phydev) +{ + return phy_set_bits_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_CTRL, + QCA808X_MMD7_CNT_CTRL_CRC_CHECK_EN | + QCA808X_MMD7_CNT_CTRL_READ_CLEAR_EN); +} +EXPORT_SYMBOL_GPL(qcom_phy_counter_config); + +int qcom_phy_update_stats(struct phy_device *phydev, + struct qcom_phy_hw_stats *hw_stats) +{ + int ret; + u32 cnt; + + /* PHY 32-bit counter for RX packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_RX_PKT_15_0); + if (ret < 0) + return ret; + + cnt = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_RX_PKT_31_16); + if (ret < 0) + return ret; + + cnt |= ret << 16; + hw_stats->rx_pkts += cnt; + + /* PHY 16-bit counter for RX CRC error packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_RX_ERR_PKT); + if (ret < 0) + return ret; + + hw_stats->rx_err_pkts += ret; + + /* PHY 32-bit counter for TX packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_TX_PKT_15_0); + if (ret < 0) + return ret; + + cnt = ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_TX_PKT_31_16); + if (ret < 0) + return ret; + + cnt |= ret << 16; + hw_stats->tx_pkts += cnt; + + /* PHY 16-bit counter for TX CRC error packets. */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_CNT_TX_ERR_PKT); + if (ret < 0) + return ret; + + hw_stats->tx_err_pkts += ret; + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_phy_update_stats); + +void qcom_phy_get_stats(struct ethtool_phy_stats *stats, + struct qcom_phy_hw_stats hw_stats) +{ + stats->tx_packets = hw_stats.tx_pkts; + stats->tx_errors = hw_stats.tx_err_pkts; + stats->rx_packets = hw_stats.rx_pkts; + stats->rx_errors = hw_stats.rx_err_pkts; +} +EXPORT_SYMBOL_GPL(qcom_phy_get_stats); diff --git a/drivers/net/phy/qcom/qcom.h b/drivers/net/phy/qcom/qcom.h index 4bb541728846..5071e7149a11 100644 --- a/drivers/net/phy/qcom/qcom.h +++ b/drivers/net/phy/qcom/qcom.h @@ -172,6 +172,9 @@ #define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B #define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A +#define AT803X_PHY_MMD3_WOL_CTRL 0x8012 +#define AT803X_WOL_EN BIT(5) + #define AT803X_DEBUG_ADDR 0x1D #define AT803X_DEBUG_DATA 0x1E @@ -192,6 +195,17 @@ #define AT803X_MIN_DOWNSHIFT 2 #define AT803X_MAX_DOWNSHIFT 9 +#define QCA808X_MMD7_CNT_CTRL 0x8029 +#define QCA808X_MMD7_CNT_CTRL_READ_CLEAR_EN BIT(1) +#define QCA808X_MMD7_CNT_CTRL_CRC_CHECK_EN BIT(0) + +#define QCA808X_MMD7_CNT_RX_PKT_31_16 0x802a +#define QCA808X_MMD7_CNT_RX_PKT_15_0 0x802b +#define QCA808X_MMD7_CNT_RX_ERR_PKT 0x802c +#define QCA808X_MMD7_CNT_TX_PKT_31_16 0x802d +#define QCA808X_MMD7_CNT_TX_PKT_15_0 0x802e +#define QCA808X_MMD7_CNT_TX_ERR_PKT 0x802f + enum stat_access_type { PHY, MMD @@ -209,12 +223,21 @@ struct at803x_ss_mask { u8 speed_shift; }; +struct qcom_phy_hw_stats { + u64 rx_pkts; + u64 rx_err_pkts; + u64 tx_pkts; + u64 tx_err_pkts; +}; + int at803x_debug_reg_read(struct phy_device *phydev, u16 reg); int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg, u16 clear, u16 set); int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data); int at803x_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); +int at8031_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol); void at803x_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol); int at803x_ack_interrupt(struct phy_device *phydev); @@ -241,3 +264,8 @@ int qca808x_led_reg_brightness_set(struct phy_device *phydev, int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg, unsigned long *delay_on, unsigned long *delay_off); +int qcom_phy_counter_config(struct phy_device *phydev); +int qcom_phy_update_stats(struct phy_device *phydev, + struct qcom_phy_hw_stats *hw_stats); +void qcom_phy_get_stats(struct ethtool_phy_stats *stats, + struct qcom_phy_hw_stats hw_stats); diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index c3dcb6257430..dd0d675149ad 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -436,9 +436,15 @@ static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev) static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol) { + int wol_events; + wol->supported = WAKE_MAGIC; - if (phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS) - & RTL8211F_WOL_EVENT_MAGIC) + + wol_events = phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS); + if (wol_events < 0) + return; + + if (wol_events & RTL8211F_WOL_EVENT_MAGIC) wol->wolopts = WAKE_MAGIC; } diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 347c1e0e94d9..5347c95d1e77 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -361,6 +361,11 @@ static void sfp_fixup_ignore_tx_fault(struct sfp *sfp) sfp->state_ignore_mask |= SFP_F_TX_FAULT; } +static void sfp_fixup_ignore_hw(struct sfp *sfp, unsigned int mask) +{ + sfp->state_hw_mask &= ~mask; +} + static void sfp_fixup_nokia(struct sfp *sfp) { sfp_fixup_long_startup(sfp); @@ -409,7 +414,19 @@ static void sfp_fixup_halny_gsfp(struct sfp *sfp) * these are possibly used for other purposes on this * module, e.g. a serial port. */ - sfp->state_hw_mask &= ~(SFP_F_TX_FAULT | SFP_F_LOS); + sfp_fixup_ignore_hw(sfp, SFP_F_TX_FAULT | SFP_F_LOS); +} + +static void sfp_fixup_potron(struct sfp *sfp) +{ + /* + * The TX_FAULT and LOS pins on this device are used for serial + * communication, so ignore them. Additionally, provide extra + * time for this device to fully start up. + */ + + sfp_fixup_long_startup(sfp); + sfp_fixup_ignore_hw(sfp, SFP_F_TX_FAULT | SFP_F_LOS); } static void sfp_fixup_rollball_cc(struct sfp *sfp) @@ -512,6 +529,8 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_F("Walsun", "HXSX-ATRC-1", sfp_fixup_fs_10gt), SFP_QUIRK_F("Walsun", "HXSX-ATRI-1", sfp_fixup_fs_10gt), + SFP_QUIRK_F("YV", "SFP+ONU-XGSPON", sfp_fixup_potron), + // OEM SFP-GE-T is a 1000Base-T module with broken TX_FAULT indicator SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault), diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c index 31463b9e5697..b6489da5cfcd 100644 --- a/drivers/net/phy/smsc.c +++ b/drivers/net/phy/smsc.c @@ -155,10 +155,29 @@ static int smsc_phy_reset(struct phy_device *phydev) static int lan87xx_config_aneg(struct phy_device *phydev) { - int rc; + u8 mdix_ctrl; int val; + int rc; + + /* When auto-negotiation is disabled (forced mode), the PHY's + * Auto-MDIX will continue toggling the TX/RX pairs. + * + * To establish a stable link, we must select a fixed MDI mode. + * If the user has not specified a fixed MDI mode (i.e., mdix_ctrl is + * 'auto'), we default to ETH_TP_MDI. This choice of a ETH_TP_MDI mode + * mirrors the behavior the hardware would exhibit if the AUTOMDIX_EN + * strap were configured for a fixed MDI connection. + */ + if (phydev->autoneg == AUTONEG_DISABLE) { + if (phydev->mdix_ctrl == ETH_TP_MDI_AUTO) + mdix_ctrl = ETH_TP_MDI; + else + mdix_ctrl = phydev->mdix_ctrl; + } else { + mdix_ctrl = phydev->mdix_ctrl; + } - switch (phydev->mdix_ctrl) { + switch (mdix_ctrl) { case ETH_TP_MDI: val = SPECIAL_CTRL_STS_OVRRD_AMDIX_; break; @@ -167,7 +186,8 @@ static int lan87xx_config_aneg(struct phy_device *phydev) SPECIAL_CTRL_STS_AMDIX_STATE_; break; case ETH_TP_MDI_AUTO: - val = SPECIAL_CTRL_STS_AMDIX_ENABLE_; + val = SPECIAL_CTRL_STS_OVRRD_AMDIX_ | + SPECIAL_CTRL_STS_AMDIX_ENABLE_; break; default: return genphy_config_aneg(phydev); @@ -183,7 +203,7 @@ static int lan87xx_config_aneg(struct phy_device *phydev) rc |= val; phy_write(phydev, SPECIAL_CTRL_STS, rc); - phydev->mdix = phydev->mdix_ctrl; + phydev->mdix = mdix_ctrl; return genphy_config_aneg(phydev); } @@ -261,6 +281,33 @@ int lan87xx_read_status(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(lan87xx_read_status); +static int lan87xx_phy_config_init(struct phy_device *phydev) +{ + int rc; + + /* The LAN87xx PHY's initial MDI-X mode is determined by the AUTOMDIX_EN + * hardware strap, but the driver cannot read the strap's status. This + * creates an unpredictable initial state. + * + * To ensure consistent and reliable behavior across all boards, + * override the strap configuration on initialization and force the PHY + * into a known state with Auto-MDIX enabled, which is the expected + * default for modern hardware. + */ + rc = phy_modify(phydev, SPECIAL_CTRL_STS, + SPECIAL_CTRL_STS_OVRRD_AMDIX_ | + SPECIAL_CTRL_STS_AMDIX_ENABLE_ | + SPECIAL_CTRL_STS_AMDIX_STATE_, + SPECIAL_CTRL_STS_OVRRD_AMDIX_ | + SPECIAL_CTRL_STS_AMDIX_ENABLE_); + if (rc < 0) + return rc; + + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + + return smsc_phy_config_init(phydev); +} + static int lan874x_phy_config_init(struct phy_device *phydev) { u16 val; @@ -695,7 +742,7 @@ static struct phy_driver smsc_phy_driver[] = { /* basic functions */ .read_status = lan87xx_read_status, - .config_init = smsc_phy_config_init, + .config_init = lan87xx_phy_config_init, .soft_reset = smsc_phy_reset, .config_aneg = lan87xx_config_aneg, diff --git a/drivers/net/phy/spi_ks8995.c b/drivers/net/phy/spi_ks8995.c index 076a370be849..d135b061d810 100644 --- a/drivers/net/phy/spi_ks8995.c +++ b/drivers/net/phy/spi_ks8995.c @@ -401,8 +401,8 @@ static const struct bin_attribute ks8995_registers_attr = { .mode = 0600, }, .size = KS8995_REGS_SIZE, - .read_new = ks8995_registers_read, - .write_new = ks8995_registers_write, + .read = ks8995_registers_read, + .write = ks8995_registers_write, }; /* ------------------------------------------------------------------------ */ |