diff options
Diffstat (limited to 'drivers/net/phy')
31 files changed, 1915 insertions, 372 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 98700d069191..a7ade7b95a2e 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -308,7 +308,7 @@ config MICREL_PHY config MICROCHIP_T1S_PHY tristate "Microchip 10BASE-T1S Ethernet PHYs" help - Currently supports the LAN8670/1/2 Rev.B1/C1/C2 and + Currently supports the LAN8670/1/2 Rev.B1/C1/C2/D0 and LAN8650/1 Rev.B0/B1 Internal PHYs. config MICROCHIP_PHY diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c index bd7a47a903ac..8f9753d4318c 100644 --- a/drivers/net/phy/adin1100.c +++ b/drivers/net/phy/adin1100.c @@ -192,16 +192,15 @@ static irqreturn_t adin_phy_handle_interrupt(struct phy_device *phydev) static int adin_set_powerdown_mode(struct phy_device *phydev, bool en) { int ret; - int val; - val = en ? ADIN_CRSM_SFT_PD_CNTRL_EN : 0; ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, - ADIN_CRSM_SFT_PD_CNTRL, val); + ADIN_CRSM_SFT_PD_CNTRL, + en ? ADIN_CRSM_SFT_PD_CNTRL_EN : 0); if (ret < 0) return ret; return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret, - (ret & ADIN_CRSM_SFT_PD_RDY) == val, + !!(ret & ADIN_CRSM_SFT_PD_RDY) == en, 1000, 30000, true); } diff --git a/drivers/net/phy/aquantia/aquantia_firmware.c b/drivers/net/phy/aquantia/aquantia_firmware.c index bbbcc9736b00..569256152689 100644 --- a/drivers/net/phy/aquantia/aquantia_firmware.c +++ b/drivers/net/phy/aquantia/aquantia_firmware.c @@ -369,7 +369,7 @@ int aqr_firmware_load(struct phy_device *phydev) * assume that, and load a new image. */ ret = aqr_firmware_load_nvmem(phydev); - if (!ret) + if (ret == -EPROBE_DEFER || !ret) return ret; ret = aqr_firmware_load_fs(phydev); diff --git a/drivers/net/phy/bcm-phy-ptp.c b/drivers/net/phy/bcm-phy-ptp.c index d3501f8487d9..65d609ed69fb 100644 --- a/drivers/net/phy/bcm-phy-ptp.c +++ b/drivers/net/phy/bcm-phy-ptp.c @@ -780,9 +780,21 @@ out: kfree_skb(skb); } -static int bcm_ptp_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *cfg, - struct netlink_ext_ack *extack) +static int bcm_ptp_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct bcm_ptp_private *priv = mii2priv(mii_ts); + + cfg->rx_filter = priv->hwts_rx ? HWTSTAMP_FILTER_PTP_V2_EVENT + : HWTSTAMP_FILTER_NONE; + cfg->tx_type = priv->tx_type; + + return 0; +} + +static int bcm_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) { struct bcm_ptp_private *priv = mii2priv(mii_ts); u16 mode, ctrl; @@ -898,7 +910,8 @@ static void bcm_ptp_init(struct bcm_ptp_private *priv) priv->mii_ts.rxtstamp = bcm_ptp_rxtstamp; priv->mii_ts.txtstamp = bcm_ptp_txtstamp; - priv->mii_ts.hwtstamp = bcm_ptp_hwtstamp; + priv->mii_ts.hwtstamp_set = bcm_ptp_hwtstamp_set; + priv->mii_ts.hwtstamp_get = bcm_ptp_hwtstamp_get; priv->mii_ts.ts_info = bcm_ptp_ts_info; priv->phydev->mii_ts = &priv->mii_ts; diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c index 74396453f5bb..b950acc9c49b 100644 --- a/drivers/net/phy/dp83640.c +++ b/drivers/net/phy/dp83640.c @@ -1176,9 +1176,21 @@ static irqreturn_t dp83640_handle_interrupt(struct phy_device *phydev) return IRQ_HANDLED; } -static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *cfg, - struct netlink_ext_ack *extack) +static int dp83640_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct dp83640_private *dp83640 = + container_of(mii_ts, struct dp83640_private, mii_ts); + + cfg->rx_filter = dp83640->hwts_rx_en; + cfg->tx_type = dp83640->hwts_tx_en; + + return 0; +} + +static int dp83640_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) { struct dp83640_private *dp83640 = container_of(mii_ts, struct dp83640_private, mii_ts); @@ -1198,7 +1210,7 @@ static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: - dp83640->hwts_rx_en = 1; + dp83640->hwts_rx_en = HWTSTAMP_FILTER_PTP_V1_L4_EVENT; dp83640->layer = PTP_CLASS_L4; dp83640->version = PTP_CLASS_V1; cfg->rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT; @@ -1206,7 +1218,7 @@ static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: - dp83640->hwts_rx_en = 1; + dp83640->hwts_rx_en = HWTSTAMP_FILTER_PTP_V2_L4_EVENT; dp83640->layer = PTP_CLASS_L4; dp83640->version = PTP_CLASS_V2; cfg->rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT; @@ -1214,7 +1226,7 @@ static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: - dp83640->hwts_rx_en = 1; + dp83640->hwts_rx_en = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; dp83640->layer = PTP_CLASS_L2; dp83640->version = PTP_CLASS_V2; cfg->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; @@ -1222,7 +1234,7 @@ static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, case HWTSTAMP_FILTER_PTP_V2_EVENT: case HWTSTAMP_FILTER_PTP_V2_SYNC: case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: - dp83640->hwts_rx_en = 1; + dp83640->hwts_rx_en = HWTSTAMP_FILTER_PTP_V2_EVENT; dp83640->layer = PTP_CLASS_L4 | PTP_CLASS_L2; dp83640->version = PTP_CLASS_V2; cfg->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; @@ -1407,7 +1419,8 @@ static int dp83640_probe(struct phy_device *phydev) dp83640->phydev = phydev; dp83640->mii_ts.rxtstamp = dp83640_rxtstamp; dp83640->mii_ts.txtstamp = dp83640_txtstamp; - dp83640->mii_ts.hwtstamp = dp83640_hwtstamp; + dp83640->mii_ts.hwtstamp_set = dp83640_hwtstamp_set; + dp83640->mii_ts.hwtstamp_get = dp83640_hwtstamp_get; dp83640->mii_ts.ts_info = dp83640_ts_info; INIT_DELAYED_WORK(&dp83640->ts_work, rx_timestamp_work); diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 36a0c1b7f59c..5f5de01c41e1 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -937,15 +937,15 @@ static void dp83867_link_change_notify(struct phy_device *phydev) * whenever there is a link change. */ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { - int val = 0; + int val; - val = phy_clear_bits(phydev, DP83867_CFG2, - DP83867_SGMII_AUTONEG_EN); - if (val < 0) - return; + val = phy_modify_changed(phydev, DP83867_CFG2, + DP83867_SGMII_AUTONEG_EN, 0); - phy_set_bits(phydev, DP83867_CFG2, - DP83867_SGMII_AUTONEG_EN); + /* Keep the in-band setting made by dp83867_config_inband() */ + if (val != 0) + phy_set_bits(phydev, DP83867_CFG2, + DP83867_SGMII_AUTONEG_EN); } } @@ -1116,6 +1116,25 @@ static int dp83867_led_polarity_set(struct phy_device *phydev, int index, DP83867_LED_POLARITY(index), polarity); } +static unsigned int dp83867_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_SGMII) + return LINK_INBAND_ENABLE | LINK_INBAND_DISABLE; + + return 0; +} + +static int dp83867_config_inband(struct phy_device *phydev, unsigned int modes) +{ + int val = 0; + + if (modes == LINK_INBAND_ENABLE) + val = DP83867_SGMII_AUTONEG_EN; + + return phy_modify(phydev, DP83867_CFG2, DP83867_SGMII_AUTONEG_EN, val); +} + static struct phy_driver dp83867_driver[] = { { .phy_id = DP83867_PHY_ID, @@ -1149,6 +1168,9 @@ static struct phy_driver dp83867_driver[] = { .led_hw_control_set = dp83867_led_hw_control_set, .led_hw_control_get = dp83867_led_hw_control_get, .led_polarity_set = dp83867_led_polarity_set, + + .inband_caps = dp83867_inband_caps, + .config_inband = dp83867_config_inband, }, }; module_phy_driver(dp83867_driver); diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c index 23af1ac194fa..d75dae6071ad 100644 --- a/drivers/net/phy/dp83td510.c +++ b/drivers/net/phy/dp83td510.c @@ -61,6 +61,7 @@ #define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15) #define DP83TD510E_MSE_DETECT 0xa85 +#define DP83TD510E_MSE_MAX U16_MAX #define DP83TD510_SQI_MAX 7 @@ -249,6 +250,64 @@ struct dp83td510_priv { #define DP83TD510E_ALCD_COMPLETE BIT(15) #define DP83TD510E_ALCD_CABLE_LENGTH GENMASK(10, 0) +static int dp83td510_get_mse_capability(struct phy_device *phydev, + struct phy_mse_capability *cap) +{ + /* DP83TD510E documents only a single (average) MSE register + * (used to derive SQI); no peak or worst-peak counters are + * described. Advertise only PHY_MSE_CAP_AVG. + */ + cap->supported_caps = PHY_MSE_CAP_AVG; + /* 10BASE-T1L is a single-pair medium, so there are no B/C/D channels. + * We still advertise PHY_MSE_CAP_CHANNEL_A to indicate that the PHY + * can attribute the measurement to a specific pair (the only one), + * rather than exposing it only as a link-aggregate. + * + * Rationale: + * - Keeps the ethtool MSE_GET selection logic consistent: per-channel + * (A/B/C/D) is preferred over WORST/LINK, so userspace receives a + * CHANNEL_A nest instead of LINK. + * - Signals to tools that "per-pair" data is available (even if there's + * just one pair), avoiding the impression that only aggregate values + * are supported. + * - Remains compatible with multi-pair PHYs and uniform UI handling. + * + * Note: WORST and other channels are not advertised on 10BASE-T1L. + */ + cap->supported_caps |= PHY_MSE_CHANNEL_A | PHY_MSE_CAP_LINK; + cap->max_average_mse = DP83TD510E_MSE_MAX; + + /* The datasheet does not specify the refresh rate or symbol count, + * but based on similar PHYs and standards, we can assume a common + * value. For 10BASE-T1L, the symbol rate is 7.5 MBd. A common + * diagnostic interval is around 1ms. + * 7.5e6 symbols/sec * 0.001 sec = 7500 symbols. + */ + cap->refresh_rate_ps = 1000000000; /* 1 ms */ + cap->num_symbols = 7500; + + return 0; +} + +static int dp83td510_get_mse_snapshot(struct phy_device *phydev, + enum phy_mse_channel channel, + struct phy_mse_snapshot *snapshot) +{ + int ret; + + if (channel != PHY_MSE_CHANNEL_LINK && + channel != PHY_MSE_CHANNEL_A) + return -EOPNOTSUPP; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_MSE_DETECT); + if (ret < 0) + return ret; + + snapshot->average_mse = ret; + + return 0; +} + static int dp83td510_led_brightness_set(struct phy_device *phydev, u8 index, enum led_brightness brightness) { @@ -893,6 +952,9 @@ static struct phy_driver dp83td510_driver[] = { .get_phy_stats = dp83td510_get_phy_stats, .update_stats = dp83td510_update_stats, + .get_mse_capability = dp83td510_get_mse_capability, + .get_mse_snapshot = dp83td510_get_mse_snapshot, + .led_brightness_set = dp83td510_led_brightness_set, .led_hw_is_supported = dp83td510_led_hw_is_supported, .led_hw_control_set = dp83td510_led_hw_control_set, diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c index 0e1b28f06f18..50684271f81a 100644 --- a/drivers/net/phy/fixed_phy.c +++ b/drivers/net/phy/fixed_phy.c @@ -19,7 +19,6 @@ #include <linux/of.h> #include <linux/idr.h> #include <linux/netdevice.h> -#include <linux/linkmode.h> #include "swphy.h" @@ -125,18 +124,13 @@ static int __fixed_phy_add(int phy_addr, fp->addr = phy_addr; fp->status = *status; + fp->status.link = true; list_add_tail(&fp->node, &fmb_phys); return 0; } -void fixed_phy_add(const struct fixed_phy_status *status) -{ - __fixed_phy_add(0, status); -} -EXPORT_SYMBOL_GPL(fixed_phy_add); - static DEFINE_IDA(phy_fixed_ida); static void fixed_phy_del(int phy_addr) @@ -179,42 +173,10 @@ struct phy_device *fixed_phy_register(const struct fixed_phy_status *status, return ERR_PTR(-EINVAL); } - /* propagate the fixed link values to struct phy_device */ - phy->link = status->link; - if (status->link) { - phy->speed = status->speed; - phy->duplex = status->duplex; - phy->pause = status->pause; - phy->asym_pause = status->asym_pause; - } - of_node_get(np); phy->mdio.dev.of_node = np; phy->is_pseudo_fixed_link = true; - switch (status->speed) { - case SPEED_1000: - linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, - phy->supported); - linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, - phy->supported); - fallthrough; - case SPEED_100: - linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, - phy->supported); - linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, - phy->supported); - fallthrough; - case SPEED_10: - default: - linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, - phy->supported); - linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, - phy->supported); - } - - phy_advertise_supported(phy); - ret = phy_device_register(phy); if (ret) { phy_device_free(phy); @@ -227,6 +189,17 @@ struct phy_device *fixed_phy_register(const struct fixed_phy_status *status, } EXPORT_SYMBOL_GPL(fixed_phy_register); +struct phy_device *fixed_phy_register_100fd(void) +{ + static const struct fixed_phy_status status = { + .speed = SPEED_100, + .duplex = DUPLEX_FULL, + }; + + return fixed_phy_register(&status, NULL); +} +EXPORT_SYMBOL_GPL(fixed_phy_register_100fd); + void fixed_phy_unregister(struct phy_device *phy) { phy_device_remove(phy); diff --git a/drivers/net/phy/mdio-open-alliance.h b/drivers/net/phy/mdio-open-alliance.h index 931e14660d75..449d0fb67093 100644 --- a/drivers/net/phy/mdio-open-alliance.h +++ b/drivers/net/phy/mdio-open-alliance.h @@ -43,4 +43,53 @@ /* Version Identifiers */ #define OATC14_IDM 0x0a00 +/* + * Open Alliance TC14 (10BASE-T1S) - Advanced Diagnostic Features Registers + * + * Refer to the OPEN Alliance documentation: + * https://opensig.org/automotive-ethernet-specifications/ + * + * Specification: + * "10BASE-T1S Advanced Diagnostic PHY Features" + * https://opensig.org/wp-content/uploads/2025/06/OPEN_Alliance_10BASE-T1S_Advanced_PHY_features_for-automotive_Ethernet_V2.1b.pdf + */ +/* Advanced Diagnostic Features Capability Register*/ +#define MDIO_OATC14_ADFCAP 0xcc00 +#define OATC14_ADFCAP_HDD_CAPABILITY GENMASK(10, 8) +#define OATC14_ADFCAP_SQIPLUS_CAPABILITY GENMASK(4, 1) +#define OATC14_ADFCAP_SQI_CAPABILITY BIT(0) + +/* Harness Defect Detection Register */ +#define MDIO_OATC14_HDD 0xcc01 +#define OATC14_HDD_CONTROL BIT(15) +#define OATC14_HDD_READY BIT(14) +#define OATC14_HDD_START_CONTROL BIT(13) +#define OATC14_HDD_VALID BIT(2) +#define OATC14_HDD_SHORT_OPEN_STATUS GENMASK(1, 0) + +/* Dynamic Channel Quality SQI Register */ +#define MDIO_OATC14_DCQ_SQI 0xcc03 +#define OATC14_DCQ_SQI_VALUE GENMASK(2, 0) + +/* Dynamic Channel Quality SQI Plus Register */ +#define MDIO_OATC14_DCQ_SQIPLUS 0xcc04 +#define OATC14_DCQ_SQIPLUS_VALUE GENMASK(7, 0) + +/* SQI is supported using 3 bits means 8 levels (0-7) */ +#define OATC14_SQI_MAX_LEVEL 7 + +/* Bus Short/Open Status: + * 0 0 - no fault; everything is ok. (Default) + * 0 1 - detected as an open or missing termination(s) + * 1 0 - detected as a short or extra termination(s) + * 1 1 - fault but fault type not detectable. More details can be available by + * vender specific register if supported. + */ +enum oatc14_hdd_status { + OATC14_HDD_STATUS_CABLE_OK = 0, + OATC14_HDD_STATUS_OPEN, + OATC14_HDD_STATUS_SHORT, + OATC14_HDD_STATUS_NOT_DETECTABLE, +}; + #endif /* __MDIO_OPEN_ALLIANCE__ */ diff --git a/drivers/net/phy/mdio-private.h b/drivers/net/phy/mdio-private.h new file mode 100644 index 000000000000..8bc6d9088af1 --- /dev/null +++ b/drivers/net/phy/mdio-private.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __MDIO_PRIVATE_H +#define __MDIO_PRIVATE_H + +/* MDIO internal helpers + */ + +int mdio_device_register_reset(struct mdio_device *mdiodev); +void mdio_device_unregister_reset(struct mdio_device *mdiodev); + +#endif /* __MDIO_PRIVATE_H */ diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 4354241137d5..afdf1ad6c0e6 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -29,37 +29,11 @@ #include <linux/string.h> #include <linux/uaccess.h> #include <linux/unistd.h> +#include "mdio-private.h" #define CREATE_TRACE_POINTS #include <trace/events/mdio.h> -static int mdiobus_register_gpiod(struct mdio_device *mdiodev) -{ - /* Deassert the optional reset signal */ - mdiodev->reset_gpio = gpiod_get_optional(&mdiodev->dev, - "reset", GPIOD_OUT_LOW); - if (IS_ERR(mdiodev->reset_gpio)) - return PTR_ERR(mdiodev->reset_gpio); - - if (mdiodev->reset_gpio) - gpiod_set_consumer_name(mdiodev->reset_gpio, "PHY reset"); - - return 0; -} - -static int mdiobus_register_reset(struct mdio_device *mdiodev) -{ - struct reset_control *reset; - - reset = reset_control_get_optional_exclusive(&mdiodev->dev, "phy"); - if (IS_ERR(reset)) - return PTR_ERR(reset); - - mdiodev->reset_ctrl = reset; - - return 0; -} - int mdiobus_register_device(struct mdio_device *mdiodev) { int err; @@ -68,17 +42,10 @@ int mdiobus_register_device(struct mdio_device *mdiodev) return -EBUSY; if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { - err = mdiobus_register_gpiod(mdiodev); + err = mdio_device_register_reset(mdiodev); if (err) return err; - err = mdiobus_register_reset(mdiodev); - if (err) { - gpiod_put(mdiodev->reset_gpio); - mdiodev->reset_gpio = NULL; - return err; - } - /* Assert the reset signal */ mdio_device_reset(mdiodev, 1); } @@ -94,8 +61,7 @@ int mdiobus_unregister_device(struct mdio_device *mdiodev) if (mdiodev->bus->mdio_map[mdiodev->addr] != mdiodev) return -EINVAL; - gpiod_put(mdiodev->reset_gpio); - reset_control_put(mdiodev->reset_ctrl); + mdio_device_unregister_reset(mdiodev); mdiodev->bus->mdio_map[mdiodev->addr] = NULL; @@ -373,7 +339,7 @@ EXPORT_SYMBOL_GPL(mdio_bus_class); * mdio_find_bus - Given the name of a mdiobus, find the mii_bus. * @mdio_name: The name of a mdiobus. * - * Returns a reference to the mii_bus, or NULL if none found. The + * Return: a reference to the mii_bus, or NULL if none found. The * embedded struct device will have its reference count incremented, * and this must be put_deviced'ed once the bus is finished with. */ @@ -391,7 +357,7 @@ EXPORT_SYMBOL(mdio_find_bus); * of_mdio_find_bus - Given an mii_bus node, find the mii_bus. * @mdio_bus_np: Pointer to the mii_bus. * - * Returns a reference to the mii_bus, or NULL if none found. The + * Return: a reference to the mii_bus, or NULL if none found. The * embedded struct device will have its reference count incremented, * and this must be put once the bus is finished with. * @@ -439,6 +405,8 @@ out: * @addr: the phy address * @regnum: register number to read * + * Return: The register value if successful, negative error code on failure + * * Read a MDIO bus register. Caller must hold the mdio bus lock. * * NOTE: MUST NOT be called from interrupt context. @@ -471,6 +439,8 @@ EXPORT_SYMBOL(__mdiobus_read); * @regnum: register number to write * @val: value to write to @regnum * + * Return: Zero if successful, negative error code on failure + * * Write a MDIO bus register. Caller must hold the mdio bus lock. * * NOTE: MUST NOT be called from interrupt context. @@ -504,8 +474,11 @@ EXPORT_SYMBOL(__mdiobus_write); * @mask: bit mask of bits to clear * @set: bit mask of bits to set * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition + * * Read, modify, and if any change, write the register value back to the - * device. Any error returns a negative number. + * device. * * NOTE: MUST NOT be called from interrupt context. */ @@ -535,6 +508,8 @@ EXPORT_SYMBOL_GPL(__mdiobus_modify_changed); * @devad: device address to read * @regnum: register number to read * + * Return: The register value if successful, negative error code on failure + * * Read a MDIO bus register. Caller must hold the mdio bus lock. * * NOTE: MUST NOT be called from interrupt context. @@ -568,6 +543,8 @@ EXPORT_SYMBOL(__mdiobus_c45_read); * @regnum: register number to write * @val: value to write to @regnum * + * Return: Zero if successful, negative error code on failure + * * Write a MDIO bus register. Caller must hold the mdio bus lock. * * NOTE: MUST NOT be called from interrupt context. @@ -603,6 +580,9 @@ EXPORT_SYMBOL(__mdiobus_c45_write); * @mask: bit mask of bits to clear * @set: bit mask of bits to set * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition + * * Read, modify, and if any change, write the register value back to the * device. Any error returns a negative number. * @@ -633,6 +613,8 @@ static int __mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, * @addr: the phy address * @regnum: register number to read * + * Return: The register value if successful, negative error code on failure + * * In case of nested MDIO bus access avoid lockdep false positives by * using mutex_lock_nested(). * @@ -658,6 +640,8 @@ EXPORT_SYMBOL(mdiobus_read_nested); * @addr: the phy address * @regnum: register number to read * + * Return: The register value if successful, negative error code on failure + * * NOTE: MUST NOT be called from interrupt context, * because the bus read/write functions may wait for an interrupt * to conclude the operation. @@ -681,6 +665,8 @@ EXPORT_SYMBOL(mdiobus_read); * @devad: device address to read * @regnum: register number to read * + * Return: The register value if successful, negative error code on failure + * * NOTE: MUST NOT be called from interrupt context, * because the bus read/write functions may wait for an interrupt * to conclude the operation. @@ -704,6 +690,8 @@ EXPORT_SYMBOL(mdiobus_c45_read); * @devad: device address to read * @regnum: register number to read * + * Return: The register value if successful, negative error code on failure + * * In case of nested MDIO bus access avoid lockdep false positives by * using mutex_lock_nested(). * @@ -731,6 +719,8 @@ EXPORT_SYMBOL(mdiobus_c45_read_nested); * @regnum: register number to write * @val: value to write to @regnum * + * Return: Zero if successful, negative error code on failure + * * In case of nested MDIO bus access avoid lockdep false positives by * using mutex_lock_nested(). * @@ -757,6 +747,8 @@ EXPORT_SYMBOL(mdiobus_write_nested); * @regnum: register number to write * @val: value to write to @regnum * + * Return: Zero if successful, negative error code on failure + * * NOTE: MUST NOT be called from interrupt context, * because the bus read/write functions may wait for an interrupt * to conclude the operation. @@ -781,6 +773,8 @@ EXPORT_SYMBOL(mdiobus_write); * @regnum: register number to write * @val: value to write to @regnum * + * Return: Zero if successful, negative error code on failure + * * NOTE: MUST NOT be called from interrupt context, * because the bus read/write functions may wait for an interrupt * to conclude the operation. @@ -806,6 +800,8 @@ EXPORT_SYMBOL(mdiobus_c45_write); * @regnum: register number to write * @val: value to write to @regnum * + * Return: Zero if successful, negative error code on failure + * * In case of nested MDIO bus access avoid lockdep false positives by * using mutex_lock_nested(). * @@ -834,6 +830,8 @@ EXPORT_SYMBOL(mdiobus_c45_write_nested); * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set + * + * Return: 0 on success, negative on any error condition */ int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set) @@ -854,6 +852,8 @@ EXPORT_SYMBOL_GPL(__mdiobus_modify); * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set + * + * Return: 0 on success, negative on any error condition */ int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set) { @@ -876,6 +876,8 @@ EXPORT_SYMBOL_GPL(mdiobus_modify); * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set + * + * Return: 0 on success, negative on any error condition */ int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum, u16 mask, u16 set) @@ -899,6 +901,9 @@ EXPORT_SYMBOL_GPL(mdiobus_c45_modify); * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set + * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition */ int mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set) @@ -922,6 +927,9 @@ EXPORT_SYMBOL_GPL(mdiobus_modify_changed); * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set + * + * Return: 1 if the register was modified, 0 if no change was needed, + * negative on any error condition */ int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad, u32 regnum, u16 mask, u16 set) @@ -942,10 +950,10 @@ EXPORT_SYMBOL_GPL(mdiobus_c45_modify_changed); * @dev: target MDIO device * @drv: given MDIO driver * - * Description: Given a MDIO device, and a MDIO driver, return 1 if - * the driver supports the device. Otherwise, return 0. This may - * require calling the devices own match function, since different classes - * of MDIO devices have different match criteria. + * Return: 1 if the driver supports the device, 0 otherwise + * + * Description: This may require calling the devices own match function, + * since different classes of MDIO devices have different match criteria. */ static int mdio_bus_match(struct device *dev, const struct device_driver *drv) { diff --git a/drivers/net/phy/mdio_bus_provider.c b/drivers/net/phy/mdio_bus_provider.c index a2391d4b7e5c..4b0637405740 100644 --- a/drivers/net/phy/mdio_bus_provider.c +++ b/drivers/net/phy/mdio_bus_provider.c @@ -249,20 +249,15 @@ static int mdiobus_scan_bus_c45(struct mii_bus *bus) */ static bool mdiobus_prevent_c45_scan(struct mii_bus *bus) { - int i; + struct phy_device *phydev; - for (i = 0; i < PHY_MAX_ADDR; i++) { - struct phy_device *phydev; - u32 oui; - - phydev = mdiobus_get_phy(bus, i); - if (!phydev) - continue; - oui = phydev->phy_id >> 10; + mdiobus_for_each_phy(bus, phydev) { + u32 oui = phydev->phy_id >> 10; if (oui == MICREL_OUI) return true; } + return false; } diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index f64176e0e197..6e90ed42cd98 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -22,6 +22,7 @@ #include <linux/string.h> #include <linux/unistd.h> #include <linux/property.h> +#include "mdio-private.h" void mdio_device_free(struct mdio_device *mdiodev) { @@ -77,6 +78,8 @@ EXPORT_SYMBOL(mdio_device_create); /** * mdio_device_register - Register the mdio device on the MDIO bus * @mdiodev: mdio_device structure to be added to the MDIO bus + * + * Return: Zero if successful, negative error code on failure */ int mdio_device_register(struct mdio_device *mdiodev) { @@ -118,6 +121,59 @@ void mdio_device_remove(struct mdio_device *mdiodev) } EXPORT_SYMBOL(mdio_device_remove); +/** + * mdio_device_register_reset - Read and initialize the reset properties of + * an mdio device + * @mdiodev: mdio_device structure + * + * Return: Zero if successful, negative error code on failure + */ +int mdio_device_register_reset(struct mdio_device *mdiodev) +{ + struct reset_control *reset; + + /* Deassert the optional reset signal */ + mdiodev->reset_gpio = gpiod_get_optional(&mdiodev->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(mdiodev->reset_gpio)) + return PTR_ERR(mdiodev->reset_gpio); + + if (mdiodev->reset_gpio) + gpiod_set_consumer_name(mdiodev->reset_gpio, "PHY reset"); + + reset = reset_control_get_optional_exclusive(&mdiodev->dev, "phy"); + if (IS_ERR(reset)) { + gpiod_put(mdiodev->reset_gpio); + mdiodev->reset_gpio = NULL; + return PTR_ERR(reset); + } + + mdiodev->reset_ctrl = reset; + + /* Read optional firmware properties */ + device_property_read_u32(&mdiodev->dev, "reset-assert-us", + &mdiodev->reset_assert_delay); + device_property_read_u32(&mdiodev->dev, "reset-deassert-us", + &mdiodev->reset_deassert_delay); + + return 0; +} + +/** + * mdio_device_unregister_reset - uninitialize the reset properties of + * an mdio device + * @mdiodev: mdio_device structure + */ +void mdio_device_unregister_reset(struct mdio_device *mdiodev) +{ + gpiod_put(mdiodev->reset_gpio); + mdiodev->reset_gpio = NULL; + reset_control_put(mdiodev->reset_ctrl); + mdiodev->reset_ctrl = NULL; + mdiodev->reset_assert_delay = 0; + mdiodev->reset_deassert_delay = 0; +} + void mdio_device_reset(struct mdio_device *mdiodev, int value) { unsigned int d; @@ -152,6 +208,8 @@ EXPORT_SYMBOL(mdio_device_reset); * * Description: Take care of setting up the mdio_device structure * and calling the driver to probe the device. + * + * Return: Zero if successful, negative error code on failure */ static int mdio_probe(struct device *dev) { @@ -202,6 +260,8 @@ static void mdio_shutdown(struct device *dev) /** * mdio_driver_register - register an mdio_driver with the MDIO layer * @drv: new mdio_driver to register + * + * Return: Zero if successful, negative error code on failure */ int mdio_driver_register(struct mdio_driver *drv) { diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 01c87c9b7702..05de68b9f719 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -101,6 +101,8 @@ #define LAN8814_CABLE_DIAG_VCT_DATA_MASK GENMASK(7, 0) #define LAN8814_PAIR_BIT_SHIFT 12 +#define LAN8814_SKUS 0xB + #define LAN8814_WIRE_PAIR_MASK 0xF /* Lan8814 general Interrupt control/status reg in GPHY specific block. */ @@ -367,6 +369,9 @@ #define LAN8842_REV_8832 0x8832 +#define LAN8814_REV_LAN8814 0x8814 +#define LAN8814_REV_LAN8818 0x8818 + struct kszphy_hw_stat { const char *string; u8 reg; @@ -449,6 +454,7 @@ struct kszphy_priv { bool rmii_ref_clk_sel; bool rmii_ref_clk_sel_val; bool clk_enable; + bool is_ptp_available; u64 stats[ARRAY_SIZE(kszphy_hw_stats)]; struct kszphy_phy_stats phy_stats; }; @@ -1056,7 +1062,7 @@ static int ksz9021_config_init(struct phy_device *phydev) #define TX_CLK_ID 0x1f /* set tx and tx_clk to "No delay adjustment" to keep 0ns - * dealy + * delay */ #define TX_ND 0x7 #define TX_CLK_ND 0xf @@ -1919,7 +1925,7 @@ static int ksz886x_config_aneg(struct phy_device *phydev) return ret; if (phydev->autoneg != AUTONEG_ENABLE) { - /* When autonegotation is disabled, we need to manually force + /* When autonegotiation is disabled, we need to manually force * the link state. If we don't do this, the PHY will keep * sending Fast Link Pulses (FLPs) which are part of the * autonegotiation process. This is not desired when @@ -2101,11 +2107,7 @@ static int ksz9477_phy_errata(struct phy_device *phydev) return err; } - err = genphy_restart_aneg(phydev); - if (err) - return err; - - return err; + return genphy_restart_aneg(phydev); } static int ksz9477_config_init(struct phy_device *phydev) @@ -2329,6 +2331,106 @@ static int kszphy_get_sqi_max(struct phy_device *phydev) return KSZ9477_SQI_MAX; } +static int kszphy_get_mse_capability(struct phy_device *phydev, + struct phy_mse_capability *cap) +{ + /* Capabilities depend on link mode: + * - 1000BASE-T: per-pair SQI registers exist => expose A..D + * and a WORST selector. + * - 100BASE-TX: HW provides a single MSE/SQI reading in the "channel A" + * register, but with auto MDI-X there is no MDI-X resolution bit, + * so we cannot map that register to a specific wire pair reliably. + * To avoid misleading per-channel data, advertise only LINK. + * Other speeds: no MSE exposure via this driver. + * + * Note: WORST is *not* a hardware selector on this family. + * We expose it because the driver computes it in software + * by scanning per-channel readouts (A..D) and picking the + * maximum average MSE. + */ + if (phydev->speed == SPEED_1000) + cap->supported_caps = PHY_MSE_CAP_CHANNEL_A | + PHY_MSE_CAP_CHANNEL_B | + PHY_MSE_CAP_CHANNEL_C | + PHY_MSE_CAP_CHANNEL_D | + PHY_MSE_CAP_WORST_CHANNEL; + else if (phydev->speed == SPEED_100) + cap->supported_caps = PHY_MSE_CAP_LINK; + else + return -EOPNOTSUPP; + + cap->max_average_mse = FIELD_MAX(KSZ9477_MMD_SQI_MASK); + cap->refresh_rate_ps = 2000000; /* 2 us */ + /* Estimated from link modulation (125 MBd per channel) and documented + * refresh rate of 2 us + */ + cap->num_symbols = 250; + + cap->supported_caps |= PHY_MSE_CAP_AVG; + + return 0; +} + +static int kszphy_get_mse_snapshot(struct phy_device *phydev, + enum phy_mse_channel channel, + struct phy_mse_snapshot *snapshot) +{ + u8 num_channels; + int ret; + + if (phydev->speed == SPEED_1000) + num_channels = 4; + else if (phydev->speed == SPEED_100) + num_channels = 1; + else + return -EOPNOTSUPP; + + if (channel == PHY_MSE_CHANNEL_WORST) { + u32 worst_val = 0; + int i; + + /* WORST is implemented in software: select the maximum + * average MSE across the available per-channel registers. + * Only defined when multiple channels exist (1000BASE-T). + */ + if (num_channels < 2) + return -EOPNOTSUPP; + + for (i = 0; i < num_channels; i++) { + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + i); + if (ret < 0) + return ret; + + ret = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret); + if (ret > worst_val) + worst_val = ret; + } + snapshot->average_mse = worst_val; + } else if (channel == PHY_MSE_CHANNEL_LINK && num_channels == 1) { + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A); + if (ret < 0) + return ret; + snapshot->average_mse = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret); + } else if (channel >= PHY_MSE_CHANNEL_A && + channel <= PHY_MSE_CHANNEL_D) { + /* Per-channel readouts are valid only for 1000BASE-T. */ + if (phydev->speed != SPEED_1000) + return -EOPNOTSUPP; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + KSZ9477_MMD_SIGNAL_QUALITY_CHAN_A + channel); + if (ret < 0) + return ret; + snapshot->average_mse = FIELD_GET(KSZ9477_MMD_SQI_MASK, ret); + } else { + return -EOPNOTSUPP; + } + + return 0; +} + static void kszphy_enable_clk(struct phy_device *phydev) { struct kszphy_priv *priv = phydev->priv; @@ -2886,6 +2988,8 @@ static int ksz886x_cable_test_get_status(struct phy_device *phydev, #define LAN_EXT_PAGE_ACCESS_ADDRESS_DATA 0x17 #define LAN_EXT_PAGE_ACCESS_CTRL_EP_FUNC 0x4000 +#define LAN8814_QSGMII_TX_CONFIG 0x35 +#define LAN8814_QSGMII_TX_CONFIG_QSGMII BIT(3) #define LAN8814_QSGMII_SOFT_RESET 0x43 #define LAN8814_QSGMII_SOFT_RESET_BIT BIT(0) #define LAN8814_QSGMII_PCS1G_ANEG_CONFIG 0x13 @@ -3043,9 +3147,9 @@ static void lan8814_flush_fifo(struct phy_device *phydev, bool egress) lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_TSU_INT_STS); } -static int lan8814_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *config, - struct netlink_ext_ack *extack) +static int lan8814_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) { struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); @@ -3557,7 +3661,7 @@ static void lan8814_ptp_disable_event(struct phy_device *phydev, int event) /* Set target to too far in the future, effectively disabling it */ lan8814_ptp_set_target(phydev, event, 0xFFFFFFFF, 0); - /* And then reload once it recheas the target */ + /* And then reload once it reaches the target */ lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, LAN8814_PTP_GENERAL_CONFIG, LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event), LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event)); @@ -4150,6 +4254,17 @@ static int lan8804_config_intr(struct phy_device *phydev) return 0; } +/* Check if the PHY has 1588 support. There are multiple skus of the PHY and + * some of them support PTP while others don't support it. This function will + * return true is the sku supports it, otherwise will return false. + */ +static bool lan8814_has_ptp(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + + return priv->is_ptp_available; +} + static irqreturn_t lan8814_handle_interrupt(struct phy_device *phydev) { int ret = IRQ_NONE; @@ -4166,6 +4281,9 @@ static irqreturn_t lan8814_handle_interrupt(struct phy_device *phydev) ret = IRQ_HANDLED; } + if (!lan8814_has_ptp(phydev)) + return ret; + while (true) { irq_status = lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_TSU_INT_STS); @@ -4227,6 +4345,9 @@ static void lan8814_ptp_init(struct phy_device *phydev) !IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING)) return; + if (!lan8814_has_ptp(phydev)) + return; + lanphy_write_page_reg(phydev, LAN8814_PAGE_PORT_REGS, TSU_HARD_RESET, TSU_HARD_RESET_); @@ -4268,7 +4389,7 @@ static void lan8814_ptp_init(struct phy_device *phydev) ptp_priv->mii_ts.rxtstamp = lan8814_rxtstamp; ptp_priv->mii_ts.txtstamp = lan8814_txtstamp; - ptp_priv->mii_ts.hwtstamp = lan8814_hwtstamp; + ptp_priv->mii_ts.hwtstamp_set = lan8814_hwtstamp_set; ptp_priv->mii_ts.ts_info = lan8814_ts_info; phydev->mii_ts = &ptp_priv->mii_ts; @@ -4356,6 +4477,9 @@ static int __lan8814_ptp_probe_once(struct phy_device *phydev, char *pin_name, static int lan8814_ptp_probe_once(struct phy_device *phydev) { + if (!lan8814_has_ptp(phydev)) + return 0; + return __lan8814_ptp_probe_once(phydev, "lan8814_ptp_pin", LAN8814_PTP_GPIO_NUM); } @@ -4379,12 +4503,24 @@ static void lan8814_setup_led(struct phy_device *phydev, int val) static int lan8814_config_init(struct phy_device *phydev) { struct kszphy_priv *lan8814 = phydev->priv; + int ret; - /* Disable ANEG with QSGMII PCS Host side */ - lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS, - LAN8814_QSGMII_PCS1G_ANEG_CONFIG, - LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA, - 0); + /* Based on the interface type select how the advertise ability is + * encoded, to set as SGMII or as USGMII. + */ + if (phydev->interface == PHY_INTERFACE_MODE_QSGMII) + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_QSGMII_TX_CONFIG, + LAN8814_QSGMII_TX_CONFIG_QSGMII, + LAN8814_QSGMII_TX_CONFIG_QSGMII); + else + ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_QSGMII_TX_CONFIG, + LAN8814_QSGMII_TX_CONFIG_QSGMII, + 0); + + if (ret < 0) + return ret; /* MDI-X setting for swap A,B transmit */ lanphy_modify_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, LAN8814_ALIGN_SWAP, @@ -4421,7 +4557,7 @@ static int lan8814_release_coma_mode(struct phy_device *phydev) static void lan8814_clear_2psp_bit(struct phy_device *phydev) { /* It was noticed that when traffic is passing through the PHY and the - * cable is removed then the LED was still one even though there is no + * cable is removed then the LED was still on even though there is no * link */ lanphy_modify_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL, LAN8814_EEE_STATE, @@ -4464,6 +4600,18 @@ static int lan8814_probe(struct phy_device *phydev) devm_phy_package_join(&phydev->mdio.dev, phydev, addr, sizeof(struct lan8814_shared_priv)); + /* There are lan8814 SKUs that don't support PTP. Make sure that for + * those skus no PTP device is created. Here we check if the SKU + * supports PTP. + */ + err = lanphy_read_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, + LAN8814_SKUS); + if (err < 0) + return err; + + priv->is_ptp_available = err == LAN8814_REV_LAN8814 || + err == LAN8814_REV_LAN8818; + if (phy_package_init_once(phydev)) { /* Reset the PHY */ lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS, @@ -4567,7 +4715,7 @@ static int lan8841_config_init(struct phy_device *phydev) phy_write_mmd(phydev, KSZ9131RN_MMD_COMMON_CTRL_REG, LAN8841_PTP_TX_VERSION, 0xff00); - /* 100BT Clause 40 improvenent errata */ + /* 100BT Clause 40 improvement errata */ phy_write_mmd(phydev, LAN8841_MMD_ANALOG_REG, LAN8841_ANALOG_CONTROL_1, LAN8841_ANALOG_CONTROL_1_PLL_TRIM(0x2)); @@ -4894,9 +5042,9 @@ static void lan8841_ptp_enable_processing(struct kszphy_ptp_priv *ptp_priv, #define LAN8841_PTP_TX_TIMESTAMP_EN 443 #define LAN8841_PTP_TX_MOD 445 -static int lan8841_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *config, - struct netlink_ext_ack *extack) +static int lan8841_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) { struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); struct phy_device *phydev = ptp_priv->phydev; @@ -5587,7 +5735,7 @@ static int lan8841_ptp_extts_on(struct kszphy_ptp_priv *ptp_priv, int pin, u16 tmp = 0; int ret; - /* Set GPIO to be intput */ + /* Set GPIO to be input */ ret = phy_set_bits_mmd(phydev, 2, LAN8841_GPIO_EN, BIT(pin)); if (ret) return ret; @@ -5776,7 +5924,7 @@ static int lan8841_probe(struct phy_device *phydev) 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.hwtstamp_set = lan8841_hwtstamp_set; ptp_priv->mii_ts.ts_info = lan8841_ts_info; phydev->mii_ts = &ptp_priv->mii_ts; @@ -6506,6 +6654,8 @@ static struct phy_driver ksphy_driver[] = { .suspend = genphy_suspend, .resume = kszphy_resume, .config_intr = lan8814_config_intr, + .inband_caps = lan8842_inband_caps, + .config_inband = lan8842_config_inband, .handle_interrupt = lan8814_handle_interrupt, .cable_test_start = lan8814_cable_test_start, .cable_test_get_status = ksz886x_cable_test_get_status, @@ -6626,6 +6776,8 @@ static struct phy_driver ksphy_driver[] = { .cable_test_get_status = ksz9x31_cable_test_get_status, .get_sqi = kszphy_get_sqi, .get_sqi_max = kszphy_get_sqi_max, + .get_mse_capability = kszphy_get_mse_capability, + .get_mse_snapshot = kszphy_get_mse_snapshot, } }; module_phy_driver(ksphy_driver); diff --git a/drivers/net/phy/microchip_rds_ptp.c b/drivers/net/phy/microchip_rds_ptp.c index e6514ce04c29..4c6326b0ceaf 100644 --- a/drivers/net/phy/microchip_rds_ptp.c +++ b/drivers/net/phy/microchip_rds_ptp.c @@ -476,9 +476,9 @@ static bool mchp_rds_ptp_rxtstamp(struct mii_timestamper *mii_ts, return true; } -static int mchp_rds_ptp_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *config, - struct netlink_ext_ack *extack) +static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) { struct mchp_rds_ptp_clock *clock = container_of(mii_ts, struct mchp_rds_ptp_clock, @@ -1281,7 +1281,7 @@ struct mchp_rds_ptp_clock *mchp_rds_ptp_probe(struct phy_device *phydev, u8 mmd, clock->mii_ts.rxtstamp = mchp_rds_ptp_rxtstamp; clock->mii_ts.txtstamp = mchp_rds_ptp_txtstamp; - clock->mii_ts.hwtstamp = mchp_rds_ptp_hwtstamp; + clock->mii_ts.hwtstamp_set = mchp_rds_ptp_hwtstamp_set; clock->mii_ts.ts_info = mchp_rds_ptp_ts_info; phydev->mii_ts = &clock->mii_ts; diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index e50a0c102a86..e601d56b2507 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -3,7 +3,7 @@ * Driver for Microchip 10BASE-T1S PHYs * * Support: Microchip Phys: - * lan8670/1/2 Rev.B1/C1/C2 + * lan8670/1/2 Rev.B1/C1/C2/D0 * lan8650/1 Rev.B0/B1 Internal PHYs */ @@ -14,6 +14,7 @@ #define PHY_ID_LAN867X_REVB1 0x0007C162 #define PHY_ID_LAN867X_REVC1 0x0007C164 #define PHY_ID_LAN867X_REVC2 0x0007C165 +#define PHY_ID_LAN867X_REVD0 0x0007C166 /* Both Rev.B0 and B1 clause 22 PHYID's are same due to B1 chip limitation */ #define PHY_ID_LAN865X_REVB 0x0007C1B3 @@ -32,6 +33,17 @@ #define COL_DET_ENABLE BIT(15) #define COL_DET_DISABLE 0x0000 +/* LAN8670/1/2 Rev.D0 Link Status Selection Register */ +#define LAN867X_REG_LINK_STATUS_CTRL 0x0012 +#define LINK_STATUS_CONFIGURATION GENMASK(12, 11) +#define LINK_STATUS_SEMAPHORE BIT(0) + +/* Link Status Configuration */ +#define LINK_STATUS_CONFIG_PLCA_STATUS 0x1 +#define LINK_STATUS_CONFIG_SEMAPHORE 0x2 + +#define LINK_STATUS_SEMAPHORE_SET 0x1 + #define LAN865X_CFGPARAM_READ_ENABLE BIT(1) /* The arrays below are pulled from the following table from AN1699 @@ -109,6 +121,21 @@ static const u16 lan865x_revb_sqi_fixup_cfg_regs[3] = { 0x00AD, 0x00AE, 0x00AF, }; +/* LAN867x Rev.D0 configuration parameters from AN1699 + * As per the Configuration Application Note AN1699 published in the below link, + * https://www.microchip.com/en-us/application-notes/an1699 + * Revision G (DS60001699G - October 2025) + */ +static const u16 lan867x_revd0_fixup_regs[8] = { + 0x0037, 0x008A, 0x0118, 0x00D6, + 0x0082, 0x00FD, 0x00FD, 0x0091, +}; + +static const u16 lan867x_revd0_fixup_values[8] = { + 0x0800, 0xBFC0, 0x029C, 0x1001, + 0x001C, 0x0C0B, 0x8C07, 0x9660, +}; + /* Pulled from AN1760 describing 'indirect read' * * write_register(0x4, 0x00D8, addr) @@ -377,6 +404,32 @@ static int lan867x_revb1_config_init(struct phy_device *phydev) return 0; } +static int lan867x_revd0_link_active_selection(struct phy_device *phydev, + bool plca_enabled) +{ + u16 value; + + if (plca_enabled) { + /* 0x1 - When PLCA is enabled: link status reflects plca_status. + */ + value = FIELD_PREP(LINK_STATUS_CONFIGURATION, + LINK_STATUS_CONFIG_PLCA_STATUS); + } else { + /* 0x2 - Link status is controlled by the value written into the + * LINK_STATUS_SEMAPHORE bit written. Here the link semaphore + * bit is written with 0x1 to set the link always active in + * CSMA/CD mode as it doesn't support autoneg. + */ + value = FIELD_PREP(LINK_STATUS_CONFIGURATION, + LINK_STATUS_CONFIG_SEMAPHORE) | + FIELD_PREP(LINK_STATUS_SEMAPHORE, + LINK_STATUS_SEMAPHORE_SET); + } + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, + LAN867X_REG_LINK_STATUS_CTRL, value); +} + /* As per LAN8650/1 Rev.B0/B1 AN1760 (Revision F (DS60001760G - June 2024)) and * LAN8670/1/2 Rev.C1/C2 AN1699 (Revision E (DS60001699F - June 2024)), under * normal operation, the device should be operated in PLCA mode. Disabling @@ -393,6 +446,14 @@ static int lan86xx_plca_set_cfg(struct phy_device *phydev, { int ret; + /* Link status selection must be configured for LAN8670/1/2 Rev.D0 */ + if (phydev->phy_id == PHY_ID_LAN867X_REVD0) { + ret = lan867x_revd0_link_active_selection(phydev, + plca_cfg->enabled); + if (ret) + return ret; + } + ret = genphy_c45_plca_set_cfg(phydev, plca_cfg); if (ret) return ret; @@ -407,6 +468,29 @@ static int lan86xx_plca_set_cfg(struct phy_device *phydev, COL_DET_CTRL0_ENABLE_BIT_MASK, COL_DET_ENABLE); } +static int lan867x_revd0_config_init(struct phy_device *phydev) +{ + int ret; + + ret = lan867x_check_reset_complete(phydev); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(lan867x_revd0_fixup_regs); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + lan867x_revd0_fixup_regs[i], + lan867x_revd0_fixup_values[i]); + if (ret) + return ret; + } + + /* Initially the PHY will be in CSMA/CD mode by default. So it is + * required to set the link always active as it doesn't support + * autoneg. + */ + return lan867x_revd0_link_active_selection(phydev, false); +} + static int lan86xx_read_status(struct phy_device *phydev) { /* The phy has some limitations, namely: @@ -482,6 +566,19 @@ static struct phy_driver microchip_t1s_driver[] = { .get_plca_status = genphy_c45_plca_get_status, }, { + PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVD0), + .name = "LAN867X Rev.D0", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .config_init = lan867x_revd0_config_init, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = lan86xx_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + .cable_test_start = genphy_c45_oatc14_cable_test_start, + .cable_test_get_status = genphy_c45_oatc14_cable_test_get_status, + .get_sqi = genphy_c45_oatc14_get_sqi, + .get_sqi_max = genphy_c45_oatc14_get_sqi_max, + }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB), .name = "LAN865X Rev.B0/B1 Internal Phy", .features = PHY_BASIC_T1S_P2MP_FEATURES, @@ -501,6 +598,7 @@ static const struct mdio_device_id __maybe_unused tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1) }, { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC1) }, { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC2) }, + { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVD0) }, { PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB) }, { } }; diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index a3593e663059..89b5b19a9bd2 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -3048,6 +3048,9 @@ static struct phy_driver motorcomm_phy_drvs[] = { .get_wol = ytphy_get_wol, .set_wol = yt8531_set_wol, .link_change_notify = yt8531_link_change_notify, + .led_hw_is_supported = yt8521_led_hw_is_supported, + .led_hw_control_set = yt8521_led_hw_control_set, + .led_hw_control_get = yt8521_led_hw_control_get, }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S), diff --git a/drivers/net/phy/mscc/mscc.h b/drivers/net/phy/mscc/mscc.h index 2d8eca54c40a..65c9d7bd9315 100644 --- a/drivers/net/phy/mscc/mscc.h +++ b/drivers/net/phy/mscc/mscc.h @@ -85,6 +85,10 @@ enum rgmii_clock_delay { #define LED_MODE_SEL_MASK(x) (GENMASK(3, 0) << LED_MODE_SEL_POS(x)) #define LED_MODE_SEL(x, mode) (((mode) << LED_MODE_SEL_POS(x)) & LED_MODE_SEL_MASK(x)) +#define MSCC_PHY_LED_BEHAVIOR 30 +#define LED_COMBINE_DIS_MASK(x) BIT(x) +#define LED_COMBINE_DIS(x, dis) (((dis) ? 1 : 0) << (x)) + #define MSCC_EXT_PAGE_CSR_CNTL_17 17 #define MSCC_EXT_PAGE_CSR_CNTL_18 18 @@ -289,12 +293,12 @@ enum rgmii_clock_delay { #define PHY_ID_VSC8540 0x00070760 #define PHY_ID_VSC8541 0x00070770 #define PHY_ID_VSC8552 0x000704e0 -#define PHY_ID_VSC856X 0x000707e0 +#define PHY_ID_VSC856X 0x000707e1 #define PHY_ID_VSC8572 0x000704d0 #define PHY_ID_VSC8574 0x000704a0 -#define PHY_ID_VSC8575 0x000707d0 -#define PHY_ID_VSC8582 0x000707b0 -#define PHY_ID_VSC8584 0x000707c0 +#define PHY_ID_VSC8575 0x000707d1 +#define PHY_ID_VSC8582 0x000707b1 +#define PHY_ID_VSC8584 0x000707c1 #define PHY_VENDOR_MSCC 0x00070400 #define MSCC_VDDMAC_1500 1500 diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c index ef0ef1570d39..2b9fb8a675a6 100644 --- a/drivers/net/phy/mscc/mscc_main.c +++ b/drivers/net/phy/mscc/mscc_main.c @@ -22,6 +22,24 @@ #include "mscc_serdes.h" #include "mscc.h" +struct vsc85xx_probe_config { + const struct vsc85xx_hw_stat *hw_stats; + size_t shared_size; + size_t nstats; + u16 supp_led_modes; + u8 nleds; + bool check_rate_magic; + bool use_package; + bool has_ptp; +}; + +static const u32 vsc85xx_default_led_modes_4[] = { + VSC8531_LINK_1000_ACTIVITY, + VSC8531_LINK_100_ACTIVITY, + VSC8531_LINK_ACTIVITY, + VSC8531_DUPLEX_COLLISION +}; + static const struct vsc85xx_hw_stat vsc85xx_hw_stats[] = { { .string = "phy_receive_errors", @@ -177,17 +195,19 @@ static int vsc85xx_led_cntl_set(struct phy_device *phydev, u8 led_num, u8 mode) { - int rc; - u16 reg_val; + u16 mask = LED_MODE_SEL_MASK(led_num); + u16 val = LED_MODE_SEL(led_num, mode); - mutex_lock(&phydev->lock); - reg_val = phy_read(phydev, MSCC_PHY_LED_MODE_SEL); - reg_val &= ~LED_MODE_SEL_MASK(led_num); - reg_val |= LED_MODE_SEL(led_num, (u16)mode); - rc = phy_write(phydev, MSCC_PHY_LED_MODE_SEL, reg_val); - mutex_unlock(&phydev->lock); + return phy_modify(phydev, MSCC_PHY_LED_MODE_SEL, mask, val); +} - return rc; +static int vsc85xx_led_combine_disable_set(struct phy_device *phydev, + u8 led_num, bool combine_disable) +{ + u16 val = LED_COMBINE_DIS(led_num, combine_disable); + u16 mask = LED_COMBINE_DIS_MASK(led_num); + + return phy_modify(phydev, MSCC_PHY_LED_BEHAVIOR, mask, val); } static int vsc85xx_mdix_get(struct phy_device *phydev, u8 *mdix) @@ -443,7 +463,7 @@ static int vsc85xx_dt_led_mode_get(struct phy_device *phydev, #endif /* CONFIG_OF_MDIO */ static int vsc85xx_dt_led_modes_get(struct phy_device *phydev, - u32 *default_mode) + const u32 *default_mode) { struct vsc8531_private *priv = phydev->priv; char led_dt_prop[28]; @@ -1724,12 +1744,6 @@ static int vsc8584_config_init(struct phy_device *phydev) * in this pre-init function. */ if (phy_package_init_once(phydev)) { - /* The following switch statement assumes that the lowest - * nibble of the phy_id_mask is always 0. This works because - * the lowest nibble of the PHY_ID's below are also 0. - */ - WARN_ON(phydev->drv->phy_id_mask & 0xf); - switch (phydev->phy_id & phydev->drv->phy_id_mask) { case PHY_ID_VSC8504: case PHY_ID_VSC8552: @@ -2224,12 +2238,13 @@ static int vsc85xx_config_inband(struct phy_device *phydev, unsigned int modes) reg_val); } -static int vsc8514_probe(struct phy_device *phydev) +static int vsc85xx_probe_common(struct phy_device *phydev, + const struct vsc85xx_probe_config *cfg, + const u32 *default_led_mode) { struct vsc8531_private *vsc8531; - u32 default_mode[4] = {VSC8531_LINK_1000_ACTIVITY, - VSC8531_LINK_100_ACTIVITY, VSC8531_LINK_ACTIVITY, - VSC8531_DUPLEX_COLLISION}; + struct device_node *np; + int ret; vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL); if (!vsc8531) @@ -2237,124 +2252,291 @@ static int vsc8514_probe(struct phy_device *phydev) phydev->priv = vsc8531; - vsc8584_get_base_addr(phydev); - devm_phy_package_join(&phydev->mdio.dev, phydev, - vsc8531->base_addr, 0); + /* Check rate magic if needed (only for non-package PHYs) */ + if (cfg->check_rate_magic) { + ret = vsc85xx_edge_rate_magic_get(phydev); + if (ret < 0) + return ret; - vsc8531->nleds = 4; - vsc8531->supp_led_modes = VSC85XX_SUPP_LED_MODES; - vsc8531->hw_stats = vsc85xx_hw_stats; - vsc8531->nstats = ARRAY_SIZE(vsc85xx_hw_stats); + vsc8531->rate_magic = ret; + } + + /* Set up package if needed */ + if (cfg->use_package) { + vsc8584_get_base_addr(phydev); + ret = devm_phy_package_join(&phydev->mdio.dev, phydev, + vsc8531->base_addr, + cfg->shared_size); + if (ret) + return ret; + } + + /* Configure LED settings */ + vsc8531->nleds = cfg->nleds; + vsc8531->supp_led_modes = cfg->supp_led_modes; + + /* Configure hardware stats */ + vsc8531->hw_stats = cfg->hw_stats; + vsc8531->nstats = cfg->nstats; vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats, sizeof(u64), GFP_KERNEL); if (!vsc8531->stats) return -ENOMEM; - return vsc85xx_dt_led_modes_get(phydev, default_mode); + /* PTP setup for VSC8584 */ + if (cfg->has_ptp) { + if (phy_package_probe_once(phydev)) { + ret = vsc8584_ptp_probe_once(phydev); + if (ret) + return ret; + } + + ret = vsc8584_ptp_probe(phydev); + if (ret) + return ret; + } + + /* + * Check for LED configuration in device tree if available + * or fall back to default `vsc8531,led-x-mode` DT properties. + */ + np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds"); + if (np) { + of_node_put(np); + + /* Force to defaults */ + for (unsigned int i = 0; i < vsc8531->nleds; i++) + vsc8531->leds_mode[i] = default_led_mode[i]; + + return 0; + } + + /* Parse LED modes from device tree */ + return vsc85xx_dt_led_modes_get(phydev, default_led_mode); } -static int vsc8574_probe(struct phy_device *phydev) +static int vsc85xx_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) { - struct vsc8531_private *vsc8531; - u32 default_mode[4] = {VSC8531_LINK_1000_ACTIVITY, - VSC8531_LINK_100_ACTIVITY, VSC8531_LINK_ACTIVITY, - VSC8531_DUPLEX_COLLISION}; + struct vsc8531_private *vsc8531 = phydev->priv; - vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL); - if (!vsc8531) - return -ENOMEM; + if (index >= vsc8531->nleds) + return -EINVAL; - phydev->priv = vsc8531; + return vsc85xx_led_cntl_set(phydev, index, value == LED_OFF ? + VSC8531_FORCE_LED_OFF : VSC8531_FORCE_LED_ON); +} - vsc8584_get_base_addr(phydev); - devm_phy_package_join(&phydev->mdio.dev, phydev, - vsc8531->base_addr, 0); +static int vsc85xx_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + static const unsigned long supported = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + struct vsc8531_private *vsc8531 = phydev->priv; - vsc8531->nleds = 4; - vsc8531->supp_led_modes = VSC8584_SUPP_LED_MODES; - vsc8531->hw_stats = vsc8584_hw_stats; - vsc8531->nstats = ARRAY_SIZE(vsc8584_hw_stats); - vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats, - sizeof(u64), GFP_KERNEL); - if (!vsc8531->stats) - return -ENOMEM; + if (index >= vsc8531->nleds) + return -EINVAL; - return vsc85xx_dt_led_modes_get(phydev, default_mode); + if (rules & ~supported) + return -EOPNOTSUPP; + + return 0; } -static int vsc8584_probe(struct phy_device *phydev) +static int vsc85xx_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) { - struct vsc8531_private *vsc8531; - u32 default_mode[4] = {VSC8531_LINK_1000_ACTIVITY, - VSC8531_LINK_100_ACTIVITY, VSC8531_LINK_ACTIVITY, - VSC8531_DUPLEX_COLLISION}; - int ret; + struct vsc8531_private *vsc8531 = phydev->priv; + u8 mode, behavior; + int rc; - if ((phydev->phy_id & MSCC_DEV_REV_MASK) != VSC8584_REVB) { - dev_err(&phydev->mdio.dev, "Only VSC8584 revB is supported.\n"); - return -ENOTSUPP; - } + if (index >= vsc8531->nleds) + return -EINVAL; - vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL); - if (!vsc8531) - return -ENOMEM; + rc = phy_read(phydev, MSCC_PHY_LED_MODE_SEL); + if (rc < 0) + return rc; + mode = (rc & LED_MODE_SEL_MASK(index)) >> LED_MODE_SEL_POS(index); - phydev->priv = vsc8531; + rc = phy_read(phydev, MSCC_PHY_LED_BEHAVIOR); + if (rc < 0) + return rc; + behavior = (rc & LED_COMBINE_DIS_MASK(index)) >> index; - vsc8584_get_base_addr(phydev); - devm_phy_package_join(&phydev->mdio.dev, phydev, vsc8531->base_addr, - sizeof(struct vsc85xx_shared_private)); + switch (mode) { + case VSC8531_LINK_ACTIVITY: + case VSC8531_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK); + break; - vsc8531->nleds = 4; - vsc8531->supp_led_modes = VSC8584_SUPP_LED_MODES; - vsc8531->hw_stats = vsc8584_hw_stats; - vsc8531->nstats = ARRAY_SIZE(vsc8584_hw_stats); - vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats, - sizeof(u64), GFP_KERNEL); - if (!vsc8531->stats) - return -ENOMEM; + case VSC8531_LINK_1000_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK); + break; - if (phy_package_probe_once(phydev)) { - ret = vsc8584_ptp_probe_once(phydev); - if (ret) - return ret; + case VSC8531_LINK_100_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_10_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_100_1000_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_10_1000_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK); + break; + + case VSC8531_LINK_10_100_ACTIVITY: + *rules = BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK); + break; + + default: + *rules = 0; + break; } - ret = vsc8584_ptp_probe(phydev); - if (ret) + if (!behavior && *rules) + *rules |= BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX); + + return 0; +} + +static int vsc85xx_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + struct vsc8531_private *vsc8531 = phydev->priv; + u8 mode = VSC8531_FORCE_LED_ON; + bool combine_disable = false; + bool has_rx, has_tx; + int ret; + + if (index >= vsc8531->nleds) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK)) + mode = VSC8531_LINK_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10)) + mode = VSC8531_LINK_10_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_100)) + mode = VSC8531_LINK_100_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_1000)) + mode = VSC8531_LINK_1000_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_100) && + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + mode = VSC8531_LINK_100_1000_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10) && + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + mode = VSC8531_LINK_10_1000_ACTIVITY; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10) && + rules & BIT(TRIGGER_NETDEV_LINK_100)) + mode = VSC8531_LINK_10_100_ACTIVITY; + + /* + * The VSC85xx PHYs provides an option to control LED behavior. By + * default, the LEDx combine function is enabled, meaning the LED + * will be on when there is link/activity or duplex/collision. If + * the combine function is disabled, the LED will be on only for + * link or duplex. + * + * To control this behavior, we check the selected rules. If both + * RX and TX activity are not selected, the LED combine function + * is disabled; otherwise, it remains enabled. + */ + has_rx = !!(rules & BIT(TRIGGER_NETDEV_RX)); + has_tx = !!(rules & BIT(TRIGGER_NETDEV_TX)); + if (!has_rx && !has_tx) + combine_disable = true; + + ret = vsc85xx_led_combine_disable_set(phydev, index, combine_disable); + if (ret < 0) return ret; - return vsc85xx_dt_led_modes_get(phydev, default_mode); + return vsc85xx_led_cntl_set(phydev, index, mode); } -static int vsc85xx_probe(struct phy_device *phydev) +static int vsc8514_probe(struct phy_device *phydev) { - struct vsc8531_private *vsc8531; - int rate_magic; - u32 default_mode[2] = {VSC8531_LINK_1000_ACTIVITY, - VSC8531_LINK_100_ACTIVITY}; + static const struct vsc85xx_probe_config vsc8514_cfg = { + .nleds = 4, + .supp_led_modes = VSC85XX_SUPP_LED_MODES, + .hw_stats = vsc85xx_hw_stats, + .nstats = ARRAY_SIZE(vsc85xx_hw_stats), + .use_package = true, + .shared_size = 0, + .has_ptp = false, + .check_rate_magic = false, + }; - rate_magic = vsc85xx_edge_rate_magic_get(phydev); - if (rate_magic < 0) - return rate_magic; + return vsc85xx_probe_common(phydev, &vsc8514_cfg, vsc85xx_default_led_modes_4); +} - vsc8531 = devm_kzalloc(&phydev->mdio.dev, sizeof(*vsc8531), GFP_KERNEL); - if (!vsc8531) - return -ENOMEM; +static int vsc8574_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc8574_cfg = { + .nleds = 4, + .supp_led_modes = VSC8584_SUPP_LED_MODES, + .hw_stats = vsc8584_hw_stats, + .nstats = ARRAY_SIZE(vsc8584_hw_stats), + .use_package = true, + .shared_size = 0, + .has_ptp = false, + .check_rate_magic = false, + }; - phydev->priv = vsc8531; + return vsc85xx_probe_common(phydev, &vsc8574_cfg, vsc85xx_default_led_modes_4); +} - vsc8531->rate_magic = rate_magic; - vsc8531->nleds = 2; - vsc8531->supp_led_modes = VSC85XX_SUPP_LED_MODES; - vsc8531->hw_stats = vsc85xx_hw_stats; - vsc8531->nstats = ARRAY_SIZE(vsc85xx_hw_stats); - vsc8531->stats = devm_kcalloc(&phydev->mdio.dev, vsc8531->nstats, - sizeof(u64), GFP_KERNEL); - if (!vsc8531->stats) - return -ENOMEM; +static int vsc8584_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc8584_cfg = { + .nleds = 4, + .supp_led_modes = VSC8584_SUPP_LED_MODES, + .hw_stats = vsc8584_hw_stats, + .nstats = ARRAY_SIZE(vsc8584_hw_stats), + .use_package = true, + .shared_size = sizeof(struct vsc85xx_shared_private), + .has_ptp = true, + .check_rate_magic = false, + }; + + return vsc85xx_probe_common(phydev, &vsc8584_cfg, vsc85xx_default_led_modes_4); +} + +static int vsc85xx_probe(struct phy_device *phydev) +{ + static const struct vsc85xx_probe_config vsc85xx_cfg = { + .nleds = 2, + .supp_led_modes = VSC85XX_SUPP_LED_MODES, + .hw_stats = vsc85xx_hw_stats, + .nstats = ARRAY_SIZE(vsc85xx_hw_stats), + .use_package = false, + .has_ptp = false, + .check_rate_magic = true, + }; - return vsc85xx_dt_led_modes_get(phydev, default_mode); + return vsc85xx_probe_common(phydev, &vsc85xx_cfg, vsc85xx_default_led_modes_4); } static void vsc85xx_remove(struct phy_device *phydev) @@ -2387,6 +2569,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_sset_count = &vsc85xx_get_sset_count, .get_strings = &vsc85xx_get_strings, .get_stats = &vsc85xx_get_stats, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8502, @@ -2411,6 +2597,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_sset_count = &vsc85xx_get_sset_count, .get_strings = &vsc85xx_get_strings, .get_stats = &vsc85xx_get_stats, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8504, @@ -2438,6 +2628,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8514, @@ -2463,6 +2657,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8530, @@ -2487,6 +2685,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_sset_count = &vsc85xx_get_sset_count, .get_strings = &vsc85xx_get_strings, .get_stats = &vsc85xx_get_stats, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8531, @@ -2511,6 +2713,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_sset_count = &vsc85xx_get_sset_count, .get_strings = &vsc85xx_get_strings, .get_stats = &vsc85xx_get_stats, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8540, @@ -2535,6 +2741,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_sset_count = &vsc85xx_get_sset_count, .get_strings = &vsc85xx_get_strings, .get_stats = &vsc85xx_get_stats, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8541, @@ -2559,6 +2769,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_sset_count = &vsc85xx_get_sset_count, .get_strings = &vsc85xx_get_strings, .get_stats = &vsc85xx_get_stats, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8552, @@ -2585,11 +2799,14 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { - .phy_id = PHY_ID_VSC856X, + PHY_ID_MATCH_EXACT(PHY_ID_VSC856X), .name = "Microsemi GE VSC856X SyncE", - .phy_id_mask = 0xfffffff0, /* PHY_GBIT_FEATURES */ .soft_reset = &genphy_soft_reset, .config_init = &vsc8584_config_init, @@ -2609,6 +2826,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8572, @@ -2625,7 +2846,7 @@ static struct phy_driver vsc85xx_driver[] = { .suspend = &genphy_suspend, .resume = &genphy_resume, .remove = &vsc85xx_remove, - .probe = &vsc8574_probe, + .probe = &vsc8584_probe, .set_wol = &vsc85xx_wol_set, .get_wol = &vsc85xx_wol_get, .get_tunable = &vsc85xx_get_tunable, @@ -2637,6 +2858,10 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { .phy_id = PHY_ID_VSC8574, @@ -2648,12 +2873,12 @@ static struct phy_driver vsc85xx_driver[] = { .config_aneg = &vsc85xx_config_aneg, .aneg_done = &genphy_aneg_done, .read_status = &vsc85xx_read_status, - .handle_interrupt = vsc85xx_handle_interrupt, + .handle_interrupt = vsc8584_handle_interrupt, .config_intr = &vsc85xx_config_intr, .suspend = &genphy_suspend, .resume = &genphy_resume, .remove = &vsc85xx_remove, - .probe = &vsc8574_probe, + .probe = &vsc8584_probe, .set_wol = &vsc85xx_wol_set, .get_wol = &vsc85xx_wol_get, .get_tunable = &vsc85xx_get_tunable, @@ -2665,11 +2890,14 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { - .phy_id = PHY_ID_VSC8575, + PHY_ID_MATCH_EXACT(PHY_ID_VSC8575), .name = "Microsemi GE VSC8575 SyncE", - .phy_id_mask = 0xfffffff0, /* PHY_GBIT_FEATURES */ .soft_reset = &genphy_soft_reset, .config_init = &vsc8584_config_init, @@ -2691,11 +2919,14 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { - .phy_id = PHY_ID_VSC8582, + PHY_ID_MATCH_EXACT(PHY_ID_VSC8582), .name = "Microsemi GE VSC8582 SyncE", - .phy_id_mask = 0xfffffff0, /* PHY_GBIT_FEATURES */ .soft_reset = &genphy_soft_reset, .config_init = &vsc8584_config_init, @@ -2717,11 +2948,14 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, }, { - .phy_id = PHY_ID_VSC8584, + PHY_ID_MATCH_EXACT(PHY_ID_VSC8584), .name = "Microsemi GE VSC8584 SyncE", - .phy_id_mask = 0xfffffff0, /* PHY_GBIT_FEATURES */ .soft_reset = &genphy_soft_reset, .config_init = &vsc8584_config_init, @@ -2744,6 +2978,10 @@ static struct phy_driver vsc85xx_driver[] = { .link_change_notify = &vsc85xx_link_change_notify, .inband_caps = vsc85xx_inband_caps, .config_inband = vsc85xx_config_inband, + .led_brightness_set = vsc85xx_led_brightness_set, + .led_hw_is_supported = vsc85xx_led_hw_is_supported, + .led_hw_control_get = vsc85xx_led_hw_control_get, + .led_hw_control_set = vsc85xx_led_hw_control_set, } }; diff --git a/drivers/net/phy/mscc/mscc_ptp.c b/drivers/net/phy/mscc/mscc_ptp.c index d692df7d975c..4865eac74b0e 100644 --- a/drivers/net/phy/mscc/mscc_ptp.c +++ b/drivers/net/phy/mscc/mscc_ptp.c @@ -1051,9 +1051,21 @@ static void vsc85xx_ts_reset_fifo(struct phy_device *phydev) val); } -static int vsc85xx_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *cfg, - struct netlink_ext_ack *extack) +static int vsc85xx_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct vsc8531_private *vsc8531 = + container_of(mii_ts, struct vsc8531_private, mii_ts); + + cfg->tx_type = vsc8531->ptp->tx_type; + cfg->rx_filter = vsc8531->ptp->rx_filter; + + return 0; +} + +static int vsc85xx_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) { struct vsc8531_private *vsc8531 = container_of(mii_ts, struct vsc8531_private, mii_ts); @@ -1611,7 +1623,8 @@ int vsc8584_ptp_probe(struct phy_device *phydev) vsc8531->mii_ts.rxtstamp = vsc85xx_rxtstamp; vsc8531->mii_ts.txtstamp = vsc85xx_txtstamp; - vsc8531->mii_ts.hwtstamp = vsc85xx_hwtstamp; + vsc8531->mii_ts.hwtstamp_set = vsc85xx_hwtstamp_set; + vsc8531->mii_ts.hwtstamp_get = vsc85xx_hwtstamp_get; vsc8531->mii_ts.ts_info = vsc85xx_ts_info; phydev->mii_ts = &vsc8531->mii_ts; diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c index 2a873f791733..8e2fd6b942b6 100644 --- a/drivers/net/phy/mxl-gpy.c +++ b/drivers/net/phy/mxl-gpy.c @@ -30,6 +30,9 @@ #define PHY_ID_GPY241B 0x67C9DE40 #define PHY_ID_GPY241BM 0x67C9DE80 #define PHY_ID_GPY245B 0x67C9DEC0 +#define PHY_ID_MXL86211C 0xC1335400 +#define PHY_ID_MXL86252 0xC1335520 +#define PHY_ID_MXL86282 0xC1335500 #define PHY_CTL1 0x13 #define PHY_CTL1_MDICD BIT(3) @@ -199,6 +202,29 @@ static int gpy_hwmon_read(struct device *dev, return 0; } +static int mxl862x2_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct phy_device *phydev = dev_get_drvdata(dev); + long tmp; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_TEMP_STA); + if (ret < 0) + return ret; + if (!ret) + return -ENODATA; + + tmp = (s16)ret; + tmp *= 78125; + tmp /= 10000; + + *value = tmp; + + return 0; +} + static umode_t gpy_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) @@ -216,19 +242,35 @@ static const struct hwmon_ops gpy_hwmon_hwmon_ops = { .read = gpy_hwmon_read, }; +static const struct hwmon_ops mxl862x2_hwmon_hwmon_ops = { + .is_visible = gpy_hwmon_is_visible, + .read = mxl862x2_hwmon_read, +}; + static const struct hwmon_chip_info gpy_hwmon_chip_info = { .ops = &gpy_hwmon_hwmon_ops, .info = gpy_hwmon_info, }; +static const struct hwmon_chip_info mxl862x2_hwmon_chip_info = { + .ops = &mxl862x2_hwmon_hwmon_ops, + .info = gpy_hwmon_info, +}; + static int gpy_hwmon_register(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; + const struct hwmon_chip_info *info; struct device *hwmon_dev; + if (phy_id_compare_model(phydev->phy_id, PHY_ID_MXL86252) || + phy_id_compare_model(phydev->phy_id, PHY_ID_MXL86282)) + info = &mxl862x2_hwmon_chip_info; + else + info = &gpy_hwmon_chip_info; + hwmon_dev = devm_hwmon_device_register_with_info(dev, NULL, phydev, - &gpy_hwmon_chip_info, - NULL); + info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } @@ -1272,6 +1314,72 @@ static struct phy_driver gpy_drivers[] = { .get_wol = gpy_get_wol, .set_loopback = gpy_loopback, }, + { + PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C), + .name = "Maxlinear Ethernet MxL86211C", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy_config_init, + .probe = gpy_probe, + .suspend = genphy_suspend, + .resume = genphy_resume, + .config_aneg = gpy_config_aneg, + .aneg_done = genphy_c45_aneg_done, + .read_status = gpy_read_status, + .config_intr = gpy_config_intr, + .handle_interrupt = gpy_handle_interrupt, + .set_wol = gpy_set_wol, + .get_wol = gpy_get_wol, + .set_loopback = gpy_loopback, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_MXL86252), + .name = "MaxLinear Ethernet MxL86252", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy_config_init, + .probe = gpy_probe, + .suspend = genphy_suspend, + .resume = genphy_resume, + .config_aneg = gpy_config_aneg, + .aneg_done = genphy_c45_aneg_done, + .read_status = gpy_read_status, + .config_intr = gpy_config_intr, + .handle_interrupt = gpy_handle_interrupt, + .set_wol = gpy_set_wol, + .get_wol = gpy_get_wol, + .set_loopback = gpy_loopback, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + }, + { + PHY_ID_MATCH_MODEL(PHY_ID_MXL86282), + .name = "MaxLinear Ethernet MxL86282", + .get_features = genphy_c45_pma_read_abilities, + .config_init = gpy_config_init, + .probe = gpy_probe, + .suspend = genphy_suspend, + .resume = genphy_resume, + .config_aneg = gpy_config_aneg, + .aneg_done = genphy_c45_aneg_done, + .read_status = gpy_read_status, + .config_intr = gpy_config_intr, + .handle_interrupt = gpy_handle_interrupt, + .set_wol = gpy_set_wol, + .get_wol = gpy_get_wol, + .set_loopback = gpy_loopback, + .led_brightness_set = gpy_led_brightness_set, + .led_hw_is_supported = gpy_led_hw_is_supported, + .led_hw_control_get = gpy_led_hw_control_get, + .led_hw_control_set = gpy_led_hw_control_set, + .led_polarity_set = gpy_led_polarity_set, + }, }; module_phy_driver(gpy_drivers); @@ -1288,6 +1396,9 @@ static const struct mdio_device_id __maybe_unused gpy_tbl[] = { {PHY_ID_MATCH_MODEL(PHY_ID_GPY241B)}, {PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)}, {PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)}, + {PHY_ID_MATCH_MODEL(PHY_ID_MXL86211C)}, + {PHY_ID_MATCH_MODEL(PHY_ID_MXL86252)}, + {PHY_ID_MATCH_MODEL(PHY_ID_MXL86282)}, { } }; MODULE_DEVICE_TABLE(mdio, gpy_tbl); diff --git a/drivers/net/phy/nxp-c45-tja11xx.c b/drivers/net/phy/nxp-c45-tja11xx.c index 87adb6508017..f526528d2e32 100644 --- a/drivers/net/phy/nxp-c45-tja11xx.c +++ b/drivers/net/phy/nxp-c45-tja11xx.c @@ -1012,9 +1012,22 @@ static bool nxp_c45_rxtstamp(struct mii_timestamper *mii_ts, return true; } -static int nxp_c45_hwtstamp(struct mii_timestamper *mii_ts, - struct kernel_hwtstamp_config *cfg, - struct netlink_ext_ack *extack) +static int nxp_c45_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy, + mii_ts); + + cfg->tx_type = priv->hwts_tx; + cfg->rx_filter = priv->hwts_rx ? HWTSTAMP_FILTER_PTP_V2_L2_EVENT + : HWTSTAMP_FILTER_NONE; + + return 0; +} + +static int nxp_c45_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) { struct nxp_c45_phy *priv = container_of(mii_ts, struct nxp_c45_phy, mii_ts); @@ -1749,7 +1762,8 @@ static int nxp_c45_probe(struct phy_device *phydev) IS_ENABLED(CONFIG_NETWORK_PHY_TIMESTAMPING)) { priv->mii_ts.rxtstamp = nxp_c45_rxtstamp; priv->mii_ts.txtstamp = nxp_c45_txtstamp; - priv->mii_ts.hwtstamp = nxp_c45_hwtstamp; + priv->mii_ts.hwtstamp_set = nxp_c45_hwtstamp_set; + priv->mii_ts.hwtstamp_get = nxp_c45_hwtstamp_get; priv->mii_ts.ts_info = nxp_c45_ts_info; phydev->mii_ts = &priv->mii_ts; ret = nxp_c45_init_ptp_clock(priv); diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 61670be0f095..d48aa7231b37 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -7,6 +7,7 @@ #include <linux/mdio.h> #include <linux/mii.h> #include <linux/phy.h> +#include <linux/ethtool_netlink.h> #include "mdio-open-alliance.h" #include "phylib-internal.h" @@ -147,12 +148,12 @@ int genphy_c45_pma_setup_forced(struct phy_device *phydev) ctrl2 |= MDIO_PMA_CTRL2_1000BT; break; case SPEED_2500: - ctrl1 |= MDIO_CTRL1_SPEED2_5G; + ctrl1 |= MDIO_PMA_CTRL1_SPEED2_5G; /* Assume 2.5Gbase-T */ ctrl2 |= MDIO_PMA_CTRL2_2_5GBT; break; case SPEED_5000: - ctrl1 |= MDIO_CTRL1_SPEED5G; + ctrl1 |= MDIO_PMA_CTRL1_SPEED5G; /* Assume 5Gbase-T */ ctrl2 |= MDIO_PMA_CTRL2_5GBT; break; @@ -485,8 +486,8 @@ static int genphy_c45_baset1_read_lpa(struct phy_device *phydev) mii_t1_adv_l_mod_linkmode_t(phydev->lp_advertising, 0); mii_t1_adv_m_mod_linkmode_t(phydev->lp_advertising, 0); - phydev->pause = 0; - phydev->asym_pause = 0; + phydev->pause = false; + phydev->asym_pause = false; return 0; } @@ -498,8 +499,8 @@ static int genphy_c45_baset1_read_lpa(struct phy_device *phydev) return val; mii_t1_adv_l_mod_linkmode_t(phydev->lp_advertising, val); - phydev->pause = val & MDIO_AN_T1_ADV_L_PAUSE_CAP ? 1 : 0; - phydev->asym_pause = val & MDIO_AN_T1_ADV_L_PAUSE_ASYM ? 1 : 0; + phydev->pause = val & MDIO_AN_T1_ADV_L_PAUSE_CAP; + phydev->asym_pause = val & MDIO_AN_T1_ADV_L_PAUSE_ASYM; val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_LP_M); if (val < 0) @@ -536,8 +537,8 @@ int genphy_c45_read_lpa(struct phy_device *phydev) phydev->lp_advertising); mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0); mii_adv_mod_linkmode_adv_t(phydev->lp_advertising, 0); - phydev->pause = 0; - phydev->asym_pause = 0; + phydev->pause = false; + phydev->asym_pause = false; return 0; } @@ -551,8 +552,8 @@ int genphy_c45_read_lpa(struct phy_device *phydev) return val; mii_adv_mod_linkmode_adv_t(phydev->lp_advertising, val); - phydev->pause = val & LPA_PAUSE_CAP ? 1 : 0; - phydev->asym_pause = val & LPA_PAUSE_ASYM ? 1 : 0; + phydev->pause = val & LPA_PAUSE_CAP; + phydev->asym_pause = val & LPA_PAUSE_ASYM; /* Read the link partner's 10G advertisement */ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); @@ -617,10 +618,10 @@ int genphy_c45_read_pma(struct phy_device *phydev) case MDIO_PMA_CTRL1_SPEED1000: phydev->speed = SPEED_1000; break; - case MDIO_CTRL1_SPEED2_5G: + case MDIO_PMA_CTRL1_SPEED2_5G: phydev->speed = SPEED_2500; break; - case MDIO_CTRL1_SPEED5G: + case MDIO_PMA_CTRL1_SPEED5G: phydev->speed = SPEED_5000; break; case MDIO_CTRL1_SPEED10G: @@ -1171,8 +1172,8 @@ int genphy_c45_read_status(struct phy_device *phydev) phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; - phydev->pause = 0; - phydev->asym_pause = 0; + phydev->pause = false; + phydev->asym_pause = false; if (phydev->autoneg == AUTONEG_ENABLE) { ret = genphy_c45_read_lpa(phydev); @@ -1573,3 +1574,261 @@ int genphy_c45_ethtool_set_eee(struct phy_device *phydev, return ret; } EXPORT_SYMBOL(genphy_c45_ethtool_set_eee); + +/** + * oatc14_cable_test_get_result_code - Convert hardware cable test status to + * ethtool result code. + * @status: The hardware-reported cable test status + * + * This helper function maps the OATC14 HDD cable test status to the + * corresponding ethtool cable test result code. It provides a translation + * between the device-specific status values and the standardized ethtool + * result codes. + * + * Return: + * * ETHTOOL_A_CABLE_RESULT_CODE_OK - Cable is OK + * * ETHTOOL_A_CABLE_RESULT_CODE_OPEN - Open circuit detected + * * ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT - Short circuit detected + * * ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC - Status not detectable or invalid + */ +static int oatc14_cable_test_get_result_code(enum oatc14_hdd_status status) +{ + switch (status) { + case OATC14_HDD_STATUS_CABLE_OK: + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + case OATC14_HDD_STATUS_OPEN: + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + case OATC14_HDD_STATUS_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + case OATC14_HDD_STATUS_NOT_DETECTABLE: + default: + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } +} + +/** + * genphy_c45_oatc14_cable_test_get_status - Get status of OATC14 10Base-T1S + * PHY cable test. + * @phydev: pointer to the PHY device structure + * @finished: pointer to a boolean set true if the test is complete + * + * Retrieves the current status of the OATC14 10Base-T1S PHY cable test. + * This function reads the OATC14 HDD register to determine whether the test + * results are valid and whether the test has finished. + * + * If the test is complete, the function reports the cable test result via + * the ethtool cable test interface using ethnl_cable_test_result(), and then + * clears the test control bit in the PHY register to reset the test state. + * + * Return: 0 on success, or a negative error code on failure (e.g. register + * read/write error). + */ +int genphy_c45_oatc14_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + int ret; + u8 sts; + + *finished = false; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD); + if (ret < 0) + return ret; + + if (!(ret & OATC14_HDD_VALID)) + return 0; + + *finished = true; + + sts = FIELD_GET(OATC14_HDD_SHORT_OPEN_STATUS, ret); + + ret = ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + oatc14_cable_test_get_result_code(sts)); + if (ret) + return ret; + + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, + MDIO_OATC14_HDD, OATC14_HDD_CONTROL); +} +EXPORT_SYMBOL(genphy_c45_oatc14_cable_test_get_status); + +/** + * genphy_c45_oatc14_cable_test_start - Start a cable test on an OATC14 + * 10Base-T1S PHY. + * @phydev: Pointer to the PHY device structure + * + * This function initiates a cable diagnostic test on a Clause 45 OATC14 + * 10Base-T1S capable PHY device. It first reads the PHY’s advanced diagnostic + * capability register to check if High Definition Diagnostics (HDD) mode is + * supported. If the PHY does not report HDD capability, cable testing is not + * supported and the function returns -EOPNOTSUPP. + * + * For PHYs that support HDD, the function sets the appropriate control bits in + * the OATC14_HDD register to enable and start the cable diagnostic test. + * + * Return: + * * 0 on success + * * -EOPNOTSUPP if the PHY does not support HDD capability + * * A negative error code on I/O or register access failures + */ +int genphy_c45_oatc14_cable_test_start(struct phy_device *phydev) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_ADFCAP); + if (ret < 0) + return ret; + + if (!(ret & OATC14_ADFCAP_HDD_CAPABILITY)) + return -EOPNOTSUPP; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD, + OATC14_HDD_CONTROL); + if (ret) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD); + if (ret < 0) + return ret; + + return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_HDD, + OATC14_HDD_START_CONTROL); +} +EXPORT_SYMBOL(genphy_c45_oatc14_cable_test_start); + +/** + * oatc14_update_sqi_capability - Read and update OATC14 10Base-T1S PHY SQI/SQI+ + * capability + * @phydev: Pointer to the PHY device structure + * + * This helper reads the OATC14 ADFCAP capability register to determine whether + * the PHY supports SQI or SQI+ reporting. + * + * SQI+ capability is detected first. The SQI+ field indicates the number of + * valid MSBs (3–8), corresponding to 8–256 SQI+ levels. When present, the + * function stores the number of SQI+ bits and computes the maximum SQI+ value + * as (2^bits - 1). + * + * If SQI+ is not supported, the function checks for basic SQI capability, + * which provides 0–7 SQI levels. + * + * On success, the capability information is stored in + * @phydev->oatc14_sqi_capability and marked as updated. + * + * Return: + * * 0 - capability successfully read and stored + * * -EOPNOTSUPP - SQI/SQI+ not supported by this PHY + * * Negative errno on read failure + */ +static int oatc14_update_sqi_capability(struct phy_device *phydev) +{ + u8 bits; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_ADFCAP); + if (ret < 0) + return ret; + + /* Check for SQI+ capability + * 0 - SQI+ is not supported + * (3-8) bits for (8-256) SQI+ levels supported + */ + bits = FIELD_GET(OATC14_ADFCAP_SQIPLUS_CAPABILITY, ret); + if (bits) { + phydev->oatc14_sqi_capability.sqiplus_bits = bits; + /* Max sqi+ level supported: (2 ^ bits) - 1 */ + phydev->oatc14_sqi_capability.sqi_max = BIT(bits) - 1; + goto update_done; + } + + /* Check for SQI capability + * 0 - SQI is not supported + * 1 - SQI is supported (0-7 levels) + */ + if (ret & OATC14_ADFCAP_SQI_CAPABILITY) { + phydev->oatc14_sqi_capability.sqi_max = OATC14_SQI_MAX_LEVEL; + goto update_done; + } + + return -EOPNOTSUPP; + +update_done: + phydev->oatc14_sqi_capability.updated = true; + return 0; +} + +/** + * genphy_c45_oatc14_get_sqi_max - Get maximum supported SQI or SQI+ level of + * OATC14 10Base-T1S PHY + * @phydev: pointer to the PHY device structure + * + * This function returns the maximum supported Signal Quality Indicator (SQI) or + * SQI+ level. The SQI capability is updated on first invocation if it has not + * already been updated. + * + * Return: + * * Maximum SQI/SQI+ level supported + * * Negative errno on capability read failure + */ +int genphy_c45_oatc14_get_sqi_max(struct phy_device *phydev) +{ + int ret; + + if (!phydev->oatc14_sqi_capability.updated) { + ret = oatc14_update_sqi_capability(phydev); + if (ret) + return ret; + } + + return phydev->oatc14_sqi_capability.sqi_max; +} +EXPORT_SYMBOL(genphy_c45_oatc14_get_sqi_max); + +/** + * genphy_c45_oatc14_get_sqi - Get Signal Quality Indicator (SQI) from an OATC14 + * 10Base-T1S PHY + * @phydev: pointer to the PHY device structure + * + * This function reads the SQI+ or SQI value from an OATC14-compatible + * 10Base-T1S PHY. If SQI+ capability is supported, the function returns the + * extended SQI+ value; otherwise, it returns the basic SQI value. The SQI + * capability is updated on first invocation if it has not already been updated. + * + * Return: + * * SQI/SQI+ value on success + * * Negative errno on read failure + */ +int genphy_c45_oatc14_get_sqi(struct phy_device *phydev) +{ + u8 shift; + int ret; + + if (!phydev->oatc14_sqi_capability.updated) { + ret = oatc14_update_sqi_capability(phydev); + if (ret) + return ret; + } + + /* Calculate and return SQI+ value if supported */ + if (phydev->oatc14_sqi_capability.sqiplus_bits) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + MDIO_OATC14_DCQ_SQIPLUS); + if (ret < 0) + return ret; + + /* SQI+ uses N MSBs out of 8 bits, left-aligned with padding 1's + * Calculate the right-shift needed to isolate the N bits. + */ + shift = 8 - phydev->oatc14_sqi_capability.sqiplus_bits; + + return (ret & OATC14_DCQ_SQIPLUS_VALUE) >> shift; + } + + /* Read and return SQI value if SQI+ capability is not supported */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_DCQ_SQI); + if (ret < 0) + return ret; + + return ret & OATC14_DCQ_SQI_VALUE; +} +EXPORT_SYMBOL(genphy_c45_oatc14_get_sqi); diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h index b7f0c6a3037a..4951a39f3828 100644 --- a/drivers/net/phy/phy-caps.h +++ b/drivers/net/phy/phy-caps.h @@ -29,6 +29,7 @@ enum { LINK_CAPA_200000FD, LINK_CAPA_400000FD, LINK_CAPA_800000FD, + LINK_CAPA_1600000FD, __LINK_CAPA_MAX, }; diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 605ca20ae192..277c034bc32f 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -17,7 +17,7 @@ */ const char *phy_speed_to_str(int speed) { - BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 121, + BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 125, "Enum ethtool_link_mode_bit_indices and phylib are out of sync. " "If a speed or mode has been added please update phy_speed_to_str " "and the PHY settings array.\n"); @@ -55,6 +55,8 @@ const char *phy_speed_to_str(int speed) return "400Gbps"; case SPEED_800000: return "800Gbps"; + case SPEED_1600000: + return "1600Gbps"; case SPEED_UNKNOWN: return "Unknown"; default: @@ -102,6 +104,49 @@ const char *phy_rate_matching_to_str(int rate_matching) EXPORT_SYMBOL_GPL(phy_rate_matching_to_str); /** + * phy_fix_phy_mode_for_mac_delays - Convenience function for fixing PHY + * mode based on whether mac adds internal delay + * + * @interface: The current interface mode of the port + * @mac_txid: True if the mac adds internal tx delay + * @mac_rxid: True if the mac adds internal rx delay + * + * Return: fixed PHY mode, or PHY_INTERFACE_MODE_NA if the interface can + * not apply the internal delay + */ +phy_interface_t phy_fix_phy_mode_for_mac_delays(phy_interface_t interface, + bool mac_txid, bool mac_rxid) +{ + if (!phy_interface_mode_is_rgmii(interface)) + return interface; + + if (mac_txid && mac_rxid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + if (mac_txid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII_RXID; + if (interface == PHY_INTERFACE_MODE_RGMII_TXID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + if (mac_rxid) { + if (interface == PHY_INTERFACE_MODE_RGMII_ID) + return PHY_INTERFACE_MODE_RGMII_TXID; + if (interface == PHY_INTERFACE_MODE_RGMII_RXID) + return PHY_INTERFACE_MODE_RGMII; + return PHY_INTERFACE_MODE_NA; + } + + return interface; +} +EXPORT_SYMBOL_GPL(phy_fix_phy_mode_for_mac_delays); + +/** * phy_interface_num_ports - Return the number of links that can be carried by * a given MAC-PHY physical link. Returns 0 if this is * unknown, the number of links else. diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 02da4a203ddd..13dd1691886d 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -405,12 +405,14 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd) return 0; case SIOCSHWTSTAMP: - if (phydev->mii_ts && phydev->mii_ts->hwtstamp) { + if (phydev->mii_ts && phydev->mii_ts->hwtstamp_set) { if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) return -EFAULT; hwtstamp_config_to_kernel(&kernel_cfg, &cfg); - ret = phydev->mii_ts->hwtstamp(phydev->mii_ts, &kernel_cfg, &extack); + ret = phydev->mii_ts->hwtstamp_set(phydev->mii_ts, + &kernel_cfg, + &extack); if (ret) return ret; @@ -476,6 +478,9 @@ int __phy_hwtstamp_get(struct phy_device *phydev, if (!phydev) return -ENODEV; + if (phydev->mii_ts && phydev->mii_ts->hwtstamp_get) + return phydev->mii_ts->hwtstamp_get(phydev->mii_ts, config); + return -EOPNOTSUPP; } @@ -493,8 +498,9 @@ int __phy_hwtstamp_set(struct phy_device *phydev, if (!phydev) return -ENODEV; - if (phydev->mii_ts && phydev->mii_ts->hwtstamp) - return phydev->mii_ts->hwtstamp(phydev->mii_ts, config, extack); + if (phydev->mii_ts && phydev->mii_ts->hwtstamp_set) + return phydev->mii_ts->hwtstamp_set(phydev->mii_ts, config, + extack); return -EOPNOTSUPP; } diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 23c808b59b6f..3a05982b39bf 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -25,6 +25,7 @@ static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = { { SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */ { SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */ { SPEED_800000, DUPLEX_FULL, {0} }, /* LINK_CAPA_800000FD */ + { SPEED_1600000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1600000FD */ }; static int speed_duplex_to_capa(int speed, unsigned int duplex) @@ -52,6 +53,7 @@ static int speed_duplex_to_capa(int speed, unsigned int duplex) case SPEED_200000: return LINK_CAPA_200000FD; case SPEED_400000: return LINK_CAPA_400000FD; case SPEED_800000: return LINK_CAPA_800000FD; + case SPEED_1600000: return LINK_CAPA_1600000FD; } return -EINVAL; diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 7a67c900e79a..81984d4ebb7c 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -251,6 +251,16 @@ static bool phy_drv_wol_enabled(struct phy_device *phydev) return wol.wolopts != 0; } +bool phy_may_wakeup(struct phy_device *phydev) +{ + /* If the PHY is using driver-model based wakeup, use that state. */ + if (phy_can_wakeup(phydev)) + return device_may_wakeup(&phydev->mdio.dev); + + return phy_drv_wol_enabled(phydev); +} +EXPORT_SYMBOL_GPL(phy_may_wakeup); + static void phy_link_change(struct phy_device *phydev, bool up) { struct net_device *netdev = phydev->attached_dev; @@ -302,7 +312,7 @@ static bool mdio_bus_phy_may_suspend(struct phy_device *phydev) /* If the PHY on the mido bus is not attached but has WOL enabled * we cannot suspend the PHY. */ - if (!netdev && phy_drv_wol_enabled(phydev)) + if (!netdev && phy_may_wakeup(phydev)) return false; /* PHY not attached? May suspend if the PHY has not already been @@ -815,8 +825,8 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id, dev->speed = SPEED_UNKNOWN; dev->duplex = DUPLEX_UNKNOWN; - dev->pause = 0; - dev->asym_pause = 0; + dev->pause = false; + dev->asym_pause = false; dev->link = 0; dev->port = PORT_TP; dev->interface = PHY_INTERFACE_MODE_GMII; @@ -1214,22 +1224,24 @@ int phy_get_c45_ids(struct phy_device *phydev) EXPORT_SYMBOL(phy_get_c45_ids); /** - * phy_find_first - finds the first PHY device on the bus + * phy_find_next - finds the next PHY device on the bus * @bus: the target MII bus + * @pos: cursor + * + * Return: next phy_device on the bus, or NULL */ -struct phy_device *phy_find_first(struct mii_bus *bus) +struct phy_device *phy_find_next(struct mii_bus *bus, struct phy_device *pos) { - struct phy_device *phydev; - int addr; + for (int addr = pos ? pos->mdio.addr + 1 : 0; + addr < PHY_MAX_ADDR; addr++) { + struct phy_device *phydev = mdiobus_get_phy(bus, addr); - for (addr = 0; addr < PHY_MAX_ADDR; addr++) { - phydev = mdiobus_get_phy(bus, addr); if (phydev) return phydev; } return NULL; } -EXPORT_SYMBOL(phy_find_first); +EXPORT_SYMBOL_GPL(phy_find_next); /** * phy_prepare_link - prepares the PHY layer to monitor link status @@ -1909,7 +1921,7 @@ int phy_suspend(struct phy_device *phydev) if (phydev->suspended || !phydrv) return 0; - phydev->wol_enabled = phy_drv_wol_enabled(phydev) || + phydev->wol_enabled = phy_may_wakeup(phydev) || (netdev && netdev->ethtool->wol_enabled); /* If the device has WOL enabled, we cannot suspend the PHY */ if (phydev->wol_enabled && !(phydrv->flags & PHY_ALWAYS_CALL_SUSPEND)) @@ -2080,8 +2092,8 @@ int genphy_setup_forced(struct phy_device *phydev) { u16 ctl; - phydev->pause = 0; - phydev->asym_pause = 0; + phydev->pause = false; + phydev->asym_pause = false; ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); @@ -2488,8 +2500,8 @@ int genphy_read_status(struct phy_device *phydev) phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED; phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; - phydev->pause = 0; - phydev->asym_pause = 0; + phydev->pause = false; + phydev->asym_pause = false; if (phydev->is_gigabit_capable) { err = genphy_read_master_slave(phydev); @@ -2542,8 +2554,8 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed) /* Signal link has changed */ *changed = true; phydev->duplex = DUPLEX_UNKNOWN; - phydev->pause = 0; - phydev->asym_pause = 0; + phydev->pause = false; + phydev->asym_pause = false; if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) { lpa = phy_read(phydev, MII_LPA); diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 918244308215..43d8380aaefb 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -93,6 +93,9 @@ struct phylink { u8 sfp_port; struct eee_config eee_cfg; + + u32 wolopts_mac; + u8 wol_sopass[SOPASS_MAX]; }; #define phylink_printk(level, pl, fmt, ...) \ @@ -2565,6 +2568,23 @@ void phylink_rx_clk_stop_unblock(struct phylink *pl) } EXPORT_SYMBOL_GPL(phylink_rx_clk_stop_unblock); +static bool phylink_mac_supports_wol(struct phylink *pl) +{ + return !!pl->mac_ops->mac_wol_set; +} + +static bool phylink_phy_supports_wol(struct phylink *pl, + struct phy_device *phydev) +{ + return phydev && (pl->config->wol_phy_legacy || phy_can_wakeup(phydev)); +} + +static bool phylink_phy_pm_speed_ctrl(struct phylink *pl) +{ + return pl->config->wol_phy_speed_ctrl && !pl->wolopts_mac && + pl->phydev && phy_may_wakeup(pl->phydev); +} + /** * phylink_suspend() - handle a network device suspend event * @pl: a pointer to a &struct phylink returned from phylink_create() @@ -2578,11 +2598,17 @@ EXPORT_SYMBOL_GPL(phylink_rx_clk_stop_unblock); * can also bring down the link between the MAC and PHY. * - If Wake-on-Lan is active, but being handled by the MAC, the MAC * still needs to receive packets, so we can not bring the link down. + * + * Note: when phylink managed Wake-on-Lan is in use, @mac_wol is ignored. + * (struct phylink_mac_ops.mac_set_wol populated.) */ void phylink_suspend(struct phylink *pl, bool mac_wol) { ASSERT_RTNL(); + if (phylink_mac_supports_wol(pl)) + mac_wol = !!pl->wolopts_mac; + if (mac_wol && (!pl->netdev || pl->netdev->ethtool->wol_enabled)) { /* Wake-on-Lan enabled, MAC handling */ mutex_lock(&pl->state_mutex); @@ -2608,6 +2634,9 @@ void phylink_suspend(struct phylink *pl, bool mac_wol) } else { phylink_stop(pl); } + + if (phylink_phy_pm_speed_ctrl(pl)) + phylink_speed_down(pl, false); } EXPORT_SYMBOL_GPL(phylink_suspend); @@ -2647,6 +2676,9 @@ void phylink_resume(struct phylink *pl) { ASSERT_RTNL(); + if (phylink_phy_pm_speed_ctrl(pl)) + phylink_speed_up(pl); + if (test_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state)) { /* Wake-on-Lan enabled, MAC handling */ @@ -2692,8 +2724,24 @@ void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol) wol->supported = 0; wol->wolopts = 0; - if (pl->phydev) - phy_ethtool_get_wol(pl->phydev, wol); + if (phylink_mac_supports_wol(pl)) { + if (phylink_phy_supports_wol(pl, pl->phydev)) + phy_ethtool_get_wol(pl->phydev, wol); + + /* Where the MAC augments the WoL support, merge its support and + * current configuration. + */ + if (~wol->wolopts & pl->wolopts_mac & WAKE_MAGICSECURE) + memcpy(wol->sopass, pl->wol_sopass, + sizeof(wol->sopass)); + + wol->supported |= pl->config->wol_mac_support; + wol->wolopts |= pl->wolopts_mac; + } else { + /* Legacy */ + if (pl->phydev) + phy_ethtool_get_wol(pl->phydev, wol); + } } EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol); @@ -2710,12 +2758,48 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol); */ int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol) { + struct ethtool_wolinfo w = { .cmd = ETHTOOL_GWOL }; int ret = -EOPNOTSUPP; + bool changed; + u32 wolopts; ASSERT_RTNL(); - if (pl->phydev) - ret = phy_ethtool_set_wol(pl->phydev, wol); + if (phylink_mac_supports_wol(pl)) { + wolopts = wol->wolopts; + + if (phylink_phy_supports_wol(pl, pl->phydev)) { + ret = phy_ethtool_set_wol(pl->phydev, wol); + if (ret != 0 && ret != -EOPNOTSUPP) + return ret; + + phy_ethtool_get_wol(pl->phydev, &w); + + /* Any Wake-on-Lan modes which the PHY is handling + * should not be passed on to the MAC. + */ + wolopts &= ~w.wolopts; + } + + wolopts &= pl->config->wol_mac_support; + changed = pl->wolopts_mac != wolopts; + if (wolopts & WAKE_MAGICSECURE) + changed |= !!memcmp(wol->sopass, pl->wol_sopass, + sizeof(wol->sopass)); + memcpy(pl->wol_sopass, wol->sopass, sizeof(pl->wol_sopass)); + + if (changed) { + ret = pl->mac_ops->mac_wol_set(pl->config, wolopts, + wol->sopass); + if (!ret) + pl->wolopts_mac = wolopts; + } else { + ret = 0; + } + } else { + if (pl->phydev) + ret = phy_ethtool_set_wol(pl->phydev, wol); + } return ret; } diff --git a/drivers/net/phy/qt2025.rs b/drivers/net/phy/qt2025.rs index 0b9400dcb4c1..aaaead6512a0 100644 --- a/drivers/net/phy/qt2025.rs +++ b/drivers/net/phy/qt2025.rs @@ -12,6 +12,7 @@ use kernel::c_str; use kernel::error::code; use kernel::firmware::Firmware; +use kernel::io::poll::read_poll_timeout; use kernel::net::phy::{ self, reg::{Mmd, C45}, @@ -19,6 +20,7 @@ use kernel::net::phy::{ }; use kernel::prelude::*; use kernel::sizes::{SZ_16K, SZ_8K}; +use kernel::time::Delta; kernel::module_phy_driver! { drivers: [PhyQT2025], @@ -93,7 +95,13 @@ impl Driver for PhyQT2025 { // The micro-controller will start running from SRAM. dev.write(C45::new(Mmd::PCS, 0xe854), 0x0040)?; - // TODO: sleep here until the hw becomes ready. + read_poll_timeout( + || dev.read(C45::new(Mmd::PCS, 0xd7fd)), + |val| *val != 0x00 && *val != 0x10, + Delta::from_millis(50), + Delta::from_secs(3), + )?; + Ok(()) } diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 16a347084293..67ecf3d4af2b 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -8,6 +8,7 @@ * Copyright (c) 2004 Freescale Semiconductor, Inc. */ #include <linux/bitops.h> +#include <linux/ethtool_netlink.h> #include <linux/of.h> #include <linux/phy.h> #include <linux/pm_wakeirq.h> @@ -89,6 +90,14 @@ #define RTL8211F_LEDCR_MASK GENMASK(4, 0) #define RTL8211F_LEDCR_SHIFT 5 +/* RTL8211F(D)(I)-VD-CG CLKOUT configuration is specified via magic values + * to undocumented register pages. The names here do not reflect the datasheet. + * Unlike other PHY models, CLKOUT configuration does not go through PHYCR2. + */ +#define RTL8211FVD_CLKOUT_PAGE 0xd05 +#define RTL8211FVD_CLKOUT_REG 0x11 +#define RTL8211FVD_CLKOUT_EN BIT(8) + /* RTL8211F RGMII configuration */ #define RTL8211F_RGMII_PAGE 0xd08 @@ -127,6 +136,32 @@ */ #define RTL822X_VND2_C22_REG(reg) (0xa400 + 2 * (reg)) +#define RTL8221B_VND2_INER 0xa4d2 +#define RTL8221B_VND2_INER_LINK_STATUS BIT(4) + +#define RTL8221B_VND2_INSR 0xa4d4 + +#define RTL8224_MII_RTCT 0x11 +#define RTL8224_MII_RTCT_ENABLE BIT(0) +#define RTL8224_MII_RTCT_PAIR_A BIT(4) +#define RTL8224_MII_RTCT_PAIR_B BIT(5) +#define RTL8224_MII_RTCT_PAIR_C BIT(6) +#define RTL8224_MII_RTCT_PAIR_D BIT(7) +#define RTL8224_MII_RTCT_DONE BIT(15) + +#define RTL8224_MII_SRAM_ADDR 0x1b +#define RTL8224_MII_SRAM_DATA 0x1c + +#define RTL8224_SRAM_RTCT_FAULT(pair) (0x8026 + (pair) * 4) +#define RTL8224_SRAM_RTCT_FAULT_BUSY BIT(0) +#define RTL8224_SRAM_RTCT_FAULT_OPEN BIT(3) +#define RTL8224_SRAM_RTCT_FAULT_SAME_SHORT BIT(4) +#define RTL8224_SRAM_RTCT_FAULT_OK BIT(5) +#define RTL8224_SRAM_RTCT_FAULT_DONE BIT(6) +#define RTL8224_SRAM_RTCT_FAULT_CROSS_SHORT BIT(7) + +#define RTL8224_SRAM_RTCT_LEN(pair) (0x8028 + (pair) * 4) + #define RTL8366RB_POWER_SAVE 0x15 #define RTL8366RB_POWER_SAVE_ON BIT(12) @@ -166,9 +201,8 @@ MODULE_AUTHOR("Johnson Leung"); MODULE_LICENSE("GPL"); struct rtl821x_priv { - u16 phycr1; - u16 phycr2; - bool has_phycr2; + bool enable_aldps; + bool disable_clk_out; struct clk *clk; /* rtl8211f */ u16 iner; @@ -218,8 +252,6 @@ static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct rtl821x_priv *priv; - u32 phy_id = phydev->drv->phy_id; - int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -230,24 +262,10 @@ static int rtl821x_probe(struct phy_device *phydev) return dev_err_probe(dev, PTR_ERR(priv->clk), "failed to get phy clock\n"); - ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1); - if (ret < 0) - return ret; - - priv->phycr1 = ret & (RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF); - if (of_property_read_bool(dev->of_node, "realtek,aldps-enable")) - priv->phycr1 |= RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF; - - priv->has_phycr2 = !(phy_id == RTL_8211FVD_PHYID); - if (priv->has_phycr2) { - ret = phy_read_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2); - if (ret < 0) - return ret; - - priv->phycr2 = ret & RTL8211F_CLKOUT_EN; - if (of_property_read_bool(dev->of_node, "realtek,clkout-disable")) - priv->phycr2 &= ~RTL8211F_CLKOUT_EN; - } + priv->enable_aldps = of_property_read_bool(dev->of_node, + "realtek,aldps-enable"); + priv->disable_clk_out = of_property_read_bool(dev->of_node, + "realtek,clkout-disable"); phydev->priv = priv; @@ -560,22 +578,11 @@ static int rtl8211c_config_init(struct phy_device *phydev) CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER); } -static int rtl8211f_config_init(struct phy_device *phydev) +static int rtl8211f_config_rgmii_delay(struct phy_device *phydev) { - struct rtl821x_priv *priv = phydev->priv; - struct device *dev = &phydev->mdio.dev; u16 val_txdly, val_rxdly; int ret; - ret = phy_modify_paged_changed(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, - RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF, - priv->phycr1); - if (ret < 0) { - dev_err(dev, "aldps mode configuration failed: %pe\n", - ERR_PTR(ret)); - return ret; - } - switch (phydev->interface) { case PHY_INTERFACE_MODE_RGMII: val_txdly = 0; @@ -605,53 +612,118 @@ static int rtl8211f_config_init(struct phy_device *phydev) RTL8211F_TXCR, RTL8211F_TX_DELAY, val_txdly); if (ret < 0) { - dev_err(dev, "Failed to update the TX delay register\n"); + phydev_err(phydev, "Failed to update the TX delay register: %pe\n", + ERR_PTR(ret)); return ret; } else if (ret) { - dev_dbg(dev, - "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n", - str_enable_disable(val_txdly)); + phydev_dbg(phydev, + "%s 2ns TX delay (and changing the value from pin-strapping RXD1 or the bootloader)\n", + str_enable_disable(val_txdly)); } else { - dev_dbg(dev, - "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", - str_enabled_disabled(val_txdly)); + phydev_dbg(phydev, + "2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", + str_enabled_disabled(val_txdly)); } ret = phy_modify_paged_changed(phydev, RTL8211F_RGMII_PAGE, RTL8211F_RXCR, RTL8211F_RX_DELAY, val_rxdly); if (ret < 0) { - dev_err(dev, "Failed to update the RX delay register\n"); + phydev_err(phydev, "Failed to update the RX delay register: %pe\n", + ERR_PTR(ret)); return ret; } else if (ret) { - dev_dbg(dev, - "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n", - str_enable_disable(val_rxdly)); + phydev_dbg(phydev, + "%s 2ns RX delay (and changing the value from pin-strapping RXD0 or the bootloader)\n", + str_enable_disable(val_rxdly)); } else { - dev_dbg(dev, - "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n", - str_enabled_disabled(val_rxdly)); + phydev_dbg(phydev, + "2ns RX delay was already %s (by pin-strapping RXD0 or bootloader configuration)\n", + str_enabled_disabled(val_rxdly)); } - if (!priv->has_phycr2) + return 0; +} + +static int rtl8211f_config_clk_out(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + int ret; + + /* The value is preserved if the device tree property is absent */ + if (!priv->disable_clk_out) + return 0; + + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) + ret = phy_modify_paged(phydev, RTL8211FVD_CLKOUT_PAGE, + RTL8211FVD_CLKOUT_REG, + RTL8211FVD_CLKOUT_EN, 0); + else + ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, + RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0); + if (ret) + return ret; + + return genphy_soft_reset(phydev); +} + +/* Advance Link Down Power Saving (ALDPS) mode changes crystal/clock behaviour, + * which causes the RXC clock signal to stop for tens to hundreds of + * milliseconds. + * + * Some MACs need the RXC clock to support their internal RX logic, so ALDPS is + * only enabled based on an opt-in device tree property. + */ +static int rtl8211f_config_aldps(struct phy_device *phydev) +{ + struct rtl821x_priv *priv = phydev->priv; + u16 mask = RTL8211F_ALDPS_PLL_OFF | + RTL8211F_ALDPS_ENABLE | + RTL8211F_ALDPS_XTAL_OFF; + + /* The value is preserved if the device tree property is absent */ + if (!priv->enable_aldps) + return 0; + + return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, + mask, mask); +} + +static int rtl8211f_config_phy_eee(struct phy_device *phydev) +{ + /* RTL8211FVD has no PHYCR2 register */ + if (phydev->drv->phy_id == RTL_8211FVD_PHYID) return 0; /* Disable PHY-mode EEE so LPI is passed to the MAC */ - ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2, - RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); + return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2, + RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); +} + +static int rtl8211f_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + int ret; + + ret = rtl8211f_config_aldps(phydev); + if (ret) { + dev_err(dev, "aldps mode configuration failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + ret = rtl8211f_config_rgmii_delay(phydev); if (ret) return ret; - ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, - RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, - priv->phycr2); - if (ret < 0) { + ret = rtl8211f_config_clk_out(phydev); + if (ret) { dev_err(dev, "clkout configuration failed: %pe\n", ERR_PTR(ret)); return ret; } - return genphy_soft_reset(phydev); + return rtl8211f_config_phy_eee(phydev); } static int rtl821x_suspend(struct phy_device *phydev) @@ -1453,6 +1525,168 @@ static int rtl822xb_c45_read_status(struct phy_device *phydev) return 0; } +static int rtl8224_cable_test_start(struct phy_device *phydev) +{ + u32 val; + int ret; + + /* disable auto-negotiation and force 1000/Full */ + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(MII_BMCR), + BMCR_ANENABLE | BMCR_SPEED100 | BMCR_SPEED10, + BMCR_SPEED1000 | BMCR_FULLDPLX); + if (ret) + return ret; + + mdelay(500); + + /* trigger cable test */ + val = RTL8224_MII_RTCT_ENABLE; + val |= RTL8224_MII_RTCT_PAIR_A; + val |= RTL8224_MII_RTCT_PAIR_B; + val |= RTL8224_MII_RTCT_PAIR_C; + val |= RTL8224_MII_RTCT_PAIR_D; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_RTCT), + RTL8224_MII_RTCT_DONE, val); +} + +static int rtl8224_sram_read(struct phy_device *phydev, u32 reg) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_SRAM_ADDR), + reg); + if (ret) + return ret; + + return phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_SRAM_DATA)); +} + +static int rtl8224_pair_len_get(struct phy_device *phydev, u32 pair) +{ + int cable_len; + u32 reg_len; + int ret; + u32 cm; + + reg_len = RTL8224_SRAM_RTCT_LEN(pair); + + ret = rtl8224_sram_read(phydev, reg_len); + if (ret < 0) + return ret; + + cable_len = ret & 0xff00; + + ret = rtl8224_sram_read(phydev, reg_len + 1); + if (ret < 0) + return ret; + + cable_len |= (ret & 0xff00) >> 8; + + cable_len -= 620; + cable_len = max(cable_len, 0); + + cm = cable_len * 100 / 78; + + return cm; +} + +static int rtl8224_cable_test_result_trans(u32 result) +{ + if (!(result & RTL8224_SRAM_RTCT_FAULT_DONE)) + return -EBUSY; + + if (result & RTL8224_SRAM_RTCT_FAULT_OK) + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + + if (result & RTL8224_SRAM_RTCT_FAULT_OPEN) + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + + if (result & RTL8224_SRAM_RTCT_FAULT_SAME_SHORT) + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + + if (result & RTL8224_SRAM_RTCT_FAULT_BUSY) + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + + if (result & RTL8224_SRAM_RTCT_FAULT_CROSS_SHORT) + return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT; + + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; +} + +static int rtl8224_cable_test_report_pair(struct phy_device *phydev, unsigned int pair) +{ + int fault_rslt; + int ret; + + ret = rtl8224_sram_read(phydev, RTL8224_SRAM_RTCT_FAULT(pair)); + if (ret < 0) + return ret; + + fault_rslt = rtl8224_cable_test_result_trans(ret); + if (fault_rslt < 0) + return 0; + + ret = ethnl_cable_test_result(phydev, pair, fault_rslt); + if (ret < 0) + return ret; + + switch (fault_rslt) { + case ETHTOOL_A_CABLE_RESULT_CODE_OPEN: + case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT: + case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT: + ret = rtl8224_pair_len_get(phydev, pair); + if (ret < 0) + return ret; + + return ethnl_cable_test_fault_length(phydev, pair, ret); + default: + return 0; + } +} + +static int rtl8224_cable_test_report(struct phy_device *phydev, bool *finished) +{ + unsigned int pair; + int ret; + + for (pair = ETHTOOL_A_CABLE_PAIR_A; pair <= ETHTOOL_A_CABLE_PAIR_D; pair++) { + ret = rtl8224_cable_test_report_pair(phydev, pair); + if (ret == -EBUSY) { + *finished = false; + return 0; + } + + if (ret < 0) + return ret; + } + + return 0; +} + +static int rtl8224_cable_test_get_status(struct phy_device *phydev, bool *finished) +{ + int ret; + + *finished = false; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL8224_MII_RTCT)); + if (ret < 0) + return ret; + + if (!(ret & RTL8224_MII_RTCT_DONE)) + return 0; + + *finished = true; + + return rtl8224_cable_test_report(phydev, finished); +} + static bool rtlgen_supports_2_5gbps(struct phy_device *phydev) { int val; @@ -1696,6 +1930,53 @@ static irqreturn_t rtl9000a_handle_interrupt(struct phy_device *phydev) return IRQ_HANDLED; } +static int rtl8221b_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR); + + return (err < 0) ? err : 0; +} + +static int rtl8221b_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + err = rtl8221b_ack_interrupt(phydev); + if (err) + return err; + + err = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, + RTL8221B_VND2_INER_LINK_STATUS); + } else { + err = phy_write_mmd(phydev, MDIO_MMD_VEND2, + RTL8221B_VND2_INER, 0); + if (err) + return err; + + err = rtl8221b_ack_interrupt(phydev); + } + + return err; +} + +static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev) +{ + int err; + + err = rtl8221b_ack_interrupt(phydev); + if (err) { + phy_error(phydev); + return IRQ_NONE; + } + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + static struct phy_driver realtek_drvs[] = { { PHY_ID_MATCH_EXACT(0x00008201), @@ -1870,6 +2151,8 @@ static struct phy_driver realtek_drvs[] = { }, { .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device, .name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)", + .config_intr = rtl8221b_config_intr, + .handle_interrupt = rtl8221b_handle_interrupt, .probe = rtl822x_probe, .config_init = rtl822xb_config_init, .get_rate_matching = rtl822xb_get_rate_matching, @@ -1894,6 +2177,8 @@ static struct phy_driver realtek_drvs[] = { }, { .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device, .name = "RTL8221B-VM-CG 2.5Gbps PHY (C45)", + .config_intr = rtl8221b_config_intr, + .handle_interrupt = rtl8221b_handle_interrupt, .probe = rtl822x_probe, .config_init = rtl822xb_config_init, .get_rate_matching = rtl822xb_get_rate_matching, @@ -1930,11 +2215,14 @@ static struct phy_driver realtek_drvs[] = { }, { PHY_ID_MATCH_EXACT(0x001ccad0), .name = "RTL8224 2.5Gbps PHY", + .flags = PHY_POLL_CABLE_TEST, .get_features = rtl822x_c45_get_features, .config_aneg = rtl822x_c45_config_aneg, .read_status = rtl822x_c45_read_status, .suspend = genphy_c45_pma_suspend, .resume = rtlgen_c45_resume, + .cable_test_start = rtl8224_cable_test_start, + .cable_test_get_status = rtl8224_cable_test_get_status, }, { PHY_ID_MATCH_EXACT(0x001cc961), .name = "RTL8366RB Gigabit Ethernet", |
