bcm84881.c (6175B)
1// SPDX-License-Identifier: GPL-2.0 2// Broadcom BCM84881 NBASE-T PHY driver, as found on a SFP+ module. 3// Copyright (C) 2019 Russell King, Deep Blue Solutions Ltd. 4// 5// Like the Marvell 88x3310, the Broadcom 84881 changes its host-side 6// interface according to the operating speed between 10GBASE-R, 7// 2500BASE-X and SGMII (but unlike the 88x3310, without the control 8// word). 9// 10// This driver only supports those aspects of the PHY that I'm able to 11// observe and test with the SFP+ module, which is an incomplete subset 12// of what this PHY is able to support. For example, I only assume it 13// supports a single lane Serdes connection, but it may be that the PHY 14// is able to support more than that. 15#include <linux/delay.h> 16#include <linux/module.h> 17#include <linux/phy.h> 18 19enum { 20 MDIO_AN_C22 = 0xffe0, 21}; 22 23static int bcm84881_wait_init(struct phy_device *phydev) 24{ 25 int val; 26 27 return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, 28 val, !(val & MDIO_CTRL1_RESET), 29 100000, 2000000, false); 30} 31 32static int bcm84881_config_init(struct phy_device *phydev) 33{ 34 switch (phydev->interface) { 35 case PHY_INTERFACE_MODE_SGMII: 36 case PHY_INTERFACE_MODE_2500BASEX: 37 case PHY_INTERFACE_MODE_10GBASER: 38 break; 39 default: 40 return -ENODEV; 41 } 42 return 0; 43} 44 45static int bcm84881_probe(struct phy_device *phydev) 46{ 47 /* This driver requires PMAPMD and AN blocks */ 48 const u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; 49 50 if (!phydev->is_c45 || 51 (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) 52 return -ENODEV; 53 54 return 0; 55} 56 57static int bcm84881_get_features(struct phy_device *phydev) 58{ 59 int ret; 60 61 ret = genphy_c45_pma_read_abilities(phydev); 62 if (ret) 63 return ret; 64 65 /* Although the PHY sets bit 1.11.8, it does not support 10M modes */ 66 linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, 67 phydev->supported); 68 linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, 69 phydev->supported); 70 71 return 0; 72} 73 74static int bcm84881_config_aneg(struct phy_device *phydev) 75{ 76 bool changed = false; 77 u32 adv; 78 int ret; 79 80 /* Wait for the PHY to finish initialising, otherwise our 81 * advertisement may be overwritten. 82 */ 83 ret = bcm84881_wait_init(phydev); 84 if (ret) 85 return ret; 86 87 /* We don't support manual MDI control */ 88 phydev->mdix_ctrl = ETH_TP_MDI_AUTO; 89 90 /* disabled autoneg doesn't seem to work with this PHY */ 91 if (phydev->autoneg == AUTONEG_DISABLE) 92 return -EINVAL; 93 94 ret = genphy_c45_an_config_aneg(phydev); 95 if (ret < 0) 96 return ret; 97 if (ret > 0) 98 changed = true; 99 100 adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); 101 ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, 102 MDIO_AN_C22 + MII_CTRL1000, 103 ADVERTISE_1000FULL | ADVERTISE_1000HALF, 104 adv); 105 if (ret < 0) 106 return ret; 107 if (ret > 0) 108 changed = true; 109 110 return genphy_c45_check_and_restart_aneg(phydev, changed); 111} 112 113static int bcm84881_aneg_done(struct phy_device *phydev) 114{ 115 int bmsr, val; 116 117 val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 118 if (val < 0) 119 return val; 120 121 bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR); 122 if (bmsr < 0) 123 return val; 124 125 return !!(val & MDIO_AN_STAT1_COMPLETE) && 126 !!(bmsr & BMSR_ANEGCOMPLETE); 127} 128 129static int bcm84881_read_status(struct phy_device *phydev) 130{ 131 unsigned int mode; 132 int bmsr, val; 133 134 val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); 135 if (val < 0) 136 return val; 137 138 if (val & MDIO_AN_CTRL1_RESTART) { 139 phydev->link = 0; 140 return 0; 141 } 142 143 val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 144 if (val < 0) 145 return val; 146 147 bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR); 148 if (bmsr < 0) 149 return val; 150 151 phydev->autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) && 152 !!(bmsr & BMSR_ANEGCOMPLETE); 153 phydev->link = !!(val & MDIO_STAT1_LSTATUS) && 154 !!(bmsr & BMSR_LSTATUS); 155 if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) 156 phydev->link = false; 157 158 linkmode_zero(phydev->lp_advertising); 159 phydev->speed = SPEED_UNKNOWN; 160 phydev->duplex = DUPLEX_UNKNOWN; 161 phydev->pause = 0; 162 phydev->asym_pause = 0; 163 phydev->mdix = 0; 164 165 if (!phydev->link) 166 return 0; 167 168 if (phydev->autoneg_complete) { 169 val = genphy_c45_read_lpa(phydev); 170 if (val < 0) 171 return val; 172 173 val = phy_read_mmd(phydev, MDIO_MMD_AN, 174 MDIO_AN_C22 + MII_STAT1000); 175 if (val < 0) 176 return val; 177 178 mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); 179 180 if (phydev->autoneg == AUTONEG_ENABLE) 181 phy_resolve_aneg_linkmode(phydev); 182 } 183 184 if (phydev->autoneg == AUTONEG_DISABLE) { 185 /* disabled autoneg doesn't seem to work, so force the link 186 * down. 187 */ 188 phydev->link = 0; 189 return 0; 190 } 191 192 /* Set the host link mode - we set the phy interface mode and 193 * the speed according to this register so that downshift works. 194 * We leave the duplex setting as per the resolution from the 195 * above. 196 */ 197 val = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x4011); 198 mode = (val & 0x1e) >> 1; 199 if (mode == 1 || mode == 2) 200 phydev->interface = PHY_INTERFACE_MODE_SGMII; 201 else if (mode == 3) 202 phydev->interface = PHY_INTERFACE_MODE_10GBASER; 203 else if (mode == 4) 204 phydev->interface = PHY_INTERFACE_MODE_2500BASEX; 205 switch (mode & 7) { 206 case 1: 207 phydev->speed = SPEED_100; 208 break; 209 case 2: 210 phydev->speed = SPEED_1000; 211 break; 212 case 3: 213 phydev->speed = SPEED_10000; 214 break; 215 case 4: 216 phydev->speed = SPEED_2500; 217 break; 218 case 5: 219 phydev->speed = SPEED_5000; 220 break; 221 } 222 223 return genphy_c45_read_mdix(phydev); 224} 225 226static struct phy_driver bcm84881_drivers[] = { 227 { 228 .phy_id = 0xae025150, 229 .phy_id_mask = 0xfffffff0, 230 .name = "Broadcom BCM84881", 231 .config_init = bcm84881_config_init, 232 .probe = bcm84881_probe, 233 .get_features = bcm84881_get_features, 234 .config_aneg = bcm84881_config_aneg, 235 .aneg_done = bcm84881_aneg_done, 236 .read_status = bcm84881_read_status, 237 }, 238}; 239 240module_phy_driver(bcm84881_drivers); 241 242/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */ 243static struct mdio_device_id __maybe_unused bcm84881_tbl[] = { 244 { 0xae025150, 0xfffffff0 }, 245 { }, 246}; 247MODULE_AUTHOR("Russell King"); 248MODULE_DESCRIPTION("Broadcom BCM84881 PHY driver"); 249MODULE_DEVICE_TABLE(mdio, bcm84881_tbl); 250MODULE_LICENSE("GPL");