18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// Broadcom BCM84881 NBASE-T PHY driver, as found on a SFP+ module. 38c2ecf20Sopenharmony_ci// Copyright (C) 2019 Russell King, Deep Blue Solutions Ltd. 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Like the Marvell 88x3310, the Broadcom 84881 changes its host-side 68c2ecf20Sopenharmony_ci// interface according to the operating speed between 10GBASE-R, 78c2ecf20Sopenharmony_ci// 2500BASE-X and SGMII (but unlike the 88x3310, without the control 88c2ecf20Sopenharmony_ci// word). 98c2ecf20Sopenharmony_ci// 108c2ecf20Sopenharmony_ci// This driver only supports those aspects of the PHY that I'm able to 118c2ecf20Sopenharmony_ci// observe and test with the SFP+ module, which is an incomplete subset 128c2ecf20Sopenharmony_ci// of what this PHY is able to support. For example, I only assume it 138c2ecf20Sopenharmony_ci// supports a single lane Serdes connection, but it may be that the PHY 148c2ecf20Sopenharmony_ci// is able to support more than that. 158c2ecf20Sopenharmony_ci#include <linux/delay.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/phy.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cienum { 208c2ecf20Sopenharmony_ci MDIO_AN_C22 = 0xffe0, 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic int bcm84881_wait_init(struct phy_device *phydev) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci int val; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, 288c2ecf20Sopenharmony_ci val, !(val & MDIO_CTRL1_RESET), 298c2ecf20Sopenharmony_ci 100000, 2000000, false); 308c2ecf20Sopenharmony_ci} 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic int bcm84881_config_init(struct phy_device *phydev) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci switch (phydev->interface) { 358c2ecf20Sopenharmony_ci case PHY_INTERFACE_MODE_SGMII: 368c2ecf20Sopenharmony_ci case PHY_INTERFACE_MODE_2500BASEX: 378c2ecf20Sopenharmony_ci case PHY_INTERFACE_MODE_10GBASER: 388c2ecf20Sopenharmony_ci break; 398c2ecf20Sopenharmony_ci default: 408c2ecf20Sopenharmony_ci return -ENODEV; 418c2ecf20Sopenharmony_ci } 428c2ecf20Sopenharmony_ci return 0; 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic int bcm84881_probe(struct phy_device *phydev) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci /* This driver requires PMAPMD and AN blocks */ 488c2ecf20Sopenharmony_ci const u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (!phydev->is_c45 || 518c2ecf20Sopenharmony_ci (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) 528c2ecf20Sopenharmony_ci return -ENODEV; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci return 0; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int bcm84881_get_features(struct phy_device *phydev) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci int ret; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci ret = genphy_c45_pma_read_abilities(phydev); 628c2ecf20Sopenharmony_ci if (ret) 638c2ecf20Sopenharmony_ci return ret; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci /* Although the PHY sets bit 1.11.8, it does not support 10M modes */ 668c2ecf20Sopenharmony_ci linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, 678c2ecf20Sopenharmony_ci phydev->supported); 688c2ecf20Sopenharmony_ci linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, 698c2ecf20Sopenharmony_ci phydev->supported); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci return 0; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int bcm84881_config_aneg(struct phy_device *phydev) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci bool changed = false; 778c2ecf20Sopenharmony_ci u32 adv; 788c2ecf20Sopenharmony_ci int ret; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Wait for the PHY to finish initialising, otherwise our 818c2ecf20Sopenharmony_ci * advertisement may be overwritten. 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_ci ret = bcm84881_wait_init(phydev); 848c2ecf20Sopenharmony_ci if (ret) 858c2ecf20Sopenharmony_ci return ret; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* We don't support manual MDI control */ 888c2ecf20Sopenharmony_ci phydev->mdix_ctrl = ETH_TP_MDI_AUTO; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci /* disabled autoneg doesn't seem to work with this PHY */ 918c2ecf20Sopenharmony_ci if (phydev->autoneg == AUTONEG_DISABLE) 928c2ecf20Sopenharmony_ci return -EINVAL; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci ret = genphy_c45_an_config_aneg(phydev); 958c2ecf20Sopenharmony_ci if (ret < 0) 968c2ecf20Sopenharmony_ci return ret; 978c2ecf20Sopenharmony_ci if (ret > 0) 988c2ecf20Sopenharmony_ci changed = true; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); 1018c2ecf20Sopenharmony_ci ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, 1028c2ecf20Sopenharmony_ci MDIO_AN_C22 + MII_CTRL1000, 1038c2ecf20Sopenharmony_ci ADVERTISE_1000FULL | ADVERTISE_1000HALF, 1048c2ecf20Sopenharmony_ci adv); 1058c2ecf20Sopenharmony_ci if (ret < 0) 1068c2ecf20Sopenharmony_ci return ret; 1078c2ecf20Sopenharmony_ci if (ret > 0) 1088c2ecf20Sopenharmony_ci changed = true; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return genphy_c45_check_and_restart_aneg(phydev, changed); 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int bcm84881_aneg_done(struct phy_device *phydev) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci int bmsr, val; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 1188c2ecf20Sopenharmony_ci if (val < 0) 1198c2ecf20Sopenharmony_ci return val; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR); 1228c2ecf20Sopenharmony_ci if (bmsr < 0) 1238c2ecf20Sopenharmony_ci return val; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci return !!(val & MDIO_AN_STAT1_COMPLETE) && 1268c2ecf20Sopenharmony_ci !!(bmsr & BMSR_ANEGCOMPLETE); 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic int bcm84881_read_status(struct phy_device *phydev) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci unsigned int mode; 1328c2ecf20Sopenharmony_ci int bmsr, val; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); 1358c2ecf20Sopenharmony_ci if (val < 0) 1368c2ecf20Sopenharmony_ci return val; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci if (val & MDIO_AN_CTRL1_RESTART) { 1398c2ecf20Sopenharmony_ci phydev->link = 0; 1408c2ecf20Sopenharmony_ci return 0; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 1448c2ecf20Sopenharmony_ci if (val < 0) 1458c2ecf20Sopenharmony_ci return val; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR); 1488c2ecf20Sopenharmony_ci if (bmsr < 0) 1498c2ecf20Sopenharmony_ci return val; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci phydev->autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) && 1528c2ecf20Sopenharmony_ci !!(bmsr & BMSR_ANEGCOMPLETE); 1538c2ecf20Sopenharmony_ci phydev->link = !!(val & MDIO_STAT1_LSTATUS) && 1548c2ecf20Sopenharmony_ci !!(bmsr & BMSR_LSTATUS); 1558c2ecf20Sopenharmony_ci if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) 1568c2ecf20Sopenharmony_ci phydev->link = false; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci linkmode_zero(phydev->lp_advertising); 1598c2ecf20Sopenharmony_ci phydev->speed = SPEED_UNKNOWN; 1608c2ecf20Sopenharmony_ci phydev->duplex = DUPLEX_UNKNOWN; 1618c2ecf20Sopenharmony_ci phydev->pause = 0; 1628c2ecf20Sopenharmony_ci phydev->asym_pause = 0; 1638c2ecf20Sopenharmony_ci phydev->mdix = 0; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (!phydev->link) 1668c2ecf20Sopenharmony_ci return 0; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (phydev->autoneg_complete) { 1698c2ecf20Sopenharmony_ci val = genphy_c45_read_lpa(phydev); 1708c2ecf20Sopenharmony_ci if (val < 0) 1718c2ecf20Sopenharmony_ci return val; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci val = phy_read_mmd(phydev, MDIO_MMD_AN, 1748c2ecf20Sopenharmony_ci MDIO_AN_C22 + MII_STAT1000); 1758c2ecf20Sopenharmony_ci if (val < 0) 1768c2ecf20Sopenharmony_ci return val; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci if (phydev->autoneg == AUTONEG_ENABLE) 1818c2ecf20Sopenharmony_ci phy_resolve_aneg_linkmode(phydev); 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci if (phydev->autoneg == AUTONEG_DISABLE) { 1858c2ecf20Sopenharmony_ci /* disabled autoneg doesn't seem to work, so force the link 1868c2ecf20Sopenharmony_ci * down. 1878c2ecf20Sopenharmony_ci */ 1888c2ecf20Sopenharmony_ci phydev->link = 0; 1898c2ecf20Sopenharmony_ci return 0; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* Set the host link mode - we set the phy interface mode and 1938c2ecf20Sopenharmony_ci * the speed according to this register so that downshift works. 1948c2ecf20Sopenharmony_ci * We leave the duplex setting as per the resolution from the 1958c2ecf20Sopenharmony_ci * above. 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_ci val = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x4011); 1988c2ecf20Sopenharmony_ci mode = (val & 0x1e) >> 1; 1998c2ecf20Sopenharmony_ci if (mode == 1 || mode == 2) 2008c2ecf20Sopenharmony_ci phydev->interface = PHY_INTERFACE_MODE_SGMII; 2018c2ecf20Sopenharmony_ci else if (mode == 3) 2028c2ecf20Sopenharmony_ci phydev->interface = PHY_INTERFACE_MODE_10GBASER; 2038c2ecf20Sopenharmony_ci else if (mode == 4) 2048c2ecf20Sopenharmony_ci phydev->interface = PHY_INTERFACE_MODE_2500BASEX; 2058c2ecf20Sopenharmony_ci switch (mode & 7) { 2068c2ecf20Sopenharmony_ci case 1: 2078c2ecf20Sopenharmony_ci phydev->speed = SPEED_100; 2088c2ecf20Sopenharmony_ci break; 2098c2ecf20Sopenharmony_ci case 2: 2108c2ecf20Sopenharmony_ci phydev->speed = SPEED_1000; 2118c2ecf20Sopenharmony_ci break; 2128c2ecf20Sopenharmony_ci case 3: 2138c2ecf20Sopenharmony_ci phydev->speed = SPEED_10000; 2148c2ecf20Sopenharmony_ci break; 2158c2ecf20Sopenharmony_ci case 4: 2168c2ecf20Sopenharmony_ci phydev->speed = SPEED_2500; 2178c2ecf20Sopenharmony_ci break; 2188c2ecf20Sopenharmony_ci case 5: 2198c2ecf20Sopenharmony_ci phydev->speed = SPEED_5000; 2208c2ecf20Sopenharmony_ci break; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci return genphy_c45_read_mdix(phydev); 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cistatic struct phy_driver bcm84881_drivers[] = { 2278c2ecf20Sopenharmony_ci { 2288c2ecf20Sopenharmony_ci .phy_id = 0xae025150, 2298c2ecf20Sopenharmony_ci .phy_id_mask = 0xfffffff0, 2308c2ecf20Sopenharmony_ci .name = "Broadcom BCM84881", 2318c2ecf20Sopenharmony_ci .config_init = bcm84881_config_init, 2328c2ecf20Sopenharmony_ci .probe = bcm84881_probe, 2338c2ecf20Sopenharmony_ci .get_features = bcm84881_get_features, 2348c2ecf20Sopenharmony_ci .config_aneg = bcm84881_config_aneg, 2358c2ecf20Sopenharmony_ci .aneg_done = bcm84881_aneg_done, 2368c2ecf20Sopenharmony_ci .read_status = bcm84881_read_status, 2378c2ecf20Sopenharmony_ci }, 2388c2ecf20Sopenharmony_ci}; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cimodule_phy_driver(bcm84881_drivers); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */ 2438c2ecf20Sopenharmony_cistatic struct mdio_device_id __maybe_unused bcm84881_tbl[] = { 2448c2ecf20Sopenharmony_ci { 0xae025150, 0xfffffff0 }, 2458c2ecf20Sopenharmony_ci { }, 2468c2ecf20Sopenharmony_ci}; 2478c2ecf20Sopenharmony_ciMODULE_AUTHOR("Russell King"); 2488c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Broadcom BCM84881 PHY driver"); 2498c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, bcm84881_tbl); 2508c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 251