diff options
Diffstat (limited to 'drivers/net/phy/dp83tg720.c')
-rw-r--r-- | drivers/net/phy/dp83tg720.c | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/drivers/net/phy/dp83tg720.c b/drivers/net/phy/dp83tg720.c index c706429b225a..0ef4d7dba065 100644 --- a/drivers/net/phy/dp83tg720.c +++ b/drivers/net/phy/dp83tg720.c @@ -3,10 +3,13 @@ * Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> */ #include <linux/bitfield.h> +#include <linux/ethtool_netlink.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/phy.h> +#include "open_alliance_helpers.h" + #define DP83TG720S_PHY_ID 0x2000a284 /* MDIO_MMD_VEND2 registers */ @@ -14,6 +17,17 @@ #define DP83TG720S_STS_MII_INT BIT(7) #define DP83TG720S_LINK_STATUS BIT(0) +/* TDR Configuration Register (0x1E) */ +#define DP83TG720S_TDR_CFG 0x1e +/* 1b = TDR start, 0b = No TDR */ +#define DP83TG720S_TDR_START BIT(15) +/* 1b = TDR auto on link down, 0b = Manual TDR start */ +#define DP83TG720S_CFG_TDR_AUTO_RUN BIT(14) +/* 1b = TDR done, 0b = TDR in progress */ +#define DP83TG720S_TDR_DONE BIT(1) +/* 1b = TDR fail, 0b = TDR success */ +#define DP83TG720S_TDR_FAIL BIT(0) + #define DP83TG720S_PHY_RESET 0x1f #define DP83TG720S_HW_RESET BIT(15) @@ -22,18 +36,155 @@ /* Power Mode 0 is Normal mode */ #define DP83TG720S_LPS_CFG3_PWR_MODE_0 BIT(0) +/* Open Aliance 1000BaseT1 compatible HDD.TDR Fault Status Register */ +#define DP83TG720S_TDR_FAULT_STATUS 0x30f + +/* Register 0x0301: TDR Configuration 2 */ +#define DP83TG720S_TDR_CFG2 0x301 + +/* Register 0x0303: TDR Configuration 3 */ +#define DP83TG720S_TDR_CFG3 0x303 + +/* Register 0x0304: TDR Configuration 4 */ +#define DP83TG720S_TDR_CFG4 0x304 + +/* Register 0x0405: Unknown Register */ +#define DP83TG720S_UNKNOWN_0405 0x405 + +/* Register 0x0576: TDR Master Link Down Control */ +#define DP83TG720S_TDR_MASTER_LINK_DOWN 0x576 + #define DP83TG720S_RGMII_DELAY_CTRL 0x602 /* In RGMII mode, Enable or disable the internal delay for RXD */ #define DP83TG720S_RGMII_RX_CLK_SEL BIT(1) /* In RGMII mode, Enable or disable the internal delay for TXD */ #define DP83TG720S_RGMII_TX_CLK_SEL BIT(0) +/* Register 0x083F: Unknown Register */ +#define DP83TG720S_UNKNOWN_083F 0x83f + #define DP83TG720S_SQI_REG_1 0x871 #define DP83TG720S_SQI_OUT_WORST GENMASK(7, 5) #define DP83TG720S_SQI_OUT GENMASK(3, 1) #define DP83TG720_SQI_MAX 7 +/** + * dp83tg720_cable_test_start - Start the cable test for the DP83TG720 PHY. + * @phydev: Pointer to the phy_device structure. + * + * This sequence is based on the documented procedure for the DP83TG720 PHY. + * + * Returns: 0 on success, a negative error code on failure. + */ +static int dp83tg720_cable_test_start(struct phy_device *phydev) +{ + int ret; + + /* Initialize the PHY to run the TDR test as described in the + * "DP83TG720S-Q1: Configuring for Open Alliance Specification + * Compliance (Rev. B)" application note. + * Most of the registers are not documented. Some of register names + * are guessed by comparing the register offsets with the DP83TD510E. + */ + + /* Force master link down */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, + DP83TG720S_TDR_MASTER_LINK_DOWN, 0x0400); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG2, + 0xa008); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG3, + 0x0928); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG4, + 0x0004); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_UNKNOWN_0405, + 0x6400); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_UNKNOWN_083F, + 0x3003); + if (ret) + return ret; + + /* Start the TDR */ + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG, + DP83TG720S_TDR_START); + if (ret) + return ret; + + return 0; +} + +/** + * dp83tg720_cable_test_get_status - Get the status of the cable test for the + * DP83TG720 PHY. + * @phydev: Pointer to the phy_device structure. + * @finished: Pointer to a boolean that indicates whether the test is finished. + * + * The function sets the @finished flag to true if the test is complete. + * + * Returns: 0 on success or a negative error code on failure. + */ +static int dp83tg720_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + int ret, stat; + + *finished = false; + + /* Read the TDR status */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_TDR_CFG); + if (ret < 0) + return ret; + + /* Check if the TDR test is done */ + if (!(ret & DP83TG720S_TDR_DONE)) + return 0; + + /* Check for TDR test failure */ + if (!(ret & DP83TG720S_TDR_FAIL)) { + int location; + + /* Read fault status */ + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, + DP83TG720S_TDR_FAULT_STATUS); + if (ret < 0) + return ret; + + /* Get fault type */ + stat = oa_1000bt1_get_ethtool_cable_result_code(ret); + + /* Determine fault location */ + location = oa_1000bt1_get_tdr_distance(ret); + if (location > 0) + ethnl_cable_test_fault_length(phydev, + ETHTOOL_A_CABLE_PAIR_A, + location); + } else { + /* Active link partner or other issues */ + stat = ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } + + *finished = true; + + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, stat); + + return phy_init_hw(phydev); +} + static int dp83tg720_config_aneg(struct phy_device *phydev) { int ret; @@ -195,12 +346,15 @@ static struct phy_driver dp83tg720_driver[] = { PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID), .name = "TI DP83TG720S", + .flags = PHY_POLL_CABLE_TEST, .config_aneg = dp83tg720_config_aneg, .read_status = dp83tg720_read_status, .get_features = genphy_c45_pma_read_ext_abilities, .config_init = dp83tg720_config_init, .get_sqi = dp83tg720_get_sqi, .get_sqi_max = dp83tg720_get_sqi_max, + .cable_test_start = dp83tg720_cable_test_start, + .cable_test_get_status = dp83tg720_cable_test_get_status, .suspend = genphy_suspend, .resume = genphy_resume, |