Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net: phy: microchip_t1: Cable Diagnostics for lan887x

Add support for cable diagnostics in lan887x PHY.
Using this we can diagnose connected/open/short wires and
also length where cable fault is occurred.

Signed-off-by: Divya Koppera <divya.koppera@microchip.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20240909114339.3446-1-divya.koppera@microchip.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Divya Koppera and committed by
Jakub Kicinski
b2c8a506 fce1e9f8

+413
+413
drivers/net/phy/microchip_t1.c
··· 175 175 #define LAN887X_LED_LINK_ACT_ANY_SPEED 0x0 176 176 177 177 /* MX chip top registers */ 178 + #define LAN887X_CHIP_HARD_RST 0xf03e 179 + #define LAN887X_CHIP_HARD_RST_RESET BIT(0) 180 + 178 181 #define LAN887X_CHIP_SOFT_RST 0xf03f 179 182 #define LAN887X_CHIP_SOFT_RST_RESET BIT(0) 180 183 ··· 191 188 #define LAN887X_EFUSE_READ_DAT9_SGMII_DIS BIT(9) 192 189 #define LAN887X_EFUSE_READ_DAT9_MAC_MODE GENMASK(1, 0) 193 190 191 + #define LAN887X_CALIB_CONFIG_100 0x437 192 + #define LAN887X_CALIB_CONFIG_100_CBL_DIAG_USE_LOCAL_SMPL BIT(5) 193 + #define LAN887X_CALIB_CONFIG_100_CBL_DIAG_STB_SYNC_MODE BIT(4) 194 + #define LAN887X_CALIB_CONFIG_100_CBL_DIAG_CLK_ALGN_MODE BIT(3) 195 + #define LAN887X_CALIB_CONFIG_100_VAL \ 196 + (LAN887X_CALIB_CONFIG_100_CBL_DIAG_CLK_ALGN_MODE |\ 197 + LAN887X_CALIB_CONFIG_100_CBL_DIAG_STB_SYNC_MODE |\ 198 + LAN887X_CALIB_CONFIG_100_CBL_DIAG_USE_LOCAL_SMPL) 199 + 200 + #define LAN887X_MAX_PGA_GAIN_100 0x44f 201 + #define LAN887X_MIN_PGA_GAIN_100 0x450 202 + #define LAN887X_START_CBL_DIAG_100 0x45a 203 + #define LAN887X_CBL_DIAG_DONE BIT(1) 204 + #define LAN887X_CBL_DIAG_START BIT(0) 205 + #define LAN887X_CBL_DIAG_STOP 0x0 206 + 207 + #define LAN887X_CBL_DIAG_TDR_THRESH_100 0x45b 208 + #define LAN887X_CBL_DIAG_AGC_THRESH_100 0x45c 209 + #define LAN887X_CBL_DIAG_MIN_WAIT_CONFIG_100 0x45d 210 + #define LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100 0x45e 211 + #define LAN887X_CBL_DIAG_CYC_CONFIG_100 0x45f 212 + #define LAN887X_CBL_DIAG_TX_PULSE_CONFIG_100 0x460 213 + #define LAN887X_CBL_DIAG_MIN_PGA_GAIN_100 0x462 214 + #define LAN887X_CBL_DIAG_AGC_GAIN_100 0x497 215 + #define LAN887X_CBL_DIAG_POS_PEAK_VALUE_100 0x499 216 + #define LAN887X_CBL_DIAG_NEG_PEAK_VALUE_100 0x49a 217 + #define LAN887X_CBL_DIAG_POS_PEAK_TIME_100 0x49c 218 + #define LAN887X_CBL_DIAG_NEG_PEAK_TIME_100 0x49d 219 + 220 + #define MICROCHIP_CABLE_NOISE_MARGIN 20 221 + #define MICROCHIP_CABLE_TIME_MARGIN 89 222 + #define MICROCHIP_CABLE_MIN_TIME_DIFF 96 223 + #define MICROCHIP_CABLE_MAX_TIME_DIFF \ 224 + (MICROCHIP_CABLE_MIN_TIME_DIFF + MICROCHIP_CABLE_TIME_MARGIN) 225 + 194 226 #define DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>" 195 227 #define DRIVER_DESC "Microchip LAN87XX/LAN937x/LAN887x T1 PHY driver" 228 + 229 + /* TEST_MODE_NORMAL: Non-hybrid results to calculate cable status(open/short/ok) 230 + * TEST_MODE_HYBRID: Hybrid results to calculate distance to fault 231 + */ 232 + enum cable_diag_mode { 233 + TEST_MODE_NORMAL, 234 + TEST_MODE_HYBRID 235 + }; 236 + 237 + /* CD_TEST_INIT: Cable test is initated 238 + * CD_TEST_DONE: Cable test is done 239 + */ 240 + enum cable_diag_state { 241 + CD_TEST_INIT, 242 + CD_TEST_DONE 243 + }; 196 244 197 245 struct access_ereg_val { 198 246 u8 mode; ··· 1474 1420 ethtool_puts(&data, lan887x_hw_stats[i].string); 1475 1421 } 1476 1422 1423 + static int lan887x_cd_reset(struct phy_device *phydev, 1424 + enum cable_diag_state cd_done) 1425 + { 1426 + u16 val; 1427 + int rc; 1428 + 1429 + /* Chip hard-reset */ 1430 + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_CHIP_HARD_RST, 1431 + LAN887X_CHIP_HARD_RST_RESET); 1432 + if (rc < 0) 1433 + return rc; 1434 + 1435 + /* Wait for reset to complete */ 1436 + rc = phy_read_poll_timeout(phydev, MII_PHYSID2, val, 1437 + ((val & GENMASK(15, 4)) == 1438 + (PHY_ID_LAN887X & GENMASK(15, 4))), 1439 + 5000, 50000, true); 1440 + if (rc < 0) 1441 + return rc; 1442 + 1443 + if (cd_done == CD_TEST_DONE) { 1444 + /* Cable diagnostics complete. Restore PHY. */ 1445 + rc = lan887x_phy_setup(phydev); 1446 + if (rc < 0) 1447 + return rc; 1448 + 1449 + rc = lan887x_phy_init(phydev); 1450 + if (rc < 0) 1451 + return rc; 1452 + 1453 + rc = lan887x_phy_reconfig(phydev); 1454 + if (rc < 0) 1455 + return rc; 1456 + } 1457 + 1458 + return 0; 1459 + } 1460 + 1461 + static int lan887x_cable_test_prep(struct phy_device *phydev, 1462 + enum cable_diag_mode mode) 1463 + { 1464 + static const struct lan887x_regwr_map values[] = { 1465 + {MDIO_MMD_VEND1, LAN887X_MAX_PGA_GAIN_100, 0x1f}, 1466 + {MDIO_MMD_VEND1, LAN887X_MIN_PGA_GAIN_100, 0x0}, 1467 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_TDR_THRESH_100, 0x1}, 1468 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_AGC_THRESH_100, 0x3c}, 1469 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MIN_WAIT_CONFIG_100, 0x0}, 1470 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100, 0x46}, 1471 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_CYC_CONFIG_100, 0x5a}, 1472 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_TX_PULSE_CONFIG_100, 0x44d5}, 1473 + {MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MIN_PGA_GAIN_100, 0x0}, 1474 + 1475 + }; 1476 + int rc; 1477 + 1478 + rc = lan887x_cd_reset(phydev, CD_TEST_INIT); 1479 + if (rc < 0) 1480 + return rc; 1481 + 1482 + /* Forcing DUT to master mode, as we don't care about 1483 + * mode during diagnostics 1484 + */ 1485 + rc = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, 1486 + MDIO_PMA_PMD_BT1_CTRL_CFG_MST); 1487 + if (rc < 0) 1488 + return rc; 1489 + 1490 + rc = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 0x80b0, 0x0038); 1491 + if (rc < 0) 1492 + return rc; 1493 + 1494 + rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, 1495 + LAN887X_CALIB_CONFIG_100, 0, 1496 + LAN887X_CALIB_CONFIG_100_VAL); 1497 + if (rc < 0) 1498 + return rc; 1499 + 1500 + for (int i = 0; i < ARRAY_SIZE(values); i++) { 1501 + rc = phy_write_mmd(phydev, values[i].mmd, values[i].reg, 1502 + values[i].val); 1503 + if (rc < 0) 1504 + return rc; 1505 + 1506 + if (mode && 1507 + values[i].reg == LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100) { 1508 + rc = phy_write_mmd(phydev, values[i].mmd, 1509 + values[i].reg, 0xa); 1510 + if (rc < 0) 1511 + return rc; 1512 + } 1513 + } 1514 + 1515 + if (mode == TEST_MODE_HYBRID) { 1516 + rc = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, 1517 + LAN887X_AFE_PORT_TESTBUS_CTRL4, 1518 + BIT(0), BIT(0)); 1519 + if (rc < 0) 1520 + return rc; 1521 + } 1522 + 1523 + /* HW_INIT 100T1, Get DUT running in 100T1 mode */ 1524 + rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26, 1525 + LAN887X_REG_REG26_HW_INIT_SEQ_EN, 1526 + LAN887X_REG_REG26_HW_INIT_SEQ_EN); 1527 + if (rc < 0) 1528 + return rc; 1529 + 1530 + /* Cable diag requires hard reset and is sensitive regarding the delays. 1531 + * Hard reset is expected into and out of cable diag. 1532 + * Wait for 50ms 1533 + */ 1534 + msleep(50); 1535 + 1536 + /* Start cable diag */ 1537 + return phy_write_mmd(phydev, MDIO_MMD_VEND1, 1538 + LAN887X_START_CBL_DIAG_100, 1539 + LAN887X_CBL_DIAG_START); 1540 + } 1541 + 1542 + static int lan887x_cable_test_chk(struct phy_device *phydev, 1543 + enum cable_diag_mode mode) 1544 + { 1545 + int val; 1546 + int rc; 1547 + 1548 + if (mode == TEST_MODE_HYBRID) { 1549 + /* Cable diag requires hard reset and is sensitive regarding the delays. 1550 + * Hard reset is expected into and out of cable diag. 1551 + * Wait for cable diag to complete. 1552 + * Minimum wait time is 50ms if the condition is not a match. 1553 + */ 1554 + rc = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, 1555 + LAN887X_START_CBL_DIAG_100, val, 1556 + ((val & LAN887X_CBL_DIAG_DONE) == 1557 + LAN887X_CBL_DIAG_DONE), 1558 + 50000, 500000, false); 1559 + if (rc < 0) 1560 + return rc; 1561 + } else { 1562 + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1563 + LAN887X_START_CBL_DIAG_100); 1564 + if (rc < 0) 1565 + return rc; 1566 + 1567 + if ((rc & LAN887X_CBL_DIAG_DONE) != LAN887X_CBL_DIAG_DONE) 1568 + return -EAGAIN; 1569 + } 1570 + 1571 + /* Stop cable diag */ 1572 + return phy_write_mmd(phydev, MDIO_MMD_VEND1, 1573 + LAN887X_START_CBL_DIAG_100, 1574 + LAN887X_CBL_DIAG_STOP); 1575 + } 1576 + 1577 + static int lan887x_cable_test_start(struct phy_device *phydev) 1578 + { 1579 + int rc, ret; 1580 + 1581 + rc = lan887x_cable_test_prep(phydev, TEST_MODE_NORMAL); 1582 + if (rc < 0) { 1583 + ret = lan887x_cd_reset(phydev, CD_TEST_DONE); 1584 + if (ret < 0) 1585 + return ret; 1586 + 1587 + return rc; 1588 + } 1589 + 1590 + return 0; 1591 + } 1592 + 1593 + static int lan887x_cable_test_report(struct phy_device *phydev) 1594 + { 1595 + int pos_peak_cycle, pos_peak_cycle_hybrid, pos_peak_in_phases; 1596 + int pos_peak_time, pos_peak_time_hybrid, neg_peak_time; 1597 + int neg_peak_cycle, neg_peak_in_phases; 1598 + int pos_peak_in_phases_hybrid; 1599 + int gain_idx, gain_idx_hybrid; 1600 + int pos_peak_phase_hybrid; 1601 + int pos_peak, neg_peak; 1602 + int distance; 1603 + int detect; 1604 + int length; 1605 + int ret; 1606 + int rc; 1607 + 1608 + /* Read non-hybrid results */ 1609 + gain_idx = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1610 + LAN887X_CBL_DIAG_AGC_GAIN_100); 1611 + if (gain_idx < 0) { 1612 + rc = gain_idx; 1613 + goto error; 1614 + } 1615 + 1616 + pos_peak = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1617 + LAN887X_CBL_DIAG_POS_PEAK_VALUE_100); 1618 + if (pos_peak < 0) { 1619 + rc = pos_peak; 1620 + goto error; 1621 + } 1622 + 1623 + neg_peak = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1624 + LAN887X_CBL_DIAG_NEG_PEAK_VALUE_100); 1625 + if (neg_peak < 0) { 1626 + rc = neg_peak; 1627 + goto error; 1628 + } 1629 + 1630 + pos_peak_time = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1631 + LAN887X_CBL_DIAG_POS_PEAK_TIME_100); 1632 + if (pos_peak_time < 0) { 1633 + rc = pos_peak_time; 1634 + goto error; 1635 + } 1636 + 1637 + neg_peak_time = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1638 + LAN887X_CBL_DIAG_NEG_PEAK_TIME_100); 1639 + if (neg_peak_time < 0) { 1640 + rc = neg_peak_time; 1641 + goto error; 1642 + } 1643 + 1644 + /* Calculate non-hybrid values */ 1645 + pos_peak_cycle = (pos_peak_time >> 7) & 0x7f; 1646 + pos_peak_in_phases = (pos_peak_cycle * 96) + (pos_peak_time & 0x7f); 1647 + neg_peak_cycle = (neg_peak_time >> 7) & 0x7f; 1648 + neg_peak_in_phases = (neg_peak_cycle * 96) + (neg_peak_time & 0x7f); 1649 + 1650 + /* Deriving the status of cable */ 1651 + if (pos_peak > MICROCHIP_CABLE_NOISE_MARGIN && 1652 + neg_peak > MICROCHIP_CABLE_NOISE_MARGIN && gain_idx >= 0) { 1653 + if (pos_peak_in_phases > neg_peak_in_phases && 1654 + ((pos_peak_in_phases - neg_peak_in_phases) >= 1655 + MICROCHIP_CABLE_MIN_TIME_DIFF) && 1656 + ((pos_peak_in_phases - neg_peak_in_phases) < 1657 + MICROCHIP_CABLE_MAX_TIME_DIFF) && 1658 + pos_peak_in_phases > 0) { 1659 + detect = LAN87XX_CABLE_TEST_SAME_SHORT; 1660 + } else if (neg_peak_in_phases > pos_peak_in_phases && 1661 + ((neg_peak_in_phases - pos_peak_in_phases) >= 1662 + MICROCHIP_CABLE_MIN_TIME_DIFF) && 1663 + ((neg_peak_in_phases - pos_peak_in_phases) < 1664 + MICROCHIP_CABLE_MAX_TIME_DIFF) && 1665 + neg_peak_in_phases > 0) { 1666 + detect = LAN87XX_CABLE_TEST_OPEN; 1667 + } else { 1668 + detect = LAN87XX_CABLE_TEST_OK; 1669 + } 1670 + } else { 1671 + detect = LAN87XX_CABLE_TEST_OK; 1672 + } 1673 + 1674 + if (detect == LAN87XX_CABLE_TEST_OK) { 1675 + distance = 0; 1676 + goto get_len; 1677 + } 1678 + 1679 + /* Re-initialize PHY and start cable diag test */ 1680 + rc = lan887x_cable_test_prep(phydev, TEST_MODE_HYBRID); 1681 + if (rc < 0) 1682 + goto cd_stop; 1683 + 1684 + /* Wait for cable diag test completion */ 1685 + rc = lan887x_cable_test_chk(phydev, TEST_MODE_HYBRID); 1686 + if (rc < 0) 1687 + goto cd_stop; 1688 + 1689 + /* Read hybrid results */ 1690 + gain_idx_hybrid = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1691 + LAN887X_CBL_DIAG_AGC_GAIN_100); 1692 + if (gain_idx_hybrid < 0) { 1693 + rc = gain_idx_hybrid; 1694 + goto error; 1695 + } 1696 + 1697 + pos_peak_time_hybrid = phy_read_mmd(phydev, MDIO_MMD_VEND1, 1698 + LAN887X_CBL_DIAG_POS_PEAK_TIME_100); 1699 + if (pos_peak_time_hybrid < 0) { 1700 + rc = pos_peak_time_hybrid; 1701 + goto error; 1702 + } 1703 + 1704 + /* Calculate hybrid values to derive cable length to fault */ 1705 + pos_peak_cycle_hybrid = (pos_peak_time_hybrid >> 7) & 0x7f; 1706 + pos_peak_phase_hybrid = pos_peak_time_hybrid & 0x7f; 1707 + pos_peak_in_phases_hybrid = pos_peak_cycle_hybrid * 96 + 1708 + pos_peak_phase_hybrid; 1709 + 1710 + /* Distance to fault calculation. 1711 + * distance = (peak_in_phases - peak_in_phases_hybrid) * 1712 + * propagationconstant. 1713 + * constant to convert number of phases to meters 1714 + * propagationconstant = 0.015953 1715 + * (0.6811 * 2.9979 * 156.2499 * 0.0001 * 0.5) 1716 + * Applying constant 1.5953 as ethtool further devides by 100 to 1717 + * convert to meters. 1718 + */ 1719 + if (detect == LAN87XX_CABLE_TEST_OPEN) { 1720 + distance = (((pos_peak_in_phases - pos_peak_in_phases_hybrid) 1721 + * 15953) / 10000); 1722 + } else if (detect == LAN87XX_CABLE_TEST_SAME_SHORT) { 1723 + distance = (((neg_peak_in_phases - pos_peak_in_phases_hybrid) 1724 + * 15953) / 10000); 1725 + } else { 1726 + distance = 0; 1727 + } 1728 + 1729 + get_len: 1730 + rc = lan887x_cd_reset(phydev, CD_TEST_DONE); 1731 + if (rc < 0) 1732 + return rc; 1733 + 1734 + length = ((u32)distance & GENMASK(15, 0)); 1735 + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, 1736 + lan87xx_cable_test_report_trans(detect)); 1737 + ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, length); 1738 + 1739 + return 0; 1740 + 1741 + cd_stop: 1742 + /* Stop cable diag */ 1743 + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 1744 + LAN887X_START_CBL_DIAG_100, 1745 + LAN887X_CBL_DIAG_STOP); 1746 + if (ret < 0) 1747 + return ret; 1748 + 1749 + error: 1750 + /* Cable diag test failed */ 1751 + ret = lan887x_cd_reset(phydev, CD_TEST_DONE); 1752 + if (ret < 0) 1753 + return ret; 1754 + 1755 + /* Return error in failure case */ 1756 + return rc; 1757 + } 1758 + 1759 + static int lan887x_cable_test_get_status(struct phy_device *phydev, 1760 + bool *finished) 1761 + { 1762 + int rc; 1763 + 1764 + rc = lan887x_cable_test_chk(phydev, TEST_MODE_NORMAL); 1765 + if (rc < 0) { 1766 + /* Let PHY statemachine poll again */ 1767 + if (rc == -EAGAIN) 1768 + return 0; 1769 + return rc; 1770 + } 1771 + 1772 + /* Cable diag test complete */ 1773 + *finished = true; 1774 + 1775 + /* Retrieve test status and cable length to fault */ 1776 + return lan887x_cable_test_report(phydev); 1777 + } 1778 + 1477 1779 static struct phy_driver microchip_t1_phy_driver[] = { 1478 1780 { 1479 1781 PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX), ··· 1868 1458 { 1869 1459 PHY_ID_MATCH_MODEL(PHY_ID_LAN887X), 1870 1460 .name = "Microchip LAN887x T1 PHY", 1461 + .flags = PHY_POLL_CABLE_TEST, 1871 1462 .probe = lan887x_probe, 1872 1463 .get_features = lan887x_get_features, 1873 1464 .config_init = lan887x_phy_init, ··· 1879 1468 .suspend = genphy_suspend, 1880 1469 .resume = genphy_resume, 1881 1470 .read_status = genphy_c45_read_status, 1471 + .cable_test_start = lan887x_cable_test_start, 1472 + .cable_test_get_status = lan887x_cable_test_get_status, 1882 1473 } 1883 1474 }; 1884 1475