// SPDX-License-Identifier: GPL-2.0 /* Driver for the Texas Instruments DP83TG720 PHY * Copyright (c) 2023 Pengutronix, Oleksij Rempel */ #include #include #include #include #include #include "open_alliance_helpers.h" #define DP83TG720S_PHY_ID 0x2000a284 /* MDIO_MMD_VEND2 registers */ #define DP83TG720S_MII_REG_10 0x10 #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) #define DP83TG720S_LPS_CFG3 0x18c /* Power modes are documented as bit fields but used as values */ /* 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; /* Autoneg is not supported and this PHY supports only one speed. * We need to care only about master/slave configuration if it was * changed by user. */ ret = genphy_c45_pma_baset1_setup_master_slave(phydev); if (ret) return ret; /* Re-read role configuration to make changes visible even if * the link is in administrative down state. */ return genphy_c45_pma_baset1_read_master_slave(phydev); } static int dp83tg720_read_status(struct phy_device *phydev) { u16 phy_sts; int ret; phydev->pause = 0; phydev->asym_pause = 0; /* Most of Clause 45 registers are not present, so we can't use * genphy_c45_read_status() here. */ phy_sts = phy_read(phydev, DP83TG720S_MII_REG_10); phydev->link = !!(phy_sts & DP83TG720S_LINK_STATUS); if (!phydev->link) { /* According to the "DP83TC81x, DP83TG72x Software * Implementation Guide", the PHY needs to be reset after a * link loss or if no link is created after at least 100ms. * * Currently we are polling with the PHY_STATE_TIME (1000ms) * interval, which is still enough for not automotive use cases. */ ret = phy_init_hw(phydev); if (ret) return ret; /* After HW reset we need to restore master/slave configuration. * genphy_c45_pma_baset1_read_master_slave() call will be done * by the dp83tg720_config_aneg() function. */ ret = dp83tg720_config_aneg(phydev); if (ret) return ret; phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; } else { /* PMA/PMD control 1 register (Register 1.0) is present, but it * doesn't contain the link speed information. * So genphy_c45_read_pma() can't be used here. */ ret = genphy_c45_pma_baset1_read_master_slave(phydev); if (ret) return ret; phydev->duplex = DUPLEX_FULL; phydev->speed = SPEED_1000; } return 0; } static int dp83tg720_get_sqi(struct phy_device *phydev) { int ret; if (!phydev->link) return 0; ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_SQI_REG_1); if (ret < 0) return ret; return FIELD_GET(DP83TG720S_SQI_OUT, ret); } static int dp83tg720_get_sqi_max(struct phy_device *phydev) { return DP83TG720_SQI_MAX; } static int dp83tg720_config_rgmii_delay(struct phy_device *phydev) { u16 rgmii_delay_mask; u16 rgmii_delay = 0; switch (phydev->interface) { case PHY_INTERFACE_MODE_RGMII: rgmii_delay = 0; break; case PHY_INTERFACE_MODE_RGMII_ID: rgmii_delay = DP83TG720S_RGMII_RX_CLK_SEL | DP83TG720S_RGMII_TX_CLK_SEL; break; case PHY_INTERFACE_MODE_RGMII_RXID: rgmii_delay = DP83TG720S_RGMII_RX_CLK_SEL; break; case PHY_INTERFACE_MODE_RGMII_TXID: rgmii_delay = DP83TG720S_RGMII_TX_CLK_SEL; break; default: return 0; } rgmii_delay_mask = DP83TG720S_RGMII_RX_CLK_SEL | DP83TG720S_RGMII_TX_CLK_SEL; return phy_modify_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_RGMII_DELAY_CTRL, rgmii_delay_mask, rgmii_delay); } static int dp83tg720_config_init(struct phy_device *phydev) { int ret; /* Software Restart is not enough to recover from a link failure. * Using Hardware Reset instead. */ ret = phy_write(phydev, DP83TG720S_PHY_RESET, DP83TG720S_HW_RESET); if (ret) return ret; /* Wait until MDC can be used again. * The wait value of one 1ms is documented in "DP83TG720S-Q1 1000BASE-T1 * Automotive Ethernet PHY with SGMII and RGMII" datasheet. */ usleep_range(1000, 2000); if (phy_interface_is_rgmii(phydev)) { ret = dp83tg720_config_rgmii_delay(phydev); if (ret) return ret; } /* In case the PHY is bootstrapped in managed mode, we need to * wake it. */ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TG720S_LPS_CFG3, DP83TG720S_LPS_CFG3_PWR_MODE_0); if (ret) return ret; /* Make role configuration visible for ethtool on init and after * rest. */ return genphy_c45_pma_baset1_read_master_slave(phydev); } 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, } }; module_phy_driver(dp83tg720_driver); static struct mdio_device_id __maybe_unused dp83tg720_tbl[] = { { PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID) }, { } }; MODULE_DEVICE_TABLE(mdio, dp83tg720_tbl); MODULE_DESCRIPTION("Texas Instruments DP83TG720S PHY driver"); MODULE_AUTHOR("Oleksij Rempel "); MODULE_LICENSE("GPL");